From 8886410cd8893a36b3266b48ef6afcece31132da Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Fri, 8 Oct 2021 16:38:38 +0300 Subject: [PATCH 01/17] Add spring-security-test --- pom.xml | 6 ++++++ shogun-lib/pom.xml | 26 +++++++++++++++++++------- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pom.xml b/pom.xml index 3facbb78c..09e459b18 100644 --- a/pom.xml +++ b/pom.xml @@ -528,6 +528,12 @@ ${spring-framework.version} test + + org.springframework.security + spring-security-test + ${spring-security.version} + test + org.mockito mockito-core diff --git a/shogun-lib/pom.xml b/shogun-lib/pom.xml index 488d35f09..d8aaf206e 100644 --- a/shogun-lib/pom.xml +++ b/shogun-lib/pom.xml @@ -202,6 +202,16 @@ evo-inflector + + org.reflections + reflections + + + + org.apache.tika + tika-core + + org.springframework @@ -209,6 +219,12 @@ test + + org.springframework.security + spring-security-test + test + + junit junit @@ -259,13 +275,9 @@ - org.reflections - reflections - - - - org.apache.tika - tika-core + com.jayway.jsonpath + json-path-assert + test From 3cb769aedd1f030b52ab936f8773b0c7a6b3dc34 Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 15:05:07 +0300 Subject: [PATCH 02/17] Make build properties optional --- .../shogun/boot/controller/ResourceController.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/shogun-boot/src/main/java/de/terrestris/shogun/boot/controller/ResourceController.java b/shogun-boot/src/main/java/de/terrestris/shogun/boot/controller/ResourceController.java index 855279707..09cd5a542 100644 --- a/shogun-boot/src/main/java/de/terrestris/shogun/boot/controller/ResourceController.java +++ b/shogun-boot/src/main/java/de/terrestris/shogun/boot/controller/ResourceController.java @@ -30,12 +30,16 @@ public class ResourceController { @Value("${KEYCLOAK_HOST:1.2.3.4}") String keycloakHost; - @Autowired + @Autowired(required = false) BuildProperties buildProperties; @RequestMapping("/") public ModelAndView home(ModelAndView modelAndView) { - modelAndView.addObject("version", buildProperties.getVersion()); + String buildVersion = "@VERSION@"; + if (buildProperties != null) { + buildVersion = buildProperties.getVersion(); + } + modelAndView.addObject("version", buildVersion); modelAndView.setViewName("index"); return modelAndView; From 8e340ddee1ec2a2f52762316263ef6c840097c62 Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 15:08:26 +0300 Subject: [PATCH 03/17] Set custom (Spring) implicit and physical naming strategy, e.g. for making camel cased fields snake-cased --- .../de/terrestris/shogun/boot/config/JdbcConfiguration.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java index 8c1abb808..42bad6292 100644 --- a/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java @@ -20,6 +20,8 @@ import com.zaxxer.hikari.HikariDataSource; import org.junit.AfterClass; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy; +import org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; @@ -93,6 +95,8 @@ public LocalContainerEntityManagerFactoryBean entityManagerFactory() { jpaProperties.put("hibernate.format_sql", env.getProperty("hibernate.format_sql")); jpaProperties.put("hibernate.default_schema", env.getProperty("hibernate.default_schema")); jpaProperties.put("hibernate.integration.envers.enabled", false); + jpaProperties.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName()); + jpaProperties.put("hibernate.physical_naming_strategy", SpringPhysicalNamingStrategy.class.getName()); result.setJpaPropertyMap(jpaProperties); From 99606313d52b2eb878894a20aa07817cfa5d2de8 Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 15:08:46 +0300 Subject: [PATCH 04/17] Update db image --- .../de/terrestris/shogun/boot/config/JdbcConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java index 42bad6292..b5bfac744 100644 --- a/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java @@ -59,7 +59,7 @@ public PostgreSQLContainer postgreSQLContainer() { if (postgreSQLContainer != null) { return postgreSQLContainer; } - DockerImageName postgis = DockerImageName.parse("docker.terrestris.de/postgis/postgis:11-3.0").asCompatibleSubstituteFor("postgres"); + DockerImageName postgis = DockerImageName.parse("postgis/postgis:13-3.1-alpine").asCompatibleSubstituteFor("postgres"); postgreSQLContainer = new PostgreSQLContainer(postgis); postgreSQLContainer.start(); return postgreSQLContainer; From 189b037a1748b0814084089d0600572e468a220d Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 15:09:30 +0300 Subject: [PATCH 05/17] Set default schema to shogun and set postgis dialect --- .../de/terrestris/shogun/boot/config/JdbcConfiguration.java | 2 +- shogun-boot/src/test/resources/jdbc.properties | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java index b5bfac744..a79f96305 100644 --- a/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/config/JdbcConfiguration.java @@ -68,7 +68,7 @@ public PostgreSQLContainer postgreSQLContainer() { @Bean public DataSource dataSource(final JdbcDatabaseContainer databaseContainer) { HikariConfig hikariConfig = new HikariConfig(); - String jdbcUrl = String.format("%s¤tSchema=shogun", databaseContainer.getJdbcUrl()); + String jdbcUrl = String.format("%s?currentSchema=shogun", databaseContainer.getJdbcUrl()); hikariConfig.setJdbcUrl(jdbcUrl); hikariConfig.setUsername(databaseContainer.getUsername()); hikariConfig.setPassword(databaseContainer.getPassword()); diff --git a/shogun-boot/src/test/resources/jdbc.properties b/shogun-boot/src/test/resources/jdbc.properties index 5afa46e29..e89bb1bd0 100644 --- a/shogun-boot/src/test/resources/jdbc.properties +++ b/shogun-boot/src/test/resources/jdbc.properties @@ -15,7 +15,8 @@ # limitations under the License. shogun.model.packages=de.terrestris.shogun.lib.model -hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +hibernate.dialect=org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect hibernate.hbm2ddl.auto=none hibernate.show_sql=false hibernate.format_sql=false +hibernate.default_schema=shogun From 1fd5fa436b10fb0aa20f5cc941b8c2eb49d9708c Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 15:09:50 +0300 Subject: [PATCH 06/17] Add some missing properties --- shogun-boot/src/test/resources/application-test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/shogun-boot/src/test/resources/application-test.yml b/shogun-boot/src/test/resources/application-test.yml index 78ecdfd8d..65f2fe30b 100644 --- a/shogun-boot/src/test/resources/application-test.yml +++ b/shogun-boot/src/test/resources/application-test.yml @@ -22,10 +22,13 @@ spring: defaultSchema: shogun session: store-type: none + messages: + basename: org/springframework/security/messages, de/terrestris/shogun/lib/messages keycloak: auth-server-url: http://localhost:8000/auth realm: SpringBootKeycloak + resource: shogun-app keycloakauth: username: admin From d0d62ae85765f0445a423b1b9a9febb62fc67a87 Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 15:50:57 +0300 Subject: [PATCH 07/17] Don't fail immediately if keycloak isn't available --- .../shogun/lib/security/SecurityContextUtil.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/shogun-lib/src/main/java/de/terrestris/shogun/lib/security/SecurityContextUtil.java b/shogun-lib/src/main/java/de/terrestris/shogun/lib/security/SecurityContextUtil.java index f5bf58167..e0cb11b38 100644 --- a/shogun-lib/src/main/java/de/terrestris/shogun/lib/security/SecurityContextUtil.java +++ b/shogun-lib/src/main/java/de/terrestris/shogun/lib/security/SecurityContextUtil.java @@ -72,8 +72,13 @@ public Optional getUserBySession() { if (user.isPresent()) { UserResource userResource = keycloakUtil.getUserResource(user.get()); - UserRepresentation userRepresentation = userResource.toRepresentation(); - user.get().setKeycloakRepresentation(userRepresentation); + try { + UserRepresentation userRepresentation = userResource.toRepresentation(); + user.get().setKeycloakRepresentation(userRepresentation); + } catch (RuntimeException exception) { + log.warn("Could not get the user representation details from Keycloak: ", exception.getMessage()); + log.trace("Full stack trace: ", exception); + } } return user; @@ -211,4 +216,5 @@ public boolean isInterceptorAdmin() { StringUtils.endsWithIgnoreCase(grantedAuthority.getAuthority(), "ADMIN") ); } + } From 98b15d65ec4ee063638814031eec5c09c46c3ed9 Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 15:51:41 +0300 Subject: [PATCH 08/17] Add NPE check for created timestamp --- .../java/de/terrestris/shogun/lib/service/BaseService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shogun-lib/src/main/java/de/terrestris/shogun/lib/service/BaseService.java b/shogun-lib/src/main/java/de/terrestris/shogun/lib/service/BaseService.java index 4a60edbc2..56ecf0b9a 100644 --- a/shogun-lib/src/main/java/de/terrestris/shogun/lib/service/BaseService.java +++ b/shogun-lib/src/main/java/de/terrestris/shogun/lib/service/BaseService.java @@ -123,7 +123,9 @@ public S update(Long id, S entity) throws IOException { // Ensure the created timestamp will not be overridden. S persistedEntity = persistedEntityOpt.orElseThrow(); - jsonObject.put("created", persistedEntity.getCreated().toInstant().toString()); + OffsetDateTime createdTimestamp = persistedEntity.getCreated(); + String serialized = createdTimestamp != null ? createdTimestamp.toInstant().toString() : null; + jsonObject.put("created", serialized); S updatedEntity = objectMapper.readerForUpdating(persistedEntity).readValue(jsonObject); From cd8c0606602c64b916fe32dd4ea71d5368724983 Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 15:52:51 +0300 Subject: [PATCH 09/17] Apply the given schema to the native query --- .../permission/UserClassPermissionRepository.java | 9 +++++---- .../permission/UserInstancePermissionRepository.java | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/shogun-lib/src/main/java/de/terrestris/shogun/lib/repository/security/permission/UserClassPermissionRepository.java b/shogun-lib/src/main/java/de/terrestris/shogun/lib/repository/security/permission/UserClassPermissionRepository.java index a57b5f514..0ac698512 100644 --- a/shogun-lib/src/main/java/de/terrestris/shogun/lib/repository/security/permission/UserClassPermissionRepository.java +++ b/shogun-lib/src/main/java/de/terrestris/shogun/lib/repository/security/permission/UserClassPermissionRepository.java @@ -19,9 +19,6 @@ import de.terrestris.shogun.lib.model.User; import de.terrestris.shogun.lib.model.security.permission.UserClassPermission; import de.terrestris.shogun.lib.repository.BaseCrudRepository; -import java.util.List; -import java.util.Optional; -import javax.persistence.QueryHint; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -29,6 +26,10 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import javax.persistence.QueryHint; +import java.util.List; +import java.util.Optional; + @Repository public interface UserClassPermissionRepository extends BaseCrudRepository, JpaSpecificationExecutor { @@ -40,6 +41,6 @@ public interface UserClassPermissionRepository extends BaseCrudRepository findAllByUser(User user); @Modifying - @Query(value = "DELETE FROM userclasspermissions u WHERE u.user_id=:userId", nativeQuery = true) + @Query(value = "DELETE FROM {h-schema}userclasspermissions u WHERE u.user_id=:userId", nativeQuery = true) void deleteAllByUserId(@Param("userId") Long userId); } diff --git a/shogun-lib/src/main/java/de/terrestris/shogun/lib/repository/security/permission/UserInstancePermissionRepository.java b/shogun-lib/src/main/java/de/terrestris/shogun/lib/repository/security/permission/UserInstancePermissionRepository.java index 70c34a6df..cfdb73fab 100644 --- a/shogun-lib/src/main/java/de/terrestris/shogun/lib/repository/security/permission/UserInstancePermissionRepository.java +++ b/shogun-lib/src/main/java/de/terrestris/shogun/lib/repository/security/permission/UserInstancePermissionRepository.java @@ -20,9 +20,6 @@ import de.terrestris.shogun.lib.model.User; import de.terrestris.shogun.lib.model.security.permission.UserInstancePermission; import de.terrestris.shogun.lib.repository.BaseCrudRepository; -import java.util.List; -import java.util.Optional; -import javax.persistence.QueryHint; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -30,6 +27,10 @@ import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import javax.persistence.QueryHint; +import java.util.List; +import java.util.Optional; + @Repository public interface UserInstancePermissionRepository extends BaseCrudRepository, JpaSpecificationExecutor { @@ -51,7 +52,7 @@ List findByEntityAndPermissionCollectionType( List findAllByUser(User user); @Modifying - @Query(value = "DELETE FROM userinstancepermissions u WHERE u.user_id=:userId", nativeQuery = true) + @Query(value = "DELETE FROM {h-schema}userinstancepermissions u WHERE u.user_id=:userId", nativeQuery = true) void deleteAllByUserId(@Param("userId") Long userId); } From 3d8cb4d7538695bb4521ca2c12446e09b10c059a Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 15:53:37 +0300 Subject: [PATCH 10/17] Add newline --- shogun-lib/src/test/resources/jdbc.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shogun-lib/src/test/resources/jdbc.properties b/shogun-lib/src/test/resources/jdbc.properties index d1117af72..9e815cd66 100644 --- a/shogun-lib/src/test/resources/jdbc.properties +++ b/shogun-lib/src/test/resources/jdbc.properties @@ -19,4 +19,4 @@ hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect hibernate.hbm2ddl.auto=update hibernate.show_sql=false hibernate.format_sql=false -hibernate.default_schema=shogun \ No newline at end of file +hibernate.default_schema=shogun From baedcb4cfae397c2500ed9b37035014cb3ce9d5d Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 15:55:51 +0300 Subject: [PATCH 11/17] Add/remove test dependencies --- shogun-boot/pom.xml | 11 +++++++++++ shogun-lib/pom.xml | 6 ------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/shogun-boot/pom.xml b/shogun-boot/pom.xml index 8fba4d67f..540eb37d1 100644 --- a/shogun-boot/pom.xml +++ b/shogun-boot/pom.xml @@ -83,6 +83,11 @@ org.springframework.data spring-data-envers + + org.springframework.security + spring-security-test + test + @@ -141,6 +146,12 @@ de.terrestris shogun-admin + + + org.mockito + mockito-inline + test + diff --git a/shogun-lib/pom.xml b/shogun-lib/pom.xml index d8aaf206e..2ec2da201 100644 --- a/shogun-lib/pom.xml +++ b/shogun-lib/pom.xml @@ -219,12 +219,6 @@ test - - org.springframework.security - spring-security-test - test - - junit junit From 959c1d15b2c60b2c831eb7c50c03bc34c9867d8e Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Sat, 16 Oct 2021 16:26:20 +0300 Subject: [PATCH 12/17] Add basic integration tests for all BaseController extensions --- .../controller/ApplicationControllerTest.java | 57 ++ .../boot/controller/BaseControllerTest.java | 569 ++++++++++++++++++ .../boot/controller/GroupControllerTest.java | 57 ++ .../boot/controller/IBaseController.java | 23 + .../boot/controller/LayerControllerTest.java | 60 ++ .../boot/controller/UserControllerTest.java | 254 ++++++++ 6 files changed, 1020 insertions(+) create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java create mode 100644 shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java new file mode 100644 index 000000000..2e948b2eb --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java @@ -0,0 +1,57 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +import de.terrestris.shogun.lib.controller.ApplicationController; +import de.terrestris.shogun.lib.model.Application; +import de.terrestris.shogun.lib.model.Group; +import de.terrestris.shogun.lib.repository.ApplicationRepository; + +import java.util.ArrayList; +import java.util.List; + +public class ApplicationControllerTest extends BaseControllerTest { + + public void setBaseEntity() { + entityClass = Application.class; + } + + public void setBasePath() { + basePath = "/applications"; + } + + public void insertTestData() { + Application entity1 = new Application(); + Application entity2 = new Application(); + Application entity3 = new Application(); + + entity1.setName("Application 1"); + entity2.setName("Application 2"); + entity3.setName("Application 3"); + + ArrayList entities = new ArrayList<>(); + + entities.add(entity1); + entities.add(entity2); + entities.add(entity3); + + List persistedEntities = (List) repository.saveAll(entities); + + testData = persistedEntities; + } + +} diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java new file mode 100644 index 000000000..6043b9086 --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java @@ -0,0 +1,569 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import de.terrestris.shogun.boot.config.ApplicationConfig; +import de.terrestris.shogun.boot.config.JdbcConfiguration; +import de.terrestris.shogun.lib.controller.BaseController; +import de.terrestris.shogun.lib.enumeration.PermissionCollectionType; +import de.terrestris.shogun.lib.model.BaseEntity; +import de.terrestris.shogun.lib.model.User; +import de.terrestris.shogun.lib.repository.BaseCrudRepository; +import de.terrestris.shogun.lib.repository.UserRepository; +import de.terrestris.shogun.lib.repository.security.permission.UserClassPermissionRepository; +import de.terrestris.shogun.lib.repository.security.permission.UserInstancePermissionRepository; +import de.terrestris.shogun.lib.service.security.permission.UserClassPermissionService; +import de.terrestris.shogun.lib.service.security.permission.UserInstancePermissionService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.keycloak.KeycloakPrincipal; +import org.keycloak.KeycloakSecurityContext; +import org.keycloak.adapters.springsecurity.account.SimpleKeycloakAccount; +import org.keycloak.adapters.springsecurity.token.KeycloakAuthenticationToken; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.idm.UserRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.servlet.server.Encoding; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +@SpringBootTest( + classes = { + ApplicationConfig.class, + JdbcConfiguration.class + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT +) +@ActiveProfiles("test") +public abstract class BaseControllerTest implements IBaseController { + + @Autowired + protected R repository; + + @Autowired + protected UserRepository userRepository; + + @Autowired + protected UserInstancePermissionRepository userInstancePermissionRepository; + + @Autowired + protected UserClassPermissionRepository userClassPermissionRepository; + + @Autowired + protected UserInstancePermissionService userInstancePermissionService; + + @Autowired + protected UserClassPermissionService userClassPermissionService; + + @Autowired + private WebApplicationContext context; + + @Autowired + protected ObjectMapper objectMapper; + + protected Class entityClass; + + protected String basePath; + + protected MockMvc mockMvc; + + protected List testData; + + protected User adminUser; + + protected User user; + + public void initMockMvc() { + this.mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .alwaysDo(print()) + .apply(springSecurity()) + .build(); + } + + public void initAdminUser() { + User adminUser = new User(); + String keycloakId = "bf5efad6-50f5-448c-b808-60dc0259d70b"; + adminUser.setKeycloakId(keycloakId); + UserRepresentation keycloakRepresentation = new UserRepresentation(); + keycloakRepresentation.setEmail("admin@shogun.de"); + keycloakRepresentation.setEnabled(true); + keycloakRepresentation.setUsername("admin"); + ArrayList realmRoles = new ArrayList<>(); + realmRoles.add("ROLE_ADMIN"); + keycloakRepresentation.setRealmRoles(realmRoles); + adminUser.setKeycloakRepresentation(keycloakRepresentation); + + User persistedAdminUser = userRepository.save(adminUser); + + this.adminUser = persistedAdminUser; + } + + public void initUser() { + User user = new User(); + String keycloakId = "01e680f5-e8a4-460f-8897-12b4cf893739"; + user.setKeycloakId(keycloakId); + UserRepresentation keycloakRepresentation = new UserRepresentation(); + keycloakRepresentation.setEmail("user@shogun.de"); + keycloakRepresentation.setEnabled(true); + keycloakRepresentation.setUsername("user"); + ArrayList realmRoles = new ArrayList<>(); + realmRoles.add("ROLE_USER"); + keycloakRepresentation.setRealmRoles(realmRoles); + user.setKeycloakRepresentation(keycloakRepresentation); + + User persistedUser = userRepository.save(user); + + this.user = persistedUser; + } + + public void cleanupPermissions() { + userInstancePermissionRepository.deleteAll(); + userClassPermissionRepository.deleteAll(); + }; + + public void deinitAdminUser() { + userRepository.delete(this.adminUser); + } + + public void deinitUser() { + userRepository.delete(this.user); + } + + public void cleanupTestData() { + repository.deleteAll(); + } + + public Authentication getMockAuthentication(User mockUser) { + IDToken idToken = new IDToken(); + idToken.setSubject(mockUser.getKeycloakId()); + + KeycloakSecurityContext securityContext = new KeycloakSecurityContext(null, null, null, idToken); + KeycloakPrincipal principal = new KeycloakPrincipal<>(mockUser.getKeycloakRepresentation().getUsername(), securityContext); + + Set roles = mockUser.getKeycloakRepresentation().getRealmRoles().stream().collect(Collectors.toSet()); + SimpleKeycloakAccount account = new SimpleKeycloakAccount(principal, roles, null); + Authentication authentication = new KeycloakAuthenticationToken(account, false); + + return authentication; + } + + @BeforeEach + public void setUp() { + initMockMvc(); + initAdminUser(); + initUser(); + setBaseEntity(); + setBasePath(); + insertTestData(); + } + + @AfterEach + public void teardown() { + cleanupPermissions(); + deinitUser(); + deinitAdminUser(); + cleanupTestData(); + } + + @Test + public void findAll_shouldDenyAccessForRoleAnonymous() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(basePath) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + } + + @Test + public void findAll_shouldReturnNoEntitiesForRoleUserWithoutExplicitPermissions() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(basePath) + .with(authentication(getMockAuthentication(this.user))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$", hasSize(0))); + } + + @Test + public void findAll_shouldReturnAllAvailableEntitiesForRoleUser() throws Exception { + + userInstancePermissionService.setPermission(this.testData.get(0), this.user, PermissionCollectionType.READ); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(basePath) + .with(authentication(getMockAuthentication(this.user))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$", hasSize(1))); + } + + @Test + public void findAll_shouldReturnAllAvailableEntitiesForRoleAdmin() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(basePath) + .with(authentication(getMockAuthentication(this.adminUser))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$", hasSize(testData.size()))); + } + + @Test + public void findOne_shouldDenyAccessForRoleAnonymous() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(String.format("%s/%s", basePath, testData.get(0).getId())) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + } + + @Test + public void findOne_shouldReturnNoEntityForRoleUserWithoutExplicitPermission() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.user))) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + @Test + public void findOne_shouldReturnAnAvailableEntityForRoleUser() throws Exception { + + userInstancePermissionService.setPermission(this.testData.get(0), this.user, PermissionCollectionType.READ); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.user))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasEntry("id", testData.get(0).getId().intValue()))); + } + + @Test + public void findOne_shouldReturnAnAvailableEntityForRoleAdmin() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.adminUser))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasEntry("id", testData.get(0).getId().intValue()))); + } + + @Test + public void add_shouldDenyAccessForRoleAnonymous() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + + List persistedEntities = repository.findAll(); + assertEquals(3, persistedEntities.size()); + } + + @Test + public void add_shouldDenyAccessForRoleUserWithoutExplicitPermission() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + + List persistedEntities = repository.findAll(); + assertEquals(3, persistedEntities.size()); + } + + @Test + public void add_shouldCreateTheEntityForRoleUser() throws Exception { + + userClassPermissionService.setPermission(entityClass, this.user, PermissionCollectionType.CREATE); + + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + List persistedEntities = repository.findAll(); + assertEquals(4, persistedEntities.size()); + } + + @Test + public void add_shouldCreateTheEntityForRoleAdmin() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + List persistedEntities = repository.findAll(); + assertEquals(4, persistedEntities.size()); + } + + @Test + public void update_shouldDenyAccessForRoleAnonymous() throws Exception { + JsonNode updateNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("created", "modified"); + updateNode = ((ObjectNode) updateNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .put(String.format("%s/%s", basePath, testData.get(0).getId())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(updateNode)) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + } + + @Test + public void update_shouldDenyAccessForRoleUserWithoutExplicitPermission() throws Exception { + JsonNode updateNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("created", "modified"); + updateNode = ((ObjectNode) updateNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .put(String.format("%s/%s", basePath, testData.get(0).getId())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(updateNode)) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + } + + @Test + public void update_shouldUpdateTheEntityForRoleUser() throws Exception { + + userInstancePermissionService.setPermission(testData.get(0), this.user, PermissionCollectionType.UPDATE); + + JsonNode updateNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("created", "modified"); + updateNode = ((ObjectNode) updateNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .put(String.format("%s/%s", basePath, testData.get(0).getId())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(updateNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + Optional updatedEntity = repository.findById(testData.get(0).getId()); + assertNotEquals(updatedEntity.get().getCreated(), updatedEntity.get().getModified()); + } + + @Test + public void update_shouldUpdateTheEntityForRoleAdmin() throws Exception { + JsonNode updateNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("created", "modified"); + updateNode = ((ObjectNode) updateNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .put(String.format("%s/%s", basePath, testData.get(0).getId())) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(updateNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + Optional updatedEntity = repository.findById(testData.get(0).getId()); + assertNotEquals(updatedEntity.get().getCreated(), updatedEntity.get().getModified()); + } + + @Test + public void delete_shouldDenyAccessForRoleAnonymous() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + + List persistedEntities = repository.findAll(); + assertEquals(3, persistedEntities.size()); + } + + @Test + public void delete_shouldDenyAccessForRoleUserWithoutExplicitPermission() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + + List persistedEntities = repository.findAll(); + assertEquals(3, persistedEntities.size()); + } + + @Test + public void delete_shouldDeleteAnAvailableEntityForRoleUser() throws Exception { + + userInstancePermissionService.setPermission(testData.get(0), this.user, PermissionCollectionType.READ_DELETE); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNoContent()); + + List persistedEntities = repository.findAll(); + assertEquals(2, persistedEntities.size()); + } + + @Test + public void delete_shouldDeleteAnAvailableEntityForRoleAdmin() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNoContent()); + + List persistedEntities = repository.findAll(); + assertEquals(2, persistedEntities.size()); + } + +} diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java new file mode 100644 index 000000000..aadb571bb --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java @@ -0,0 +1,57 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +import de.terrestris.shogun.lib.controller.GroupController; +import de.terrestris.shogun.lib.model.Group; +import de.terrestris.shogun.lib.repository.GroupRepository; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class GroupControllerTest extends BaseControllerTest { + + public void setBaseEntity() { + entityClass = Group.class; + } + + public void setBasePath() { + basePath = "/groups"; + } + + public void insertTestData() { + Group entity1 = new Group(); + Group entity2 = new Group(); + Group entity3 = new Group(); + + entity1.setKeycloakId(UUID.randomUUID().toString()); + entity2.setKeycloakId(UUID.randomUUID().toString()); + entity3.setKeycloakId(UUID.randomUUID().toString()); + + ArrayList entities = new ArrayList<>(); + + entities.add(entity1); + entities.add(entity2); + entities.add(entity3); + + List persistedEntities = (List) repository.saveAll(entities); + + testData = persistedEntities; + } + +} diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java new file mode 100644 index 000000000..58cd53dcf --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java @@ -0,0 +1,23 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +public interface IBaseController { + void setBaseEntity(); + void setBasePath(); + void insertTestData(); +} diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java new file mode 100644 index 000000000..ef9f009e5 --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java @@ -0,0 +1,60 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +import de.terrestris.shogun.lib.controller.LayerController; +import de.terrestris.shogun.lib.enumeration.LayerType; +import de.terrestris.shogun.lib.model.Layer; +import de.terrestris.shogun.lib.repository.LayerRepository; + +import java.util.ArrayList; +import java.util.List; + +public class LayerControllerTest extends BaseControllerTest { + + public void setBaseEntity() { + entityClass = Layer.class; + } + + public void setBasePath() { + basePath = "/layers"; + } + + public void insertTestData() { + Layer entity1 = new Layer(); + Layer entity2 = new Layer(); + Layer entity3 = new Layer(); + + entity1.setName("Layer 1"); + entity1.setType(LayerType.WMS); + entity2.setName("Layer 2"); + entity2.setType(LayerType.WFS); + entity3.setName("Layer 3"); + entity3.setType(LayerType.TILEWMS); + + ArrayList entities = new ArrayList<>(); + + entities.add(entity1); + entities.add(entity2); + entities.add(entity3); + + List persistedEntities = (List) repository.saveAll(entities); + + testData = persistedEntities; + } + +} diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java new file mode 100644 index 000000000..6b1f46bc6 --- /dev/null +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java @@ -0,0 +1,254 @@ +/* SHOGun, https://terrestris.github.io/shogun/ + * + * Copyright © 2020-present terrestris GmbH & Co. KG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0.txt + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package de.terrestris.shogun.boot.controller; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import de.terrestris.shogun.lib.controller.UserController; +import de.terrestris.shogun.lib.enumeration.PermissionCollectionType; +import de.terrestris.shogun.lib.model.User; +import de.terrestris.shogun.lib.repository.UserRepository; +import org.junit.jupiter.api.Test; +import org.springframework.boot.web.servlet.server.Encoding; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; + +public class UserControllerTest extends BaseControllerTest { + + public void setBaseEntity () { + entityClass = User.class; + }; + + public void setBasePath() { + basePath = "/users"; + } + + public void insertTestData() { + User entity1 = new User(); + User entity2 = new User(); + User entity3 = new User(); + + entity1.setKeycloakId(UUID.randomUUID().toString()); + entity2.setKeycloakId(UUID.randomUUID().toString()); + entity3.setKeycloakId(UUID.randomUUID().toString()); + + ArrayList entities = new ArrayList<>(); + + entities.add(entity1); + entities.add(entity2); + entities.add(entity3); + + List persistedEntities = (List) repository.saveAll(entities); + + testData = persistedEntities; + } + + @Test + @Override + public void findAll_shouldReturnAllAvailableEntitiesForRoleAdmin() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .get(basePath) + .with(authentication(getMockAuthentication(this.adminUser))) + ) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").isArray()) + .andExpect(jsonPath("$", hasSize(testData.size() + 2))); + } + + @Test + @Override + public void add_shouldDenyAccessForRoleAnonymous() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + + List persistedEntities = repository.findAll(); + assertEquals(5, persistedEntities.size()); + } + + @Test + @Override + public void add_shouldDenyAccessForRoleUserWithoutExplicitPermission() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + + List persistedEntities = repository.findAll(); + assertEquals(5, persistedEntities.size()); + } + + @Test + @Override + public void add_shouldCreateTheEntityForRoleUser() throws Exception { + + userClassPermissionService.setPermission(entityClass, this.user, PermissionCollectionType.CREATE); + + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + List persistedEntities = repository.findAll(); + assertEquals(6, persistedEntities.size()); + } + + @Test + @Override + public void add_shouldCreateTheEntityForRoleAdmin() throws Exception { + JsonNode insertNode = objectMapper.valueToTree(testData.get(0)); + List fieldsToRemove = List.of("id", "created", "modified"); + insertNode = ((ObjectNode) insertNode).remove(fieldsToRemove); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .post(String.format("%s", basePath)) + .contentType(MediaType.APPLICATION_JSON) + .characterEncoding(Encoding.DEFAULT_CHARSET.toString()) + .content(objectMapper.writeValueAsString(insertNode)) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("$").exists()) + .andExpect(jsonPath("$", hasKey("id"))); + + List persistedEntities = repository.findAll(); + assertEquals(6, persistedEntities.size()); + } + + @Test + @Override + public void delete_shouldDenyAccessForRoleAnonymous() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().is3xxRedirection()) + .andExpect(MockMvcResultMatchers.redirectedUrl("/sso/login")); + + List persistedEntities = repository.findAll(); + assertEquals(5, persistedEntities.size()); + } + + @Test + @Override + public void delete_shouldDenyAccessForRoleUserWithoutExplicitPermission() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.user))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNotFound()); + + List persistedEntities = repository.findAll(); + assertEquals(5, persistedEntities.size()); + } + + @Test + @Override + public void delete_shouldDeleteAnAvailableEntityForRoleUser() throws Exception { + + userInstancePermissionService.setPermission(testData.get(0), this.user, PermissionCollectionType.DELETE); + + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNoContent()); + + List persistedEntities = repository.findAll(); + assertEquals(4, persistedEntities.size()); + } + + @Test + @Override + public void delete_shouldDeleteAnAvailableEntityForRoleAdmin() throws Exception { + this.mockMvc + .perform( + MockMvcRequestBuilders + .delete(String.format("%s/%s", basePath, testData.get(0).getId())) + .with(authentication(getMockAuthentication(this.adminUser))) + .with(csrf()) + ) + .andExpect(MockMvcResultMatchers.status().isNoContent()); + + List persistedEntities = repository.findAll(); + assertEquals(4, persistedEntities.size()); + } +} From c595ad50cd64b2d071b248f12006ffb1babfa791 Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Mon, 18 Oct 2021 13:16:13 +0300 Subject: [PATCH 13/17] Remove unneeded dependency --- shogun-boot/pom.xml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/shogun-boot/pom.xml b/shogun-boot/pom.xml index 540eb37d1..57cd4c121 100644 --- a/shogun-boot/pom.xml +++ b/shogun-boot/pom.xml @@ -146,12 +146,6 @@ de.terrestris shogun-admin - - - org.mockito - mockito-inline - test - From 69fa96011b0855ecdc3a14eee251c30af6c0d1f1 Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Mon, 18 Oct 2021 13:22:00 +0300 Subject: [PATCH 14/17] Remove unneeded scope --- shogun-boot/pom.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/shogun-boot/pom.xml b/shogun-boot/pom.xml index 57cd4c121..6906791a0 100644 --- a/shogun-boot/pom.xml +++ b/shogun-boot/pom.xml @@ -46,7 +46,6 @@ org.springframework.boot spring-boot-starter-test - test org.springframework.boot @@ -86,7 +85,6 @@ org.springframework.security spring-security-test - test From b2335452725c87f3e1ee30f0602a31481c9880f9 Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Mon, 18 Oct 2021 13:38:04 +0300 Subject: [PATCH 15/17] Check license against a pattern --- .licenserc.yaml | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/.licenserc.yaml b/.licenserc.yaml index 03310abb3..9464e3f85 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -2,22 +2,8 @@ header: license: spdx-id: Apache-2.0 copyright-owner: terrestris GmbH & Co. KG - content: | - SHOGun, https://terrestris.github.io/shogun/ - - Copyright © 2020-present terrestris GmbH & Co. KG - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0.txt - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. + pattern: | + SHOGun, https:\/\/terrestris\.github\.io\/shogun\/\n\nCopyright © \d{4}-present terrestris GmbH & Co\. KG\n\nLicensed under the Apache License, Version 2\.0 \(the "License"\);\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n https:\/\/www\.apache\.org\/licenses\/LICENSE-2\.0\.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.\nSee the License for the specific language governing permissions and\nlimitations under the License\. paths-ignore: - '.github' From 85faa01f7bcf9e80c79a35913625cd8dd7e6efda Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Mon, 18 Oct 2021 13:40:05 +0300 Subject: [PATCH 16/17] Adjust license header --- .../shogun/boot/controller/ApplicationControllerTest.java | 2 +- .../terrestris/shogun/boot/controller/BaseControllerTest.java | 2 +- .../terrestris/shogun/boot/controller/GroupControllerTest.java | 2 +- .../de/terrestris/shogun/boot/controller/IBaseController.java | 2 +- .../terrestris/shogun/boot/controller/LayerControllerTest.java | 2 +- .../terrestris/shogun/boot/controller/UserControllerTest.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java index 2e948b2eb..32e970528 100644 --- a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/ApplicationControllerTest.java @@ -1,6 +1,6 @@ /* SHOGun, https://terrestris.github.io/shogun/ * - * Copyright © 2020-present terrestris GmbH & Co. KG + * Copyright © 2021-present terrestris GmbH & Co. KG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java index 6043b9086..b85232fc1 100644 --- a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/BaseControllerTest.java @@ -1,6 +1,6 @@ /* SHOGun, https://terrestris.github.io/shogun/ * - * Copyright © 2020-present terrestris GmbH & Co. KG + * Copyright © 2021-present terrestris GmbH & Co. KG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java index aadb571bb..da6e3a251 100644 --- a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/GroupControllerTest.java @@ -1,6 +1,6 @@ /* SHOGun, https://terrestris.github.io/shogun/ * - * Copyright © 2020-present terrestris GmbH & Co. KG + * Copyright © 2021-present terrestris GmbH & Co. KG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java index 58cd53dcf..b732ae9b9 100644 --- a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/IBaseController.java @@ -1,6 +1,6 @@ /* SHOGun, https://terrestris.github.io/shogun/ * - * Copyright © 2020-present terrestris GmbH & Co. KG + * Copyright © 2021-present terrestris GmbH & Co. KG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java index ef9f009e5..c1c709fd0 100644 --- a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/LayerControllerTest.java @@ -1,6 +1,6 @@ /* SHOGun, https://terrestris.github.io/shogun/ * - * Copyright © 2020-present terrestris GmbH & Co. KG + * Copyright © 2021-present terrestris GmbH & Co. KG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java index 6b1f46bc6..af41607f7 100644 --- a/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java +++ b/shogun-boot/src/test/java/de/terrestris/shogun/boot/controller/UserControllerTest.java @@ -1,6 +1,6 @@ /* SHOGun, https://terrestris.github.io/shogun/ * - * Copyright © 2020-present terrestris GmbH & Co. KG + * Copyright © 2021-present terrestris GmbH & Co. KG * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From fffcf208e9840be111234ffcdf03246a8153459f Mon Sep 17 00:00:00 2001 From: Daniel Koch Date: Mon, 18 Oct 2021 15:21:52 +0300 Subject: [PATCH 17/17] Fix license pattern --- .licenserc.yaml | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/.licenserc.yaml b/.licenserc.yaml index 9464e3f85..24b19a7d2 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -1,9 +1,20 @@ header: - license: - spdx-id: Apache-2.0 - copyright-owner: terrestris GmbH & Co. KG - pattern: | - SHOGun, https:\/\/terrestris\.github\.io\/shogun\/\n\nCopyright © \d{4}-present terrestris GmbH & Co\. KG\n\nLicensed under the Apache License, Version 2\.0 \(the "License"\);\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n https:\/\/www\.apache\.org\/licenses\/LICENSE-2\.0\.txt\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an "AS IS" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied\.\nSee the License for the specific language governing permissions and\nlimitations under the License\. + pattern: | + SHOGun, https://terrestris.github.io/shogun/ + + Copyright © \d{4}-present terrestris GmbH & Co. KG + + Licensed under the Apache License, Version 2.0 \(the "License"\); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0.txt + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. paths-ignore: - '.github' @@ -25,5 +36,6 @@ header: - 'docs' - '**/*.md' - '**/*.sql' + - '**/*.iml' comment: on-failure