diff --git a/.gitignore b/.gitignore index 549e00a2a96fa9d7c5dbc9859664a78d980158c2..9ddbf280ee467a8bf60fb940d990634d1f0cbabf 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ build/ ### VS Code ### .vscode/ + +bin/** diff --git a/Jenkinsfile b/Jenkinsfile index e43ba8c76af10922d1187684d5918787d207b504..75ff9a330f970e0ac309847e5206523235a2eceb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -106,32 +106,16 @@ pipeline { } } - stage ('Deploy SBOM to DependencyTrack') { - steps { - script { - IMAGE_TAG = buildVersionName() - - configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) { - withCredentials([string(credentialsId: 'dependency-track-api-key', variable: 'API_KEY')]) { - - catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { - sh "mvn --no-transfer-progress -s $MAVEN_SETTINGS io.github.pmckeown:dependency-track-maven-plugin:upload-bom -Ddependency-track.apiKey=$API_KEY -Ddependency-track.projectVersion=${IMAGE_TAG} -Ddependency-track.dependencyTrackBaseUrl=https://dependency-track.ozg-sh.de" - } - } - } - } - } - } - stage('Build and publish Docker image') { steps { script { + IMAGE_TAG = buildVersionName() FAILED_STAGE=env.STAGE_NAME } configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) { withCredentials([usernamePassword(credentialsId: 'jenkins-nexus-login', usernameVariable: 'USER', passwordVariable: 'PASSWORD')]) { - sh 'mvn -s $MAVEN_SETTINGS spring-boot:build-image -DskipTests -Dmaven.wagon.http.retryHandler.count=3 $BUILD_PROFILE -Ddocker.publishRegistry.username=${USER} -Ddocker.publishRegistry.password=${PASSWORD} -Dbuild.number=$BUILD_NUMBER -DimageTag=$IMAGE_TAG -DpublishImage=true' + sh 'mvn --no-transfer-progress -s $MAVEN_SETTINGS -Dspring-boot.build-image.publish=true -DskipTests -Dmaven.wagon.http.retryHandler.count=3 $BUILD_PROFILE -Ddocker.publishRegistry.username=${USER} -Ddocker.publishRegistry.password=${PASSWORD} -Dbuild.number=$BUILD_NUMBER spring-boot:build-image -Dspring-boot.build-image.imageName=docker.ozg-sh.de/administration:${IMAGE_TAG}' } } } @@ -189,6 +173,23 @@ pipeline { } } } + + stage ('Deploy SBOM to DependencyTrack') { + steps { + script { + IMAGE_TAG_WO_COMMIT = buildVersionNameWithoutCommidId() + + configFileProvider([configFile(fileId: 'maven-settings', variable: 'MAVEN_SETTINGS')]) { + withCredentials([string(credentialsId: 'dependency-track-api-key', variable: 'API_KEY')]) { + + catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { + sh "mvn --no-transfer-progress -s $MAVEN_SETTINGS io.github.pmckeown:dependency-track-maven-plugin:upload-bom -Ddependency-track.apiKey=$API_KEY -Ddependency-track.projectVersion=${IMAGE_TAG_WO_COMMIT} -Ddependency-track.dependencyTrackBaseUrl=https://dependency-track.ozg-sh.de" + } + } + } + } + } + } } post { @@ -241,6 +242,12 @@ String buildVersionName() { } return "${getPomVersion()}-${validateBranchName(env.BRANCH_NAME)}-${env.GIT_COMMIT.take(7)}".replaceAll("_", "-") } +String buildVersionNameWithoutCommidId() { + if (isReleaseBranch()) { + return getPomVersion() + } + return "${getPomVersion()}-${validateBranchName(env.BRANCH_NAME)}".replaceAll("_", "-") +} String getPomVersion() { def pom = readMavenPom file: 'pom.xml' diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..e3822b358f647ddbe6db35957b929ea29326cc0d --- /dev/null +++ b/README.md @@ -0,0 +1,27 @@ +# Administration +Die Anwendung Administration der OZG-Cloud stellt ein Web-UI zur Verwaltung der globalen Einstellungen der OZG-Cloud zur Verfügung. Über dieses erfolgt die fachliche Administration der OZG-Cloud durch den Mandanten oder eine durch den Mandanten beauftragte Institution. + +## Architektur + +### Client + +Der Client ist mit Angular entwickelt und bindet sich per REST-API an das Backend. Der Client wird im mono-repo der OZG-Cloud Clients mit entwickelt und ist ebenfalls auf [code.schleswig-holstein.de](https://code.schleswig-holstein.de/ozg-cloud/app/frontend-clients/-/tree/main/alfa-client/apps/admin?ref_type=heads) zu finden. + +### Backend + +Das Backend (in diesem Repo zu finden) ist eine spring-boot basierte Java Anwendung. + +* spring data rest - das Rest Api für den Client wird per spring data rest aufgebaut. +* spring cloud server - zur Übertragung der Konfigurationen an die einzelnen Dienste der OZG-Cloud (im Kontext der OZG-Cloud 'Manager' genannt) wird eine Schnittstelle nach dem spring cloud server Standard bereit gestellt. + +Die Konfiguration wird in einer MongoDB gespeichert. + +## Entwicklung + +Um ein neue Konfiguration einzubauen, müssen folgende Klassen angelegt werden: + +* Datenklassen - Die Klassen mit den eigentlichen Konfigurationsparameter. Die Klasse wird nach der Fachlichkeit benannt. Diese wird mit @TypeAlias annotiert. +* Repository - MongoRepository mit @RepositoryRestResource annotation. Die findAll Methode muss überschrieben werden. +* Klasse als Datentransferobjekt (DTO) - Format für die Spring Config Server Schnittstelle für einen Manager. Bennenung daher: FachlichkeitManagerSettingDto.java +* Mapper - Mapped die Konfigurationsparamter in das Dto. +* DtoService - Stellt die Settings für einen MAnager bereit. Wird mit @DtoService annotiert. Dadurch ist es eine Spring-Bean und wird automatisch aufgerufen, wenn eine Konfiguration für den jeweiligen Manager geladen wird. Benennung: FachlichkeitManagerSettingDtoService \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0b91b4cf1a7876b05b238bace29be0cbb60b9f61..92c640bfb567d54ec0e7cccf307744ae3b0e7dad 100644 --- a/pom.xml +++ b/pom.xml @@ -31,9 +31,10 @@ <parent> <groupId>de.ozgcloud.common</groupId> <artifactId>ozgcloud-common-parent</artifactId> - <version>4.6.0</version> + <version>4.9.0-SNAPSHOT</version> <relativePath/> </parent> + <groupId>de.ozgcloud</groupId> <artifactId>administration</artifactId> <version>1.3.0-SNAPSHOT</version> @@ -46,12 +47,12 @@ <imageTag>build-latest</imageTag> <publishImage>false</publishImage> <build.number>SET_BY_JENKINS</build.number> - <spring-cloud-config-server.version>4.1.2</spring-cloud-config-server.version> + + <spring-cloud-config-server.version>4.1.4</spring-cloud-config-server.version> <testcontainers-keycloak.version>3.3.1</testcontainers-keycloak.version> - <keycloak-admin-client.version>24.0.5</keycloak-admin-client.version> + <mongock.version>5.4.0</mongock.version> - <lombok-mapstruct-binding.version>0.2.0</lombok-mapstruct-binding.version> - <mapstruct-processor.version>${mapstruct.version}</mapstruct-processor.version> + <zufi-manager.version>1.6.0</zufi-manager.version> <shedlock.version>5.16.0</shedlock.version> </properties> @@ -66,42 +67,46 @@ <!-- Spring --> <dependency> - <groupId>net.devh</groupId> - <artifactId>grpc-client-spring-boot-starter</artifactId> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> - <groupId>io.grpc</groupId> - <artifactId>grpc-inprocess</artifactId> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-actuator</artifactId> + <artifactId>spring-boot-starter-hateoas</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-data-mongodb</artifactId> + <artifactId>spring-boot-starter-security</artifactId> </dependency> + <dependency> <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-web</artifactId> + <artifactId>spring-boot-starter-data-mongodb</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-data-rest</artifactId> + <artifactId>spring-boot-starter-actuator</artifactId> </dependency> + <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-hateoas</artifactId> + <groupId>net.devh</groupId> + <artifactId>grpc-client-spring-boot-starter</artifactId> + </dependency> + <dependency> + <groupId>io.grpc</groupId> + <artifactId>grpc-inprocess</artifactId> </dependency> + <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-server</artifactId> <version>${spring-cloud-config-server.version}</version> </dependency> - <dependency> - <groupId>org.springframework.boot</groupId> - <artifactId>spring-boot-starter-security</artifactId> - </dependency> + <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> @@ -132,7 +137,6 @@ <dependency> <groupId>org.keycloak</groupId> <artifactId>keycloak-admin-client</artifactId> - <version>${keycloak-admin-client.version}</version> </dependency> <!-- tools --> @@ -143,7 +147,7 @@ <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> - <version>${mapstruct-processor.version}</version> + <version>${mapstruct.version}</version> </dependency> <!-- commons --> @@ -266,6 +270,7 @@ </plugin> </plugins> </pluginManagement> + <plugins> <plugin> <groupId>org.sonarsource.scanner.maven</groupId> @@ -282,60 +287,10 @@ <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> - <configuration> - <image> - <name>${imageName}:${imageTag}</name> - <publish>${publishImage}</publish> - </image> - <docker> - <publishRegistry> - <username>${docker.publishRegistry.username}</username> - <password>${docker.publishRegistry.password}</password> - </publishRegistry> - </docker> - <excludes> - <exclude> - <groupId>org.projectlombok</groupId> - <artifactId>lombok</artifactId> - </exclude> - </excludes> - </configuration> - <executions> - <execution> - <id>build-info</id> - <goals> - <goal>build-info</goal> - </goals> - <configuration> - <additionalProperties> - <number>${build.number}</number> - </additionalProperties> - </configuration> - </execution> - </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <configuration> - <annotationProcessorPaths> - <path> - <groupId>org.mapstruct</groupId> - <artifactId>mapstruct-processor</artifactId> - <version>${mapstruct-processor.version}</version> - </path> - <path> - <groupId>org.projectlombok</groupId> - <artifactId>lombok</artifactId> - <version>${lombok.version}</version> - </path> - <path> - <groupId>org.projectlombok</groupId> - <artifactId>lombok-mapstruct-binding</artifactId> - <version>${lombok-mapstruct-binding.version}</version> - </path> - </annotationProcessorPaths> - </configuration> </plugin> </plugins> </build> diff --git a/src/main/java/de/ozgcloud/admin/AdministrationRepositoryRestConfigurer.java b/src/main/java/de/ozgcloud/admin/AdministrationRepositoryRestConfigurer.java index 2ab9b687e22cad4e59a978b6558a6a366f60895a..31bcf3b864fd772838ebe1ef3555084c984af6b6 100644 --- a/src/main/java/de/ozgcloud/admin/AdministrationRepositoryRestConfigurer.java +++ b/src/main/java/de/ozgcloud/admin/AdministrationRepositoryRestConfigurer.java @@ -23,17 +23,20 @@ */ package de.ozgcloud.admin; +import jakarta.validation.Validation; + import org.springframework.context.annotation.Configuration; -import org.springframework.data.rest.core.config.RepositoryRestConfiguration; +import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener; import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer; -import org.springframework.hateoas.server.core.DefaultLinkRelationProvider; -import org.springframework.web.servlet.config.annotation.CorsRegistry; + +import de.ozgcloud.admin.common.DelegatingValidatorAdapter; @Configuration public class AdministrationRepositoryRestConfigurer implements RepositoryRestConfigurer { @Override - public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config, CorsRegistry cors) { - config.setLinkRelationProvider(new DefaultLinkRelationProvider()); + public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener listener) { + var validator = Validation.buildDefaultValidatorFactory().getValidator(); + listener.addValidator("beforeCreate", new DelegatingValidatorAdapter(validator)); } } diff --git a/src/main/java/de/ozgcloud/admin/OzgCloudRestLinksConfiguration.java b/src/main/java/de/ozgcloud/admin/OzgCloudRestLinksConfiguration.java new file mode 100644 index 0000000000000000000000000000000000000000..5bf4c55db0af6b7785b80fac20e6c047a4f264c4 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/OzgCloudRestLinksConfiguration.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2025 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.ozgcloud.admin; + +import java.util.Optional; + +import org.aopalliance.intercept.MethodInvocation; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; +import org.springframework.data.mapping.PersistentProperty; +import org.springframework.data.mapping.context.PersistentEntities; +import org.springframework.data.repository.support.Repositories; +import org.springframework.data.rest.core.Path; +import org.springframework.data.rest.core.config.RepositoryRestConfiguration; +import org.springframework.data.rest.core.mapping.PropertyAwareResourceMapping; +import org.springframework.data.rest.core.mapping.RepositoryResourceMappings; +import org.springframework.data.rest.core.mapping.ResourceDescription; +import org.springframework.data.rest.core.mapping.ResourceMapping; +import org.springframework.data.rest.core.mapping.ResourceMetadata; +import org.springframework.data.rest.core.mapping.SearchResourceMappings; +import org.springframework.data.rest.core.mapping.SupportedHttpMethods; +import org.springframework.hateoas.LinkRelation; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.method.SecuredAuthorizationManager; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.util.SimpleMethodInvocation; + +import de.ozgcloud.common.errorhandling.TechnicalException; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; + +@Configuration +class OzgCloudRestLinksConfiguration { + @Primary + @Bean + RepositoryResourceMappings ozgCloudResourceMappings(Repositories repositories, PersistentEntities persistentEntities, + RepositoryRestConfiguration repositoryRestConfiguration) { + return new OzgCloudDelegatingRepositoryResourceMappings(repositories, persistentEntities, repositoryRestConfiguration); + } +} + +@Log4j2 +class OzgCloudDelegatingRepositoryResourceMappings extends RepositoryResourceMappings { + + private final Repositories repositories; + + public OzgCloudDelegatingRepositoryResourceMappings(Repositories repositories, PersistentEntities entities, + RepositoryRestConfiguration configuration) { + super(repositories, entities, configuration); + + this.repositories = repositories; + } + + @Override + public ResourceMetadata getMetadataFor(Class<?> type) { + return new OzgCloudDelegatingResourceMetadata(super.getMetadataFor(type), repositories); + } + + @RequiredArgsConstructor + static class OzgCloudDelegatingResourceMetadata implements ResourceMetadata { + + private final ResourceMetadata metadata; + private final Repositories repositories; + private final SecuredAuthorizationManager authManager = new SecuredAuthorizationManager(); + + @Override + public LinkRelation getItemResourceRel() { + return metadata.getItemResourceRel(); + } + + @Override + public ResourceDescription getItemResourceDescription() { + return metadata.getItemResourceDescription(); + } + + @Override + public Optional<Class<?>> getExcerptProjection() { + return metadata.getExcerptProjection(); + } + + @Override + public boolean isExported() { + return isAccessPermitted(metadata.getDomainType()) && metadata.isExported(); + } + + boolean isAccessPermitted(Class<?> type) { + var repository = repositories.getRepositoryFor(type); + return repository.map(repo -> authManager.check(() -> SecurityContextHolder.getContext().getAuthentication(), getFindAllInvocation(repo))) + .map(AuthorizationDecision::isGranted) + .orElse(true); + } + + MethodInvocation getFindAllInvocation(Object repository) { + try { + var method = repository.getClass().getMethod("findAll"); + return new SimpleMethodInvocation(repository, method); + } catch (NoSuchMethodException | SecurityException e) { + LOG.error(e.getMessage(), e); + throw new TechnicalException("Error on extracting findAll Repository Method", e); + } + } + + @Override + public LinkRelation getRel() { + return metadata.getRel(); + } + + @Override + public Path getPath() { + return metadata.getPath(); + } + + @Override + public boolean isPagingResource() { + return metadata.isPagingResource(); + } + + @Override + public ResourceDescription getDescription() { + return metadata.getDescription(); + } + + @Override + public Class<?> getDomainType() { + return metadata.getDomainType(); + } + + @Override + public boolean isExported(PersistentProperty<?> property) { + return metadata.isExported(property); + } + + @Override + public PropertyAwareResourceMapping getProperty(String mappedPath) { + return metadata.getProperty(mappedPath); + } + + @Override + public ResourceMapping getMappingFor(PersistentProperty<?> property) { + return metadata.getMappingFor(property); + } + + @Override + public SearchResourceMappings getSearchResourceMappings() { + return metadata.getSearchResourceMappings(); + } + + @Override + public SupportedHttpMethods getSupportedHttpMethods() { + return metadata.getSupportedHttpMethods(); + } + } +} diff --git a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java index 4d54c2e3ad0bbaee5d719022999b34a735022257..4feced27c6e16b2ecfc4837c17adf9a8e3468bbe 100644 --- a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java +++ b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java @@ -23,8 +23,9 @@ */ package de.ozgcloud.admin; -import de.ozgcloud.admin.common.user.CurrentUserService; -import de.ozgcloud.admin.common.user.UserRole; +import java.util.ArrayList; +import java.util.List; + import org.springframework.boot.autoconfigure.data.rest.RepositoryRestProperties; import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.Link; @@ -34,9 +35,6 @@ import org.springframework.stereotype.Component; import lombok.RequiredArgsConstructor; -import java.util.ArrayList; -import java.util.List; - @Component @RequiredArgsConstructor public class RootModelAssembler implements RepresentationModelAssembler<Root, EntityModel<Root>> { @@ -44,23 +42,17 @@ public class RootModelAssembler implements RepresentationModelAssembler<Root, En private final RepositoryRestProperties restProperties; - private final CurrentUserService currentUserService; - @Override public EntityModel<Root> toModel(Root root) { List<Link> links = buildRootModelLinks(); - return EntityModel.of( - root, - links); + return EntityModel.of(root, links); } List<Link> buildRootModelLinks() { List<Link> links = new ArrayList<>(); var rootLinkBuilder = WebMvcLinkBuilder.linkTo(RootController.class); links.add(rootLinkBuilder.withSelfRel()); - if (currentUserService.hasRole(UserRole.ADMIN_ADMIN)) { - links.add(buildConfigLink()); - } + links.add(buildConfigLink()); return links; } @@ -68,7 +60,6 @@ public class RootModelAssembler implements RepresentationModelAssembler<Root, En var rootLinkBuilder = WebMvcLinkBuilder.linkTo(RootController.class); return Link.of( rootLinkBuilder.toUriComponentsBuilder().replacePath(restProperties.getBasePath()).toUriString(), - REL_CONFIGURATION - ); + REL_CONFIGURATION); } } diff --git a/src/main/java/de/ozgcloud/admin/common/AbstractLinkedResourceDeserializer.java b/src/main/java/de/ozgcloud/admin/common/AbstractLinkedResourceDeserializer.java deleted file mode 100644 index 106c3616b4a08d51e8ee413587fed710fc0dba98..0000000000000000000000000000000000000000 --- a/src/main/java/de/ozgcloud/admin/common/AbstractLinkedResourceDeserializer.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.URLDecoder; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; - -import org.apache.commons.lang3.StringUtils; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.BeanProperty; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.deser.ContextualDeserializer; -import com.fasterxml.jackson.databind.deser.std.StdDeserializer; - -import de.ozgcloud.common.datatype.StringBasedValue; -import lombok.Getter; - -abstract class AbstractLinkedResourceDeserializer extends StdDeserializer<Object> implements ContextualDeserializer { - - private static final long serialVersionUID = 1L; - - @Getter - private BeanProperty beanProperty; - - @Getter - private final JavaType targetType; - - protected AbstractLinkedResourceDeserializer() { - super(Object.class); - targetType = null; - } - - protected AbstractLinkedResourceDeserializer(BeanProperty beanProperty) { - super(Object.class); - this.beanProperty = beanProperty; - this.targetType = beanProperty.getType(); - } - - @Override - public Object deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException { - if (jsonParser.isExpectedStartArrayToken()) { - Collection<Object> idList = targetType.getRawClass().isAssignableFrom(Set.class) ? new HashSet<>() : new ArrayList<>(); - - while (!jsonParser.nextToken().isStructEnd()) { - idList.add(extractId(jsonParser.getText())); - } - return idList; - } else { - return extractId(jsonParser.getText()); - } - } - - Object extractId(String url) { - Class<?> type; - if (targetType.isCollectionLikeType()) { - type = targetType.getContentType().getRawClass(); - } else { - type = targetType.getRawClass(); - } - - if (String.class.isAssignableFrom(type)) { - return extractStringId(url); - } - if (Long.class.isAssignableFrom(type) || Long.TYPE.isAssignableFrom(type)) { - return extractLongId(url); - } - if (StringBasedValue.class.isAssignableFrom(type)) { - return extractStringBasedValue(type, url); - } - return buildByBuilder(url); - } - - abstract Object buildByBuilder(String url); - - public static Long extractLongId(String uri) { - var trimedUri = StringUtils.trimToNull(uri); - if (Objects.isNull(trimedUri)) { - return null; - } - return Long.parseLong(URLDecoder.decode(trimedUri.substring(trimedUri.lastIndexOf('/') + 1), StandardCharsets.UTF_8)); - } - - private StringBasedValue extractStringBasedValue(Class<?> type, String url) { - String value = extractStringId(url); - Method fromMethod; - try { - fromMethod = type.getMethod("from", String.class); - } catch (NoSuchMethodException e) { - throw new IllegalStateException( - String.format("Cannot generate Id from type '%s'. Missing 'from' Method.", targetType.getRawClass().getSimpleName())); - } - try { - return (StringBasedValue) fromMethod.invoke(null, value); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new IllegalStateException( - String.format("Cannot generate Id from type '%s'. Error calling 'from' Method.", targetType.getRawClass().getSimpleName()), - e); - } - } - - public static String extractStringId(String url) { - return URLDecoder.decode(url.substring(url.lastIndexOf('/') + 1), StandardCharsets.UTF_8); - } -} diff --git a/src/main/java/de/ozgcloud/admin/common/AbstractLinkedResourceSerializer.java b/src/main/java/de/ozgcloud/admin/common/AbstractLinkedResourceSerializer.java deleted file mode 100644 index aff16f22a29698cf591680353e4bf07800810d04..0000000000000000000000000000000000000000 --- a/src/main/java/de/ozgcloud/admin/common/AbstractLinkedResourceSerializer.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import java.io.IOException; -import java.util.Collection; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.ser.ContextualSerializer; - -import de.ozgcloud.common.errorhandling.TechnicalException; - -abstract class AbstractLinkedResourceSerializer extends JsonSerializer<Object> implements ContextualSerializer { - @Override - public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { - - if (value instanceof Collection) { - gen.writeStartArray(); - ((Collection<?>) value).forEach(val -> writeObject(gen, buildLink(val))); - gen.writeEndArray(); - } else { - writeObject(gen, buildLink(value)); - } - } - - abstract String buildLink(Object id); - - abstract IdExtractor<Object> getExtractor(); - - void writeObject(JsonGenerator gen, Object value) { - try { - gen.writeObject(value); - } catch (IOException e) { - throw new TechnicalException("Error writing String to json", e); - } - } -} diff --git a/src/main/java/de/ozgcloud/admin/setting/SettingValidator.java b/src/main/java/de/ozgcloud/admin/common/DelegatingValidatorAdapter.java similarity index 75% rename from src/main/java/de/ozgcloud/admin/setting/SettingValidator.java rename to src/main/java/de/ozgcloud/admin/common/DelegatingValidatorAdapter.java index 54ecb425791b213835169e3303fc74b2296abe4c..870b17f79246c3db1491c81b56ad9fd8efab4c18 100644 --- a/src/main/java/de/ozgcloud/admin/setting/SettingValidator.java +++ b/src/main/java/de/ozgcloud/admin/common/DelegatingValidatorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung @@ -21,27 +21,30 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.setting; +package de.ozgcloud.admin.common; + +import java.util.Objects; import jakarta.validation.ConstraintViolationException; -import jakarta.validation.Validation; -import org.springframework.stereotype.Component; import org.springframework.validation.Errors; import org.springframework.validation.Validator; -@Component -class SettingValidator implements Validator { +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class DelegatingValidatorAdapter implements Validator { + + private final jakarta.validation.Validator delegate; @Override public boolean supports(Class<?> clazz) { - return Setting.class.equals(clazz); + return !Objects.isNull(delegate); } @Override public void validate(Object target, Errors errors) { - var validator = Validation.buildDefaultValidatorFactory().getValidator(); - var constraintViolations = validator.validate(target); + var constraintViolations = delegate.validate(target); if (!constraintViolations.isEmpty()) { throw new ConstraintViolationException(constraintViolations); } diff --git a/src/main/java/de/ozgcloud/admin/common/ObjectBuilder.java b/src/main/java/de/ozgcloud/admin/common/DtoService.java similarity index 70% rename from src/main/java/de/ozgcloud/admin/common/ObjectBuilder.java rename to src/main/java/de/ozgcloud/admin/common/DtoService.java index a08385af64361f8e178d9a03451f81853eeda9d8..6e260a3fb3ff15674ca5b88717426c40df1ec3ac 100644 --- a/src/main/java/de/ozgcloud/admin/common/ObjectBuilder.java +++ b/src/main/java/de/ozgcloud/admin/common/DtoService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung @@ -23,11 +23,17 @@ */ package de.ozgcloud.admin.common; -import com.fasterxml.jackson.databind.BeanProperty; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; -public interface ObjectBuilder<T> { +import org.springframework.stereotype.Service; - T build(Object id); +@Service +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface DtoService { - ObjectBuilder<T> constructContextAware(BeanProperty property); + String[] value(); } diff --git a/src/main/java/de/ozgcloud/admin/common/LinkedResource.java b/src/main/java/de/ozgcloud/admin/common/LinkedResource.java deleted file mode 100644 index 4798f516bf52910ab2641ecba167540622dfb101..0000000000000000000000000000000000000000 --- a/src/main/java/de/ozgcloud/admin/common/LinkedResource.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Inherited; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -import org.springframework.core.annotation.AliasFor; - -import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -@Retention(RetentionPolicy.RUNTIME) -@Target(ElementType.FIELD) -@Inherited - -@JacksonAnnotationsInside -@JsonSerialize(using = LinkedResourceSerializer.class) -@JsonDeserialize(using = LinkedResourceDeserializer.class) -public @interface LinkedResource { - - Class<?> controllerClass(); - - Class<? extends IdExtractor<Object>> extractor() default ToStringExtractor.class; - - @AliasFor(annotation = JsonDeserialize.class, attribute = "builder") - Class<? extends ObjectBuilder<Object>> builder() default IdBuilder.class; -} \ No newline at end of file diff --git a/src/main/java/de/ozgcloud/admin/common/LinkedResourceDeserializer.java b/src/main/java/de/ozgcloud/admin/common/LinkedResourceDeserializer.java deleted file mode 100644 index 7130e16af20c1f774e599b3096bced026c1b7bbe..0000000000000000000000000000000000000000 --- a/src/main/java/de/ozgcloud/admin/common/LinkedResourceDeserializer.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import java.lang.reflect.InvocationTargetException; - -import org.apache.commons.lang3.reflect.ConstructorUtils; - -import com.fasterxml.jackson.databind.BeanProperty; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; - -import de.ozgcloud.common.errorhandling.TechnicalException; - -public class LinkedResourceDeserializer extends AbstractLinkedResourceDeserializer { - - private static final long serialVersionUID = 1L; - - private LinkedResource annotation; - - protected LinkedResourceDeserializer() { - super(); - } - - protected LinkedResourceDeserializer(BeanProperty beanProperty) { - super(beanProperty); - this.annotation = beanProperty.getAnnotation(LinkedResource.class); - } - - @Override - Object buildByBuilder(String url) { - ObjectBuilder<?> builder; - try { - builder = ConstructorUtils.invokeConstructor(annotation.builder()).constructContextAware(getBeanProperty()); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { - throw new TechnicalException("Error instanciating Builder.", e); - } - return builder.build(extractStringId(url)); - } - - @Override - public JsonDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) { - return new LinkedResourceDeserializer(property); - } - -} diff --git a/src/main/java/de/ozgcloud/admin/common/LinkedResourceSerializer.java b/src/main/java/de/ozgcloud/admin/common/LinkedResourceSerializer.java deleted file mode 100644 index 28a098c82185730476c0f85f4eb299a7bdd3ed13..0000000000000000000000000000000000000000 --- a/src/main/java/de/ozgcloud/admin/common/LinkedResourceSerializer.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; - -import java.lang.reflect.InvocationTargetException; - -import org.apache.commons.lang3.reflect.ConstructorUtils; - -import com.fasterxml.jackson.databind.BeanProperty; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializerProvider; - -import de.ozgcloud.common.errorhandling.TechnicalException; -import lombok.NoArgsConstructor; - -@NoArgsConstructor -public class LinkedResourceSerializer extends AbstractLinkedResourceSerializer { - - private LinkedResource annotation; - - private LinkedResourceSerializer(LinkedResource annotation) { - this.annotation = annotation; - } - - @Override - public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) { - return new LinkedResourceSerializer(property.getAnnotation(LinkedResource.class)); - } - - @Override - String buildLink(Object id) { - return linkTo(annotation.controllerClass()).slash(getExtractor().extractId(id)).toString(); - } - - @Override - IdExtractor<Object> getExtractor() { - try { - return ConstructorUtils.invokeConstructor(annotation.extractor()); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) { - throw new TechnicalException("Error instanciating IdExtractor", e); - } - } -} diff --git a/src/main/java/de/ozgcloud/admin/common/ModelBuilder.java b/src/main/java/de/ozgcloud/admin/common/ModelBuilder.java deleted file mode 100644 index cd90619981aa59db2f29a1f158c76000773aacb2..0000000000000000000000000000000000000000 --- a/src/main/java/de/ozgcloud/admin/common/ModelBuilder.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BooleanSupplier; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; - -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.reflect.FieldUtils; -import org.springframework.hateoas.EntityModel; -import org.springframework.hateoas.Link; -import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; - -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; - -@Log4j2 -public class ModelBuilder<T> { - - private static final Map<Class<?>, Map<Object, List<Field>>> ANNOTATED_FIELDS_BY_ANNOTATION = new ConcurrentHashMap<>(); - - private final T entity; - private final EntityModel<T> model; - - private final List<Link> links = new LinkedList<>(); - private final List<Function<EntityModel<T>, EntityModel<T>>> mapper = new LinkedList<>(); - - private ModelBuilder(T entity) { - this.entity = entity; - this.model = null; - } - - private ModelBuilder(EntityModel<T> model) { - this.entity = null; - this.model = model; - } - - public static <T> ModelBuilder<T> fromEntity(T entity) { - return new ModelBuilder<>(entity); - } - - public static <T> ModelBuilder<T> fromModel(EntityModel<T> model) { - return new ModelBuilder<>(model); - } - - public ModelBuilder<T> addLink(Link link) { - links.add(link); - return this; - } - - public ModelBuilder<T> addLink(Optional<Link> link) { - link.ifPresent(links::add); - return this; - } - - public ModelBuilder<T> addLinks(Link... links) { - this.links.addAll(Arrays.asList(links)); - return this; - } - - public ModelBuilder<T> addLinks(Collection<Link> links) { - this.links.addAll(links); - return this; - } - - public ConditionalLinkAdder ifMatch(Predicate<T> predicate) { - return new ConditionalLinkAdder(predicate.test(Objects.isNull(entity) ? model.getContent() : entity)); - } - - public ConditionalLinkAdder ifMatch(BooleanSupplier guard) { - return new ConditionalLinkAdder(guard.getAsBoolean()); - } - - public ModelBuilder<T> map(UnaryOperator<EntityModel<T>> mapper) { - this.mapper.add(mapper); - return this; - } - - public EntityModel<T> buildModel() { - var filteredLinks = links.stream().filter(Objects::nonNull).collect(Collectors.toSet()); - - EntityModel<T> buildedModel = Objects.isNull(model) ? EntityModel.of(entity) : model; - buildedModel = buildedModel.add(filteredLinks); - - addLinkByLinkedResourceAnnotationIfMissing(buildedModel); - - return applyMapper(buildedModel); - } - - private EntityModel<T> applyMapper(EntityModel<T> resource) { - Iterator<Function<EntityModel<T>, EntityModel<T>>> i = mapper.iterator(); - EntityModel<T> result = resource; - while (i.hasNext()) { - result = i.next().apply(result); - } - return result; - } - - private void addLinkByLinkedResourceAnnotationIfMissing(EntityModel<T> resource) { - getFields(LinkedResource.class).stream() - .filter(field -> shouldAddLink(resource, field)) - .forEach(field -> handleLinkedResourceField(resource, field)); - } - - private void handleLinkedResourceField(EntityModel<T> resource, Field field) { - getEntityFieldValue(field).map(Object::toString).filter(StringUtils::isNotBlank).ifPresent(val -> resource - .add(WebMvcLinkBuilder.linkTo(field.getAnnotation(LinkedResource.class).controllerClass()).slash(val) - .withRel(sanitizeName(field.getName())))); - } - - private boolean shouldAddLink(EntityModel<T> resource, Field field) { - return !(field.getType().isArray() || Collection.class.isAssignableFrom(field.getType()) || resource.hasLink(sanitizeName(field.getName()))); - } - - private List<Field> getFields(Class<? extends Annotation> annotationClass) { - var fields = Optional.ofNullable(ANNOTATED_FIELDS_BY_ANNOTATION.get(getEntity().getClass())) - .map(fieldsByAnnotation -> fieldsByAnnotation.get(annotationClass)) - .orElseGet(Collections::emptyList); - - if (CollectionUtils.isEmpty(fields)) { - fields = FieldUtils.getFieldsListWithAnnotation(getEntity().getClass(), annotationClass); - - updateFields(annotationClass, fields); - } - return fields; - } - - private void updateFields(Class<? extends Annotation> annotationClass, List<Field> fields) { - var annotationMap = Optional.ofNullable(ANNOTATED_FIELDS_BY_ANNOTATION.get(getEntity().getClass())).orElseGet(HashMap::new); - annotationMap.put(annotationClass, fields); - - ANNOTATED_FIELDS_BY_ANNOTATION.put(annotationClass, annotationMap); - } - - private String sanitizeName(String fieldName) { - if (fieldName.endsWith("Id")) { - return fieldName.substring(0, fieldName.indexOf("Id")); - } - return fieldName; - } - - private Optional<Object> getEntityFieldValue(Field field) { - try { - field.setAccessible(true); - Optional<Object> value = Optional.ofNullable(field.get(getEntity())); - field.setAccessible(false); - return value; - } catch (IllegalArgumentException | IllegalAccessException e) { - LOG.warn("Cannot access field value of LinkedResource field.", e); - } - return Optional.empty(); - } - - private T getEntity() { - return Optional.ofNullable(entity == null ? model.getContent() : entity) - .orElseThrow(() -> new IllegalStateException("Entity must not null for ModelBuilding")); - } - - @RequiredArgsConstructor - public class ConditionalLinkAdder { - - public final boolean conditionFulfilled; - - public ModelBuilder<T> addLink(Supplier<Link> linkSupplier) { - if (conditionFulfilled) { - addLink(linkSupplier.get()); - } - return ModelBuilder.this; - } - - public ModelBuilder<T> addLinkIfPresent(Supplier<Optional<Link>> linkSupplier) { - if (conditionFulfilled) { - addLink(linkSupplier.get()); - } - return ModelBuilder.this; - } - - public ModelBuilder<T> addLink(Function<T, Link> linkBuilder) { - if (conditionFulfilled) { - addLink(linkBuilder.apply(getEntity())); - } - return ModelBuilder.this; - } - - public ModelBuilder<T> addLink(Link link) { - if (conditionFulfilled) { - links.add(link); - } - return ModelBuilder.this; - } - - public ModelBuilder<T> addLink(Optional<Link> link) { - if (conditionFulfilled) { - link.ifPresent(links::add); - } - return ModelBuilder.this; - } - - public ModelBuilder<T> addLinks(Link... links) { - if (conditionFulfilled) { - ModelBuilder.this.links.addAll(Arrays.asList(links)); - } - return ModelBuilder.this; - } - - @SafeVarargs - public final ModelBuilder<T> addLinks(Supplier<Link>... linkSuppliers) { - if (conditionFulfilled) { - for (int i = 0; i < linkSuppliers.length; i++) { - ModelBuilder.this.links.add(linkSuppliers[i].get()); - } - } - return ModelBuilder.this; - } - - public final ModelBuilder<T> addLinks(Supplier<Collection<Link>> linksSupplier) { - if (conditionFulfilled) { - linksSupplier.get().forEach(ModelBuilder.this.links::add); - } - - return ModelBuilder.this; - } - - } -} \ No newline at end of file diff --git a/src/main/java/de/ozgcloud/admin/common/IdExtractor.java b/src/main/java/de/ozgcloud/admin/common/SettingDtoService.java similarity index 84% rename from src/main/java/de/ozgcloud/admin/common/IdExtractor.java rename to src/main/java/de/ozgcloud/admin/common/SettingDtoService.java index 658b06586cd447274ef701dc8942af43300a814a..e99addc6f1bc9cbf172a6c0be1baebbf29cd9812 100644 --- a/src/main/java/de/ozgcloud/admin/common/IdExtractor.java +++ b/src/main/java/de/ozgcloud/admin/common/SettingDtoService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung @@ -23,7 +23,9 @@ */ package de.ozgcloud.admin.common; -@FunctionalInterface -public interface IdExtractor<T> { - String extractId(T object); -} \ No newline at end of file +public interface SettingDtoService { + + String getSettingKeyName(); + + Object getDataTransferObject(); +} diff --git a/src/main/java/de/ozgcloud/admin/common/user/UserRole.java b/src/main/java/de/ozgcloud/admin/common/user/UserRole.java index d4745c0a2a8bb515dcc694024383e187aee03f36..8c28ea3ebe1bc531ad2447297ec9f3f23e370554 100644 --- a/src/main/java/de/ozgcloud/admin/common/user/UserRole.java +++ b/src/main/java/de/ozgcloud/admin/common/user/UserRole.java @@ -28,5 +28,6 @@ import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PRIVATE) public class UserRole { + public static final String DATENBEAUFTRAGUNG = "DATENBEAUFTRAGUNG"; public static final String ADMIN_ADMIN = "ADMIN_ADMIN"; } diff --git a/src/main/java/de/ozgcloud/admin/reporting/AggregationMapping.java b/src/main/java/de/ozgcloud/admin/reporting/AggregationMapping.java new file mode 100644 index 0000000000000000000000000000000000000000..d923d2c01a69af97787af47de8444cab6d086dd4 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/reporting/AggregationMapping.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2025 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.ozgcloud.admin.reporting; + +import java.util.List; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; + +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.TypeAlias; +import org.springframework.data.mongodb.core.mapping.Document; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +import lombok.Builder; +import lombok.Getter; +import lombok.Singular; +import lombok.ToString; +import lombok.extern.jackson.Jacksonized; + +@Document("settings") +@TypeAlias(AggregationMapping.TYPE_ALIAS) +@Jacksonized +@Builder +@Getter +@ToString +public class AggregationMapping { + + public static final String TYPE_ALIAS = "AggregationMapping"; + + @Id + @JsonIgnore + private String id; + @Builder.Default + private String name = "aggregationMapping"; + + @Valid + private FormIdentifier formIdentifier; + + @NotEmpty + @Singular + private List<FieldMapping> mappings; + + @Builder + @Getter + @ToString + static class FieldMapping { + @NotBlank + private String sourcePath; + @NotBlank + private String targetPath; + } + + @Builder + @Getter + @ToString + static class FormIdentifier { + @NotBlank + private String formEngineName; + @NotBlank + private String formId; + } +} diff --git a/src/test/java/de/ozgcloud/admin/common/EntityModelTestFactory.java b/src/main/java/de/ozgcloud/admin/reporting/AggregationMappingMapper.java similarity index 61% rename from src/test/java/de/ozgcloud/admin/common/EntityModelTestFactory.java rename to src/main/java/de/ozgcloud/admin/reporting/AggregationMappingMapper.java index 1f01a94247938090c4e85f0d9c96d3ec399aeb6c..81a25c3042c172767fa810162764de2d72b9b3d9 100644 --- a/src/test/java/de/ozgcloud/admin/common/EntityModelTestFactory.java +++ b/src/main/java/de/ozgcloud/admin/reporting/AggregationMappingMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung @@ -21,23 +21,18 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.common; +package de.ozgcloud.admin.reporting; -import org.springframework.hateoas.EntityModel; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.NullValueCheckStrategy; -import de.ozgcloud.admin.organisationseinheit.OrganisationsEinheit; -import lombok.NoArgsConstructor; +import de.ozgcloud.admin.reporting.ReportingAggregationManagerSettingDto.AggregationMappingDto; -public class EntityModelTestFactory { +@Mapper(nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS) +public interface AggregationMappingMapper { - public static final NullableEntityModel NULLABLE = createNullable(); - - private static NullableEntityModel createNullable() { - return new NullableEntityModel(); - } - - @NoArgsConstructor - private static class NullableEntityModel extends EntityModel<OrganisationsEinheit> { - - } + @Mapping(target = "fieldMapping", ignore = true) + @Mapping(target = "fieldMappings", source = "mappings") + AggregationMappingDto toMapping(AggregationMapping setting); } diff --git a/src/main/java/de/ozgcloud/admin/common/ToStringExtractor.java b/src/main/java/de/ozgcloud/admin/reporting/AggregationMappingRepository.java similarity index 59% rename from src/main/java/de/ozgcloud/admin/common/ToStringExtractor.java rename to src/main/java/de/ozgcloud/admin/reporting/AggregationMappingRepository.java index dade3573176baac48e7f6df83e0c45a4c1cb9389..249aa175f5c7a313ba1e69dd178926fd29c8f2a0 100644 --- a/src/main/java/de/ozgcloud/admin/common/ToStringExtractor.java +++ b/src/main/java/de/ozgcloud/admin/reporting/AggregationMappingRepository.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung @@ -21,15 +21,19 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.common; +package de.ozgcloud.admin.reporting; -import lombok.NoArgsConstructor; +import java.util.List; -@NoArgsConstructor -class ToStringExtractor implements IdExtractor<Object> { +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.security.access.annotation.Secured; +@Secured("ROLE_DATENBEAUFTRAGUNG") +@RepositoryRestResource +interface AggregationMappingRepository extends MongoRepository<AggregationMapping, String> { @Override - public String extractId(Object object) { - return object.toString(); - } + @Query("{'_class': 'AggregationMapping'}") + List<AggregationMapping> findAll(); } diff --git a/src/main/java/de/ozgcloud/admin/setting/DataRestConfiguration.java b/src/main/java/de/ozgcloud/admin/reporting/ReportingAggregationManagerSettingDto.java similarity index 54% rename from src/main/java/de/ozgcloud/admin/setting/DataRestConfiguration.java rename to src/main/java/de/ozgcloud/admin/reporting/ReportingAggregationManagerSettingDto.java index b93f96d8665b904d133d44f9e3682b649e5ed5a6..00e74b76d4eaad2b3bbe9937c781f505852ff942 100644 --- a/src/main/java/de/ozgcloud/admin/setting/DataRestConfiguration.java +++ b/src/main/java/de/ozgcloud/admin/reporting/ReportingAggregationManagerSettingDto.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung @@ -21,23 +21,42 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.setting; +package de.ozgcloud.admin.reporting; -import org.springframework.context.annotation.Configuration; -import org.springframework.data.rest.core.event.ValidatingRepositoryEventListener; -import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurer; +import java.util.List; -import lombok.RequiredArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.Singular; +import lombok.ToString; -@Configuration -@RequiredArgsConstructor -public class DataRestConfiguration implements RepositoryRestConfigurer { +@Builder +@Getter +@ToString +public class ReportingAggregationManagerSettingDto { - private final SettingValidator settingValidator; + @Singular + private List<AggregationMappingDto> aggregationMappings; - @Override - public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener v) { - v.addValidator("beforeSave", settingValidator); - v.addValidator("beforeCreate", settingValidator); + @Builder + @Getter + static class AggregationMappingDto { + private FormIdentifierDto formIdentifier; + @Singular + private List<FieldMappingDto> fieldMappings; } -} \ No newline at end of file + + @Builder + @Getter + static class FormIdentifierDto { + private String formEngineName; + private String formId; + } + + @Builder + @Getter + static class FieldMappingDto { + private String sourcePath; + private String targetPath; + } +} diff --git a/src/main/java/de/ozgcloud/admin/reporting/ReportingAggregationManagerSettingDtoService.java b/src/main/java/de/ozgcloud/admin/reporting/ReportingAggregationManagerSettingDtoService.java new file mode 100644 index 0000000000000000000000000000000000000000..acb46bf40ebaf81a11b896ed6c2d55c7048f29e7 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/reporting/ReportingAggregationManagerSettingDtoService.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2025 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.ozgcloud.admin.reporting; + +import java.util.stream.Stream; + +import de.ozgcloud.admin.common.DtoService; +import de.ozgcloud.admin.common.SettingDtoService; +import de.ozgcloud.admin.setting.SettingConstants; +import de.ozgcloud.admin.setting.SettingRepository; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@DtoService(SettingConstants.AGGREGATION_MANAGER) +class ReportingAggregationManagerSettingDtoService implements SettingDtoService { + + private final AggregationMappingMapper mapper; + + private final SettingRepository repository; + + @Override + public ReportingAggregationManagerSettingDto getDataTransferObject() { + return mapToDto(repository.findByType("AggregationMapping", AggregationMapping.class)); + } + + ReportingAggregationManagerSettingDto mapToDto(Stream<AggregationMapping> settings) { + return ReportingAggregationManagerSettingDto.builder() + .aggregationMappings(settings.map(mapper::toMapping).toList()) + .build(); + } + + @Override + public String getSettingKeyName() { + return "aggregation"; + } +} diff --git a/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java b/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java index b3d46b9d610865098fc760e8a94e94ee79f594f2..9d8cd455bdd726f2d31207ac96c8740cb4139dbd 100644 --- a/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java +++ b/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java @@ -56,16 +56,13 @@ import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class SecurityConfiguration { - private final AdminAuthenticationEntryPoint authenticationEntryPoint; - - private final OAuth2Properties oAuth2Properties; - static final String RESOURCE_ACCESS_KEY = "resource_access"; - static final String SIMPLE_GRANT_AUTHORITY_PREFIX = "ROLE_"; - static final String ROLES_KEY = "roles"; + private final AdminAuthenticationEntryPoint authenticationEntryPoint; + private final OAuth2Properties oAuth2Properties; + @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -77,8 +74,8 @@ public class SecurityConfiguration { http.authorizeHttpRequests(requests -> requests .requestMatchers(HttpMethod.GET, "/api/environment").permitAll() - .requestMatchers("/api/configuration").hasRole(UserRole.ADMIN_ADMIN) - .requestMatchers("/api/configuration/**").hasRole(UserRole.ADMIN_ADMIN) + .requestMatchers("/api/configuration").hasAnyRole(UserRole.ADMIN_ADMIN, UserRole.DATENBEAUFTRAGUNG) + .requestMatchers("/api/configuration/**").hasAnyRole(UserRole.ADMIN_ADMIN, UserRole.DATENBEAUFTRAGUNG) .requestMatchers("/api").authenticated() .requestMatchers("/api/**").authenticated() .requestMatchers("/actuator").permitAll() @@ -89,11 +86,11 @@ public class SecurityConfiguration { return http.build(); } + // TODO OZG-4954 replace with spring defaults @Bean JwtAuthenticationConverter jwtAuthenticationConverter() { var jwtConverter = new JwtAuthenticationConverter(); - jwtConverter.setJwtGrantedAuthoritiesConverter( - this::convertJwtToGrantedAuthorities); + jwtConverter.setJwtGrantedAuthoritiesConverter(this::convertJwtToGrantedAuthorities); jwtConverter.setPrincipalClaimName(StandardClaimNames.PREFERRED_USERNAME); return jwtConverter; } diff --git a/src/test/java/de/ozgcloud/admin/common/LinkedResourceTestObject.java b/src/main/java/de/ozgcloud/admin/setting/MapBasedApplicationSettingDto.java similarity index 67% rename from src/test/java/de/ozgcloud/admin/common/LinkedResourceTestObject.java rename to src/main/java/de/ozgcloud/admin/setting/MapBasedApplicationSettingDto.java index d30ab26ef5568705572702d1e5401c215a5b40cb..178302ff18d14392d8f33ab8249c40c32c13c7b9 100644 --- a/src/test/java/de/ozgcloud/admin/common/LinkedResourceTestObject.java +++ b/src/main/java/de/ozgcloud/admin/setting/MapBasedApplicationSettingDto.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung @@ -21,18 +21,25 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.common; +package de.ozgcloud.admin.setting; -import de.ozgcloud.admin.common.user.TestId; -import de.ozgcloud.admin.organisationseinheit.OrganisationsEinheitController; -import lombok.AllArgsConstructor; +import java.util.Map; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; + +import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.Singular; +@Builder @Getter -@AllArgsConstructor -@NoArgsConstructor -class LinkedResourceTestObject { - @LinkedResource(controllerClass = OrganisationsEinheitController.class) - private TestId id; +public class MapBasedApplicationSettingDto implements ApplicationSettingDTO { + + @Singular + private Map<String, Object> settings; + + @JsonAnyGetter + public Map<String, Object> getSettings() { + return settings; + } } diff --git a/src/main/java/de/ozgcloud/admin/setting/SettingConstants.java b/src/main/java/de/ozgcloud/admin/setting/SettingConstants.java index 9c40c6120a702447e27ebc8093025568763de5d3..afcf09344d8f76dc3519c635b99bfc933e185d24 100644 --- a/src/main/java/de/ozgcloud/admin/setting/SettingConstants.java +++ b/src/main/java/de/ozgcloud/admin/setting/SettingConstants.java @@ -32,4 +32,6 @@ public class SettingConstants { static final String REL = "settings"; public static final String PATH = "settings"; + + public static final String AGGREGATION_MANAGER = "OzgCloud_AggregationManager"; } diff --git a/src/main/java/de/ozgcloud/admin/setting/SettingEnvironmentRepository.java b/src/main/java/de/ozgcloud/admin/setting/SettingEnvironmentRepository.java index db793f2f49805d18a48037b96817cdd1aa4c88e3..a6e74eac7d909eeefc363eebb43cb5cf078e49e5 100644 --- a/src/main/java/de/ozgcloud/admin/setting/SettingEnvironmentRepository.java +++ b/src/main/java/de/ozgcloud/admin/setting/SettingEnvironmentRepository.java @@ -23,6 +23,8 @@ */ package de.ozgcloud.admin.setting; +import static de.ozgcloud.admin.setting.SettingConstants.*; + import java.util.Map; import java.util.Optional; @@ -41,13 +43,12 @@ import lombok.RequiredArgsConstructor; @Component public class SettingEnvironmentRepository implements EnvironmentRepository { - private final SettingService settingService; - - private final ObjectMapper objectMapper; - private static final String ALFA = "Alfa"; private static final String VORGANG_MANAGER = "OzgCloud_VorgangManager"; + private final SettingService settingService; + private final ObjectMapper objectMapper; + @Override public Environment findOne(String application, String profile, String label) { return buildEnvironment(application, findAnwendungSettingDTO(application)); @@ -59,6 +60,8 @@ public class SettingEnvironmentRepository implements EnvironmentRepository { return Optional.of(settingService.getAlfaSettingDTO()); case VORGANG_MANAGER: return Optional.of(settingService.getVorgangManagerSettingDTO()); + case AGGREGATION_MANAGER: + return Optional.of(settingService.getAggregationManagerSettingDto()); default: return Optional.empty(); } diff --git a/src/main/java/de/ozgcloud/admin/setting/SettingRepository.java b/src/main/java/de/ozgcloud/admin/setting/SettingRepository.java index 5180dea45a85d6007717a4e2a3d64cbb6ad7c6d9..ddeeae9fd0a5cb80dec50bf3c9b5c41492318c3a 100644 --- a/src/main/java/de/ozgcloud/admin/setting/SettingRepository.java +++ b/src/main/java/de/ozgcloud/admin/setting/SettingRepository.java @@ -23,13 +23,25 @@ */ package de.ozgcloud.admin.setting; +import java.util.List; import java.util.Optional; +import java.util.stream.Stream; import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.security.access.annotation.Secured; @RepositoryRestResource(collectionResourceRel = SettingConstants.REL, path = SettingConstants.PATH) -interface SettingRepository extends MongoRepository<Setting, String> { +public interface SettingRepository extends MongoRepository<Setting, String> { + + @Secured("ROLE_ADMIN_ADMIN") + @Override + @Query("{'_class': 'Setting'}") + List<Setting> findAll(); Optional<Setting> findOneByName(String name); + + @Query("{'_class': ?0}") + <T> Stream<T> findByType(String typeName, Class<T> clazz); } diff --git a/src/main/java/de/ozgcloud/admin/setting/SettingService.java b/src/main/java/de/ozgcloud/admin/setting/SettingService.java index 51b8529add8a7e42cb367f1fc6b4a062f8eaa4cb..aa9c8e2d7eea1a4eadf373331b4c369578968218 100644 --- a/src/main/java/de/ozgcloud/admin/setting/SettingService.java +++ b/src/main/java/de/ozgcloud/admin/setting/SettingService.java @@ -23,11 +23,25 @@ */ package de.ozgcloud.admin.setting; +import static de.ozgcloud.admin.setting.SettingConstants.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import java.util.stream.Stream; + +import jakarta.annotation.PostConstruct; +import org.apache.commons.lang3.tuple.Pair; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.stereotype.Service; +import static java.util.stream.Collectors.*; + +import de.ozgcloud.admin.common.DtoService; +import de.ozgcloud.admin.common.SettingDtoService; import de.ozgcloud.admin.organisationseinheit.OrganisationsEinheitSettings; import de.ozgcloud.admin.setting.postfach.PostfachSettingBody; import lombok.RequiredArgsConstructor; @@ -41,6 +55,22 @@ class SettingService { private final SettingRepository repository; private final SettingMapper mapper; + private final ListableBeanFactory beanFactory; + + private Map<String, List<SettingDtoService>> dtoServiceByManager; + + @PostConstruct + void initDtoServices() { + this.dtoServiceByManager = beanFactory.getBeansWithAnnotation(DtoService.class).values().stream() + .map(SettingDtoService.class::cast) + .flatMap(this::getManagerForService) + .collect(Collectors.groupingBy(Pair::getKey, mapping(Pair::getValue, toList()))); + } + + private Stream<Pair<String, SettingDtoService>> getManagerForService(SettingDtoService dtoService) { + var annotation = dtoService.getClass().getDeclaredAnnotation(DtoService.class); + return Arrays.stream(annotation.value()).map(manager -> Pair.of(manager, dtoService)); + } public AlfaSettingDTO getAlfaSettingDTO() { var postfachData = getSettingWithPostfachFromDb(); @@ -75,4 +105,20 @@ class SettingService { OrganisationsEinheitWithSettings::organisationsEinheitId, OrganisationsEinheitWithSettings::settings)); } + + public ApplicationSettingDTO getAggregationManagerSettingDto() { + return MapBasedApplicationSettingDto.builder() + .settings(buildSettingsMap(AGGREGATION_MANAGER)) + .build(); + } + + private Map<String, Object> buildSettingsMap(String managerName) { + return getDtoService(managerName).stream() + .collect(Collectors.toMap(SettingDtoService::getSettingKeyName, SettingDtoService::getDataTransferObject)); + } + + private List<SettingDtoService> getDtoService(String managerName) { + return dtoServiceByManager.getOrDefault(managerName, Collections.emptyList()); + } + } diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml index c5bb57b37de68a6c4b610d764df4023e48e248f4..7faeb3acc343d6320c0c77fcfcd3da7aca6dffbb 100644 --- a/src/main/resources/application-local.yaml +++ b/src/main/resources/application-local.yaml @@ -1,3 +1,6 @@ +logging: + config: classpath:log4j2-local.xml + spring: data: mongodb: diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index f4a4954da71dc34a7be41330ce9d37169793c969..0ff2d17a8b35216926b5e4f4732c8d30fba13146 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -21,9 +21,27 @@ # unter der Lizenz sind dem Lizenztext zu entnehmen. logging: level: - ROOT: INFO + ROOT: WARN '[de.ozgcloud]': INFO +spring: + application: + name: OzgCloud_Administration + data: + mongodb: + authentication-database: admin + rest: + basePath: /api/configuration + cloud: + config: + server: + prefix: /configserver + security: + oauth2: + resourceserver: + jwt: + issuer-uri: ${ozgcloud.oauth2.auth-server-url}/realms/${ozgcloud.oauth2.realm} + management: server: port: 8081 @@ -55,23 +73,7 @@ mongock: - de.ozgcloud.admin.migration enabled: true -spring: - application: - name: OzgCloud_Administration - data: - mongodb: - authentication-database: admin - rest: - basePath: /api/configuration - cloud: - config: - server: - prefix: /configserver - security: - oauth2: - resourceserver: - jwt: - issuer-uri: ${ozgcloud.oauth2.auth-server-url}/realms/${ozgcloud.oauth2.realm} + ozgcloud: keycloak: diff --git a/src/test/java/de/ozgcloud/admin/AdministrationRepositoryRestConfigurerTest.java b/src/test/java/de/ozgcloud/admin/AdministrationRepositoryRestConfigurerTest.java deleted file mode 100644 index a45fc711558b6073f96283db4ce04e98ad691077..0000000000000000000000000000000000000000 --- a/src/test/java/de/ozgcloud/admin/AdministrationRepositoryRestConfigurerTest.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.InjectMocks; -import org.springframework.data.rest.core.config.RepositoryRestConfiguration; -import org.springframework.hateoas.server.LinkRelationProvider; -import org.springframework.hateoas.server.core.DefaultLinkRelationProvider; - -class AdministrationRepositoryRestConfigurerTest { - - @InjectMocks - private AdministrationRepositoryRestConfigurer configurer; - - @Nested - class TestConfigureRepositoryRestConfiguration { - - @Captor - private ArgumentCaptor<LinkRelationProvider> linkRelationProviderArgumentCaptor; - - @Test - void shouldUseDefaultLinkRelationProvider() { - var configuration = mock(RepositoryRestConfiguration.class); - - configurer.configureRepositoryRestConfiguration(configuration, null); - - verify(configuration).setLinkRelationProvider(linkRelationProviderArgumentCaptor.capture()); - assertThat(linkRelationProviderArgumentCaptor.getValue()).isInstanceOf(DefaultLinkRelationProvider.class); - } - } - -} \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/admin/OzgCloudDelegatingResourceMetadataTest.java b/src/test/java/de/ozgcloud/admin/OzgCloudDelegatingResourceMetadataTest.java new file mode 100644 index 0000000000000000000000000000000000000000..12253c9025542a549059c4979f7cbc458c151c36 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/OzgCloudDelegatingResourceMetadataTest.java @@ -0,0 +1,152 @@ +package de.ozgcloud.admin; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.Optional; + +import org.aopalliance.intercept.MethodInvocation; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.repository.support.Repositories; +import org.springframework.data.rest.core.mapping.ResourceMetadata; +import org.springframework.security.authorization.AuthorizationDecision; +import org.springframework.security.authorization.method.SecuredAuthorizationManager; + +import de.ozgcloud.admin.OzgCloudDelegatingRepositoryResourceMappings.OzgCloudDelegatingResourceMetadata; +import de.ozgcloud.common.test.ReflectionTestUtils; + +class OzgCloudDelegatingResourceMetadataTest { + + @Spy + @InjectMocks + private OzgCloudDelegatingResourceMetadata delegatingResourceMetadata; + + @Mock + private ResourceMetadata metadata; + @Mock + private Repositories repositories; + @Mock + private SecuredAuthorizationManager authManager; + + @Nested + class TestIsExported { + + @Test + void shouldReturnTrue() { + when(metadata.isExported()).thenReturn(true); + doReturn(true).when(delegatingResourceMetadata).isAccessPermitted(any()); + + var result = delegatingResourceMetadata.isExported(); + + assertThat(result).isTrue(); + } + + @Test + void shouldReturnFalseIfNotPermitted() { + doReturn(false).when(delegatingResourceMetadata).isAccessPermitted(any()); + + var result = delegatingResourceMetadata.isExported(); + + assertThat(result).isFalse(); + } + + @Test + void shouldReturnFalseIfNotExported() { + doReturn(true).when(delegatingResourceMetadata).isAccessPermitted(any()); + when(metadata.isExported()).thenReturn(false); + + var result = delegatingResourceMetadata.isExported(); + + assertThat(result).isFalse(); + } + } + + @Nested + class TestIsAccessPermitted { + @Mock + private Object repository; + @Mock + private AuthorizationDecision decision; + + private final MethodInvocation invocation = Mockito.mock(MethodInvocation.class); + + @BeforeEach + void initMock() { + when(repositories.getRepositoryFor(any())).thenReturn(Optional.of(repository)); + + doReturn(invocation).when(delegatingResourceMetadata).getFindAllInvocation(any()); + } + + @BeforeEach + void mockAuthManager() { + when(authManager.check(any(), any())).thenReturn(decision); + + ReflectionTestUtils.setField(delegatingResourceMetadata, "authManager", authManager); + } + + @Test + void shouldCallDecisionManager() { + delegatingResourceMetadata.isAccessPermitted(Object.class); + + verify(decision).isGranted(); + } + + @Test + void shouldReturnTrueIfGranted() { + when(decision.isGranted()).thenReturn(true); + + var result = delegatingResourceMetadata.isAccessPermitted(any()); + + assertThat(result).isTrue(); + } + + @Test + void shouldReturnFalseIfNotGranted() { + when(decision.isGranted()).thenReturn(false); + + var result = delegatingResourceMetadata.isAccessPermitted(any()); + + assertThat(result).isFalse(); + } + + @Test + void shouldCallAuthManager() { + delegatingResourceMetadata.isAccessPermitted(Object.class); + + verify(authManager).check(any(), eq(invocation)); + } + + @Test + void shouldReturnTrueIfNotSecured() { + when(authManager.check(any(), any())).thenReturn(null); + + var result = delegatingResourceMetadata.isAccessPermitted(any()); + + assertThat(result).isTrue(); + } + } + + @Nested + class TestGetFindAllInvocation { + + @Mock + private MongoRepository<String, Object> repository; + + @Test + void shouldReturnMethod() { + var method = delegatingResourceMetadata.getFindAllInvocation(repository); + + assertThat(method).isNotNull() + .extracting(invocation -> invocation.getMethod().getName()).isEqualTo("findAll"); + } + } + +} diff --git a/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java b/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java index 0427eb6fbed96cef7143a875cb42d6510654c6ce..5c4ed0d6bc38bf1c40399d0fad93cc488644b05d 100644 --- a/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java +++ b/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java @@ -39,9 +39,6 @@ import org.mockito.Spy; import org.springframework.boot.autoconfigure.data.rest.RepositoryRestProperties; import org.springframework.hateoas.Link; -import de.ozgcloud.admin.common.user.CurrentUserService; -import de.ozgcloud.admin.common.user.UserRole; - class RootModelAssemblerTest { private static final String BASE_PATH = "/api/base"; @@ -51,15 +48,12 @@ class RootModelAssemblerTest { @Mock private RepositoryRestProperties restProperties; - @Mock - private CurrentUserService currentUserService; @DisplayName("Entity Model") @Nested class TestEntityModel { @BeforeEach void beforeEach() { - Mockito.when(currentUserService.hasRole(UserRole.ADMIN_ADMIN)).thenReturn(true); Mockito.when(restProperties.getBasePath()).thenReturn(BASE_PATH); } @@ -92,7 +86,6 @@ class RootModelAssemblerTest { @Test void shouldHaveHrefToBasePathIfAuthorized() { Mockito.when(restProperties.getBasePath()).thenReturn(BASE_PATH); - Mockito.when(currentUserService.hasRole(UserRole.ADMIN_ADMIN)).thenReturn(true); List<Link> links = modelAssembler.buildRootModelLinks(); @@ -101,15 +94,6 @@ class RootModelAssemblerTest { Link.of(BASE_PATH, REL_CONFIGURATION)); } - @Test - void shouldNotHaveHrefToBasePathIfUnauthorized() { - Mockito.when(currentUserService.hasRole(UserRole.ADMIN_ADMIN)).thenReturn(false); - - List<Link> links = modelAssembler.buildRootModelLinks(); - - assertThat(links).containsExactly( - Link.of(RootController.PATH)); - } } } \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/admin/common/CollectionModelBuilderTest.java b/src/test/java/de/ozgcloud/admin/common/CollectionModelBuilderTest.java deleted file mode 100644 index 8327c18845e9ead36fc76bfff226334adac98881..0000000000000000000000000000000000000000 --- a/src/test/java/de/ozgcloud/admin/common/CollectionModelBuilderTest.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import static org.assertj.core.api.Assertions.*; - -import java.util.List; -import java.util.stream.Stream; - -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.hateoas.Link; - -import de.ozgcloud.admin.organisationseinheit.OrganisationsEinheit; -import de.ozgcloud.admin.organisationseinheit.OrganisationsEinheitTestFactory; - -class CollectionModelBuilderTest { - - private final String HREF = "http://test"; - private final String REL = "rel"; - - @Nested - class TestBuildModel { - - @Test - void shouldBuildModel() { - var vorgang = OrganisationsEinheitTestFactory.create(); - - var model = CollectionModelBuilder.fromEntities(List.of(vorgang)).buildModel(); - - assertThat(model.getContent()).hasSize(1).first().usingRecursiveComparison().isEqualTo(vorgang); - } - } - - @Nested - class TestAddLink { - - @Test - void shouldAddLink() { - var model = CollectionModelBuilder.fromEntities(List.of()).addLink(Link.of(HREF, REL)).buildModel(); - - assertThat(model.getLinks()).hasSize(1).first().extracting(Link::getHref, link -> link.getRel().value()).containsExactly(HREF, REL); - } - } - - @Nested - class TestIfMatch { - - @Nested - class TestWithBooleanSupplier { - - @Test - void shouldAddLink() { - var model = CollectionModelBuilder.fromEntities(List.of()).ifMatch(() -> true).addLink(Link.of(HREF, REL)).buildModel(); - - assertThat(model.getLinks()).hasSize(1).first().extracting(Link::getHref, link -> link.getRel().value()).containsExactly(HREF, REL); - } - - @Test - void shouldNotAddLink() { - var model = CollectionModelBuilder.fromEntities(List.of()).ifMatch(() -> false).addLink(Link.of(HREF, REL)).buildModel(); - - assertThat(model.getLinks()).isEmpty(); - } - } - - @Nested - class TestWithPredicate { - - private final Stream<OrganisationsEinheit> wiedervorlageStream = Stream.of(OrganisationsEinheitTestFactory.create()); - - @Test - void shouldAddLink() { - var model = CollectionModelBuilder.fromEntities(wiedervorlageStream) - .ifMatch(wiedervorlagen -> true) - .addLink(Link.of(HREF, REL)) - .buildModel(); - - assertThat(model.getLinks()).hasSize(1).first().extracting(Link::getHref, link -> link.getRel().value()).containsExactly(HREF, REL); - } - - @Test - void shouldNotAddLink() { - var model = CollectionModelBuilder.fromEntities(wiedervorlageStream) - .ifMatch(wiedervorlagen -> false) - .addLink(Link.of(HREF, REL)) - .buildModel(); - - assertThat(model.getLinks()).isEmpty(); - } - } - } - -} \ No newline at end of file diff --git a/src/test/java/de/ozgcloud/admin/common/IdBuilderTest.java b/src/test/java/de/ozgcloud/admin/common/IdBuilderTest.java deleted file mode 100644 index 608fe7e6e3607c04bdcedca85b29238bd840db66..0000000000000000000000000000000000000000 --- a/src/test/java/de/ozgcloud/admin/common/IdBuilderTest.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.databind.BeanProperty; - -class IdBuilderTest { - - @DisplayName("Test building ID when deserializing linked resources") - @Nested - class TestBuilingId { - private static final String ID = "id"; - - @Test - void shouldBuildId() { - IdBuilder idBuilder = new IdBuilder(); - - var idObject = idBuilder.build(ID); - - assertThat(idObject).isInstanceOf(Object.class).asString().isEqualTo(ID); - } - - @Test - void shouldCreateObjectBuilder() { - BeanProperty property = mock(BeanProperty.class); - ObjectBuilder<Object> idBuilder = new IdBuilder().constructContextAware(property); - - assertThat(idBuilder).isNotNull(); - } - } -} diff --git a/src/test/java/de/ozgcloud/admin/common/LinkedResourceDeserializerTest.java b/src/test/java/de/ozgcloud/admin/common/LinkedResourceDeserializerTest.java deleted file mode 100644 index 57b29873233059f02882fe1b56c5f4079f57c792..0000000000000000000000000000000000000000 --- a/src/test/java/de/ozgcloud/admin/common/LinkedResourceDeserializerTest.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import static org.assertj.core.api.Assertions.*; - -import java.io.IOException; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.core.exc.StreamReadException; -import com.fasterxml.jackson.databind.DatabindException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import de.ozgcloud.admin.common.user.TestId; -import de.ozgcloud.admin.organisationseinheit.OrganisationsEinheitTestFactory; - -class LinkedResourceDeserializerTest { - private static final String TEST_JSON = "{\"id\":\"/api/vorgangs/" + OrganisationsEinheitTestFactory.ID + "\"}"; - - @DisplayName("Test the deserilization of linked resource json") - @Nested - class TestDeserialization { - @Test - void shouldDeserialize() throws IOException { - LinkedResourceTestObject res = new ObjectMapper().readValue(TEST_JSON.getBytes(), LinkedResourceTestObject.class); - - assertThat(res).hasFieldOrPropertyWithValue("id", TestId.from(OrganisationsEinheitTestFactory.ID)); - } - - } - -} diff --git a/src/test/java/de/ozgcloud/admin/common/LinkedResourceSerializerTest.java b/src/test/java/de/ozgcloud/admin/common/LinkedResourceSerializerTest.java deleted file mode 100644 index 05764d09b88e72edd2032e861bbc58cd863a36c9..0000000000000000000000000000000000000000 --- a/src/test/java/de/ozgcloud/admin/common/LinkedResourceSerializerTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import static org.assertj.core.api.Assertions.*; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; - -import de.ozgcloud.admin.common.user.TestId; -import de.ozgcloud.admin.organisationseinheit.OrganisationsEinheitTestFactory; - -class LinkedResourceSerializerTest { - - @DisplayName("Test the json serialization of linked resource annotations") - @Nested - class TestSerialization { - @Test - void shouldSerialize() throws JsonProcessingException { - var testObj = new LinkedResourceTestObject(TestId.from(OrganisationsEinheitTestFactory.ID)); - - String serialized = new ObjectMapper().writeValueAsString(testObj); - - assertThat(serialized).isEqualTo("{\"id\":\"/api/organisationseinheits/" + OrganisationsEinheitTestFactory.ID + "\"}"); - } - - } -} diff --git a/src/test/java/de/ozgcloud/admin/common/ModelBuilderTest.java b/src/test/java/de/ozgcloud/admin/common/ModelBuilderTest.java deleted file mode 100644 index eaaf4500527625ff9707498335e93fb6bc6210a3..0000000000000000000000000000000000000000 --- a/src/test/java/de/ozgcloud/admin/common/ModelBuilderTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2024 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.ozgcloud.admin.common; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -import java.util.UUID; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.NullAndEmptySource; -import org.mockito.Mock; -import org.springframework.context.ApplicationContext; -import org.springframework.core.env.Environment; -import org.springframework.web.bind.annotation.RequestMapping; - -import lombok.Builder; - -class ModelBuilderTest { - - @DisplayName("Add link by annotation if missing") - @Nested - class TestAddLinkByAnnotationIfMissing { - - private static final String USER_MANAGER_URL = "http://localhost"; - private static final String USER_MANAGER_PROFILE_TEMPLATE = "/api/profile/%s"; - - @Mock - private ApplicationContext context; - - private TestEntity entity = TestEntityTestFactory.create(); - - @Test - void shouldHaveAddLinkByLinkedResource() { - var model = ModelBuilder.fromEntity(entity).buildModel(); - - assertThat(model.getLink(TestController.FILE_REL).get().getHref()).isEqualTo(TestController.PATH + "/" + TestEntityTestFactory.FILE); - } - - @ParameterizedTest - @NullAndEmptySource - void shouldNotAddLinkByLinkedRessourceIfFieldValueIsNotSet(String fileId) { - var model = ModelBuilder.fromEntity(TestEntityTestFactory.createBuilder().file(fileId).build()).buildModel(); - - assertThat(model.getLink(TestController.FILE_REL)).isEmpty(); - } - } -} - -@Builder -class TestEntity { - - @LinkedResource(controllerClass = TestController.class) - private String file; -} - -@RequestMapping(TestController.PATH) -class TestController { - - static final String PATH = "/api/test"; - - static final String USER_REL = "user"; - static final String FILE_REL = "file"; - -} - -class TestEntityTestFactory { - - static final String USER = UUID.randomUUID().toString(); - static final String FILE = UUID.randomUUID().toString(); - - public static TestEntity create() { - return createBuilder().build(); - } - - public static TestEntity.TestEntityBuilder createBuilder() { - return TestEntity.builder() - .file(FILE); - } -} diff --git a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceITCase.java b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceITCase.java index dc560c4ed05543fb219f0d6e98376dbbe6c977e8..8f121650c6aa2d05a7904a7d2097996a91bfb793 100644 --- a/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceITCase.java +++ b/src/test/java/de/ozgcloud/admin/keycloak/KeycloakApiServiceITCase.java @@ -29,6 +29,7 @@ import static org.assertj.core.groups.Tuple.tuple; import java.util.List; import java.util.Optional; +import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.keycloak.representations.idm.GroupRepresentation; @@ -122,9 +123,9 @@ class KeycloakApiServiceITCase { private String getOrganisationsEinheitId(GroupRepresentation group) { var attributes = group.getAttributes(); - return attributes.containsKey(properties.getOrganisationsEinheitIdKey()) ? - attributes.get(properties.getOrganisationsEinheitIdKey()).getFirst() : - null; + return attributes.containsKey(properties.getOrganisationsEinheitIdKey()) + ? attributes.get(properties.getOrganisationsEinheitIdKey()).getFirst() + : null; } } @@ -175,7 +176,7 @@ class KeycloakApiServiceITCase { var groupId = service.addGroup(groupToAdd); assertThat(findGroupInKeycloak(groupId)).isPresent().get().extracting(GroupRepresentation::getSubGroups) - .asList().isEmpty(); + .asInstanceOf(InstanceOfAssertFactories.LIST).isEmpty(); } private GroupRepresentation createUniqueGroupRepresentation(String nameSuffix) { diff --git a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitITCase.java b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitITCase.java index 8483a40cc9f385ecc6c48cbcb1a57a58ac89ed60..6d136531fc4b9de0489a0a69a8ec2232a987ba12 100644 --- a/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitITCase.java +++ b/src/test/java/de/ozgcloud/admin/organisationseinheit/OrganisationsEinheitITCase.java @@ -25,7 +25,6 @@ package de.ozgcloud.admin.organisationseinheit; import static org.mockito.Mockito.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.util.List; @@ -82,22 +81,22 @@ class OrganisationsEinheitITCase { void shouldContainList() { var response = mockMvc.perform(get(PATH)).andExpect(status().isOk()); - response.andDo(print()).andExpect(jsonPath("$._embedded.organisationsEinheitList").isNotEmpty()); + response.andExpect(jsonPath("$._embedded.organisationsEinheits").isNotEmpty()); } @SneakyThrows @Test void shouldContainOrganisationsEinheit() { mockMvc.perform(get(PATH)) - .andExpect(jsonPath("$._embedded.organisationsEinheitList[0].name").value(OrganisationsEinheitTestFactory.NAME)) - .andExpect(jsonPath("$._embedded.organisationsEinheitList[0].organisationsEinheitId").value( + .andExpect(jsonPath("$._embedded.organisationsEinheits[0].name").value(OrganisationsEinheitTestFactory.NAME)) + .andExpect(jsonPath("$._embedded.organisationsEinheits[0].organisationsEinheitId").value( OrganisationsEinheitTestFactory.ORGANISATIONS_EINHEIT_ID)) .andExpect( - jsonPath("$._embedded.organisationsEinheitList[0].syncResult").value(OrganisationsEinheitTestFactory.SYNC_RESULT.name())) - .andExpect(jsonPath("$._embedded.organisationsEinheitList[0].zufiId").doesNotExist()) - .andExpect(jsonPath("$._embedded.organisationsEinheitList[0].parentId").doesNotExist()) - .andExpect(jsonPath("$._embedded.organisationsEinheitList[0].keycloakId").doesNotExist()) - .andExpect(jsonPath("$._embedded.organisationsEinheitList[0].id").doesNotExist()); + jsonPath("$._embedded.organisationsEinheits[0].syncResult").value(OrganisationsEinheitTestFactory.SYNC_RESULT.name())) + .andExpect(jsonPath("$._embedded.organisationsEinheits[0].zufiId").doesNotExist()) + .andExpect(jsonPath("$._embedded.organisationsEinheits[0].parentId").doesNotExist()) + .andExpect(jsonPath("$._embedded.organisationsEinheits[0].keycloakId").doesNotExist()) + .andExpect(jsonPath("$._embedded.organisationsEinheits[0].id").doesNotExist()); } } diff --git a/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingDtoTestFactory.java b/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingDtoTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..f0c601d43a316e0456cd6b73acd012535d1d6033 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingDtoTestFactory.java @@ -0,0 +1,22 @@ +package de.ozgcloud.admin.reporting; + +import static de.ozgcloud.admin.reporting.AggregationMappingTestFactory.*; + +import de.ozgcloud.admin.reporting.ReportingAggregationManagerSettingDto.AggregationMappingDto; +import de.ozgcloud.admin.reporting.ReportingAggregationManagerSettingDto.FieldMappingDto; +import de.ozgcloud.admin.reporting.ReportingAggregationManagerSettingDto.FormIdentifierDto; + +public class AggregationMappingDtoTestFactory { + + public static final FormIdentifierDto FORM_IDENTIFIER = FormIdentifierDto.builder().formId(FORM_ID).formEngineName(FORM_ENGINE_NAME).build(); + + public static AggregationMappingDto create() { + return createBuilder().build(); + } + + public static AggregationMappingDto.AggregationMappingDtoBuilder createBuilder() { + return AggregationMappingDto.builder() + .formIdentifier(FORM_IDENTIFIER) + .fieldMapping(FieldMappingDto.builder().sourcePath(SOURCE_PATH).targetPath(TARGET_PATH).build()); + } +} diff --git a/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingMapperTest.java b/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingMapperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..6f98c2ede8306f10acc1d5fd7e75c7da9b0662cd --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingMapperTest.java @@ -0,0 +1,26 @@ +package de.ozgcloud.admin.reporting; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mapstruct.factory.Mappers; +import org.mockito.InjectMocks; + +class AggregationMappingMapperTest { + + @InjectMocks + private AggregationMappingMapper mapper = Mappers.getMapper(AggregationMappingMapper.class); + + @Nested + class TestToMapping { + + @Test + void shouldMapToDto() { + var dto = mapper.toMapping(AggregationMappingTestFactory.create()); + + assertThat(dto).usingRecursiveComparison().isEqualTo(AggregationMappingDtoTestFactory.create()); + } + } + +} diff --git a/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingRepositoryITCase.java b/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingRepositoryITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..a92bcc6b3e210f27a066f54f3dc28db551f45086 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingRepositoryITCase.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2025 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.ozgcloud.admin.reporting; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.security.authorization.AuthorizationDeniedException; +import org.springframework.security.test.context.support.WithMockUser; + +import de.ozgcloud.admin.common.user.UserRole; +import de.ozgcloud.admin.setting.SettingTestFactory; +import de.ozgcloud.common.test.DataITCase; + +@DataITCase +class AggregationMappingRepositoryITCase { + + @Autowired + private AggregationMappingRepository repository; + + @Autowired + private MongoOperations operations; + + @BeforeEach + void dropCollection() { + operations.dropCollection("settings"); + } + + @Test + @WithMockUser(roles = UserRole.DATENBEAUFTRAGUNG) + void shouldLoadOnlyFieldMapping() { + operations.save(AggregationMappingTestFactory.create()); + operations.save(SettingTestFactory.create()); + + var result = repository.findAll(); + + assertThat(result).hasSize(1); + } + + @Test + @WithMockUser + void shouldThrowExceptionOnMissingRole() { + operations.save(AggregationMappingTestFactory.create()); + + assertThatThrownBy(() -> repository.findAll()).isInstanceOf(AuthorizationDeniedException.class); + } + +} diff --git a/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingTestFactory.java b/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingTestFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..5d41bec21628058b6a5f84ef2858746b1b348d26 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/reporting/AggregationMappingTestFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2025 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.ozgcloud.admin.reporting; + +import java.util.UUID; + +import de.ozgcloud.admin.reporting.AggregationMapping.FieldMapping; +import de.ozgcloud.admin.reporting.AggregationMapping.FormIdentifier; + +public class AggregationMappingTestFactory { + + public static final String ID = UUID.randomUUID().toString(); + public static final String FORM_ENGINE_NAME = "A12"; + public static final String FORM_ID = "42"; + + public static final String SOURCE_PATH = "name"; + public static final String TARGET_PATH = "antragsteller.name"; + + public static AggregationMapping create() { + return createBuilder().build(); + } + + public static AggregationMapping.AggregationMappingBuilder createBuilder() { + return AggregationMapping.builder() + .id(UUID.randomUUID().toString()) + .formIdentifier(FormIdentifier.builder() + .formEngineName(FORM_ENGINE_NAME) + .formId(FORM_ID) + .build()) + .mapping(FieldMapping.builder().sourcePath(SOURCE_PATH).targetPath(TARGET_PATH).build()); + } + +} diff --git a/src/main/java/de/ozgcloud/admin/common/IdBuilder.java b/src/test/java/de/ozgcloud/admin/reporting/ReportingAggregationMAnagerSettingDtoTestFactory.java similarity index 64% rename from src/main/java/de/ozgcloud/admin/common/IdBuilder.java rename to src/test/java/de/ozgcloud/admin/reporting/ReportingAggregationMAnagerSettingDtoTestFactory.java index f893795312adcde8c17d1c0da58cf1eaa4e5cf26..27dc2735fa90521e76642ad8eda61e74c308db75 100644 --- a/src/main/java/de/ozgcloud/admin/common/IdBuilder.java +++ b/src/test/java/de/ozgcloud/admin/reporting/ReportingAggregationMAnagerSettingDtoTestFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Das Land Schleswig-Holstein vertreten durch den + * Copyright (C) 2025 Das Land Schleswig-Holstein vertreten durch den * Ministerpräsidenten des Landes Schleswig-Holstein * Staatskanzlei * Abteilung Digitalisierung und zentrales IT-Management der Landesregierung @@ -21,22 +21,17 @@ * Die sprachspezifischen Genehmigungen und Beschränkungen * unter der Lizenz sind dem Lizenztext zu entnehmen. */ -package de.ozgcloud.admin.common; +package de.ozgcloud.admin.reporting; -import com.fasterxml.jackson.databind.BeanProperty; +class ReportingAggregationMAnagerSettingDtoTestFactory { -import lombok.NoArgsConstructor; - -@NoArgsConstructor -class IdBuilder implements ObjectBuilder<Object> { - - @Override - public Object build(Object id) { - return id; + public static ReportingAggregationManagerSettingDto create() { + return createBuilder().build(); } - @Override - public ObjectBuilder<Object> constructContextAware(BeanProperty property) { - return new IdBuilder(); + public static ReportingAggregationManagerSettingDto.ReportingAggregationManagerSettingDtoBuilder createBuilder() { + return ReportingAggregationManagerSettingDto.builder() + .aggregationMapping(AggregationMappingDtoTestFactory.create()); } + } diff --git a/src/test/java/de/ozgcloud/admin/reporting/ReportingAggregationManagerSettingDtoServiceTest.java b/src/test/java/de/ozgcloud/admin/reporting/ReportingAggregationManagerSettingDtoServiceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..38b5d5e310295cecf06d3fd9d4ff723f14c00d54 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/reporting/ReportingAggregationManagerSettingDtoServiceTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2025 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.ozgcloud.admin.reporting; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import java.util.stream.Stream; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; + +import de.ozgcloud.admin.setting.SettingRepository; + +class ReportingAggregationManagerSettingDtoServiceTest { + + @Spy + @InjectMocks + private ReportingAggregationManagerSettingDtoService service; + + @Mock + private AggregationMappingMapper mapper; + @Mock + private SettingRepository repository; + + @Nested + class TestMapToDto { + + @BeforeEach + void setupMapperMock() { + when(mapper.toMapping(any())).thenReturn(AggregationMappingDtoTestFactory.create()); + } + + @Test + void shouldCallMapper() { + var mapping = AggregationMappingTestFactory.create(); + service.mapToDto(Stream.of(mapping)); + + verify(mapper).toMapping(mapping); + } + + @Test + void shouldBuildDto() { + var dto = service.mapToDto(Stream.of(AggregationMappingTestFactory.create())); + + assertThat(dto).usingRecursiveComparison().isEqualTo(ReportingAggregationMAnagerSettingDtoTestFactory.create()); + } + } + + @Nested + class TestGetDataTransferObject { + + @Captor + private ArgumentCaptor<Stream<AggregationMapping>> streamCaptor; + + @BeforeEach + void disableMapToDtoFunction() { + doReturn(ReportingAggregationMAnagerSettingDtoTestFactory.create()).when(service).mapToDto(any()); + } + + @Test + void shouldCallRepository() { + service.getDataTransferObject(); + + verify(repository).findByType(AggregationMapping.TYPE_ALIAS, AggregationMapping.class); + } + + @Test + void shouldCallMapToDto() { + AggregationMapping mapping = AggregationMappingTestFactory.create(); + when(repository.findByType(any(), any())).thenReturn(Stream.of(mapping)); + + service.getDataTransferObject(); + + verify(service).mapToDto(streamCaptor.capture()); + assertThat(streamCaptor.getValue()).contains(mapping); + } + } + +} diff --git a/src/test/java/de/ozgcloud/admin/reporting/ReportingSettingITCase.java b/src/test/java/de/ozgcloud/admin/reporting/ReportingSettingITCase.java new file mode 100644 index 0000000000000000000000000000000000000000..2f347868585a14067918bae7d96351cde99fb9e6 --- /dev/null +++ b/src/test/java/de/ozgcloud/admin/reporting/ReportingSettingITCase.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2025 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.ozgcloud.admin.reporting; + +import static org.assertj.core.api.Assertions.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.web.servlet.MockMvc; + +import de.ozgcloud.admin.common.user.UserRole; +import de.ozgcloud.common.test.DataITCase; +import de.ozgcloud.common.test.TestUtils; +import lombok.SneakyThrows; + +@AutoConfigureMockMvc +@WithMockUser(roles = UserRole.DATENBEAUFTRAGUNG) +@DataITCase +class ReportingSettingITCase { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private MongoOperations mongoOperations; + + @BeforeEach + void clearDatabase() { + mongoOperations.dropCollection("settings"); + } + + @Test + @SneakyThrows + void shouldHaveLinkToAggregationMapping() { + mockMvc.perform(get("/api/configuration")) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$._links.aggregationMappings").exists()); + } + + @Test + @SneakyThrows + @WithMockUser(roles = UserRole.ADMIN_ADMIN) + void shouldNotHaveLinkOnMissingRole() { + mockMvc.perform(get("/api/configuration")) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$._links.aggregationMappings").doesNotExist()); + } + + @Test + @SneakyThrows + void shouldDenyWhileMissingFields() { + mockMvc.perform(post("/api/configuration/aggregationMappings").with(csrf()) + .contentType(MediaType.APPLICATION_JSON).content("{}")) + .andExpect(status().isUnprocessableEntity()); + } + + @Disabled("is returning 500") + @Test + @SneakyThrows + @WithMockUser(roles = UserRole.ADMIN_ADMIN) + void shouldNotAllowAddingMapping() { + mockMvc.perform(post("/api/configuration/aggregationMappings").with(csrf()) + .contentType(MediaType.APPLICATION_JSON).content(TestUtils.loadTextFile("reporting/request.json"))) + .andExpect(status().is(403)); + } + + @Test + @SneakyThrows + void shouldAddAggregationMappings() { + mockMvc.perform(post("/api/configuration/aggregationMappings").with(csrf()) + .contentType(MediaType.APPLICATION_JSON).content(TestUtils.loadTextFile("reporting/request.json"))) + .andExpect(status().isCreated()); + + var collection = mongoOperations.getCollection("settings"); + assertThat(collection.countDocuments()).isEqualTo(1); + var mapping = mongoOperations.findAll(AggregationMapping.class).getFirst(); + assertThat(mapping).usingRecursiveComparison().ignoringFields("id").isEqualTo(AggregationMappingTestFactory.create()); + } + + @Test + @SneakyThrows + void shouldListReportings() { + mockMvc.perform(get("/api/configuration/aggregationMappings")) + .andExpect(status().is2xxSuccessful()); + } + +} diff --git a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java index 00e842613b7e62c8d62236a7f589f5e4affe887d..f8a507f213af4b16ae613bfca2560f5422f35ce1 100644 --- a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java +++ b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationITCase.java @@ -35,6 +35,7 @@ import org.junit.jupiter.params.provider.ValueSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.http.HttpStatus; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; @@ -137,15 +138,10 @@ class SecurityConfigurationITCase { @DisplayName("with authentication") @Nested class TestWithAuthentication { - static final String CLAIMS = """ - { - "preferredUsername": "testUser", - "scope": "openid testscope" - }"""; @Test @SneakyThrows - @WithJwt(CLAIMS) + @WithMockUser void shouldAllowApiEndpoint() { var result = doPerformAuthenticated("/api"); @@ -154,8 +150,8 @@ class SecurityConfigurationITCase { @Test @SneakyThrows - @WithJwt(CLAIMS) - void shouldForbidSettingsEndpoint() { + @WithMockUser + void shouldForbidSettingsEndpointOnMissingRole() { var result = doPerformAuthenticated("/api/configuration/settings"); result.andExpect(status().isForbidden()); @@ -163,7 +159,7 @@ class SecurityConfigurationITCase { @Test @SneakyThrows - @WithJwt(CLAIMS) + @WithMockUser void shouldForbidConfigurationsEndpoint() { var result = doPerformAuthenticated("/api/configuration"); @@ -180,17 +176,9 @@ class SecurityConfigurationITCase { @Nested class TestWithAdminRole { - static final String CLAIMS = """ - { - "preferredUsername": "testUser", - "scope": "openid testscope", - "resource_access": { "admin": { "roles": ["ADMIN_ADMIN"] } } - }"""; - - @Test @SneakyThrows - @WithJwt(CLAIMS) + @WithMockUser(roles = "ADMIN_ADMIN") void shouldAllowSettings() { var result = mockMvc.perform(get("/api/configuration/settings")); @@ -199,7 +187,7 @@ class SecurityConfigurationITCase { @Test @SneakyThrows - @WithJwt(CLAIMS) + @WithMockUser(roles = "ADMIN_ADMIN") void shouldAllowConfiguration() { var result = mockMvc.perform(get("/api/configuration")); diff --git a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationTest.java b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationTest.java index df88ea5f503807b4b58878d8ced4e09eff353d79..c84999918e651d428abdb93d54e6dff5f0c7eb35 100644 --- a/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationTest.java +++ b/src/test/java/de/ozgcloud/admin/security/SecurityConfigurationTest.java @@ -23,10 +23,10 @@ */ package de.ozgcloud.admin.security; -import static de.ozgcloud.admin.security.JwtTestFactory.*; import static de.ozgcloud.admin.security.SecurityConfiguration.*; import static java.util.Collections.*; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.List; @@ -77,9 +77,8 @@ class SecurityConfigurationTest { @Test void shouldUsePreferredUsername() { var preferredName = LoremIpsum.getInstance().getName(); - var jwtWithPreferredName = JwtTestFactory.createBuilder() - .claim(StandardClaimNames.PREFERRED_USERNAME, preferredName) - .build(); + var jwtWithPreferredName = JwtTestFactory.createBuilder().claim(StandardClaimNames.PREFERRED_USERNAME, + preferredName).build(); var jwtAuthenticationConverter = securityConfiguration.jwtAuthenticationConverter(); @@ -109,8 +108,10 @@ class SecurityConfigurationTest { @BeforeEach void mock() { - var keycloakRoles = List.of(ROLE_1, JwtTestFactory.ROLE_2, JwtTestFactory.ROLE_3); - expectedSecurityRoleStrings = keycloakRoles.stream().map(role -> SIMPLE_GRANT_AUTHORITY_PREFIX + role).toList(); + var keycloakRoles = List.of(JwtTestFactory.ROLE_1, + JwtTestFactory.ROLE_2, JwtTestFactory.ROLE_3); + expectedSecurityRoleStrings = keycloakRoles.stream().map(role -> SIMPLE_GRANT_AUTHORITY_PREFIX + + role).toList(); doReturn(keycloakRoles).when(securityConfiguration).getRolesFromJwt(any()); } @@ -131,14 +132,14 @@ class SecurityConfigurationTest { var grantedAuthorities = securityConfiguration.convertJwtToGrantedAuthorities(jwt); - var securityRoles = grantedAuthorities - .stream() + var securityRoles = grantedAuthorities.stream() .map(GrantedAuthority::getAuthority).toList(); assertThat(securityRoles).containsAll(expectedSecurityRoleStrings); } } @DisplayName("get roles from jwt") + @Nested class TestGetRolesFromJwt { @@ -158,16 +159,16 @@ class SecurityConfigurationTest { private static Stream<Arguments> getIncompleteJwt() { return Stream.of(JwtTestFactory.create(), - JwtTestFactory.createBuilder().claim(RESOURCE_ACCESS_KEY, Map.of()).build(), - JwtTestFactory.createBuilder().claim(RESOURCE_ACCESS_KEY, Map.of("admin", Map.of())).build(), - JwtTestFactory.createWithRoles(emptyList()).build()) + JwtTestFactory.createBuilder().claim(RESOURCE_ACCESS_KEY, Map.of()).build(), + JwtTestFactory.createBuilder().claim(RESOURCE_ACCESS_KEY, Map.of("admin", Map.of())).build(), + JwtTestFactory.createWithRoles(emptyList()).build()) .map(Arguments::of); } @DisplayName("should return resource_access.admin.roles list") @Test void shouldReturnResourceAccessAdminRolesList() { - var expectedRoles = List.of(ROLE_1, JwtTestFactory.ROLE_2, JwtTestFactory.ROLE_3); + var expectedRoles = List.of(JwtTestFactory.ROLE_1, JwtTestFactory.ROLE_2, JwtTestFactory.ROLE_3); var jwtWithRoles = JwtTestFactory.createWithRoles(expectedRoles).build(); var roleStrings = securityConfiguration.getRolesFromJwt(jwtWithRoles); @@ -176,4 +177,5 @@ class SecurityConfigurationTest { } } + } diff --git a/src/test/java/de/ozgcloud/admin/setting/SettingEnvironmentITCase.java b/src/test/java/de/ozgcloud/admin/setting/SettingEnvironmentITCase.java index c50d09934b9cfd835215a4589a0c70000fdc9419..4fe44a5e5de623e8c08dfd39cb6073058c497ed5 100644 --- a/src/test/java/de/ozgcloud/admin/setting/SettingEnvironmentITCase.java +++ b/src/test/java/de/ozgcloud/admin/setting/SettingEnvironmentITCase.java @@ -38,6 +38,7 @@ import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.ResultActions; +import de.ozgcloud.admin.common.user.UserRole; import de.ozgcloud.admin.organisationseinheit.OrganisationsEinheit; import de.ozgcloud.admin.organisationseinheit.OrganisationsEinheitTestFactory; import de.ozgcloud.admin.setting.postfach.PostfachSettingBodyTestFactory; @@ -46,7 +47,7 @@ import lombok.SneakyThrows; @DataITCase @AutoConfigureMockMvc -@WithMockUser +@WithMockUser(roles = UserRole.ADMIN_ADMIN) class SettingEnvironmentITCase { @Autowired diff --git a/src/test/java/de/ozgcloud/admin/setting/SettingRepositoryITCase.java b/src/test/java/de/ozgcloud/admin/setting/SettingRepositoryITCase.java index 3fb17b47e79176424afab28bcc63cf082a17ade2..4f7dc9e6b6b543a1c701b5c875cf70244600f442 100644 --- a/src/test/java/de/ozgcloud/admin/setting/SettingRepositoryITCase.java +++ b/src/test/java/de/ozgcloud/admin/setting/SettingRepositoryITCase.java @@ -29,31 +29,46 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.security.test.context.support.WithMockUser; +import de.ozgcloud.admin.common.user.UserRole; +import de.ozgcloud.admin.reporting.AggregationMappingTestFactory; import de.ozgcloud.common.test.DataITCase; @DataITCase +@WithMockUser(roles = UserRole.ADMIN_ADMIN) class SettingRepositoryITCase { + @Autowired private MongoOperations mongoOperations; @Autowired private SettingRepository repository; - private final static String SETTING_NAME = "Name"; - private Setting setting = SettingTestFactory.createBuilder() - .name(SETTING_NAME) - .build(); - @Nested class TestFindOneByName { @Test void shouldGetSavedData() { mongoOperations.dropCollection(Setting.COLLECTION_NAME); - mongoOperations.save(setting); + mongoOperations.save(SettingTestFactory.create()); + + var settingFromDb = repository.findOneByName(SettingTestFactory.SETTING_NAME).get(); + + assertThat(settingFromDb).usingRecursiveComparison().ignoringFields("id").isEqualTo(SettingTestFactory.create()); + } + } + + @Nested + class TestFindByType { + + @Test + void shouldReturnOnlySetting() { + mongoOperations.dropCollection(Setting.COLLECTION_NAME); + mongoOperations.save(SettingTestFactory.create()); + mongoOperations.save(AggregationMappingTestFactory.create()); - var settingFromDb = repository.findOneByName(SETTING_NAME).get(); + var loaded = repository.findByType("Setting", Setting.class); - assertThat(settingFromDb).usingRecursiveComparison().isEqualTo(setting); + assertThat(loaded).hasSize(1).first().isInstanceOf(Setting.class); } } } diff --git a/src/test/java/de/ozgcloud/admin/setting/SettingTestFactory.java b/src/test/java/de/ozgcloud/admin/setting/SettingTestFactory.java index 07e9533f4895b545042a35186f8d96080dd14952..54c4ae3b42514c653e28615ccc8b54e0cbc5f7b3 100644 --- a/src/test/java/de/ozgcloud/admin/setting/SettingTestFactory.java +++ b/src/test/java/de/ozgcloud/admin/setting/SettingTestFactory.java @@ -27,12 +27,14 @@ import de.ozgcloud.common.test.TestUtils; public class SettingTestFactory { + static final String SETTING_NAME = "Name"; + public static Setting create() { return createBuilder().build(); } public static Setting.SettingBuilder createBuilder() { - return Setting.builder(); + return Setting.builder().name(SETTING_NAME); } public static String buildSettingJson(Setting setting, String settingBodyString) { diff --git a/src/test/resources/application-itcase.yaml b/src/test/resources/application-itcase.yaml index 1217c868aaa994671a26171005505ce110d401a5..1361c8ede1a7c0664bb56cdd718ac67942899c4c 100644 --- a/src/test/resources/application-itcase.yaml +++ b/src/test/resources/application-itcase.yaml @@ -1,3 +1,9 @@ +logging: + level: + '[org.springframework.data.mongodb]': WARN + '[org.springframework.security]': WARN + config: classpath:log4j2-local.xml + spring: data: mongodb: diff --git a/src/test/resources/reporting/request.json b/src/test/resources/reporting/request.json new file mode 100644 index 0000000000000000000000000000000000000000..c2db9de85906742429d7bcd56bc4ae61a28f2580 --- /dev/null +++ b/src/test/resources/reporting/request.json @@ -0,0 +1,12 @@ +{ + "formIdentifier": { + "formEngineName": "A12", + "formId": "42" + }, + "mappings": [ + { + "sourcePath": "name", + "targetPath": "antragsteller.name" + } + ] +} \ No newline at end of file