-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #11 from cabinetoffice/feature/GAP-1922-migrate-ap…
…plicant Feature/gap 1922 migrate applicant
- Loading branch information
Showing
11 changed files
with
373 additions
and
4 deletions.
There are no files selected for viewing
39 changes: 36 additions & 3 deletions
39
src/main/java/gov/cabinetoffice/gap/adminbackend/controllers/UserController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,59 @@ | ||
package gov.cabinetoffice.gap.adminbackend.controllers; | ||
|
||
import com.auth0.jwt.interfaces.DecodedJWT; | ||
import gov.cabinetoffice.gap.adminbackend.dtos.MigrateUserDto; | ||
import gov.cabinetoffice.gap.adminbackend.dtos.UserDTO; | ||
import gov.cabinetoffice.gap.adminbackend.exceptions.ForbiddenException; | ||
import gov.cabinetoffice.gap.adminbackend.exceptions.UnauthorizedException; | ||
import gov.cabinetoffice.gap.adminbackend.mappers.UserMapper; | ||
import gov.cabinetoffice.gap.adminbackend.models.AdminSession; | ||
import gov.cabinetoffice.gap.adminbackend.security.AuthManager; | ||
import gov.cabinetoffice.gap.adminbackend.services.JwtService; | ||
import gov.cabinetoffice.gap.adminbackend.services.UserService; | ||
import gov.cabinetoffice.gap.adminbackend.utils.HelperUtils; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.log4j.Log4j; | ||
import lombok.extern.log4j.Log4j2; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
import java.util.Objects; | ||
|
||
import static org.springframework.util.ObjectUtils.isEmpty; | ||
|
||
@RequiredArgsConstructor | ||
@RestController | ||
@RequestMapping("/users") | ||
@Log4j2 | ||
public class UserController { | ||
|
||
private final UserMapper userMapper; | ||
|
||
private final JwtService jwtService; | ||
|
||
private final UserService userService; | ||
|
||
@GetMapping("/loggedInUser") | ||
public ResponseEntity<UserDTO> getLoggedInUserDetails() { | ||
AdminSession session = HelperUtils.getAdminSessionForAuthenticatedUser(); | ||
return ResponseEntity.ok(userMapper.adminSessionToUserDTO(session)); | ||
} | ||
|
||
@PatchMapping("/migrate") | ||
public ResponseEntity<String> migrateUser(@RequestBody MigrateUserDto migrateUserDto, | ||
@RequestHeader("Authorization") String token) { | ||
// Called from our user service only. Does not have an admin session so authing | ||
// via the jwt | ||
if (isEmpty(token) || !token.startsWith("Bearer ")) | ||
return ResponseEntity.status(401).body("Migrate user: Expected Authorization header not provided"); | ||
final DecodedJWT decodedJWT = jwtService.verifyToken(token.split(" ")[1]); | ||
if (!Objects.equals(decodedJWT.getSubject(), migrateUserDto.getOneLoginSub())) | ||
return ResponseEntity.status(403) | ||
.body("User not authorized to migrate user: " + migrateUserDto.getOneLoginSub()); | ||
|
||
userService.migrateUser(migrateUserDto.getOneLoginSub(), migrateUserDto.getColaSub()); | ||
return ResponseEntity.ok("User migrated successfully"); | ||
} | ||
|
||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/gov/cabinetoffice/gap/adminbackend/dtos/MigrateUserDto.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package gov.cabinetoffice.gap.adminbackend.dtos; | ||
|
||
import lombok.Builder; | ||
import lombok.Data; | ||
|
||
import java.util.UUID; | ||
|
||
@Data | ||
@Builder | ||
public class MigrateUserDto { | ||
|
||
private String oneLoginSub; | ||
|
||
private UUID colaSub; | ||
|
||
} |
5 changes: 5 additions & 0 deletions
5
src/main/java/gov/cabinetoffice/gap/adminbackend/exceptions/UnauthorizedException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/main/java/gov/cabinetoffice/gap/adminbackend/repositories/GrantApplicantRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package gov.cabinetoffice.gap.adminbackend.repositories; | ||
|
||
import gov.cabinetoffice.gap.adminbackend.dtos.submission.GrantApplicant; | ||
import gov.cabinetoffice.gap.adminbackend.entities.GapUser; | ||
import org.springframework.data.jpa.repository.JpaRepository; | ||
|
||
import java.util.Optional; | ||
|
||
public interface GrantApplicantRepository extends JpaRepository<GrantApplicant, Long> { | ||
|
||
Optional<GrantApplicant> findByUserId(String userId); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/main/java/gov/cabinetoffice/gap/adminbackend/services/UserService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package gov.cabinetoffice.gap.adminbackend.services; | ||
|
||
import gov.cabinetoffice.gap.adminbackend.repositories.GapUserRepository; | ||
import gov.cabinetoffice.gap.adminbackend.repositories.GrantApplicantRepository; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.util.UUID; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class UserService { | ||
|
||
private final GapUserRepository gapUserRepository; | ||
|
||
private final GrantApplicantRepository grantApplicantRepository; | ||
|
||
@Transactional | ||
public void migrateUser(final String oneLoginSub, final UUID colaSub) { | ||
gapUserRepository.findByUserSub(colaSub.toString()).ifPresent(gapUser -> { | ||
gapUser.setUserSub(oneLoginSub); | ||
gapUserRepository.save(gapUser); | ||
}); | ||
|
||
grantApplicantRepository.findByUserId(colaSub.toString()).ifPresent(grantApplicant -> { | ||
grantApplicant.setUserId(oneLoginSub); | ||
grantApplicantRepository.save(grantApplicant); | ||
}); | ||
} | ||
|
||
} |
105 changes: 105 additions & 0 deletions
105
src/test/java/gov/cabinetoffice/gap/adminbackend/controllers/UserControllerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package gov.cabinetoffice.gap.adminbackend.controllers; | ||
|
||
import com.auth0.jwt.interfaces.DecodedJWT; | ||
import gov.cabinetoffice.gap.adminbackend.dtos.MigrateUserDto; | ||
import gov.cabinetoffice.gap.adminbackend.exceptions.UnauthorizedException; | ||
import gov.cabinetoffice.gap.adminbackend.mappers.UserMapper; | ||
import gov.cabinetoffice.gap.adminbackend.mappers.ValidationErrorMapperImpl; | ||
import gov.cabinetoffice.gap.adminbackend.services.JwtService; | ||
import gov.cabinetoffice.gap.adminbackend.services.UserService; | ||
import gov.cabinetoffice.gap.adminbackend.utils.HelperUtils; | ||
import gov.cabinetoffice.gap.adminbackend.utils.TestDecodedJwt; | ||
import org.junit.jupiter.api.Test; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; | ||
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; | ||
import org.springframework.boot.test.mock.mockito.MockBean; | ||
import org.springframework.boot.test.mock.mockito.SpyBean; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.test.context.ContextConfiguration; | ||
import org.springframework.test.web.servlet.MockMvc; | ||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; | ||
import org.springframework.web.context.WebApplicationContext; | ||
|
||
import javax.annotation.Resource; | ||
import java.util.UUID; | ||
|
||
import static org.mockito.Mockito.*; | ||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
|
||
@WebMvcTest(UserController.class) | ||
@AutoConfigureMockMvc(addFilters = false) | ||
@ContextConfiguration(classes = { UserController.class, ControllerExceptionHandler.class }) | ||
class UserControllerTest { | ||
|
||
@Resource | ||
private WebApplicationContext context; | ||
|
||
@MockBean | ||
private JwtService jwtService; | ||
|
||
@MockBean | ||
private UserService userService; | ||
|
||
@MockBean | ||
private UserMapper userMapper; | ||
|
||
@SpyBean | ||
private ValidationErrorMapperImpl validationErrorMapper; | ||
|
||
@Autowired | ||
private MockMvc mockMvc; | ||
|
||
@Test | ||
void migrateUser_HappyPath() throws Exception { | ||
final MigrateUserDto migrateUserDto = MigrateUserDto.builder().colaSub(UUID.randomUUID()) | ||
.oneLoginSub("oneLoginSub").build(); | ||
final DecodedJWT decodedJWT = TestDecodedJwt.builder().subject("oneLoginSub").build(); | ||
when(jwtService.verifyToken("jwt")).thenReturn(decodedJWT); | ||
|
||
mockMvc.perform(MockMvcRequestBuilders.patch("/users/migrate").contentType(MediaType.APPLICATION_JSON) | ||
.content(HelperUtils.asJsonString(migrateUserDto)).header(HttpHeaders.AUTHORIZATION, "Bearer jwt")) | ||
.andExpect(status().isOk()).andReturn(); | ||
verify(userService).migrateUser("oneLoginSub", migrateUserDto.getColaSub()); | ||
} | ||
|
||
@Test | ||
void migrateUser_NoJwt() throws Exception { | ||
final MigrateUserDto migrateUserDto = MigrateUserDto.builder().colaSub(UUID.randomUUID()) | ||
.oneLoginSub("oneLoginSub").build(); | ||
final DecodedJWT decodedJWT = TestDecodedJwt.builder().subject("oneLoginSub").build(); | ||
when(jwtService.verifyToken("jwt")).thenReturn(decodedJWT); | ||
|
||
mockMvc.perform(MockMvcRequestBuilders.patch("/users/migrate").contentType(MediaType.APPLICATION_JSON) | ||
.content(HelperUtils.asJsonString(migrateUserDto)).header(HttpHeaders.AUTHORIZATION, "")) | ||
.andExpect(status().isUnauthorized()).andReturn(); | ||
verify(userService, times(0)).migrateUser("oneLoginSub", migrateUserDto.getColaSub()); | ||
} | ||
|
||
@Test | ||
void migrateUser_InvalidJwt() throws Exception { | ||
final MigrateUserDto migrateUserDto = MigrateUserDto.builder().colaSub(UUID.randomUUID()) | ||
.oneLoginSub("oneLoginSub").build(); | ||
doThrow(new UnauthorizedException("Invalid JWT")).when(jwtService).verifyToken("jwt"); | ||
|
||
mockMvc.perform(MockMvcRequestBuilders.patch("/users/migrate").contentType(MediaType.APPLICATION_JSON) | ||
.content(HelperUtils.asJsonString(migrateUserDto)).header(HttpHeaders.AUTHORIZATION, "Bearer jwt")) | ||
.andExpect(status().isUnauthorized()).andReturn(); | ||
verify(userService, times(0)).migrateUser("oneLoginSub", migrateUserDto.getColaSub()); | ||
} | ||
|
||
@Test | ||
void migrateUser_JwtDoesNotMatchMUserToMigrate() throws Exception { | ||
final MigrateUserDto migrateUserDto = MigrateUserDto.builder().colaSub(UUID.randomUUID()) | ||
.oneLoginSub("oneLoginSub").build(); | ||
final DecodedJWT decodedJWT = TestDecodedJwt.builder().subject("anotherUsersOneLoginSub").build(); | ||
when(jwtService.verifyToken("jwt")).thenReturn(decodedJWT); | ||
|
||
mockMvc.perform(MockMvcRequestBuilders.patch("/users/migrate").contentType(MediaType.APPLICATION_JSON) | ||
.content(HelperUtils.asJsonString(migrateUserDto)).header(HttpHeaders.AUTHORIZATION, "Bearer jwt")) | ||
.andExpect(status().isForbidden()).andReturn(); | ||
verify(userService, times(0)).migrateUser("oneLoginSub", migrateUserDto.getColaSub()); | ||
} | ||
|
||
} |
88 changes: 88 additions & 0 deletions
88
src/test/java/gov/cabinetoffice/gap/adminbackend/services/UserServiceTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package gov.cabinetoffice.gap.adminbackend.services; | ||
|
||
import gov.cabinetoffice.gap.adminbackend.dtos.submission.GrantApplicant; | ||
import gov.cabinetoffice.gap.adminbackend.entities.GapUser; | ||
import gov.cabinetoffice.gap.adminbackend.repositories.GapUserRepository; | ||
import gov.cabinetoffice.gap.adminbackend.repositories.GrantApplicantRepository; | ||
import org.junit.jupiter.api.Test; | ||
import org.mockito.InjectMocks; | ||
import org.mockito.Mock; | ||
import org.mockito.Spy; | ||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; | ||
|
||
import java.util.Optional; | ||
import java.util.UUID; | ||
|
||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.*; | ||
|
||
@SpringJUnitConfig | ||
class UserServiceTest { | ||
|
||
@Spy | ||
@InjectMocks | ||
private UserService userService; | ||
|
||
@Mock | ||
private GapUserRepository gapUserRepository; | ||
|
||
@Mock | ||
private GrantApplicantRepository grantApplicantRepository; | ||
|
||
private final String oneLoginSub = "oneLoginSub"; | ||
|
||
private final UUID colaSub = UUID.randomUUID(); | ||
|
||
@Test | ||
void migrateUserNoMatches() { | ||
when(gapUserRepository.findByUserSub(any())).thenReturn(Optional.empty()); | ||
when(grantApplicantRepository.findByUserId(any())).thenReturn(Optional.empty()); | ||
|
||
userService.migrateUser(oneLoginSub, colaSub); | ||
|
||
verify(gapUserRepository, times(0)).save(any()); | ||
verify(grantApplicantRepository, times(0)).save(any()); | ||
} | ||
|
||
@Test | ||
void migrateUserMatchesGapUser() { | ||
final GapUser gapUser = GapUser.builder().build(); | ||
when(gapUserRepository.findByUserSub(any())).thenReturn(Optional.of(gapUser)); | ||
when(grantApplicantRepository.findByUserId(any())).thenReturn(Optional.empty()); | ||
|
||
userService.migrateUser(oneLoginSub, colaSub); | ||
gapUser.setUserSub(oneLoginSub); | ||
|
||
verify(gapUserRepository, times(1)).save(gapUser); | ||
verify(grantApplicantRepository, times(0)).save(any()); | ||
} | ||
|
||
@Test | ||
void migrateUserMatchesGrantApplicant() { | ||
final GrantApplicant grantApplicant = GrantApplicant.builder().build(); | ||
when(grantApplicantRepository.findByUserId(any())).thenReturn(Optional.of(grantApplicant)); | ||
when(gapUserRepository.findByUserSub(any())).thenReturn(Optional.empty()); | ||
|
||
userService.migrateUser(oneLoginSub, colaSub); | ||
grantApplicant.setUserId(oneLoginSub); | ||
|
||
verify(gapUserRepository, times(0)).save(any()); | ||
verify(grantApplicantRepository, times(1)).save(grantApplicant); | ||
} | ||
|
||
@Test | ||
void migrateUserMatchesGrantApplicantAndGapUser() { | ||
final GrantApplicant grantApplicant = GrantApplicant.builder().build(); | ||
final GapUser gapUser = GapUser.builder().build(); | ||
when(grantApplicantRepository.findByUserId(any())).thenReturn(Optional.of(grantApplicant)); | ||
when(gapUserRepository.findByUserSub(any())).thenReturn(Optional.of(gapUser)); | ||
|
||
userService.migrateUser(oneLoginSub, colaSub); | ||
grantApplicant.setUserId(oneLoginSub); | ||
gapUser.setUserSub(oneLoginSub); | ||
|
||
verify(gapUserRepository, times(1)).save(gapUser); | ||
verify(grantApplicantRepository, times(1)).save(grantApplicant); | ||
} | ||
|
||
} |
Oops, something went wrong.