diff --git a/README.md b/README.md index 152c12bb9db365ccfc39a5e245400ae8f383a3b4..5256c01371cc73c98ea4b40bd591164f8484df87 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,13 @@ The following guides illustrate how to use some features concretely: * [Spring Data with MongoDB](https://spring.io/projects/spring-data-mongodb) * [gRPC-Spring-Boot-Starter Documentation](https://yidongnan.github.io/grpc-spring-boot-starter/en/) +### Properties + +| Name | Description | Default value | +|-------------------------------------------|------------------------------------------------------------------------------|----------------------| +| ozgcloud.infomanager.allowed-client-names | List of names of grpc clients allowed to access the getServiceUrlOfNachricht | Antragsraum | +| ozgcloud.infomanager.postfach-id-key-name | Name of the key used to read the postfach id from the auth token | legacyPostkorbHandle | + ### Useful commands Create an entry using grpcurl: @@ -31,7 +38,7 @@ Create an entry using grpcurl: Add a 'Nachricht' to the database using grpcUrl: ``` -./grpcurl --plaintext -d '{"nachricht": {"postfachId": "28721c6f-b78f-4d5c-a048-19fd2fc429d2", "vorgangId":"6358fd4146811d04010f44d0", "nachrichtId":"1", "nachrichtenListUrl":"static://localhost:9091"}}' localhost:9091 de.mgm.bup.ozg.applicationroom.NachrichtService.SaveNewNachricht +./grpcurl --plaintext -d '{"nachricht": {"postfachId": "28721c6f-b78f-4d5c-a048-19fd2fc429d2", "vorgangId":"6358fd4146811d04010f44d0", "nachrichtId":"1", "nachrichtenListUrl":"static://localhost:9091"}}' localhost:9091 de.mgm.bup.ozg.antragsraum.infomanager.NachrichtService.SaveNewNachricht ``` Query the InfoManager for available services diff --git a/ozg-info-manager-server/build.gradle b/ozg-info-manager-server/build.gradle index c0bd510de6ca63f8581c4a85a45fbb4899cb55cd..d23ea0e2dd81b9784be56047d96db83c09024e91 100644 --- a/ozg-info-manager-server/build.gradle +++ b/ozg-info-manager-server/build.gradle @@ -1,7 +1,9 @@ buildscript { ext { grpcTestVersion = '1.19.0' - netDevhGrpcVersion = '2.14.0.RELEASE' + netDevhGrpcVersion = '2.15.0.RELEASE' + modulithVersion = '1.0.2' + grpcVersion = '1.57.2' infoManagerInterfaceVersion = '0.0.2-1.a7b24d5-SNAPSHOT' } } @@ -65,23 +67,40 @@ license { } dependencies { + implementation 'org.springframework.modulith:spring-modulith-starter-core' implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.security:spring-security-config' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' implementation "net.devh:grpc-server-spring-boot-starter:${netDevhGrpcVersion}" implementation "com.mgmtp.bup.ozg:ozg-info-manager-interface:${infoManagerInterfaceVersion}" compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' + testImplementation 'org.springframework.modulith:spring-modulith-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.junit.jupiter:junit-jupiter' testImplementation 'org.testcontainers:testcontainers' testImplementation 'org.testcontainers:junit-jupiter' testImplementation 'org.testcontainers:mongodb' + testImplementation 'com.github.dasniko:testcontainers-keycloak:3.0.0' + testImplementation 'org.springframework.boot:spring-boot-starter-webflux' testImplementation "io.grpc:grpc-testing:${grpcTestVersion}" testImplementation "net.devh:grpc-client-spring-boot-starter:${netDevhGrpcVersion}" + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' +} + +dependencyManagement { + imports { + mavenBom "org.springframework.modulith:spring-modulith-bom:${modulithVersion}" + mavenBom "io.grpc:grpc-bom:${grpcVersion}" + } } tasks.named('test') { diff --git a/ozg-info-manager-server/lombok.config b/ozg-info-manager-server/lombok.config new file mode 100644 index 0000000000000000000000000000000000000000..d07dd9b0e2b0281fbf514a968b9451cb6af62f93 --- /dev/null +++ b/ozg-info-manager-server/lombok.config @@ -0,0 +1,30 @@ +# +# Copyright (C) 2022 Das Land Schleswig-Holstein vertreten durch den +# Ministerpräsidenten des Landes Schleswig-Holstein +# Staatskanzlei +# Abteilung Digitalisierung und zentrales IT-Management der Landesregierung +# +# Lizenziert unter der EUPL, Version 1.2 oder - sobald +# diese von der Europäischen Kommission genehmigt wurden - +# Folgeversionen der EUPL ("Lizenz"); +# Sie dürfen dieses Werk ausschließlich gemäß +# dieser Lizenz nutzen. +# Eine Kopie der Lizenz finden Sie hier: +# +# https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 +# +# Sofern nicht durch anwendbare Rechtsvorschriften +# gefordert oder in schriftlicher Form vereinbart, wird +# die unter der Lizenz verbreitete Software "so wie sie +# ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - +# ausdrücklich oder stillschweigend - verbreitet. +# Die sprachspezifischen Genehmigungen und Beschränkungen +# unter der Lizenz sind dem Lizenztext zu entnehmen. +# + +lombok.log.fieldName=LOG +lombok.log.slf4j.flagUsage = ERROR +lombok.log.log4j.flagUsage = ERROR +lombok.data.flagUsage = ERROR +lombok.nonNull.exceptionType = IllegalArgumentException +lombok.addLombokGeneratedAnnotation = true \ No newline at end of file diff --git a/ozg-info-manager-server/src/main/helm/templates/ozg-info-manager-configmap.yaml b/ozg-info-manager-server/src/main/helm/templates/ozg-info-manager-configmap.yaml index ae8e607b5b1c9e36a72d174564ae842404014924..2bf85ab5123a536111ccb92a0989798023c62fef 100644 --- a/ozg-info-manager-server/src/main/helm/templates/ozg-info-manager-configmap.yaml +++ b/ozg-info-manager-server/src/main/helm/templates/ozg-info-manager-configmap.yaml @@ -4,3 +4,4 @@ metadata: name: "{{ .Values.name }}-configmap" data: mongodb.autoIndexCreation: "{{ .Values.mongodb.autoIndexCreation }}" + jwt.issuerUri: {{ .Values.jwt.issuerUri }} diff --git a/ozg-info-manager-server/src/main/helm/templates/ozg-info-manager-deployment.yaml b/ozg-info-manager-server/src/main/helm/templates/ozg-info-manager-deployment.yaml index d698f2ca4c57bca0747fa59b56a8fdc00e126c67..7aacfa1229805de55dc114d6bf3552c5e6fbcf2e 100644 --- a/ozg-info-manager-server/src/main/helm/templates/ozg-info-manager-deployment.yaml +++ b/ozg-info-manager-server/src/main/helm/templates/ozg-info-manager-deployment.yaml @@ -34,6 +34,11 @@ spec: configMapKeyRef: name: "{{ .Values.name }}-configmap" key: mongodb.autoIndexCreation + - name: SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI + valueFrom: + configMapKeyRef: + name: "{{ .Values.name }}-configmap" + key: jwt.issuerUri resources: requests: memory: "1Gi" diff --git a/ozg-info-manager-server/src/main/helm/values.yaml b/ozg-info-manager-server/src/main/helm/values.yaml index 89bc586311d774a86396c4260cc10d579ce4d42c..e34db6dd36a61defc7f7cc39d9c3e9895ea25a9a 100644 --- a/ozg-info-manager-server/src/main/helm/values.yaml +++ b/ozg-info-manager-server/src/main/helm/values.yaml @@ -12,4 +12,6 @@ mongodb: database: appuser: name: SET_BY_ANSIBLE - password: SET_BY_ANSIBLE \ No newline at end of file + password: SET_BY_ANSIBLE +jwt: + issuerUri: jkl \ No newline at end of file diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/GrpcConfig.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/GrpcConfig.java deleted file mode 100644 index 4bea8b80d90d4aa65426d205a57ccaa0d8691470..0000000000000000000000000000000000000000 --- a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/GrpcConfig.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) 2023. - * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein - * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung - * - * Lizenziert unter der EUPL, Version 1.2 oder - sobald - * diese von der Europäischen Kommission genehmigt wurden - - * Folgeversionen der EUPL ("Lizenz"); - * Sie dürfen dieses Werk ausschließlich gemäß - * dieser Lizenz nutzen. - * Eine Kopie der Lizenz finden Sie hier: - * - * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 - * - * Sofern nicht durch anwendbare Rechtsvorschriften - * gefordert oder in schriftlicher Form vereinbart, wird - * die unter der Lizenz verbreitete Software "so wie sie - * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - - * ausdrücklich oder stillschweigend - verbreitet. - * Die sprachspezifischen Genehmigungen und Beschränkungen - * unter der Lizenz sind dem Lizenztext zu entnehmen. - */ - - -package de.mgm.bup.ozg.antragsraum; - -import org.springframework.boot.autoconfigure.ImportAutoConfiguration; -import org.springframework.context.annotation.Configuration; - -@Configuration -@ImportAutoConfiguration({ - net.devh.boot.grpc.common.autoconfigure.GrpcCommonCodecAutoConfiguration.class, - net.devh.boot.grpc.common.autoconfigure.GrpcCommonTraceAutoConfiguration.class, - - net.devh.boot.grpc.server.autoconfigure.GrpcAdviceAutoConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcHealthServiceAutoConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcMetadataConsulConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcMetadataEurekaConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcMetadataNacosConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcMetadataZookeeperConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcReflectionServiceAutoConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcServerMetricAutoConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcServerSecurityAutoConfiguration.class, - net.devh.boot.grpc.server.autoconfigure.GrpcServerTraceAutoConfiguration.class -}) -class GrpcConfig { -} diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/InfoManagerApplication.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/InfoManagerApplication.java index 830bd2cd346e63165cb97a60c58d4235566fd2d5..f0f6be8269d08d2202d259aff6bba8b14f009dee 100644 --- a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/InfoManagerApplication.java +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/InfoManagerApplication.java @@ -24,8 +24,12 @@ package de.mgm.bup.ozg.antragsraum; +import de.mgm.bup.ozg.antragsraum.common.CallBeanFactoryPostProcessor; +import de.mgm.bup.ozg.antragsraum.common.CallScope; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; @SpringBootApplication public class InfoManagerApplication { @@ -34,4 +38,14 @@ public class InfoManagerApplication { SpringApplication.run(InfoManagerApplication.class, args); } + @Bean + public CallScope callScope() { + return new CallScope(); + } + + @Bean + public static BeanFactoryPostProcessor beanFactoryPostProcessor(CallScope callScope) { + return new CallBeanFactoryPostProcessor(callScope); + } + } diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/NachrichtEvent.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/NachrichtEvent.java index fc6cd88db581adf9bb0c47659a0243be2d5f8540..d6585186eb25aa6347a1c7691aea9b4f7a38bcf5 100644 --- a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/NachrichtEvent.java +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/NachrichtEvent.java @@ -25,6 +25,7 @@ package de.mgm.bup.ozg.antragsraum; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.Builder; import lombok.Getter; import lombok.Setter; @@ -40,10 +41,13 @@ public class NachrichtEvent { @Id private ObjectId id; @NotBlank(message = "Postfach id is missing") + @Size(max = 128, message = "Invalid PostfachId") private String postfachId; @NotBlank(message = "Vorgang id is missing") + @Size(max = 64, message = "Invalid VorgangId") private String vorgangId; @NotBlank(message = "Nachricht id is missing") + @Size(max = 64, message = "Invalid NachrichtId") private String nachrichtId; @NotBlank(message = "Address of nachricht list endpoint is missing") private String nachrichtenListUrl; diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/SecurityConfiguration.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/SecurityConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..fa555eb7c0a35450f6478b4cfaddaec66914dbf3 --- /dev/null +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/SecurityConfiguration.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum; + +import de.mgm.bup.ozg.antragsraum.infomanager.InformationServiceGrpc; +import lombok.extern.log4j.Log4j2; +import net.devh.boot.grpc.server.security.authentication.BearerAuthenticationReader; +import net.devh.boot.grpc.server.security.authentication.CompositeGrpcAuthenticationReader; +import net.devh.boot.grpc.server.security.authentication.GrpcAuthenticationReader; +import net.devh.boot.grpc.server.security.check.AccessPredicate; +import net.devh.boot.grpc.server.security.check.GrpcSecurityMetadataSource; +import net.devh.boot.grpc.server.security.check.ManualGrpcSecurityMetadataSource; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.JwtDecoders; +import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; + +import java.util.ArrayList; +import java.util.List; + +@Configuration +@Log4j2 +public class SecurityConfiguration { + @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") + private String issuerUrl; + + @Bean + JwtAuthenticationProvider jwtAuthenticationProvider() { + var provider = new JwtAuthenticationProvider(jwtDecoder()); + provider.setJwtAuthenticationConverter(jwtAuthenticationConverter()); + return provider; + } + + @Bean + JwtAuthenticationConverter jwtAuthenticationConverter() { + return new JwtAuthenticationConverter(); + } + + + @Bean + AuthenticationManager authenticationManager() { + return new ProviderManager(List.of(jwtAuthenticationProvider())); + } + + @Bean + public JwtDecoder jwtDecoder() { + return JwtDecoders.fromIssuerLocation(issuerUrl); + } + + @Bean + GrpcAuthenticationReader authenticationReader() { + final List<GrpcAuthenticationReader> readers = new ArrayList<>(); + readers.add(new BearerAuthenticationReader(BearerTokenAuthenticationToken::new)); + return new CompositeGrpcAuthenticationReader(readers); + } + + @Bean + GrpcSecurityMetadataSource grpcSecurityMetadataSource() { + final ManualGrpcSecurityMetadataSource source = new ManualGrpcSecurityMetadataSource(); + source.setDefault(AccessPredicate.denyAll()); + source.set(InformationServiceGrpc.getGetInformationMethod(), AccessPredicate.authenticated()); + source.set(InformationServiceGrpc.getGetServiceUrlOfNachrichtMethod(), AccessPredicate.permitAll()); + return source; + } +} diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/CallBeanFactoryPostProcessor.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/CallBeanFactoryPostProcessor.java new file mode 100644 index 0000000000000000000000000000000000000000..fc4a536477712ce8a54e96b5fc0348bf7d275212 --- /dev/null +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/CallBeanFactoryPostProcessor.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ + +package de.mgm.bup.ozg.antragsraum.common; + +import lombok.RequiredArgsConstructor; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanFactoryPostProcessor; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +@RequiredArgsConstructor +public class CallBeanFactoryPostProcessor implements BeanFactoryPostProcessor { + private final CallScope callScope; + + @Override + public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { + beanFactory.registerScope(CallScope.SCOPE_NAME, callScope); + } +} diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/CallContextAttachingInterceptor.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/CallContextAttachingInterceptor.java new file mode 100644 index 0000000000000000000000000000000000000000..e9fc89d74cb56c13c27184121e4cb1df846537a8 --- /dev/null +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/CallContextAttachingInterceptor.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ + +package de.mgm.bup.ozg.antragsraum.common; + +import io.grpc.Metadata; +import io.grpc.ServerCall; +import io.grpc.ServerCallHandler; +import io.grpc.ServerInterceptor; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor; + +@Log4j2 +@GrpcGlobalServerInterceptor +@RequiredArgsConstructor +public class CallContextAttachingInterceptor implements ServerInterceptor { + private final @NonNull ContextService contextService; + + @Override + public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next) { + contextService.readMetadata(headers); + + LOG.debug("Got metadata: {}", headers); + + return next.startCall(call, headers); + } +} diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/CallScope.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/CallScope.java new file mode 100644 index 0000000000000000000000000000000000000000..91d9b5d4ab2cdc346c6c206c5d67c1408f9903a5 --- /dev/null +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/CallScope.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ + +package de.mgm.bup.ozg.antragsraum.common; + +import lombok.NonNull; +import lombok.extern.log4j.Log4j2; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.Scope; +import org.springframework.core.NamedInheritableThreadLocal; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +@Log4j2 +public class CallScope implements Scope { + public static final String SCOPE_NAME = "call"; + + static final ThreadLocal<Map<String, Object>> scopedObjectsHolder = new NamedInheritableThreadLocal<>("Call Context"); + static final ThreadLocal<Map<String, Runnable>> destructionCallbacksHolder = new NamedInheritableThreadLocal<>( + "Call Context Destruction Callbacks"); + + public void startScope() { + LOG.debug("START Call-Scope"); + scopedObjectsHolder.set(new ConcurrentHashMap<>()); + destructionCallbacksHolder.set(new ConcurrentHashMap<>()); + } + + public void endScope() { + scopedObjectsHolder.remove(); + callAllDestructionCallbacks(); + destructionCallbacksHolder.remove(); + LOG.debug("END Call-Scope"); + } + + void callAllDestructionCallbacks() { + destructionCallbacksHolder.get().values().forEach(Runnable::run); + } + + @Override + public @NonNull Object get(@NonNull String name, @NonNull ObjectFactory<?> objectFactory) { + if (!scopedObjectsHolder.get().containsKey(name)) { + scopedObjectsHolder.get().put(name, objectFactory.getObject()); + } + + return scopedObjectsHolder.get().get(name); + } + + @Override + public Object remove(@NonNull String name) { + destructionCallbacksHolder.get().remove(name); + return scopedObjectsHolder.get().remove(name); + } + + @Override + public void registerDestructionCallback(@NonNull String name, @NonNull Runnable callback) { + destructionCallbacksHolder.get().put(name, callback); + + } + + @Override + public Object resolveContextualObject(@NonNull String key) { + return scopedObjectsHolder.get().get(key); + } + + @Override + public String getConversationId() { + return Thread.currentThread().getName(); + } +} diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/ContextService.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/ContextService.java new file mode 100644 index 0000000000000000000000000000000000000000..a8859a7b9bb11e5d00617a69ce4a415b65a8e9a7 --- /dev/null +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/ContextService.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ + +package de.mgm.bup.ozg.antragsraum.common; + +import io.grpc.Metadata; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; +import org.springframework.stereotype.Service; + +import java.util.Objects; +import java.util.Optional; + +@Log4j2 +@Service +@RequiredArgsConstructor +public class ContextService { + public static final String KEY_CLIENT_NAME = "CLIENT_NAME-bin"; + public static final String KEY_REQUEST_ID = "REQUEST_ID-bin"; + public static final String KEY_AUTHENTICATION_ID = "AUTHENTICATION-bin"; + RequestAttributes requestAttributes; + + void readMetadata(Metadata headers) { + setToken(headers); + var requestAttributesBuilder = RequestAttributes.builder(); + + getRequestId(headers).ifPresent(id -> requestAttributesBuilder.requestId(new String(id))); + getClientName(headers).ifPresent(name -> requestAttributesBuilder.clientName(new String(name))); + + requestAttributes = requestAttributesBuilder.build(); + } + + void setToken(Metadata headers) { + var token = headers.get(ContextService.createKeyOf(ContextService.KEY_AUTHENTICATION_ID)); + if (Objects.nonNull(token)) { + SecurityContextHolder.getContext().setAuthentication(new BearerTokenAuthenticationToken(new String(token))); + } + } + + Optional<byte[]> getRequestId(Metadata headers) { + return Optional.ofNullable(headers.get(ContextService.createKeyOf(ContextService.KEY_REQUEST_ID))); + } + + Optional<byte[]> getClientName(Metadata headers) { + return Optional.ofNullable(headers.get(ContextService.createKeyOf(ContextService.KEY_CLIENT_NAME))); + } + + static Metadata.Key<byte[]> createKeyOf(String key) { + return Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER); + } +} diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/RequestAttributes.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/RequestAttributes.java new file mode 100644 index 0000000000000000000000000000000000000000..4bd14b59cd31fdfd8cdce0fca3c4bfd3027f9d23 --- /dev/null +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/common/RequestAttributes.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ + +package de.mgm.bup.ozg.antragsraum.common; + +import lombok.*; +import org.springframework.context.annotation.Scope; +import org.springframework.context.annotation.ScopedProxyMode; +import org.springframework.stereotype.Component; + +import java.util.UUID; + +@Component +@Scope(scopeName = "call", proxyMode = ScopedProxyMode.TARGET_CLASS) +@Getter +@Builder +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@NoArgsConstructor +public class RequestAttributes { + @Builder.Default + private String requestId = UUID.randomUUID().toString(); + private String clientName; +} diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/infomanager/InfoManagerProperties.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/infomanager/InfoManagerProperties.java new file mode 100644 index 0000000000000000000000000000000000000000..25c8828c5e781cc313a5b080d076abe98c4d871a --- /dev/null +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/infomanager/InfoManagerProperties.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ + +package de.mgm.bup.ozg.antragsraum.infomanager; + +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.util.List; + +@Getter +@Configuration +@ConfigurationProperties(prefix = InfoManagerProperties.PREFIX) +public class InfoManagerProperties { + static final String PREFIX = "ozgcloud.infomanager"; + private List<String> allowedClientNames = List.of("Antragsraum"); + private String postfachIdKeyName = "legacyPostkorbHandle"; +} diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcService.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcService.java index cf28c4b5583f565e44c450700ef74c2980f6e684..660949aa73e708197c2b5eff63e4d6a41c9f8db0 100644 --- a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcService.java +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcService.java @@ -24,43 +24,94 @@ package de.mgm.bup.ozg.antragsraum.infomanager; +import de.mgm.bup.ozg.antragsraum.common.RequestAttributes; import io.grpc.stub.StreamObserver; import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; import net.devh.boot.grpc.server.service.GrpcService; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.util.ObjectUtils; import java.util.ArrayList; +@Log4j2 @GrpcService @RequiredArgsConstructor public class InformationGrpcService extends InformationServiceGrpc.InformationServiceImplBase { private final InformationService service; + private final RequestAttributes requestAttributes; + private final InfoManagerProperties infoManagerProperties; @Override public void getInformation(GrpcInformationRequest request, StreamObserver<GrpcInformationResponse> responseStreamObserver) { - var nachrichten = new ArrayList<GrpcNachricht>(); - var information = service.getInformationOfPostfach(request.getPostfachId()); - - information.forEach(info -> nachrichten.add(GrpcNachricht.newBuilder() - .setNachrichtenListUrl(info.getNachrichtenListUrl()) - .setPostfachId(info.getPostfachId()) - .setNachrichtId(info.getNachrichtId()) - .setVorgangId(info.getVorgangId()) - .build() - )); - - responseStreamObserver.onNext(GrpcInformationResponse.newBuilder().addAllNachrichten(nachrichten).build()); - responseStreamObserver.onCompleted(); + if (checkForAccessToPostfach(request.getPostfachId(), responseStreamObserver)) { + var nachrichten = new ArrayList<GrpcNachricht>(); + var information = service.getInformationOfPostfach(request.getPostfachId()); + + information.forEach(info -> nachrichten.add(GrpcNachricht.newBuilder() + .setNachrichtenListUrl(info.getNachrichtenListUrl()) + .setPostfachId(info.getPostfachId()) + .setNachrichtId(info.getNachrichtId()) + .setVorgangId(info.getVorgangId()) + .build() + )); + + responseStreamObserver.onNext(GrpcInformationResponse.newBuilder().addAllNachrichten(nachrichten).build()); + responseStreamObserver.onCompleted(); + } + } + + boolean checkForAccessToPostfach(String requestedPostfachId, StreamObserver<GrpcInformationResponse> responseStreamObserver) { + var auth = SecurityContextHolder.getContext().getAuthentication(); + boolean success = false; + try { + if (auth instanceof JwtAuthenticationToken jwtAuthenticationToken) { + var userPostfachId = jwtAuthenticationToken.getToken().getClaims().get(infoManagerProperties.getPostfachIdKeyName()); + + if (ObjectUtils.isEmpty(userPostfachId)) { + var msg = "Invalid postfach_id_key_name [%s] configured. Unable get Postfach id from token".formatted(infoManagerProperties.getPostfachIdKeyName()); + LOG.error(msg); + throw new IllegalStateException(msg); + } + + if (!userPostfachId.equals(requestedPostfachId)) { + LOG.error("User " + jwtAuthenticationToken.getToken().getClaims().get("sid") + " is not allowed to access events of postfach id " + requestedPostfachId); + throw new SecurityException("User not allowed"); + } + + success = true; + } else { + var msg = "Invalid auth configuration. Expected JwtAuthenticationToken class but got %s".formatted(auth.getClass().getName()); + LOG.error(msg); + throw new IllegalStateException(msg); + } + } catch (SecurityException | IllegalStateException e) { + responseStreamObserver.onError(e); + } + + return success; } @Override public void getServiceUrlOfNachricht(GrpcServiceUrlRequest request, StreamObserver<GrpcServiceUrlResponse> responseStreamObserver) { - var urlOptional = service.getUrlOfNachricht(request.getNachrichtId()); + if (canAccessByName()) { + + var urlOptional = service.getUrlOfNachricht(request.getNachrichtId()); - var responseBuilder = GrpcServiceUrlResponse.newBuilder(); + var responseBuilder = GrpcServiceUrlResponse.newBuilder(); - urlOptional.ifPresent(responseBuilder::setUrl); + urlOptional.ifPresent(responseBuilder::setUrl); + + responseStreamObserver.onNext(responseBuilder.build()); + responseStreamObserver.onCompleted(); + } else { + LOG.error("Client " + requestAttributes.getClientName() + " not allowed"); + responseStreamObserver.onError(new SecurityException("Client not allowed")); + } + } - responseStreamObserver.onNext(responseBuilder.build()); - responseStreamObserver.onCompleted(); + boolean canAccessByName() { + return infoManagerProperties.getAllowedClientNames().contains(requestAttributes.getClientName()); } } diff --git a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/nachrichteventmanager/NachrichtenService.java b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/nachrichteventmanager/NachrichtenService.java index 188b15c37ab41a40ea63aa2fc0b43641c5fbba90..e9e0f5bc2084165c1b50e0f95e9cc31cecfa37dd 100644 --- a/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/nachrichteventmanager/NachrichtenService.java +++ b/ozg-info-manager-server/src/main/java/de/mgm/bup/ozg/antragsraum/nachrichteventmanager/NachrichtenService.java @@ -25,14 +25,13 @@ package de.mgm.bup.ozg.antragsraum.nachrichteventmanager; import de.mgm.bup.ozg.antragsraum.NachrichtEvent; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @Service +@RequiredArgsConstructor class NachrichtenService { - - @Autowired - NachrichtenRepository repository; + private final NachrichtenRepository repository; void save(NachrichtEvent nachrichtEvent) { repository.save(nachrichtEvent); diff --git a/ozg-info-manager-server/src/main/resources/application.properties b/ozg-info-manager-server/src/main/resources/application.properties index a95f855ce39e1b36086175b94cb10cb1f8453f7a..a187ef199f78e41073268c12bc220f0fa6a08126 100644 --- a/ozg-info-manager-server/src/main/resources/application.properties +++ b/ozg-info-manager-server/src/main/resources/application.properties @@ -25,4 +25,6 @@ management.endpoint.info.enabled=true management.endpoint.health.enabled=true management.endpoints.web.exposure.include=health,info management.endpoint.health.show-components=always +spring.security.oauth2.resourceserver.jwt.issuer-uri=http://localhost:8081/realms/antragsraum +ozgcloud.infomanager.allowed-client-names=Antragsraum diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/GrpcTestConfig.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/GrpcTestConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..c47e412f6c3254f3707c0e6ead51710be0693915 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/GrpcTestConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ + +package de.mgm.bup.ozg.antragsraum; + +import net.devh.boot.grpc.client.autoconfigure.*; +import net.devh.boot.grpc.common.autoconfigure.GrpcCommonCodecAutoConfiguration; +import net.devh.boot.grpc.common.autoconfigure.GrpcCommonTraceAutoConfiguration; +import org.springframework.boot.autoconfigure.ImportAutoConfiguration; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ImportAutoConfiguration({ + GrpcCommonCodecAutoConfiguration.class, + GrpcCommonTraceAutoConfiguration.class, + + GrpcClientAutoConfiguration.class, + GrpcClientHealthAutoConfiguration.class, + GrpcClientMetricAutoConfiguration.class, + GrpcClientSecurityAutoConfiguration.class, + GrpcClientTraceAutoConfiguration.class, + GrpcDiscoveryClientAutoConfiguration.class, +}) +class GrpcTestConfig { +} + diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/InfoManagerApplicationITCase.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/InfoManagerApplicationITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..aead5cc7a38dbf35ea701d0681d74fd8916caec4 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/InfoManagerApplicationITCase.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +@SpringBootTest +@SpringJUnitConfig(classes = {GrpcTestConfig.class}) +class InfoManagerApplicationITCase { + @Test + void shouldLoadContext() { + } +} \ No newline at end of file diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/InfoManagerApplicationTest.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/InfoManagerApplicationTest.java new file mode 100644 index 0000000000000000000000000000000000000000..e9d45c0e398f3ea330246072333708a90f67a3a7 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/InfoManagerApplicationTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum; + +import de.mgm.bup.ozg.antragsraum.common.CallScope; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.assertj.core.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +class InfoManagerApplicationTest { + @Spy + @InjectMocks + private InfoManagerApplication application; + @Test + void shouldCreateCallScope() { + var callScope = application.callScope(); + + assertThat(callScope).isNotNull(); + } + + @Test + void shouldCreateBeanFactoryPostProcessor() { + var beanProcessor = InfoManagerApplication.beanFactoryPostProcessor(new CallScope()); + + assertThat(beanProcessor).isNotNull(); + } +} \ No newline at end of file diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/KeycloakExtension.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/KeycloakExtension.java new file mode 100644 index 0000000000000000000000000000000000000000..caf8f11275607d98d387b5d47b802e87070ae360 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/KeycloakExtension.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum; + +import dasniko.testcontainers.keycloak.KeycloakContainer; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + +public class KeycloakExtension implements BeforeAllCallback, AfterAllCallback { + public static final String REALMS_ANTRAGSRAUM = "/realms/antragsraum"; + + @Override + public void afterAll(ExtensionContext context) { + // do nothing, Testcontainers handles container shutdown + } + + @Override + public void beforeAll(ExtensionContext context) { + KeycloakContainer keycloak = new KeycloakContainer().withRealmImportFile("realm-export.json"); + keycloak.start(); + + String url = keycloak.getAuthServerUrl() + REALMS_ANTRAGSRAUM; + System.setProperty("spring.security.oauth2.resourceserver.jwt.issuer-uri", url); + } +} diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/ModularityTests.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/ModularityTests.java new file mode 100644 index 0000000000000000000000000000000000000000..cfcf9f01f642bf6422b1de0a42f76280c4095bc3 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/ModularityTests.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum; + +import org.junit.jupiter.api.Test; +import org.springframework.modulith.core.ApplicationModules; +import org.springframework.modulith.docs.Documenter; + +class ModularityTests { + @Test + void verifyModularity() { + ApplicationModules.of(InfoManagerApplication.class); + } + + @Test + void writeDocumentationSnippets() { + + var modules = ApplicationModules.of(InfoManagerApplication.class).verify(); + + new Documenter(modules) + .writeModulesAsPlantUml() + .writeIndividualModulesAsPlantUml(); + } +} diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/NachrichtEventTestFactory.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/NachrichtEventTestFactory.java index 4f8a927ba99cc6367a8285eadd0842c3310b905c..3c2df4f8e3ed7334201a467c4fbf7dc52e18dcd7 100644 --- a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/NachrichtEventTestFactory.java +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/NachrichtEventTestFactory.java @@ -27,7 +27,8 @@ import java.util.UUID; public class NachrichtEventTestFactory { public final static String VORGANG_ID = UUID.randomUUID().toString(); public final static String NACHRICHT_ID = UUID.randomUUID().toString(); - public final static String POSTFACH_ID = UUID.randomUUID().toString(); + public final static String POSTFACH_ID = "abc123-jsdkfjks-sdfhks-2636hdh"; + public final static String EMPTY_POSTFACH_ID = "73228744-a28a-4485-845b-d8cc02422760"; public final static String URL = "http://localhost"; public static NachrichtEvent create() { diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/TokenSupplier.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/TokenSupplier.java new file mode 100644 index 0000000000000000000000000000000000000000..7ea7c658015382e0e0bac4f05e17c6714a874b46 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/TokenSupplier.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum; + +import lombok.extern.log4j.Log4j2; +import org.apache.http.client.utils.URIBuilder; +import org.springframework.boot.json.JacksonJsonParser; +import org.springframework.http.MediaType; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.BodyInserters; +import org.springframework.web.reactive.function.client.WebClient; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collections; + +@Log4j2 +public class TokenSupplier { + public String get(String issuerUrl, String userName) { + var token = getToken(issuerUrl, userName); + LOG.info("TokenSupplier token: " + token); + return token; + } + + String getToken(String issuerUrl, String userName) { + try { + URI authorizationURI = new URIBuilder(issuerUrl + "/protocol/openid-connect/token").build(); + WebClient webclient = WebClient.builder().build(); + MultiValueMap<String, String> formData = new LinkedMultiValueMap<>(); + formData.put("grant_type", Collections.singletonList("password")); + formData.put("client_id", Collections.singletonList("antragsraum-api")); + formData.put("username", Collections.singletonList(userName)); + formData.put("password", Collections.singletonList("s3cr3t")); + + String result = webclient.post() + .uri(authorizationURI) + .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .body(BodyInserters.fromFormData(formData)) + .retrieve() + .bodyToMono(String.class) + .block(); + + JacksonJsonParser jsonParser = new JacksonJsonParser(); + + return jsonParser.parseMap(result).get("access_token").toString(); + } catch (URISyntaxException e) { + LOG.error("Error loading token from keycloak.", e); + } + + return null; + } +} diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/CallBeanFactoryPostProcessorTest.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/CallBeanFactoryPostProcessorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..ef9bbadeb57bd6b2dcb88a6bd498738d38a42548 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/CallBeanFactoryPostProcessorTest.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum.common; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CallBeanFactoryPostProcessorTest { + @Spy + private ConfigurableListableBeanFactory configurableListableBeanFactory; + + @Test + void shouldRegisterPostProcessBeanFactory() { + var processor = new CallBeanFactoryPostProcessor(new CallScope()); + + processor.postProcessBeanFactory(configurableListableBeanFactory); + + verify(configurableListableBeanFactory).registerScope(eq(CallScope.SCOPE_NAME), any(CallScope.class)); + } +} \ No newline at end of file diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/CallContextAttachingInterceptorTest.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/CallContextAttachingInterceptorTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7b175524ee0ce188b97996f3db99728e927576d5 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/CallContextAttachingInterceptorTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum.common; + +import io.grpc.Metadata; +import io.grpc.ServerCallHandler; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CallContextAttachingInterceptorTest { + @InjectMocks + private CallContextAttachingInterceptor interceptor; + @Mock + private ContextService contextService; + + @Mock + ServerCallHandler<?, ?> handler; + + @Test + void shouldInterceptCall() { + interceptor.interceptCall(null, MetadataTestFactory.create(), handler); + + verify(handler).startCall(any(), any()); + } + + @Test + void shouldFillContextService() { + interceptor.interceptCall(null, MetadataTestFactory.create(), handler); + + verify(contextService).readMetadata(any(Metadata.class)); + } +} \ No newline at end of file diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/CallScopeTest.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/CallScopeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..48a6a60ab928aa7dfd379305999b199f88c5755d --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/CallScopeTest.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum.common; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.ObjectFactory; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class CallScopeTest { + public static final String TEST = "Test"; + public static final String TEST_NEW = "TestNew"; + @Spy + @InjectMocks + private CallScope callScope; + + @Nested + class TestFreshCallScope { + + @Test + void shouldStartScope() { + callScope.startScope(); + + assertThat(CallScope.scopedObjectsHolder).isNotNull(); + assertThat(CallScope.destructionCallbacksHolder).isNotNull(); + } + + @Test + void shouldEndScope() { + callScope.endScope(); + + verify(callScope).callAllDestructionCallbacks(); + } + + @Test + void getConversationId() { + var id = callScope.getConversationId(); + + assertThat(id).isEqualTo(Thread.currentThread().getName()); + } + } + + @Nested + class TestCallScope { + @Mock + private ObjectFactory<Object> objectFactory; + + @BeforeEach + void setupEmptyCallScope() { + callScope.startScope(); + CallScope.scopedObjectsHolder.get().put(TEST, objectFactory); + } + + @Test + void shouldGetAdded() { + var newObjectFactory = mock(ObjectFactory.class); + var object = new Object(); + when(newObjectFactory.getObject()).thenReturn(object); + + var recievedObject = callScope.get(TEST_NEW, newObjectFactory); + + assertThat(recievedObject).isNotNull().isEqualTo(object); + } + + @Test + void shouldGetNullName() { + var newObjectFactory = mock(ObjectFactory.class); + + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> callScope.get(null, newObjectFactory)); + } + + @Test + void shouldGetNullFactory() { + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> callScope.get(TEST, null)); + } + + @Test + void shouldGetExisting() { + var recievedObject = callScope.get(TEST, mock(ObjectFactory.class)); + + assertThat(recievedObject).isNotNull().isEqualTo(objectFactory); + } + + @Test + void remove() { + var res = callScope.remove(TEST); + + assertThat(res).isNotNull().isEqualTo(objectFactory); + } + + @Test + void removeNull() { + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> callScope.remove(null)); + } + + @Test + void registerDestructionCallback() { + var callBack = mock(Runnable.class); + + callScope.registerDestructionCallback(TEST, callBack); + + assertThat(CallScope.destructionCallbacksHolder.get()).containsEntry(TEST, callBack); + } + + @Test + void registerDestructionCallbackNullName() { + var callBack = mock(Runnable.class); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> callScope.registerDestructionCallback(null, callBack)); + } + + @Test + void registerDestructionCallbackNullCallback() { + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> callScope.registerDestructionCallback(TEST, null)); + } + + @Test + void resolveContextualObject() { + var contextualObject = callScope.resolveContextualObject(TEST); + + assertThat(contextualObject).isEqualTo(objectFactory); + } + + @Test + void resolveContextualObjectNullName() { + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> callScope.resolveContextualObject(null)); + } + } +} \ No newline at end of file diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/ContextServiceTest.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/ContextServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..280ca8aa408bd96bfb4b529599f72dfe9472de42 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/ContextServiceTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum.common; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.context.SecurityContextHolder; + +import static de.mgm.bup.ozg.antragsraum.common.MetadataTestFactory.*; +import static org.assertj.core.api.Assertions.*; + +@ExtendWith(MockitoExtension.class) +class ContextServiceTest { + @Spy + @InjectMocks + private ContextService contextService; + + @Nested + class TestCallContext { + + @BeforeEach + void init() { + contextService.readMetadata(MetadataTestFactory.create()); + } + + @Test + void shouldGetRequestId() { + var requestId = contextService.getRequestId(MetadataTestFactory.create()); + + assertThat(requestId).isPresent().hasValue(REQUEST_ID_BYTES); + } + + @Test + void shouldGetClientName() { + var clientName = contextService.getClientName(MetadataTestFactory.create()); + + assertThat(clientName).isPresent().hasValue(CLIENT_NAME_BYTES); + } + + @Test + void shouldSetAuthToken() { + contextService.setToken(MetadataTestFactory.create()); + + var auth = SecurityContextHolder.getContext().getAuthentication(); + + assertThat(auth).isNotNull(); + } + + @Test + void shouldReadRequestAttributesRequestId() { + assertThat(contextService.requestAttributes.getRequestId()).isEqualTo(new String(REQUEST_ID_BYTES)); + } + + @Test + void shouldReadRequestAttributesClientName() { + assertThat(contextService.requestAttributes.getClientName()).isEqualTo(new String(CLIENT_NAME_BYTES)); + } + + @Test + void shouldReadRequestAttributesAuth() { + var auth = SecurityContextHolder.getContext().getAuthentication(); + assertThat(auth).isNotNull(); + } + } + + @Nested + class TestEmptyCallContext { + @BeforeEach + void init() { + contextService.readMetadata(MetadataTestFactory.createEmpty()); + } + + @Test + void shouldReadRequestAttributesRequestId() { + assertThat(contextService.requestAttributes.getRequestId()).isNotEmpty(); + } + + @Test + void shouldReadRequestAttributesClientName() { + assertThat(contextService.requestAttributes.getClientName()).isNull(); + } + + @Test + void shouldReadRequestAttributesAuth() { + var auth = SecurityContextHolder.getContext().getAuthentication(); + assertThat(auth).isNull(); + } + } +} \ No newline at end of file diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/MetadataTestFactory.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/MetadataTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..46cf3f951e596af3c60cb79d825d66ba77bd4b33 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/common/MetadataTestFactory.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum.common; + +import io.grpc.Metadata; +import org.springframework.security.oauth2.jwt.Jwt; + +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.UUID; + +import static de.mgm.bup.ozg.antragsraum.common.ContextService.*; + +public class MetadataTestFactory { + public static byte[] CLIENT_NAME_BYTES = "ANTRAGSRAUM".getBytes(); + public static byte[] REQUEST_ID_BYTES = UUID.randomUUID().toString().getBytes(); + + public static Metadata create() { + var metadata = new Metadata(); + + metadata.put(createKeyOf(KEY_CLIENT_NAME), CLIENT_NAME_BYTES); + metadata.put(createKeyOf(KEY_REQUEST_ID), REQUEST_ID_BYTES); + metadata.put(createKeyOf(KEY_AUTHENTICATION_ID), createToken().getTokenValue().getBytes()); + + return metadata; + } + + public static Metadata createEmpty() { + return new Metadata(); + } + + public static Metadata.Key<byte[]> createKeyOf(String key) { + return Metadata.Key.of(key, Metadata.BINARY_BYTE_MARSHALLER); + } + + public static Jwt createToken() { + String jwtTokenPayload = """ + { + "exp": 1699951718, + "iat": 1699951418, + "jti": "3ebe34c0-875b-4253-8428-a1736546b3d0", + "iss": "http://localhost:32804/realms/antragsraum", + "aud": "account", + "sub": "80b69dc1-7d37-43df-9efe-64584f5d25c8", + "typ": "Bearer", + "azp": "antragsraum-api", + "session_state": "35aeb2f0-9f88-46e6-befa-b49773afbf0b", + "allowed-origins": [ + "http://localhost:8080", + "/*", + "https://localhost:8080" + ], + "resource_access": { + "account": { + "roles": [ + "manage-account", + "manage-account-links", + "view-profile" + ] + } + }, + "scope": "email profile", + "sid": "35aeb2f0-9f88-46e6-befa-b49773afbf0b", + "email_verified": false, + "name": "Jane Doe", + "preferred_username": "janedoe", + "given_name": "Jane", + "family_name": "Doe", + "email": "jane.doe@ozg-cloud.com" + } + """; + return Jwt.withTokenValue(jwtTokenPayload) + .header("alg", "RS256") + .header("typ", "jwt") + .header("kid", "NZ4PVRP0uUtxqh7CNBsLUdHSd5rbpGbIG247kepoadc") + .claim("iss", "https://localhost/realms/antragsraum") + .claim("sub", "281c4558-550c-413b-9972-2d2e5bde6b9b") + .claim("iat", Instant.now()) + .claim("exp", Instant.now().plus(1, ChronoUnit.DAYS)) + .claim("preferred_username", "janedoe") + .build(); + } +} diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceNoEventsITCase.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceNoEventsITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..645de2f456ce248d636568b0620446713758da3e --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceNoEventsITCase.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum.infomanager; + + +import de.mgm.bup.ozg.antragsraum.KeycloakExtension; +import de.mgm.bup.ozg.antragsraum.SecurityConfiguration; +import net.devh.boot.grpc.client.inject.GrpcClient; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static de.mgm.bup.ozg.antragsraum.NachrichtEventTestFactory.*; +import static org.assertj.core.api.Assertions.*; + +@SpringBootTest(properties = { + "grpc.server.inProcessName=test", + "grpc.server.port=-1", + "grpc.client.inProcess.address=in-process:test" +}) +@SpringJUnitConfig(classes = {InformationGrpcTestConfiguration.class, UserWithOutEventsConfiguration.class, SecurityConfiguration.class}) +@ExtendWith(KeycloakExtension.class) +@DirtiesContext +class InformationGrpcServiceNoEventsITCase { + @GrpcClient("inProcess") + private InformationServiceGrpc.InformationServiceBlockingStub service; + + @Nested + class TestNoInformationAvailable { + + @Test + void responseShouldHaveEmptyList() { + var request = GrpcInformationRequest.newBuilder().setPostfachId(EMPTY_POSTFACH_ID).build(); + + var response = service.getInformation(request); + + var values = response.getNachrichtenList(); + assertThat(values).isEmpty(); + } + + @Test + void responseShouldHaveEmptyUrl() { + var request = GrpcServiceUrlRequest.newBuilder().setNachrichtId("nothing").build(); + + var response = service.getServiceUrlOfNachricht(request); + + var values = response.getUrl(); + assertThat(values).isEmpty(); + } + } +} \ No newline at end of file diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceTest.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceTest.java index da326ea1d57eef5e5ee4e8576638c180a25e23cc..a9356cb794ca5b88f057b6b0b04f7af7377cc0fd 100644 --- a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceTest.java +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceTest.java @@ -25,6 +25,7 @@ package de.mgm.bup.ozg.antragsraum.infomanager; import de.mgm.bup.ozg.antragsraum.NachrichtEvent; import de.mgm.bup.ozg.antragsraum.NachrichtEventTestFactory; +import de.mgm.bup.ozg.antragsraum.common.RequestAttributes; import io.grpc.stub.StreamObserver; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; @@ -32,16 +33,24 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.*; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import java.util.List; +import java.util.Map; import java.util.Optional; +import static de.mgm.bup.ozg.antragsraum.NachrichtEventTestFactory.*; import static org.assertj.core.api.Assertions.*; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class InformationGrpcServiceTest { + public static final String CLIENT_NAME = "Antragsraum"; + public static final String POSTFACH_ID = "legacyPostkorbHandle"; @Spy @InjectMocks InformationGrpcService informationGrpcService; @@ -58,6 +67,12 @@ class InformationGrpcServiceTest { @Captor ArgumentCaptor<GrpcInformationResponse> informationResponseArgumentCaptor; + @Mock + private RequestAttributes requestAttributes; + + @Mock + private InfoManagerProperties infoManagerProperties; + @Nested class TestInformationAvailable { final NachrichtEvent firstNachrichtEvent = NachrichtEventTestFactory.create(); @@ -66,6 +81,7 @@ class InformationGrpcServiceTest { void init() { var information = List.of(firstNachrichtEvent, NachrichtEventTestFactory.create()); when(service.getInformationOfPostfach(anyString())).thenReturn(information); + when(infoManagerProperties.getPostfachIdKeyName()).thenReturn(POSTFACH_ID); } @Test @@ -108,6 +124,8 @@ class InformationGrpcServiceTest { class TestServiceUrlAvailable { @BeforeEach void init() { + when(infoManagerProperties.getAllowedClientNames()).thenReturn(List.of(CLIENT_NAME)); + when(requestAttributes.getClientName()).thenReturn(CLIENT_NAME); when(service.getUrlOfNachricht(anyString())).thenReturn(Optional.of(NachrichtEventTestFactory.URL)); } @@ -130,12 +148,37 @@ class InformationGrpcServiceTest { } } + @Nested + class TestServiceUrlAvailableSecurityError { + @BeforeEach + void init() { + when(infoManagerProperties.getAllowedClientNames()).thenReturn(List.of(CLIENT_NAME)); + when(requestAttributes.getClientName()).thenReturn("OTHER"); + } + + @Test + void shouldCallOnError() { + informationGrpcService.getServiceUrlOfNachricht( + GrpcServiceUrlRequest.newBuilder().setNachrichtId(NachrichtEventTestFactory.NACHRICHT_ID).build(), urlResponseObserver + ); + + verify(urlResponseObserver).onError(any(SecurityException.class)); + } + } + @Nested class TestNoInformationAvailable { @BeforeEach void init() { List<NachrichtEvent> information = List.of(); when(service.getInformationOfPostfach(anyString())).thenReturn(information); + var jwtAuth = mock(JwtAuthenticationToken.class); + var jwt = mock(Jwt.class); + when(jwt.getClaims()).thenReturn(Map.of(POSTFACH_ID, NachrichtEventTestFactory.POSTFACH_ID)); + when(jwtAuth.getToken()).thenReturn(jwt); + SecurityContextHolder.getContext().setAuthentication(jwtAuth); + + when(infoManagerProperties.getPostfachIdKeyName()).thenReturn(POSTFACH_ID); } @Test @@ -160,8 +203,86 @@ class InformationGrpcServiceTest { } } + @Nested + class TestAccessCheck { + @Mock + private JwtAuthenticationToken token; + + @Mock + private Jwt jwt; + + @Mock + StreamObserver<GrpcInformationResponse> responseStreamObserver; + + @BeforeEach + void init() { + SecurityContextHolder.getContext().setAuthentication(token); + when(infoManagerProperties.getPostfachIdKeyName()).thenReturn(POSTFACH_ID); + } + + @Test + void shouldAccessPostfach() { + Map<String, Object> claims = Map.of(POSTFACH_ID, NachrichtEventTestFactory.POSTFACH_ID); + when(jwt.getClaims()).thenReturn(claims); + when(token.getToken()).thenReturn(jwt); + + var res = informationGrpcService.checkForAccessToPostfach(NachrichtEventTestFactory.POSTFACH_ID, responseStreamObserver); + + assertThat(res).isTrue(); + } + + @Test + void shouldNotAccessPostfach() { + Map<String, Object> claims = Map.of(POSTFACH_ID, NachrichtEventTestFactory.POSTFACH_ID); + when(jwt.getClaims()).thenReturn(claims); + when(token.getToken()).thenReturn(jwt); + + var res = informationGrpcService.checkForAccessToPostfach(EMPTY_POSTFACH_ID, responseStreamObserver); + + assertThat(res).isFalse(); + } + + @Test + void shouldThrowSecurityException() { + Map<String, Object> claims = Map.of(POSTFACH_ID, NachrichtEventTestFactory.POSTFACH_ID); + when(jwt.getClaims()).thenReturn(claims); + when(token.getToken()).thenReturn(jwt); + + informationGrpcService.checkForAccessToPostfach(EMPTY_POSTFACH_ID, responseStreamObserver); + + verify(responseStreamObserver).onError(any(SecurityException.class)); + } + + @Test + void shouldThrowIllegalStateException() { + when(jwt.getClaims()).thenReturn(Map.of()); + when(token.getToken()).thenReturn(jwt); + + informationGrpcService.checkForAccessToPostfach(POSTFACH_ID, responseStreamObserver); + + verify(responseStreamObserver).onError(any(IllegalStateException.class)); + } + } + + @Nested + class TestExceptionOnOtherPostfachId { + @Mock + StreamObserver<GrpcInformationResponse> responseStreamObserver; + + @Test + void shouldThrowIllegalStateException() { + SecurityContextHolder.getContext().setAuthentication(mock(Authentication.class)); + + informationGrpcService.checkForAccessToPostfach(EMPTY_POSTFACH_ID, responseStreamObserver); + + verify(responseStreamObserver).onError(any(IllegalStateException.class)); + } + } + @Test void shouldCallOnCompleteForGrpcInformationRequest() { + SecurityContextHolder.getContext().setAuthentication(mock(Authentication.class)); + informationGrpcService.getInformation( GrpcInformationRequest.newBuilder().setPostfachId(NachrichtEventTestFactory.POSTFACH_ID).build(), informationResponseStreamObserver ); @@ -171,6 +292,8 @@ class InformationGrpcServiceTest { @Test void shouldCallOnCompleteForGrpcServiceUrlRequest() { + when(requestAttributes.getClientName()).thenReturn(CLIENT_NAME); + informationGrpcService.getServiceUrlOfNachricht( GrpcServiceUrlRequest.newBuilder().setNachrichtId(NachrichtEventTestFactory.NACHRICHT_ID).build(), urlResponseObserver ); diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceITCase.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceWithEventsITCase.java similarity index 76% rename from ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceITCase.java rename to ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceWithEventsITCase.java index c82d9786ebe40101046536fee5500de4e9a3662f..84f457fdcbfed09c05ee414601e2cc465eb5eaff 100644 --- a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceITCase.java +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcServiceWithEventsITCase.java @@ -23,14 +23,19 @@ package de.mgm.bup.ozg.antragsraum.infomanager; +import de.mgm.bup.ozg.antragsraum.KeycloakExtension; import de.mgm.bup.ozg.antragsraum.NachrichtEventTestFactory; +import de.mgm.bup.ozg.antragsraum.SecurityConfiguration; +import io.grpc.StatusRuntimeException; import net.devh.boot.grpc.client.inject.GrpcClient; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import static de.mgm.bup.ozg.antragsraum.NachrichtEventTestFactory.*; import static org.assertj.core.api.Assertions.*; @SpringBootTest(properties = { @@ -38,29 +43,29 @@ import static org.assertj.core.api.Assertions.*; "grpc.server.port=-1", "grpc.client.inProcess.address=in-process:test" }) -@SpringJUnitConfig(classes = {InformationGrpcTestConfiguration.class}) +@SpringJUnitConfig(classes = {InformationGrpcTestConfiguration.class, UserWithEventsConfiguration.class, SecurityConfiguration.class}) +@ExtendWith(KeycloakExtension.class) @DirtiesContext -class InformationGrpcServiceITCase { +class InformationGrpcServiceWithEventsITCase { @GrpcClient("inProcess") private InformationServiceGrpc.InformationServiceBlockingStub service; @Nested class TestInformationAvailable { - @Test void shouldLoadInformation() { - var request = GrpcInformationRequest.newBuilder().setPostfachId(NachrichtEventTestFactory.POSTFACH_ID).build(); + var request = GrpcInformationRequest.newBuilder().setPostfachId(POSTFACH_ID).build(); var response = service.getInformation(request); var values = response.getNachrichtenList(); assertThat(values).hasSize(1); - assertThat(values.get(0).getPostfachId()).isEqualTo(NachrichtEventTestFactory.POSTFACH_ID); + assertThat(values.get(0).getPostfachId()).isEqualTo(POSTFACH_ID); } @Test void shouldLoadUrl() { - var request = GrpcServiceUrlRequest.newBuilder().setNachrichtId(NachrichtEventTestFactory.NACHRICHT_ID).build(); + var request = GrpcServiceUrlRequest.newBuilder().setNachrichtId(NACHRICHT_ID).build(); var response = service.getServiceUrlOfNachricht(request); @@ -69,26 +74,13 @@ class InformationGrpcServiceITCase { } @Nested - class TestNoInformationAvailable { - - @Test - void responseShouldHaveEmptyList() { - var request = GrpcInformationRequest.newBuilder().setPostfachId("nothing").build(); - - var response = service.getInformation(request); - - var values = response.getNachrichtenList(); - assertThat(values).isEmpty(); - } + class TestNoAccess { @Test - void responseShouldHaveEmptyUrl() { - var request = GrpcServiceUrlRequest.newBuilder().setNachrichtId("nothing").build(); - - var response = service.getServiceUrlOfNachricht(request); + void responseShouldThrowStatusRuntimeException() { + var request = GrpcInformationRequest.newBuilder().setPostfachId("not_the_postfach_id_of_the_user").build(); - var values = response.getUrl(); - assertThat(values).isEmpty(); + assertThatExceptionOfType(StatusRuntimeException.class).isThrownBy(() -> service.getInformation(request)); } } } \ No newline at end of file diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcTestConfiguration.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcTestConfiguration.java index 2ff3086f7deafc8378f0028a53887fad5ca64b8f..ed1252a7d63b3b72afae914e4acddf5da1e42965 100644 --- a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcTestConfiguration.java +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/InformationGrpcTestConfiguration.java @@ -23,30 +23,47 @@ package de.mgm.bup.ozg.antragsraum.infomanager; import de.mgm.bup.ozg.antragsraum.NachrichtEventTestFactory; +import de.mgm.bup.ozg.antragsraum.common.RequestAttributes; +import lombok.extern.log4j.Log4j2; import net.devh.boot.grpc.client.autoconfigure.GrpcClientAutoConfiguration; +import net.devh.boot.grpc.client.autoconfigure.GrpcClientSecurityAutoConfiguration; +import net.devh.boot.grpc.common.autoconfigure.GrpcCommonCodecAutoConfiguration; +import net.devh.boot.grpc.common.autoconfigure.GrpcCommonTraceAutoConfiguration; import net.devh.boot.grpc.server.autoconfigure.GrpcServerAutoConfiguration; import net.devh.boot.grpc.server.autoconfigure.GrpcServerFactoryAutoConfiguration; +import net.devh.boot.grpc.server.autoconfigure.GrpcServerSecurityAutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; import java.util.Optional; +import java.util.UUID; import static org.mockito.Mockito.*; +@Log4j2 @Configuration @ImportAutoConfiguration({ + GrpcCommonCodecAutoConfiguration.class, + GrpcCommonTraceAutoConfiguration.class, GrpcServerAutoConfiguration.class, GrpcServerFactoryAutoConfiguration.class, - GrpcClientAutoConfiguration.class}) + GrpcServerSecurityAutoConfiguration.class, + GrpcClientAutoConfiguration.class, + GrpcClientSecurityAutoConfiguration.class}) public class InformationGrpcTestConfiguration { + + @Bean InformationService informationService() { var service = mock(InformationService.class); when( service.getInformationOfPostfach(NachrichtEventTestFactory.POSTFACH_ID)) .thenReturn(List.of(NachrichtEventTestFactory.create())); + when( + service.getInformationOfPostfach(NachrichtEventTestFactory.EMPTY_POSTFACH_ID)) + .thenReturn(List.of()); when( service.getUrlOfNachricht(NachrichtEventTestFactory.NACHRICHT_ID)) .thenReturn(Optional.of(NachrichtEventTestFactory.URL)); @@ -58,8 +75,19 @@ public class InformationGrpcTestConfiguration { return mock(InformationRepository.class); } + @Bean + RequestAttributes requestAttributes() { + return RequestAttributes.builder().clientName("Antragsraum").requestId(UUID.randomUUID().toString()).build(); + } + + @Bean + InfoManagerProperties infoManagerProperties() { + return new InfoManagerProperties(); + } + @Bean InformationGrpcService informationGrpcService() { - return new InformationGrpcService(informationService()); + return new InformationGrpcService(informationService(), requestAttributes(), infoManagerProperties()); } + } diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/UserWithEventsConfiguration.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/UserWithEventsConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..de71ff15198dceb24039b914d37a4c694ca5e21d --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/UserWithEventsConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum.infomanager; + +import de.mgm.bup.ozg.antragsraum.TokenSupplier; +import io.grpc.CallCredentials; +import net.devh.boot.grpc.client.security.CallCredentialsHelper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class UserWithEventsConfiguration { + @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") + private String issuerUrl; + + @Bean + CallCredentials grpcCredentials() { + return CallCredentialsHelper.bearerAuth(new TokenSupplier().get(issuerUrl, "jane.doe@ozg-cloud.com")); + } +} diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/UserWithOutEventsConfiguration.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/UserWithOutEventsConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..1afadcbc731fb98b04f94fb9aadfc27da549e585 --- /dev/null +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/infomanager/UserWithOutEventsConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023. + * Das Land Schleswig-Holstein vertreten durch den Ministerpräsidenten des Landes Schleswig-Holstein + * Staatskanzlei Abteilung Digitalisierung und zentrales IT-Management der Landesregierung + * + * Lizenziert unter der EUPL, Version 1.2 oder - sobald + * diese von der Europäischen Kommission genehmigt wurden - + * Folgeversionen der EUPL ("Lizenz"); + * Sie dürfen dieses Werk ausschließlich gemäß + * dieser Lizenz nutzen. + * Eine Kopie der Lizenz finden Sie hier: + * + * https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 + * + * Sofern nicht durch anwendbare Rechtsvorschriften + * gefordert oder in schriftlicher Form vereinbart, wird + * die unter der Lizenz verbreitete Software "so wie sie + * ist", OHNE JEGLICHE GEWÄHRLEISTUNG ODER BEDINGUNGEN - + * ausdrücklich oder stillschweigend - verbreitet. + * Die sprachspezifischen Genehmigungen und Beschränkungen + * unter der Lizenz sind dem Lizenztext zu entnehmen. + */ +package de.mgm.bup.ozg.antragsraum.infomanager; + +import de.mgm.bup.ozg.antragsraum.TokenSupplier; +import io.grpc.CallCredentials; +import net.devh.boot.grpc.client.security.CallCredentialsHelper; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class UserWithOutEventsConfiguration { + @Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}") + private String issuerUrl; + + @Bean + CallCredentials grpcCredentials() { + return CallCredentialsHelper.bearerAuth(new TokenSupplier().get(issuerUrl, "maxmustermann@ozg-cloud.com")); + } +} diff --git a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/nachrichteventmanager/NachrichtEventGrpcServiceITCase.java b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/nachrichteventmanager/NachrichtEventGrpcServiceITCase.java index 1c734e1f73fab8748f8850d05f6f3265192b7cc6..57a9262ebfad7b8454875494ab6562a95d31673e 100644 --- a/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/nachrichteventmanager/NachrichtEventGrpcServiceITCase.java +++ b/ozg-info-manager-server/src/test/java/de/mgm/bup/ozg/antragsraum/nachrichteventmanager/NachrichtEventGrpcServiceITCase.java @@ -22,8 +22,10 @@ */ package de.mgm.bup.ozg.antragsraum.nachrichteventmanager; +import de.mgm.bup.ozg.antragsraum.KeycloakExtension; import de.mgm.bup.ozg.antragsraum.NachrichtEvent; import de.mgm.bup.ozg.antragsraum.NachrichtEventTestFactory; +import de.mgm.bup.ozg.antragsraum.SecurityConfiguration; import de.mgm.bup.ozg.antragsraum.infomanager.GrpcFinishedNachrichtRequest; import de.mgm.bup.ozg.antragsraum.infomanager.GrpcNachricht; import de.mgm.bup.ozg.antragsraum.infomanager.GrpcNewNachrichtRequest; @@ -31,6 +33,7 @@ import de.mgm.bup.ozg.antragsraum.infomanager.NachrichtServiceGrpc; import net.devh.boot.grpc.client.inject.GrpcClient; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; @@ -42,7 +45,8 @@ import static org.assertj.core.api.Assertions.*; "grpc.server.port=-1", "grpc.client.inProcess.address=in-process:test" }) -@SpringJUnitConfig(classes = {NachrichtEventGrpcTestConfiguration.class}) +@SpringJUnitConfig(classes = {NachrichtEventGrpcTestConfiguration.class, SecurityConfiguration.class}) +@ExtendWith(KeycloakExtension.class) @DirtiesContext class NachrichtEventGrpcServiceITCase { diff --git a/ozg-info-manager-server/src/test/resources/application.properties b/ozg-info-manager-server/src/test/resources/application.properties index 4e6ecb06ecdc90ac715ee82bf7910a1bdaaedda5..02912f8fc43e14e7cbadfde1212a8aea22d84d67 100644 --- a/ozg-info-manager-server/src/test/resources/application.properties +++ b/ozg-info-manager-server/src/test/resources/application.properties @@ -22,4 +22,5 @@ spring.data.mongodb.database=InformationManager spring.data.mongodb.port=${mongodb.container.port} spring.data.mongodb.host=localhost -spring.data.mongodb.auto-index-creation=true \ No newline at end of file +spring.data.mongodb.auto-index-creation=true +logging.level.org.springframework.security=DEBUG diff --git a/ozg-info-manager-server/src/test/resources/realm-export.json b/ozg-info-manager-server/src/test/resources/realm-export.json new file mode 100644 index 0000000000000000000000000000000000000000..2b74987ddf6d4add48e52b736f63af40ef039472 --- /dev/null +++ b/ozg-info-manager-server/src/test/resources/realm-export.json @@ -0,0 +1,1845 @@ +{ + "id": "antragsraum", + "realm": "antragsraum", + "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, + "defaultRole": { + "id": "ae0de638-8b16-4d7f-9c1d-e1a55129f895", + "name": "default-roles-antragsraum", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "antragsraum" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account" + ] + } + ] + }, + "clients": [ + { + "id": "f4acf69c-1490-444e-9b1a-dfa4016b54a1", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/antragsraum/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/antragsraum/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "cbfe941f-fe9a-4b79-aeb6-043b7993ffcc", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/antragsraum/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/antragsraum/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "bfff2bda-a626-489f-8b07-9989582dd452", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "6c504e4d-4ad5-48ee-a47b-6c31d8d9f67e", + "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", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "c683c33f-a584-46b1-af9f-d401ba93af85", + "clientId": "antragsraum-api", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "protocolMappers": [ + { + "id": "c5237a00-d3ea-4e87-9caf-5146b02d1a15", + "name": "PostkorbHandle", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "postkorbhandle", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "legacyPostkorbHandle", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "d71dc523-1f66-4cce-a23f-83a13ba3744b", + "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", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "36b87703-dfab-4c8e-b2a6-2bd96c89c141", + "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", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "440114dd-9b2c-4c4b-b192-8dec233715db", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/antragsraum/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/antragsraum/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "20f83504-601b-43d8-8725-38af0d7471d8", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "b7ffedbd-ba94-4fd4-ba1e-0145252e10ef", + "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": "7f93ba40-1f5e-48ff-b332-3cfe659072cb", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + }, + { + "id": "014cf82a-ac04-4fff-880b-019a225d3bbd", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "0c36e47f-4e3d-4cad-aa18-5521d3bbc7f2", + "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": "abf78f87-6282-4a04-9ea6-4270978c7507", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "171f5552-b4dc-45c1-b166-5738b7bbb08d", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "92c00b3e-ec76-4497-aa56-3df225e82e24", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "b1856e10-88e1-4f14-b9f0-6673523f6e39", + "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": "d94d0d20-6329-47ce-a4d4-b0f80d702ab7", + "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": "9dc9f261-26ed-4dab-bdbb-735f7a50f90e", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "b9e049bb-7cbf-483a-a55a-c777b7403229", + "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": "35bdeedc-92e7-484b-9282-70574247d263", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + }, + { + "id": "ab2aac65-a55e-406e-b42e-40d42e6c4388", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + } + ] + }, + { + "id": "c8df543e-eb97-42e6-8aa2-63a8252afc9e", + "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": "b8714689-b19f-40da-b184-cc882028e8a4", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + }, + { + "id": "e00ad84f-2f3c-4f13-be8a-3931103042c6", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "54a0ac8d-4b46-413a-8d63-1822b424e2f1", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "d52bd17c-98c1-4dcc-9e16-5736ddf6347e", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "18f13b5b-b7b3-4daa-afc5-ec25d6395075", + "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": "9371941e-b465-4413-a742-1036fafb2485", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "09b1bbe9-5be6-477f-bcf5-bb661d7fe2a2", + "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": "58083ae1-5f33-4830-b975-e9f8da33920f", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "id": "6a87985d-12e6-4ea6-9e40-14fe46ad4668", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "d2393d95-5f3e-4f08-a876-d4971845e5ad", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "7ce8e5e6-2f50-4b32-bd0a-7db014146baf", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "e935814c-cf70-447d-85da-854bee14f403", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "b2a73b0c-fd84-4894-85cf-05b9a8b9a329", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "1f4d67e2-7083-4bb8-a666-361a6762161d", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "5425cfb0-f259-43ba-bd31-8ab2b935e76c", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + }, + { + "id": "2fb74508-49c3-46dd-b620-61a4a461c7bc", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "457d4a3f-f47e-4ed4-a7da-5950c822e434", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "d2748ba4-aac9-486f-bbfc-ec398e273eec", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "6e78fd09-6cac-4c66-84e4-299b58a1eefc", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "e44bd071-8a85-49a3-8649-09985f7e8a61", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "da7000a8-8a8b-4df7-a4b9-7f78f3d13406", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "profile", + "roles", + "web-origins", + "role_list", + "email" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "phone", + "microprofile-jwt", + "address" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": {}, + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "80d5424d-79ab-44f4-818e-bc3b593641de", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "b578a18a-e340-49a1-ace1-8092ed29f28c", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-property-mapper", + "oidc-usermodel-property-mapper", + "saml-role-list-mapper", + "oidc-address-mapper", + "saml-user-attribute-mapper" + ] + } + }, + { + "id": "9311fd52-8330-4065-b987-7a0ce8589d6a", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "dcd5d2fe-da81-492e-ae6b-c026792e3154", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "1ed1d774-d6d9-45ee-8a22-61125a01a479", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "717825ce-f267-4468-99cb-b09126b00570", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "6fb4db12-299d-4b7e-a100-8c318248d5e3", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "bf2d5422-07a0-4317-9e01-0d2a1be9b582", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "saml-role-list-mapper", + "oidc-usermodel-attribute-mapper", + "saml-user-attribute-mapper", + "oidc-address-mapper", + "oidc-full-name-mapper", + "saml-user-property-mapper", + "oidc-sha256-pairwise-sub-mapper" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "be18f5ef-4151-43e2-a607-1852f53acc76", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "52fb51ee-b5aa-41c3-88d2-a0b11b6d4072", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "9e839a58-a36e-420f-bebc-7466c4b578c9", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "93d3b307-cf5a-484c-a612-43cb4923501a", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + } + ] + }, + "internationalizationEnabled": false, + "supportedLocales": [], + "authenticationFlows": [ + { + "id": "e285f1b9-bc15-4457-851d-abc9cb54953d", + "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": "65944e23-1f0d-47cf-b5d8-358463b0d382", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "235ca7f6-27ad-4817-a686-cec020f7a091", + "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": "3d8dc16b-07e8-4535-ad1a-4ce79df1dd07", + "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": "3fa38b47-45a2-4eea-85a6-620b877db701", + "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": "126eee75-5f90-4a46-92c5-6fbb209d764a", + "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": "d9d8e610-41f8-45e3-a097-9961aa5e6d5f", + "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": "b01b7a9e-597b-4a12-8ccb-bfb707686dbf", + "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": "33a40ef5-e611-4182-b1a5-55aa930a839b", + "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": "5e148805-8a3f-4cbd-9a75-862da8e3fcdf", + "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": "38fd27d7-9914-47b3-aa11-1079cc059236", + "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": "58a3dcd5-56d1-4003-a1a1-dfafaf71ffa1", + "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": "4ed42c08-7dc6-47bf-8ac6-27e262973fdb", + "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": "b4ff61a6-56c7-4160-9047-b9abab13cfd9", + "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": "68b55fff-3f1e-4cce-a181-0b4fdc8ab1e5", + "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": "d3456a10-f6a1-4767-9385-baed652e82c4", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Authentication Options", + "userSetupAllowed": false + } + ] + }, + { + "id": "7b50c4ff-25c2-4ff9-8f4b-95c1144bb30a", + "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": "edc22a19-8b8d-4bca-bdfb-446df319e4b5", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "b5f2931c-badf-4c09-96fb-7a0af5860b9f", + "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": "cc118d9d-31b8-4fa9-ba7c-2fc65936612a", + "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": "5219b9c4-0664-4b61-8fd4-c9ecc9954c4b", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "f48e0b44-86c9-4db0-87da-97433d873b80", + "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": "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" + }, + "keycloakVersion": "17.0.1", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + }, + "users": [ + { + "username": "janedoe", + "email": "jane.doe@ozg-cloud.com", + "firstName": "Jane", + "lastName": "Doe", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "s3cr3t" + } + ], + "clientRoles": { + "account": [ + "view-profile", + "manage-account" + ] + }, + "attributes": { + "postkorbhandle": "abc123-jsdkfjks-sdfhks-2636hdh" + } + }, + { + "username": "maxmustermann", + "email": "maxmustermann@ozg-cloud.com", + "firstName": "Max", + "lastName": "Mustermann", + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "s3cr3t" + } + ], + "clientRoles": { + "account": [ + "view-profile", + "manage-account" + ] + }, + "attributes": { + "postkorbhandle": "73228744-a28a-4485-845b-d8cc02422760" + } + } + ] +} \ No newline at end of file diff --git a/ozg-info-manager-server/src/test/resources/user.json b/ozg-info-manager-server/src/test/resources/user.json new file mode 100644 index 0000000000000000000000000000000000000000..01f2c01cb71623d8bd06009868590522888e964c --- /dev/null +++ b/ozg-info-manager-server/src/test/resources/user.json @@ -0,0 +1,15 @@ +{ + "iss": "https://localhost/realms/antragsraum", + "sub": "281c4558-550c-413b-9972-2d2e5bde6b9b", + "iat": 1695992542, + "exp": 1695992642, + "preferred_username": "janedoe", + "realm_access": { + "roles": [ + "admin", + "ROLE_AUTHORIZED_PERSONNEL" + ] + }, + "email": "jane.doe@ozg-cloud.com", + "scope": "openid email" +} \ No newline at end of file