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..e46ad2ef92300b6c3299336aa0f43edb896c3536 --- /dev/null +++ b/src/main/java/de/ozgcloud/admin/OzgCloudRestLinksConfiguration.java @@ -0,0 +1,151 @@ +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; + private final SecuredAuthorizationManager authManager = new SecuredAuthorizationManager(); + + 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 + class OzgCloudDelegatingResourceMetadata implements ResourceMetadata { + private final ResourceMetadata metadata; + private final Repositories repositories; + + @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(false); + } + + 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/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/AggregationMappingRepository.java b/src/main/java/de/ozgcloud/admin/reporting/AggregationMappingRepository.java index e9dec7d51364e8e1c3324b8adb92b68e5bf0955f..0479f7aa080a25ebf5b168e5057c869e14f6fd9b 100644 --- a/src/main/java/de/ozgcloud/admin/reporting/AggregationMappingRepository.java +++ b/src/main/java/de/ozgcloud/admin/reporting/AggregationMappingRepository.java @@ -5,7 +5,9 @@ import java.util.List; 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 diff --git a/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java b/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java index 6fb633d5387436af1a9f73663bf4d72ae11bae11..5c01f460971563cb4bdb94b88d0af9157db9c3f3 100644 --- a/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java +++ b/src/main/java/de/ozgcloud/admin/security/SecurityConfiguration.java @@ -61,8 +61,10 @@ 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").hasRole(UserRole.DATENBEAUFTRAGUNG) +// .requestMatchers("/api/configuration/**").hasRole(UserRole.DATENBEAUFTRAGUNG) + .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() diff --git a/src/test/java/de/ozgcloud/admin/reporting/ReportingSettingITCase.java b/src/test/java/de/ozgcloud/admin/reporting/ReportingSettingITCase.java index 1f0a50a1cdcfdaedd590a6407b1c72ecb66e7206..d00164a895ded80f62aa387a09cf948afa17bdcc 100644 --- a/src/test/java/de/ozgcloud/admin/reporting/ReportingSettingITCase.java +++ b/src/test/java/de/ozgcloud/admin/reporting/ReportingSettingITCase.java @@ -6,6 +6,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder 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; @@ -14,12 +15,13 @@ 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 = "ADMIN_ADMIN") +@WithMockUser(roles = "DATENBEAUFTRAGUNG") @DataITCase class ReportingSettingITCase { @@ -36,12 +38,21 @@ class ReportingSettingITCase { @Test @SneakyThrows - void shouldHaveLinkToReporting() { + 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() { @@ -50,9 +61,20 @@ class ReportingSettingITCase { .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 shouldAddReportingSetting() { + @WithMockUser(roles = "DATENBEAUFTRAGUNG") + void shouldAddAggregationMappings() { mockMvc.perform(post("/api/configuration/aggregationMappings").with(csrf()) .contentType(MediaType.APPLICATION_JSON).content(TestUtils.loadTextFile("reporting/request.json"))) .andExpect(status().isCreated());