diff --git a/db-server/repo-config/config-record-manager-app.ttl b/db-server/repo-config/config-record-manager-app.ttl new file mode 100644 index 00000000..01197c1e --- /dev/null +++ b/db-server/repo-config/config-record-manager-app.ttl @@ -0,0 +1,33 @@ +@prefix rdfs: . +@prefix rep: . +@prefix sail: . +@prefix xsd: . +@prefix graphdb: . + +<#record-manager-app> a rep:Repository; + rep:repositoryID "record-manager-app"; + rep:repositoryImpl [ + rep:repositoryType "graphdb:SailRepository"; + [ + graphdb:base-URL "http://example.org/owlim#"; + graphdb:check-for-inconsistencies "false"; + graphdb:defaultNS ""; + graphdb:disable-sameAs "true"; + graphdb:enable-context-index "true"; + graphdb:enable-literal-index "true"; + graphdb:enablePredicateList "true"; + graphdb:entity-id-size "32"; + graphdb:entity-index-size "10000000"; + graphdb:imports ""; + graphdb:in-memory-literal-properties "true"; + graphdb:owlim-license ""; + graphdb:query-limit-results "0"; + graphdb:query-timeout "0"; + graphdb:read-only "false"; + graphdb:repository-type "file-repository"; + graphdb:storage-folder "storage"; + graphdb:throw-QueryEvaluationException-on-timeout "false"; + sail:sailType "graphdb:Sail" + ] + ]; + rdfs:label "Record Manager Repository" . diff --git a/docker-compose.yml b/docker-compose.yml index b279a279..7f4a5aef 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -34,9 +34,9 @@ services: FORMGENREPOSITORYURL: "http://db-server:7200/repositories/record-manager-formgen" FORMGENSERVICEURL: "http://s-pipes-engine:8080/s-pipes/service?_pId=clone&sgovRepositoryUrl=https%3A%2F%2Fgraphdb.onto.fel.cvut.cz%2Frepositories%2Fkodi-slovnik-gov-cz" SECURITY_PROVIDER: "oidc" + SERVER_SERVLET_CONTEXTPATH: "/record-manager-server" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUERURI: "http://localhost:8088/realms/record-manager" SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWKSETURI: "http://auth-server:8080/realms/record-manager/protocol/openid-connect/certs" - SERVER_SERVLET_CONTEXTPATH: "/record-manager-server" s-pipes-engine: image: "ghcr.io/kbss-cvut/s-pipes/s-pipes-engine:latest" @@ -64,7 +64,7 @@ services: auth-server: image: "ghcr.io/kbss-cvut/keycloak-graphdb-user-replicator/keycloak-graphdb:latest" command: - - start --import-realm + - start --import-realm --features="token-exchange,admin-fine-grained-authz" environment: KC_IMPORT: realm-export.json KC_HOSTNAME_URL: "http://localhost:8088" diff --git a/src/main/java/cz/cvut/kbss/study/config/SecurityConfig.java b/src/main/java/cz/cvut/kbss/study/config/SecurityConfig.java index a46bf110..148aade2 100644 --- a/src/main/java/cz/cvut/kbss/study/config/SecurityConfig.java +++ b/src/main/java/cz/cvut/kbss/study/config/SecurityConfig.java @@ -2,8 +2,10 @@ import cz.cvut.kbss.study.exception.RecordManagerException; import cz.cvut.kbss.study.security.CsrfHeaderFilter; +import cz.cvut.kbss.study.security.CustomSwitchUserFilter; import cz.cvut.kbss.study.security.SecurityConstants; import cz.cvut.kbss.study.service.ConfigReader; +import cz.cvut.kbss.study.service.security.UserDetailsService; import cz.cvut.kbss.study.util.ConfigParam; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -20,10 +22,12 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.access.intercept.AuthorizationFilter; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.authentication.HttpStatusEntryPoint; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; +import org.springframework.security.web.authentication.switchuser.SwitchUserFilter; import org.springframework.security.web.csrf.CsrfFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; @@ -31,7 +35,11 @@ import java.net.MalformedURLException; import java.net.URL; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; @ConditionalOnProperty(prefix = "security", name = "provider", havingValue = "internal", matchIfMissing = true) @Configuration @@ -66,10 +74,13 @@ public SecurityConfig(AuthenticationFailureHandler authenticationFailureHandler, } @Bean - public SecurityFilterChain filterChain(HttpSecurity http, ConfigReader config) throws Exception { + public SecurityFilterChain filterChain(HttpSecurity http, ConfigReader config, + UserDetailsService userDetailsService) throws Exception { LOG.debug("Using internal security mechanisms."); final AuthenticationManager authManager = buildAuthenticationManager(http); - http.authorizeHttpRequests((auth) -> auth.anyRequest().permitAll()) + http.authorizeHttpRequests( + (auth) -> auth.requestMatchers("/rest/users/impersonate").hasAuthority(SecurityConstants.ROLE_ADMIN) + .anyRequest().permitAll()) .cors((auth) -> auth.configurationSource(corsConfigurationSource(config))) .csrf(AbstractHttpConfigurer::disable) .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class) @@ -80,6 +91,8 @@ public SecurityFilterChain filterChain(HttpSecurity http, ConfigReader config) t .logout((auth) -> auth.logoutUrl(SecurityConstants.LOGOUT_URI) .logoutSuccessHandler(logoutSuccessHandler) .invalidateHttpSession(true).deleteCookies(COOKIES_TO_DESTROY)) + .sessionManagement((auth) -> auth.maximumSessions(1)) + .addFilterAfter(switchUserFilter(userDetailsService), AuthorizationFilter.class) .authenticationManager(authManager); return http.build(); } @@ -139,4 +152,15 @@ private static Optional getApplicationUrlOrigin(ConfigReader configReade throw new RecordManagerException("Invalid configuration parameter " + ConfigParam.APP_CONTEXT + ".", e); } } + + @Bean + public SwitchUserFilter switchUserFilter(UserDetailsService userDetailsService) { + final SwitchUserFilter filter = new CustomSwitchUserFilter(); + filter.setUserDetailsService(userDetailsService); + filter.setUsernameParameter("username"); + filter.setSwitchUserUrl("/rest/users/impersonate"); + filter.setExitUserUrl("/rest/users/impersonate/logout"); + filter.setSuccessHandler(authenticationSuccessHandler); + return filter; + } } diff --git a/src/main/java/cz/cvut/kbss/study/rest/OidcUserController.java b/src/main/java/cz/cvut/kbss/study/rest/OidcUserController.java index c55d3f21..0c01c193 100644 --- a/src/main/java/cz/cvut/kbss/study/rest/OidcUserController.java +++ b/src/main/java/cz/cvut/kbss/study/rest/OidcUserController.java @@ -1,5 +1,6 @@ package cz.cvut.kbss.study.rest; +import cz.cvut.kbss.study.exception.NotFoundException; import cz.cvut.kbss.study.model.Institution; import cz.cvut.kbss.study.model.User; import cz.cvut.kbss.study.security.SecurityConstants; @@ -9,6 +10,7 @@ import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -40,6 +42,17 @@ public User getCurrent() { return userService.getCurrentUser(); } + @PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "') or #username == authentication.name or " + + "hasRole('" + SecurityConstants.ROLE_USER + "') and @securityUtils.areFromSameInstitution(#username)") + @GetMapping(value = "/{username}", produces = MediaType.APPLICATION_JSON_VALUE) + public User getByUsername(@PathVariable("username") String username) { + final User user = userService.findByUsername(username); + if (user == null) { + throw NotFoundException.create("User", username); + } + return user; + } + @PreAuthorize( "hasRole('" + SecurityConstants.ROLE_ADMIN + "') " + "or hasRole('" + SecurityConstants.ROLE_USER + "') and @securityUtils.isMemberOfInstitution(#institutionKey)") diff --git a/src/main/java/cz/cvut/kbss/study/rest/UserController.java b/src/main/java/cz/cvut/kbss/study/rest/UserController.java index 42fac83e..b0976217 100644 --- a/src/main/java/cz/cvut/kbss/study/rest/UserController.java +++ b/src/main/java/cz/cvut/kbss/study/rest/UserController.java @@ -3,14 +3,11 @@ import cz.cvut.kbss.study.exception.NotFoundException; import cz.cvut.kbss.study.model.Institution; import cz.cvut.kbss.study.model.User; -import cz.cvut.kbss.study.model.Vocabulary; import cz.cvut.kbss.study.rest.exception.BadRequestException; import cz.cvut.kbss.study.rest.util.RestUtils; import cz.cvut.kbss.study.security.SecurityConstants; -import cz.cvut.kbss.study.security.model.UserDetails; import cz.cvut.kbss.study.service.InstitutionService; import cz.cvut.kbss.study.service.UserService; -import cz.cvut.kbss.study.service.security.SecurityUtils; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -208,19 +205,4 @@ private User getByToken(String token) { assert token != null; return userService.findByToken(token); } - - @PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "')") - @PostMapping(value = "/impersonate", consumes = MediaType.TEXT_PLAIN_VALUE) - @ResponseStatus(HttpStatus.NO_CONTENT) - public void impersonate(@RequestBody String username) { - User user = getByUsername(username); - if (user.getTypes().contains(Vocabulary.s_c_administrator)) { - throw new BadRequestException("Cannot impersonate admin."); - } - UserDetails ud = new UserDetails(user); - SecurityUtils.setCurrentUser(ud); - if (LOG.isTraceEnabled()) { - LOG.trace("User {} impersonated.", user.getUsername()); - } - } } diff --git a/src/main/java/cz/cvut/kbss/study/security/CustomSwitchUserFilter.java b/src/main/java/cz/cvut/kbss/study/security/CustomSwitchUserFilter.java new file mode 100644 index 00000000..80723dc4 --- /dev/null +++ b/src/main/java/cz/cvut/kbss/study/security/CustomSwitchUserFilter.java @@ -0,0 +1,22 @@ +package cz.cvut.kbss.study.security; + +import cz.cvut.kbss.study.rest.exception.BadRequestException; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.switchuser.SwitchUserFilter; + +/** + * Extends default user switching logic by preventing switching to an admin account. + */ +public class CustomSwitchUserFilter extends SwitchUserFilter { + + @Override + protected Authentication attemptSwitchUser(HttpServletRequest request) throws AuthenticationException { + final Authentication switchTo = super.attemptSwitchUser(request); + if (switchTo.getAuthorities().stream().anyMatch(a -> SecurityConstants.ROLE_ADMIN.equals(a.getAuthority()))) { + throw new BadRequestException("Cannot impersonate admin."); + } + return switchTo; + } +} diff --git a/src/main/java/cz/cvut/kbss/study/security/model/UserDetails.java b/src/main/java/cz/cvut/kbss/study/security/model/UserDetails.java index 78eb1eac..90c882f5 100644 --- a/src/main/java/cz/cvut/kbss/study/security/model/UserDetails.java +++ b/src/main/java/cz/cvut/kbss/study/security/model/UserDetails.java @@ -65,7 +65,7 @@ public String getUsername() { @Override public boolean isAccountNonExpired() { - return false; + return true; } @Override @@ -86,4 +86,12 @@ public boolean isEnabled() { public User getUser() { return user; } + + @Override + public String toString() { + return "UserDetails{" + + "user=" + user + + ", authorities=" + authorities + + '}'; + } } diff --git a/src/main/java/cz/cvut/kbss/study/service/security/SecurityUtils.java b/src/main/java/cz/cvut/kbss/study/service/security/SecurityUtils.java index fd51a950..b5af68bf 100644 --- a/src/main/java/cz/cvut/kbss/study/service/security/SecurityUtils.java +++ b/src/main/java/cz/cvut/kbss/study/service/security/SecurityUtils.java @@ -3,6 +3,7 @@ import cz.cvut.kbss.study.exception.NotFoundException; import cz.cvut.kbss.study.model.PatientRecord; import cz.cvut.kbss.study.model.User; +import cz.cvut.kbss.study.model.Vocabulary; import cz.cvut.kbss.study.persistence.dao.PatientRecordDao; import cz.cvut.kbss.study.persistence.dao.UserDao; import cz.cvut.kbss.study.security.model.Role; @@ -16,6 +17,7 @@ import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.oauth2.core.oidc.OidcUserInfo; import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.web.server.authentication.SwitchUserWebFilter; import org.springframework.stereotype.Service; import java.util.List; @@ -40,15 +42,15 @@ public SecurityUtils(UserDao userDao, PatientRecordDao patientRecordDao, ConfigR * Sets the current security context to the user represented by the provided user details. *

* Note that this method erases credentials from the provided user details for security reasons. - * + *

* This method should be used only when internal authentication is used. * * @param userDetails User details */ public static AbstractAuthenticationToken setCurrentUser(UserDetails userDetails) { - final UsernamePasswordAuthenticationToken - token = new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), - userDetails.getAuthorities()); + final UsernamePasswordAuthenticationToken token = + UsernamePasswordAuthenticationToken.authenticated(userDetails, userDetails.getPassword(), + userDetails.getAuthorities()); token.setDetails(userDetails); token.eraseCredentials(); // Do not pass credentials around @@ -70,9 +72,13 @@ public User getCurrentUser() { if (principal instanceof Jwt) { return resolveAccountFromOAuthPrincipal((Jwt) principal); } else { - assert principal instanceof String; - final String username = context.getAuthentication().getPrincipal().toString(); - return userDao.findByUsername(username); + final String username = context.getAuthentication().getName(); + final User user = userDao.findByUsername(username); + if (context.getAuthentication().getAuthorities().stream().anyMatch(a -> a.getAuthority().equals( + SwitchUserWebFilter.ROLE_PREVIOUS_ADMINISTRATOR))) { + user.addType(Vocabulary.s_c_impersonator); + } + return user; } } @@ -81,7 +87,8 @@ private User resolveAccountFromOAuthPrincipal(Jwt principal) { final List roles = new OidcGrantedAuthoritiesExtractor(config).extractRoles(principal); final User user = userDao.findByUsername(userInfo.getPreferredUsername()); if (user == null) { - throw new NotFoundException("User with username '" + userInfo.getPreferredUsername() + "' not found in repository."); + throw new NotFoundException( + "User with username '" + userInfo.getPreferredUsername() + "' not found in repository."); } roles.stream().map(Role::forName).filter(Optional::isPresent).forEach(r -> user.addType(r.get().getType())); return user; diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml index 997332de..4c13c81d 100644 --- a/src/main/resources/logback.xml +++ b/src/main/resources/logback.xml @@ -47,6 +47,12 @@ + + + + + + diff --git a/src/main/resources/model.ttl b/src/main/resources/model.ttl index e290cd26..39067f2b 100644 --- a/src/main/resources/model.ttl +++ b/src/main/resources/model.ttl @@ -159,5 +159,9 @@ rm:patient-record rdf:type owl:Class ; rm:user rdf:type owl:Class ; rdfs:label "User"@en . +### http://onto.fel.cvut.cz/ontologies/record-manager/impersonator +rm:impersonator rdf:type owl:Class ; + rdfs:label "Impersonator"@en . + ### Generated by the OWL API (version 4.2.8.20170104-2310) https://github.com/owlcs/owlapi diff --git a/src/test/java/cz/cvut/kbss/study/environment/util/Environment.java b/src/test/java/cz/cvut/kbss/study/environment/util/Environment.java index d45af3da..e4b16fc3 100644 --- a/src/test/java/cz/cvut/kbss/study/environment/util/Environment.java +++ b/src/test/java/cz/cvut/kbss/study/environment/util/Environment.java @@ -34,9 +34,11 @@ public static void setCurrentUser(User user) { currentUser = user; final UserDetails userDetails = new UserDetails(user); SecurityContext context = new SecurityContextImpl(); - context.setAuthentication( - new UsernamePasswordAuthenticationToken(userDetails.getUsername(), userDetails.getPassword(), - userDetails.getAuthorities())); + final UsernamePasswordAuthenticationToken token = + UsernamePasswordAuthenticationToken.authenticated(userDetails, userDetails.getPassword(), + userDetails.getAuthorities()); + token.setDetails(userDetails); + context.setAuthentication(token); SecurityContextHolder.setContext(context); } diff --git a/src/test/java/cz/cvut/kbss/study/rest/UserControllerTest.java b/src/test/java/cz/cvut/kbss/study/rest/UserControllerTest.java index 5cad18ac..ba0159fe 100644 --- a/src/test/java/cz/cvut/kbss/study/rest/UserControllerTest.java +++ b/src/test/java/cz/cvut/kbss/study/rest/UserControllerTest.java @@ -6,7 +6,6 @@ import cz.cvut.kbss.study.environment.util.Environment; import cz.cvut.kbss.study.model.Institution; import cz.cvut.kbss.study.model.User; -import cz.cvut.kbss.study.model.Vocabulary; import cz.cvut.kbss.study.service.InstitutionService; import cz.cvut.kbss.study.service.UserService; import org.junit.jupiter.api.BeforeEach; @@ -22,10 +21,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -338,41 +335,4 @@ public void sendInvitationDeleteReturnsResponseStatusNoContent() throws Exceptio assertEquals(HttpStatus.NO_CONTENT, HttpStatus.valueOf(result.getResponse().getStatus())); verify(userServiceMock).findByUsername(username); } - - @Test - public void impersonateReturnsResponseStatusNoContent() throws Exception { - final String username = "tom"; - - when(userServiceMock.findByUsername(username)).thenReturn(Environment.getCurrentUser()); - - final MvcResult result = mockMvc.perform(post("/users/impersonate/") - .content(username) - .contentType(MediaType.TEXT_PLAIN_VALUE)) - .andReturn(); - - assertEquals(HttpStatus.NO_CONTENT, HttpStatus.valueOf(result.getResponse().getStatus())); - verify(userServiceMock).findByUsername(username); - } - - @Test - public void impersonateAdministratorReturnsResponseStatusBadRequest() throws Exception { - final String username = "tom"; - final Institution institution = Generator.generateInstitution(); - final User user = Generator.generateUser(institution); - - Set types = new HashSet<>(); - types.add(Vocabulary.s_c_administrator); - types.add(Vocabulary.s_c_doctor); - user.setTypes(types); - - when(userServiceMock.findByUsername(username)).thenReturn(user); - - final MvcResult result = mockMvc.perform(post("/users/impersonate/") - .content(username) - .contentType(MediaType.TEXT_PLAIN_VALUE)) - .andReturn(); - - assertEquals(HttpStatus.BAD_REQUEST, HttpStatus.valueOf(result.getResponse().getStatus())); - verify(userServiceMock).findByUsername(username); - } } diff --git a/src/test/java/cz/cvut/kbss/study/security/CustomSwitchUserFilterTest.java b/src/test/java/cz/cvut/kbss/study/security/CustomSwitchUserFilterTest.java new file mode 100644 index 00000000..f7e2b4b4 --- /dev/null +++ b/src/test/java/cz/cvut/kbss/study/security/CustomSwitchUserFilterTest.java @@ -0,0 +1,61 @@ +package cz.cvut.kbss.study.security; + +import cz.cvut.kbss.study.environment.generator.Generator; +import cz.cvut.kbss.study.environment.util.Environment; +import cz.cvut.kbss.study.model.User; +import cz.cvut.kbss.study.model.Vocabulary; +import cz.cvut.kbss.study.rest.exception.BadRequestException; +import cz.cvut.kbss.study.security.model.UserDetails; +import cz.cvut.kbss.study.service.security.UserDetailsService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.security.core.Authentication; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class CustomSwitchUserFilterTest { + + @Mock + private UserDetailsService userDetailsService; + + private CustomSwitchUserFilter sut; + + @BeforeEach + void setUp() { + this.sut = new CustomSwitchUserFilter(); + sut.setUserDetailsService(userDetailsService); + } + + @Test + void attemptSwitchUserSwitchesCurrentUserToTarget() { + final User source = Generator.generateUser(null); + source.addType(Vocabulary.s_c_administrator); + Environment.setCurrentUser(source); + final User target = Generator.generateUser(null); + when(userDetailsService.loadUserByUsername(target.getUsername())).thenReturn(new UserDetails(target)); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("username", target.getUsername()); + final Authentication result = sut.attemptSwitchUser(request); + assertEquals(target.getUsername(), result.getName()); + } + + @Test + void attemptSwitchUserThrowsBadRequestExceptionWhenTargetUserIsAdmin() { + final User source = Generator.generateUser(null); + source.addType(Vocabulary.s_c_administrator); + Environment.setCurrentUser(source); + final User target = Generator.generateUser(null); + target.addType(Vocabulary.s_c_administrator); + when(userDetailsService.loadUserByUsername(target.getUsername())).thenReturn(new UserDetails(target)); + final MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("username", target.getUsername()); + assertThrows(BadRequestException.class, () -> sut.attemptSwitchUser(request)); + } +} \ No newline at end of file diff --git a/src/test/java/cz/cvut/kbss/study/security/OntologyAuthenticationProviderTest.java b/src/test/java/cz/cvut/kbss/study/security/OntologyAuthenticationProviderTest.java index c2ece819..f71cd019 100644 --- a/src/test/java/cz/cvut/kbss/study/security/OntologyAuthenticationProviderTest.java +++ b/src/test/java/cz/cvut/kbss/study/security/OntologyAuthenticationProviderTest.java @@ -50,7 +50,7 @@ public void successfulAuthenticationSetsSecurityContext() { final Authentication result = provider.authenticate(auth); assertTrue(result.isAuthenticated()); assertNotNull(SecurityContextHolder.getContext()); - assertEquals(BaseServiceTestRunner.USERNAME, SecurityContextHolder.getContext().getAuthentication().getPrincipal()); + assertEquals(BaseServiceTestRunner.USERNAME, SecurityContextHolder.getContext().getAuthentication().getName()); } @Test diff --git a/src/test/java/cz/cvut/kbss/study/service/security/SecurityUtilsTest.java b/src/test/java/cz/cvut/kbss/study/service/security/SecurityUtilsTest.java index 4cdcdd11..9a3894f4 100644 --- a/src/test/java/cz/cvut/kbss/study/service/security/SecurityUtilsTest.java +++ b/src/test/java/cz/cvut/kbss/study/service/security/SecurityUtilsTest.java @@ -9,6 +9,7 @@ import cz.cvut.kbss.study.persistence.dao.PatientRecordDao; import cz.cvut.kbss.study.persistence.dao.UserDao; import cz.cvut.kbss.study.security.SecurityConstants; +import cz.cvut.kbss.study.security.model.UserDetails; import cz.cvut.kbss.study.service.ConfigReader; import cz.cvut.kbss.study.util.ConfigParam; import cz.cvut.kbss.study.util.IdentificationUtils; @@ -19,15 +20,18 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.web.authentication.switchuser.SwitchUserFilter; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.List; +import java.util.Set; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasItem; @@ -183,4 +187,15 @@ void getCurrentUserEnhancesRetrievedUserWithTypesCorrespondingToRolesSpecifiedIn final User result = sut.getCurrentUser(); assertThat(result.getTypes(), hasItem(Vocabulary.s_c_administrator)); } + + @Test + void getCurrentUserEnhancesRetrievedUserWithImpersonatorTypeWhenItHasSwitchAuthorityRole() { + final UserDetails userDetails = + new UserDetails(user, Set.of(new SimpleGrantedAuthority(SwitchUserFilter.ROLE_PREVIOUS_ADMINISTRATOR))); + SecurityUtils.setCurrentUser(userDetails); + when(userDao.findByUsername(user.getUsername())).thenReturn(user); + final User result = sut.getCurrentUser(); + assertEquals(user, result); + assertThat(result.getTypes(), hasItem(Vocabulary.s_c_impersonator)); + } }