diff --git a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java index 4feced27c6e16b2ecfc4837c17adf9a8e3468bbe..3b564cf37f4483a8b793e1f5c845fdfce4c9ad69 100644 --- a/src/main/java/de/ozgcloud/admin/RootModelAssembler.java +++ b/src/main/java/de/ozgcloud/admin/RootModelAssembler.java @@ -33,6 +33,7 @@ import org.springframework.hateoas.server.RepresentationModelAssembler; import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder; import org.springframework.stereotype.Component; +import de.ozgcloud.admin.common.user.CurrentUserService; import lombok.RequiredArgsConstructor; @Component @@ -41,6 +42,7 @@ public class RootModelAssembler implements RepresentationModelAssembler<Root, En static final String REL_CONFIGURATION = "configuration"; private final RepositoryRestProperties restProperties; + private final CurrentUserService currentUserService; @Override public EntityModel<Root> toModel(Root root) { @@ -52,7 +54,9 @@ public class RootModelAssembler implements RepresentationModelAssembler<Root, En List<Link> links = new ArrayList<>(); var rootLinkBuilder = WebMvcLinkBuilder.linkTo(RootController.class); links.add(rootLinkBuilder.withSelfRel()); - links.add(buildConfigLink()); + if (currentUserService.hasConfigurationPermission()) { + links.add(buildConfigLink()); + } return links; } diff --git a/src/main/java/de/ozgcloud/admin/common/user/CurrentUserHelper.java b/src/main/java/de/ozgcloud/admin/common/user/CurrentUserHelper.java index b4fd2c459c18cd81dca191d69eb2da662c935c96..123ecc4d4751770e33f3a9234d20a22662932d93 100644 --- a/src/main/java/de/ozgcloud/admin/common/user/CurrentUserHelper.java +++ b/src/main/java/de/ozgcloud/admin/common/user/CurrentUserHelper.java @@ -26,9 +26,10 @@ package de.ozgcloud.admin.common.user; import java.util.Collection; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; -import org.apache.commons.lang3.StringUtils; import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.core.Authentication; @@ -46,20 +47,25 @@ class CurrentUserHelper { private static final Predicate<Authentication> IS_TRUSTED = auth -> !TRUST_RESOLVER.isAnonymous(auth); public static boolean hasRole(String role) { + return hasAnyRole(Set.of(role)); + } + + public static boolean hasAnyRole(Set<String> roles) { var auth = getAuthentication(); if ((Objects.isNull(auth)) || (Objects.isNull(auth.getPrincipal()))) { return false; } - return containsRole(auth.getAuthorities(), role); + return containsAnyRole(auth.getAuthorities(), roles); } - static boolean containsRole(Collection<? extends GrantedAuthority> authorities, String role) { + static boolean containsAnyRole(Collection<? extends GrantedAuthority> authorities, Set<String> roles) { if (Objects.isNull(authorities)) { return false; } - return authorities.stream().anyMatch(a -> StringUtils.equalsIgnoreCase(addRolePrefixIfMissing(role), a.getAuthority())); + var rolesWithPrefix = roles.stream().map(CurrentUserHelper::addRolePrefixIfMissing).collect(Collectors.toSet()); + return authorities.stream().anyMatch(a -> rolesWithPrefix.contains(a.getAuthority())); } static String addRolePrefixIfMissing(String roleToCheck) { diff --git a/src/main/java/de/ozgcloud/admin/common/user/CurrentUserService.java b/src/main/java/de/ozgcloud/admin/common/user/CurrentUserService.java index fc045ec0c3fcb19d9fc3b060bd140ceb0890368a..0959eafefcfcec06dccb4b75fe704928e21edc12 100644 --- a/src/main/java/de/ozgcloud/admin/common/user/CurrentUserService.java +++ b/src/main/java/de/ozgcloud/admin/common/user/CurrentUserService.java @@ -30,4 +30,8 @@ public class CurrentUserService { public boolean hasRole(String role) { return CurrentUserHelper.hasRole(role); } + + public boolean hasConfigurationPermission() { + return CurrentUserHelper.hasAnyRole(UserRole.CONFIGURATION_ROLES); + } } 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 8c28ea3ebe1bc531ad2447297ec9f3f23e370554..391e3f5a70f6b1865cef7b4e4e09d73f5d57870d 100644 --- a/src/main/java/de/ozgcloud/admin/common/user/UserRole.java +++ b/src/main/java/de/ozgcloud/admin/common/user/UserRole.java @@ -23,6 +23,8 @@ */ package de.ozgcloud.admin.common.user; +import java.util.Set; + import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -30,4 +32,6 @@ import lombok.NoArgsConstructor; public class UserRole { public static final String DATENBEAUFTRAGUNG = "DATENBEAUFTRAGUNG"; public static final String ADMIN_ADMIN = "ADMIN_ADMIN"; + + public static final Set<String> CONFIGURATION_ROLES = Set.of(ADMIN_ADMIN, DATENBEAUFTRAGUNG); } diff --git a/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java b/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java index 5c4ed0d6bc38bf1c40399d0fad93cc488644b05d..d3933e7611bc6c2682d105a3bd92f022f14421a2 100644 --- a/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java +++ b/src/test/java/de/ozgcloud/admin/RootModelAssemblerTest.java @@ -25,6 +25,7 @@ package de.ozgcloud.admin; import static de.ozgcloud.admin.RootModelAssembler.*; import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; import java.util.List; @@ -34,34 +35,39 @@ 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.boot.autoconfigure.data.rest.RepositoryRestProperties; import org.springframework.hateoas.Link; +import de.ozgcloud.admin.common.user.CurrentUserService; + class RootModelAssemblerTest { private static final String BASE_PATH = "/api/base"; + @Spy @InjectMocks private RootModelAssembler modelAssembler; @Mock private RepositoryRestProperties restProperties; + @Mock + private CurrentUserService currentUserService; @DisplayName("Entity Model") @Nested class TestEntityModel { + + private final List<Link> links = List.of(Link.of(RootController.PATH)); + @BeforeEach void beforeEach() { - Mockito.when(restProperties.getBasePath()).thenReturn(BASE_PATH); + doReturn(links).when(modelAssembler).buildRootModelLinks(); } @Test void shouldHaveRoot() { var givenRoot = RootTestFactory.create(); - List<Link> links = List.of(); - Mockito.when(modelAssembler.buildRootModelLinks()).thenReturn(links); var resultRoot = modelAssembler.toModel(givenRoot).getContent(); @@ -70,9 +76,6 @@ class RootModelAssemblerTest { @Test void shouldHaveLinks() { - List<Link> links = List.of(Link.of(RootController.PATH)); - Mockito.when(modelAssembler.buildRootModelLinks()).thenReturn(links); - var modelLinks = modelAssembler.toModel(RootTestFactory.create()).getLinks(); assertThat(modelLinks).containsAll(links); @@ -84,14 +87,46 @@ class RootModelAssemblerTest { class TestBuildRootModelLinks { @Test - void shouldHaveHrefToBasePathIfAuthorized() { - Mockito.when(restProperties.getBasePath()).thenReturn(BASE_PATH); + void shouldCheckConfigurationPermission() { + modelAssembler.buildRootModelLinks(); + + verify(currentUserService).hasConfigurationPermission(); + } + + @Nested + class TestOnHasConfigurationPermission { + + @BeforeEach + void hasConfigurationPermission() { + when(currentUserService.hasConfigurationPermission()).thenReturn(true); + when(restProperties.getBasePath()).thenReturn(BASE_PATH); + } + + @Test + void shouldHaveHrefToConfiguration() { + var links = modelAssembler.buildRootModelLinks(); + + assertThat(links).containsExactly( + Link.of(RootController.PATH), + Link.of(BASE_PATH, REL_CONFIGURATION)); + } + } + + @Nested + class TestOnNotHasConfigurationPermission { + + @BeforeEach + void hasNotConfigurationPermission() { + when(currentUserService.hasConfigurationPermission()).thenReturn(false); + } - List<Link> links = modelAssembler.buildRootModelLinks(); + @Test + void shouldHaveOnlySelfLink() { + var links = modelAssembler.buildRootModelLinks(); - assertThat(links).containsExactly( - Link.of(RootController.PATH), - Link.of(BASE_PATH, REL_CONFIGURATION)); + assertThat(links).containsExactly( + Link.of(RootController.PATH)); + } } } diff --git a/src/test/java/de/ozgcloud/admin/common/user/CurrentUserHelperTest.java b/src/test/java/de/ozgcloud/admin/common/user/CurrentUserHelperTest.java index 46780c05d9f9325f47601d1caad303bb53aba19a..1c171505ed751a6e5e4ecc0d9ba72d06f93b1f01 100644 --- a/src/test/java/de/ozgcloud/admin/common/user/CurrentUserHelperTest.java +++ b/src/test/java/de/ozgcloud/admin/common/user/CurrentUserHelperTest.java @@ -24,9 +24,11 @@ package de.ozgcloud.admin.common.user; import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; -import java.util.Collection; import java.util.List; +import java.util.Set; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -35,7 +37,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.Mock; import org.mockito.MockedStatic; -import org.mockito.Mockito; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -43,23 +44,58 @@ import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; +import com.thedeanda.lorem.LoremIpsum; + class CurrentUserHelperTest { @DisplayName("Has role") @Nested class TestHasRole { @Mock - private final Authentication mockAuthentication = Mockito.mock(Authentication.class); + private Authentication mockAuthentication; + @Mock + private User mockPrincipal; + + private final String role = LoremIpsum.getInstance().getWords(1); + + @Test + void shouldCallHasAnyRole() { + try (MockedStatic<CurrentUserHelper> mockUserHelper = mockStatic(CurrentUserHelper.class, CALLS_REAL_METHODS)) { + mockUserHelper.when(() -> CurrentUserHelper.hasAnyRole(any())).thenReturn(false); + + CurrentUserHelper.hasRole(role); + + mockUserHelper.verify(() -> CurrentUserHelper.hasAnyRole(Set.of(role))); + } + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + void shouldReturnValue(boolean hasRole) { + try (MockedStatic<CurrentUserHelper> mockUserHelper = mockStatic(CurrentUserHelper.class, CALLS_REAL_METHODS)) { + mockUserHelper.when(() -> CurrentUserHelper.hasAnyRole(any())).thenReturn(hasRole); + + var result = CurrentUserHelper.hasRole(UserRole.ADMIN_ADMIN); + + assertThat(result).isEqualTo(hasRole); + } + } + } + + @Nested + class TestHasAnyRole { + @Mock + private Authentication mockAuthentication; @Mock - private final User mockPrincipal = Mockito.mock(User.class); + private User mockPrincipal; + + private final Set<String> roles = Set.of(LoremIpsum.getInstance().getWords(1)); @Test void shouldReturnFalseOnMissingAuthentication() { - try (MockedStatic<CurrentUserHelper> mockUserHelper = Mockito.mockStatic( - CurrentUserHelper.class, - Mockito.CALLS_REAL_METHODS)) { + try (MockedStatic<CurrentUserHelper> mockUserHelper = mockStatic(CurrentUserHelper.class)) { mockUserHelper.when(CurrentUserHelper::getAuthentication).thenReturn(null); - boolean hasRole = CurrentUserHelper.hasRole(UserRole.ADMIN_ADMIN); + boolean hasRole = CurrentUserHelper.hasAnyRole(roles); assertThat(hasRole).isFalse(); } @@ -67,66 +103,72 @@ class CurrentUserHelperTest { @Test void shouldReturnFalseOnMissingPrincipal() { - Mockito.when(mockAuthentication.getPrincipal()).thenReturn(null); - try (MockedStatic<CurrentUserHelper> mockUserHelper = Mockito.mockStatic( - CurrentUserHelper.class, - Mockito.CALLS_REAL_METHODS)) { + when(mockAuthentication.getPrincipal()).thenReturn(null); + try (MockedStatic<CurrentUserHelper> mockUserHelper = mockStatic(CurrentUserHelper.class, CALLS_REAL_METHODS)) { mockUserHelper.when(CurrentUserHelper::getAuthentication).thenReturn(mockAuthentication); - boolean hasRole = CurrentUserHelper.hasRole(UserRole.ADMIN_ADMIN); + boolean hasRole = CurrentUserHelper.hasAnyRole(roles); assertThat(hasRole).isFalse(); } } + @Test + void shouldCallContainsAnyRole() { + when(mockAuthentication.getPrincipal()).thenReturn(mockPrincipal); + try (MockedStatic<CurrentUserHelper> mockUserHelper = mockStatic(CurrentUserHelper.class, CALLS_REAL_METHODS)) { + mockUserHelper.when(CurrentUserHelper::getAuthentication).thenReturn(mockAuthentication); + + CurrentUserHelper.hasAnyRole(roles); + + mockUserHelper.verify(() -> CurrentUserHelper.containsAnyRole(mockAuthentication.getAuthorities(), roles)); + } + } + @ParameterizedTest - @ValueSource(booleans = {false, true}) - void shouldReturnValue(boolean containsRoleValue) { - Mockito.when(mockAuthentication.getPrincipal()).thenReturn(mockPrincipal); - List<GrantedAuthority> authorities = List.of(); - Mockito.<Collection<? extends GrantedAuthority>>when(mockAuthentication.getAuthorities()).thenReturn(authorities); - - try (MockedStatic<CurrentUserHelper> mockUserHelper = Mockito.mockStatic( - CurrentUserHelper.class, - Mockito.CALLS_REAL_METHODS)) { + @ValueSource(booleans = { false, true }) + void shouldReturnValue(boolean hasRole) { + when(mockAuthentication.getPrincipal()).thenReturn(mockPrincipal); + try (MockedStatic<CurrentUserHelper> mockUserHelper = mockStatic(CurrentUserHelper.class, CALLS_REAL_METHODS)) { mockUserHelper.when(CurrentUserHelper::getAuthentication).thenReturn(mockAuthentication); - mockUserHelper.when(() -> CurrentUserHelper.containsRole(Mockito.anyList(), Mockito.anyString())) - .thenReturn(containsRoleValue); + mockUserHelper.when(() -> CurrentUserHelper.containsAnyRole(any(), any())).thenReturn(hasRole); - boolean hasRole = CurrentUserHelper.hasRole(UserRole.ADMIN_ADMIN); + var result = CurrentUserHelper.hasAnyRole(roles); - mockUserHelper.verify(() -> CurrentUserHelper.containsRole(mockAuthentication.getAuthorities(), UserRole.ADMIN_ADMIN)); - assertThat(hasRole).isEqualTo(containsRoleValue); + assertThat(result).isEqualTo(hasRole); } } } - @DisplayName("Contains role") @Nested - class TestContainsRole { + class TestContainsAnyRole { @Test - void shouldNotContainRoleIfAuthoritiesIsNull() { - boolean containsRole = CurrentUserHelper.containsRole(null, UserRole.ADMIN_ADMIN); + void shouldReturnFalseIfAuthoritiesIsNull() { + boolean containsRole = CurrentUserHelper.containsAnyRole(null, Set.of(LoremIpsum.getInstance().getWords(1))); assertThat(containsRole).isFalse(); } @Test - void shouldNotContainRole() { + void shouldFalseOnNoMatchingRole() { + var userRole = LoremIpsum.getInstance().getWords(1); + var rolesToCheck = Set.of(LoremIpsum.getInstance().getWords(1), LoremIpsum.getInstance().getWords(1)); List<GrantedAuthority> authorities = List.of( - new SimpleGrantedAuthority(CurrentUserHelper.ROLE_PREFIX + "OTHER")); + new SimpleGrantedAuthority(CurrentUserHelper.ROLE_PREFIX + userRole)); - boolean containsRole = CurrentUserHelper.containsRole(authorities, UserRole.ADMIN_ADMIN); + boolean containsRole = CurrentUserHelper.containsAnyRole(authorities, rolesToCheck); assertThat(containsRole).isFalse(); } @Test - void shouldContainRole() { - Collection<? extends GrantedAuthority> authorities = List.of( - new SimpleGrantedAuthority(CurrentUserHelper.ROLE_PREFIX + UserRole.ADMIN_ADMIN)); + void shouldReturnTrueOnMatchingRole() { + var userRole = LoremIpsum.getInstance().getWords(1); + var rolesToCheck = Set.of(LoremIpsum.getInstance().getWords(1), userRole); + List<GrantedAuthority> authorities = List.of( + new SimpleGrantedAuthority(CurrentUserHelper.ROLE_PREFIX + userRole)); - boolean containsRole = CurrentUserHelper.containsRole(authorities, UserRole.ADMIN_ADMIN); + boolean containsRole = CurrentUserHelper.containsAnyRole(authorities, rolesToCheck); assertThat(containsRole).isTrue(); } @@ -166,13 +208,13 @@ class CurrentUserHelperTest { @Nested class TestGetAuthentication { @Mock - private final SecurityContext mockSecurityContext = Mockito.mock(SecurityContext.class); + private final SecurityContext mockSecurityContext = mock(SecurityContext.class); @Test void shouldThrowIfNoAuthenticatedUser() { - Mockito.when(mockSecurityContext.getAuthentication()).thenReturn(null); + when(mockSecurityContext.getAuthentication()).thenReturn(null); - try (MockedStatic<SecurityContextHolder> contextHolder = Mockito.mockStatic(SecurityContextHolder.class)) { + try (MockedStatic<SecurityContextHolder> contextHolder = mockStatic(SecurityContextHolder.class)) { contextHolder.when(SecurityContextHolder::getContext).thenReturn(mockSecurityContext); assertThatIllegalStateException() @@ -183,10 +225,10 @@ class CurrentUserHelperTest { @Test void shouldPassAuthentication() { - Authentication mockAuthentication = Mockito.mock(Authentication.class); - Mockito.when(mockSecurityContext.getAuthentication()).thenReturn(mockAuthentication); + Authentication mockAuthentication = mock(Authentication.class); + when(mockSecurityContext.getAuthentication()).thenReturn(mockAuthentication); - try (MockedStatic<SecurityContextHolder> contextHolder = Mockito.mockStatic(SecurityContextHolder.class)) { + try (MockedStatic<SecurityContextHolder> contextHolder = mockStatic(SecurityContextHolder.class)) { contextHolder.when(SecurityContextHolder::getContext).thenReturn(mockSecurityContext); Authentication authentication = CurrentUserHelper.getAuthentication(); diff --git a/src/test/java/de/ozgcloud/admin/common/user/CurrentUserServiceTest.java b/src/test/java/de/ozgcloud/admin/common/user/CurrentUserServiceTest.java index 09f5132426aa7b5850be192689b12cfb2cac6608..0627188e3cc55fd68cd3079e4cedb4d029f3c917 100644 --- a/src/test/java/de/ozgcloud/admin/common/user/CurrentUserServiceTest.java +++ b/src/test/java/de/ozgcloud/admin/common/user/CurrentUserServiceTest.java @@ -23,14 +23,16 @@ */ package de.ozgcloud.admin.common.user; +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + 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.ValueSource; import org.mockito.MockedStatic; -import org.mockito.Mockito; - -import static org.assertj.core.api.Assertions.assertThat; class CurrentUserServiceTest { private final CurrentUserService currentUserService = new CurrentUserService(); @@ -39,12 +41,10 @@ class CurrentUserServiceTest { @Nested class TestHasRole { @ParameterizedTest - @ValueSource(booleans = {false, true}) + @ValueSource(booleans = { false, true }) void shouldReturnValue(boolean hasRoleValue) { - try (MockedStatic<CurrentUserHelper> mockUserHelper = Mockito.mockStatic( - CurrentUserHelper.class) - ){ - mockUserHelper.when(() -> CurrentUserHelper.hasRole(Mockito.anyString())) + try (MockedStatic<CurrentUserHelper> mockUserHelper = mockStatic(CurrentUserHelper.class)) { + mockUserHelper.when(() -> CurrentUserHelper.hasRole(anyString())) .thenReturn(hasRoleValue); boolean hasRole = currentUserService.hasRole(UserRole.ADMIN_ADMIN); @@ -54,4 +54,29 @@ class CurrentUserServiceTest { } } } + + @Nested + class TestHasConfigurationPermission { + + @Test + void shouldCallCurrentUserHelper() { + try (MockedStatic<CurrentUserHelper> mockUserHelper = mockStatic(CurrentUserHelper.class)) { + currentUserService.hasConfigurationPermission(); + + mockUserHelper.verify(() -> CurrentUserHelper.hasAnyRole(UserRole.CONFIGURATION_ROLES)); + } + } + + @ParameterizedTest + @ValueSource(booleans = { false, true }) + void shouldReturnValue(boolean hasConfigurationPermission) { + try (MockedStatic<CurrentUserHelper> mockUserHelper = mockStatic(CurrentUserHelper.class)) { + mockUserHelper.when(() -> CurrentUserHelper.hasAnyRole(any())).thenReturn(hasConfigurationPermission); + + var result = currentUserService.hasConfigurationPermission(); + + assertThat(result).isEqualTo(hasConfigurationPermission); + } + } + } }