diff --git a/hawkbit-repository/hawkbit-repository-test/pom.xml b/hawkbit-repository/hawkbit-repository-test/pom.xml index 64989fc87..cbf4411a5 100644 --- a/hawkbit-repository/hawkbit-repository-test/pom.xml +++ b/hawkbit-repository/hawkbit-repository-test/pom.xml @@ -75,6 +75,10 @@ org.springframework.boot spring-boot-starter-web + + org.springframework.security + spring-security-test + org.springframework spring-context-support diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java index 22bdb4e5b..5db0499dd 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithSpringAuthorityRule.java @@ -171,7 +171,7 @@ public class WithSpringAuthorityRule implements BeforeEachCallback, AfterEachCal }; } - private static class SecurityContextWithUser implements SecurityContext { + static class SecurityContextWithUser implements SecurityContext { private static final long serialVersionUID = 1L; private final WithUser annotation; diff --git a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithUser.java b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithUser.java index 5a58053ed..25d517071 100644 --- a/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithUser.java +++ b/hawkbit-repository/hawkbit-repository-test/src/main/java/org/eclipse/hawkbit/repository/test/util/WithUser.java @@ -9,6 +9,10 @@ */ package org.eclipse.hawkbit.repository.test.util; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.test.context.support.WithSecurityContext; +import org.springframework.security.test.context.support.WithSecurityContextFactory; + import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; @@ -21,6 +25,7 @@ import java.lang.annotation.Target; */ @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.METHOD, ElementType.TYPE }) +@WithSecurityContext(factory = WithUser.WithUserPrincipalSecurityContextFactory.class) @Inherited public @interface WithUser { @@ -74,4 +79,11 @@ public @interface WithUser { String[] removeFromAllPermission() default {}; boolean controller() default false; -} + + class WithUserPrincipalSecurityContextFactory implements WithSecurityContextFactory { + @Override + public SecurityContext createSecurityContext(final WithUser withUserPrincipal) { + return new WithSpringAuthorityRule.SecurityContextWithUser(withUserPrincipal); + } + } +} \ No newline at end of file diff --git a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResourceTest.java b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResourceTest.java index 8fdc4c36d..d9451557f 100644 --- a/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResourceTest.java +++ b/hawkbit-rest/hawkbit-mgmt-resource/src/test/java/org/eclipse/hawkbit/mgmt/rest/resource/MgmtBasicAuthResourceTest.java @@ -13,15 +13,41 @@ import io.qameta.allure.Description; import io.qameta.allure.Feature; import io.qameta.allure.Story; import org.eclipse.hawkbit.mgmt.rest.api.MgmtRestConstants; +import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration; +import org.eclipse.hawkbit.repository.test.TestConfiguration; +import org.eclipse.hawkbit.repository.test.matcher.EventVerifier; +import org.eclipse.hawkbit.repository.test.util.CleanupTestExecutionListener; +import org.eclipse.hawkbit.repository.test.util.JUnitTestLoggerExtension; +import org.eclipse.hawkbit.repository.test.util.SharedSqlTestDatabaseExtension; +import org.eclipse.hawkbit.repository.test.util.WithSpringAuthorityRule; import org.eclipse.hawkbit.repository.test.util.WithUser; +import org.eclipse.hawkbit.rest.RestConfiguration; +import org.eclipse.hawkbit.rest.filter.ExcludePathAwareShallowETagFilter; +import org.eclipse.hawkbit.rest.util.FilterHttpResponse; import org.eclipse.hawkbit.rest.util.MockMvcResultPrinter; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.stream.test.binder.TestSupportBinderAutoConfiguration; import org.springframework.hateoas.MediaTypes; import org.springframework.http.HttpHeaders; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.util.Base64Utils; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.filter.CharacterEncodingFilter; +import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.CONTROLLER_ROLE; +import static org.eclipse.hawkbit.im.authentication.SpPermission.SpringEvalExpressions.SYSTEM_ROLE; import static org.hamcrest.CoreMatchers.equalTo; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -30,24 +56,49 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. /** * Test for {@link MgmtBasicAuthResource}. - * */ +@ActiveProfiles({ "test" }) +@ExtendWith({ JUnitTestLoggerExtension.class, SharedSqlTestDatabaseExtension.class }) +@SpringBootTest +// destroy the context after each test class because otherwise we get problem +// when context is +// refreshed we e.g. get two instances of CacheManager which leads to very +// strange test failures. +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +// Cleaning repository will fire "delete" events. We won't count them to the +// test execution. So, the order execution between EventVerifier and Cleanup is +// important! +@TestExecutionListeners(listeners = { EventVerifier.class, CleanupTestExecutionListener.class }, + mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS) +@TestPropertySource(properties = "spring.main.allow-bean-definition-overriding=true") +@WebAppConfiguration +@AutoConfigureMockMvc +@ContextConfiguration(classes = { MgmtApiConfiguration.class, RestConfiguration.class, + RepositoryApplicationConfiguration.class, TestConfiguration.class, + TestSupportBinderAutoConfiguration.class }) +//@TestPropertySource(locations = "classpath:/mgmt-test.properties") @Feature("Component Tests - Management API") @Story("Basic auth Userinfo Resource") -public class MgmtBasicAuthResourceTest extends AbstractManagementApiIntegrationTest{ +public class MgmtBasicAuthResourceTest { private static final String TEST_USER = "testUser"; private static final String DEFAULT = "default"; - // Need another mockMvc to bypass the default Basic auth security @Autowired - MockMvc mockMvcWithSecurity; + MockMvc defaultMock; + + @Autowired + private FilterHttpResponse filterHttpResponse; + @Autowired + private CharacterEncodingFilter characterEncodingFilter; + @Autowired + protected WebApplicationContext webApplicationContext; @Test @Description("Test of userinfo api with basic auth validation") @WithUser(principal = TEST_USER) public void validateBasicAuthWithUserDetails() throws Exception { - mvc.perform(get(MgmtRestConstants.AUTH_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print()) + withSecurityMock().perform(get(MgmtRestConstants.AUTH_V1_REQUEST_MAPPING)).andDo(MockMvcResultPrinter.print()) .andDo(MockMvcResultPrinter.print()) .andExpect(status().isOk()) .andExpect(content().contentType(MediaTypes.HAL_JSON_VALUE)) @@ -58,7 +109,7 @@ public class MgmtBasicAuthResourceTest extends AbstractManagementApiIntegrationT @Test @Description("Test of userinfo api with invalid basic auth fails") public void validateBasicAuthFailsWithInvalidCredentials() throws Exception { - mockMvcWithSecurity.perform(get(MgmtRestConstants.AUTH_V1_REQUEST_MAPPING) + defaultMock.perform(get(MgmtRestConstants.AUTH_V1_REQUEST_MAPPING) .header(HttpHeaders.AUTHORIZATION, getBasicAuth("wrongUser", "wrongSecret"))) .andDo(MockMvcResultPrinter.print()) .andExpect(status().isUnauthorized()); @@ -67,4 +118,14 @@ public class MgmtBasicAuthResourceTest extends AbstractManagementApiIntegrationT private String getBasicAuth(final String username, final String password) { return "Basic " + Base64Utils.encodeToString((username + ":" + password).getBytes()); } + + private MockMvc withSecurityMock() throws Exception { + return createMvcWebAppContext(webApplicationContext).build(); + } + + private DefaultMockMvcBuilder createMvcWebAppContext(final WebApplicationContext context) { + final DefaultMockMvcBuilder createMvcWebAppContext = MockMvcBuilders.webAppContextSetup(context); + + return createMvcWebAppContext; + } } diff --git a/hawkbit-runtime/hawkbit-ddi-server/src/main/java/org/eclipse/hawkbit/app/ddi/DDIStart.java b/hawkbit-runtime/hawkbit-ddi-server/src/main/java/org/eclipse/hawkbit/app/ddi/DDIStart.java index bc3eebd8d..f41f4a595 100644 --- a/hawkbit-runtime/hawkbit-ddi-server/src/main/java/org/eclipse/hawkbit/app/ddi/DDIStart.java +++ b/hawkbit-runtime/hawkbit-ddi-server/src/main/java/org/eclipse/hawkbit/app/ddi/DDIStart.java @@ -14,7 +14,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.servlet.mvc.support.RedirectAttributes; @@ -52,7 +51,7 @@ public class DDIStart { @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true) - public static class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { + public static class MethodSecurityConfig { } } diff --git a/hawkbit-runtime/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/MgmtServerStart.java b/hawkbit-runtime/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/MgmtServerStart.java index 5ca89511d..942ab9321 100644 --- a/hawkbit-runtime/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/MgmtServerStart.java +++ b/hawkbit-runtime/hawkbit-mgmt-server/src/main/java/org/eclipse/hawkbit/app/mgmt/MgmtServerStart.java @@ -14,7 +14,6 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; -import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.servlet.mvc.support.RedirectAttributes; @@ -52,7 +51,7 @@ public class MgmtServerStart { @Configuration @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true) - public static class MethodSecurityConfig extends GlobalMethodSecurityConfiguration { + public static class MethodSecurityConfig { } } diff --git a/hawkbit-runtime/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/PreAuthorizeEnabledTest.java b/hawkbit-runtime/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/PreAuthorizeEnabledTest.java new file mode 100644 index 000000000..3b079b915 --- /dev/null +++ b/hawkbit-runtime/hawkbit-mgmt-server/src/test/java/org/eclipse/hawkbit/app/mgmt/PreAuthorizeEnabledTest.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.app.mgmt; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; +import org.eclipse.hawkbit.im.authentication.SpPermission; +import org.eclipse.hawkbit.repository.test.util.WithUser; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +@Feature("Integration Test - Security") +@Story("PreAuthorized enabled") +public class PreAuthorizeEnabledTest extends AbstractSecurityTest { + + @Test + @Description("Tests whether request fail if a role is forbidden for the user") + @WithUser(authorities = { SpPermission.READ_TARGET } ) + public void failIfNoRole() throws Exception { + mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> + assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value())); + } + + @Test + @Description("Tests whether request succeed if a role is granted for the user") + @WithUser(authorities = { SpPermission.READ_REPOSITORY }) + public void successIfHasRole() throws Exception { + mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> { + assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()); + }); + } +} \ No newline at end of file diff --git a/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/PreAuthorizeEnabledTest.java b/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/PreAuthorizeEnabledTest.java new file mode 100644 index 000000000..6345d559b --- /dev/null +++ b/hawkbit-runtime/hawkbit-update-server/src/test/java/org/eclipse/hawkbit/app/PreAuthorizeEnabledTest.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2023 Bosch.IO GmbH and others + * + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.hawkbit.app; + +import io.qameta.allure.Description; +import io.qameta.allure.Feature; +import io.qameta.allure.Story; +import org.eclipse.hawkbit.im.authentication.SpPermission; +import org.eclipse.hawkbit.repository.test.util.WithUser; +import org.junit.jupiter.api.Test; +import org.springframework.http.HttpStatus; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; + +@Feature("Integration Test - Security") +@Story("PreAuthorized enabled") +public class PreAuthorizeEnabledTest extends AbstractSecurityTest { + + @Test + @Description("Tests whether request fail if a role is forbidden for the user") + @WithUser(authorities = { SpPermission.READ_TARGET } ) + public void failIfNoRole() throws Exception { + mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> + assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.FORBIDDEN.value())); + } + + @Test + @Description("Tests whether request succeed if a role is granted for the user") + @WithUser(authorities = { SpPermission.READ_REPOSITORY }) + public void successIfHasRole() throws Exception { + mvc.perform(get("/rest/v1/distributionsets")).andExpect(result -> { + assertThat(result.getResponse().getStatus()).isEqualTo(HttpStatus.OK.value()); + }); + } +} \ No newline at end of file