Skip to content
Snippets Groups Projects
Commit f3039055 authored by OZGCloud's avatar OZGCloud
Browse files

OZG-1614 OZG-1804 OZG-1805 impl link, endpoints and filter for generating jwt token

parent 1e22fed5
No related branches found
No related tags found
No related merge requests found
Showing
with 551 additions and 6 deletions
...@@ -73,6 +73,16 @@ ...@@ -73,6 +73,16 @@
<artifactId>keycloak-admin-client</artifactId> <artifactId>keycloak-admin-client</artifactId>
</dependency> </dependency>
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
<!-- own projects --> <!-- own projects -->
<dependency> <dependency>
<groupId>de.itvsh.ozg.pluto</groupId> <groupId>de.itvsh.ozg.pluto</groupId>
......
package de.itvsh.goofy;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.auth0.jwt.exceptions.JWTVerificationException;
import de.itvsh.goofy.common.errorhandling.TechnicalException;
import lombok.extern.log4j.Log4j2;
@Log4j2
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
static final String PARAM_DOWNLOAD_TOKEN = "downloadToken";
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
var token = getDownloadToken(request);
if (StringUtils.isNotBlank(token)) {
LOG.debug("JwtAuthenticationFilter download token found");
try {
LOG.debug("JwtAuthenticationFilter verify...");
jwtTokenUtil.verifyToken(token);
LOG.debug("JwtAuthenticationFilter verification successfull.");
doFilter(request, response, filterChain);
} catch (JWTVerificationException e) {
LOG.warn("JwtVerficationException", e);
throw new TechnicalException("download token not valid", e.getCause());
}
} else {
doFilter(request, response, filterChain);
}
}
private String getDownloadToken(HttpServletRequest request) {
return request.getParameter(PARAM_DOWNLOAD_TOKEN);
}
void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
filterChain.doFilter(request, response);
}
}
\ No newline at end of file
package de.itvsh.goofy;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
@Component
public class JwtTokenUtil implements Serializable {
private static final long serialVersionUID = -2550185165626007488L;
public static final long JWT_TOKEN_VALIDITY_MS = 60000;
public static final String TOKEN_TYPE = "JWT";
public static final String TOKEN_ISSUER = "secure-api";
public static final String TOKEN_AUDIENCE = "secure-app";
public static final String ROLE_CLAIM = "roles";
public static final String FIRSTNAME_CLAIM = "firstName";
public static final String LASTNAME_CLAIM = "lastName";
@Value("${kop.auth.token-secret}")
private String secret;
public String getUserIdFromToken(String token) {
return getClaimFromToken(token, Claims::getSubject);
}
private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
final Claims claims = getAllClaimsFromToken(token);
return claimsResolver.apply(claims);
}
public String getUserFirstNameFromToken(String token) {
Claims claims = getAllClaimsFromToken(token);
return claims.get(FIRSTNAME_CLAIM).toString();
}
public String getUserLastNameFromToken(String token) {
Claims claims = getAllClaimsFromToken(token);
return claims.get(LASTNAME_CLAIM).toString();
}
private ArrayList<Map<String, String>> getRoleClaims(String token) {
Claims claims = getAllClaimsFromToken(token);
return (ArrayList<Map<String, String>>) claims.get(ROLE_CLAIM);
}
private Claims getAllClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(secret.getBytes()).parseClaimsJws(token).getBody();
}
public List<SimpleGrantedAuthority> getRolesFromToken(String token) {
ArrayList<Map<String, String>> claimsList = getRoleClaims(token);
return claimsList.stream()//
.flatMap(rolesMap -> rolesMap.entrySet().stream())//
.map(roleEntry -> new SimpleGrantedAuthority(roleEntry.getValue()))//
.collect(Collectors.toList());
}
public String generateToken(String userId, String userFirstName, String userLastName, Collection<? extends GrantedAuthority> authorities) {
Map<String, Object> claims = new HashMap<>();
claims.put(FIRSTNAME_CLAIM, userFirstName);
claims.put(LASTNAME_CLAIM, userLastName);
claims.put(ROLE_CLAIM, authorities);
return doGenerateToken(claims, userId);
}
private String doGenerateToken(Map<String, Object> claims, String subject) {
return Jwts.builder()//
.setClaims(claims)//
.setSubject(subject)//
.setHeaderParam("typ", TOKEN_TYPE)//
.setIssuer(TOKEN_ISSUER).setIssuedAt(new Date(System.currentTimeMillis()))//
.setExpiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY_MS))//
.setAudience(TOKEN_AUDIENCE)//
.signWith(SignatureAlgorithm.HS512, secret.getBytes())//
.compact();
}
public void verifyToken(String token) throws JWTVerificationException {
var algorithm = Algorithm.HMAC512(secret);
var verifier = JWT.require(algorithm).build();
verifier.verify(token);
}
}
\ No newline at end of file
...@@ -2,16 +2,22 @@ package de.itvsh.goofy; ...@@ -2,16 +2,22 @@ package de.itvsh.goofy;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
import java.net.URI;
import java.time.Instant; import java.time.Instant;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.info.BuildProperties; import org.springframework.boot.info.BuildProperties;
import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.EntityModel;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import de.itvsh.goofy.common.ModelBuilder; import de.itvsh.goofy.common.ModelBuilder;
import de.itvsh.goofy.common.file.OzgFileDataController;
import de.itvsh.goofy.common.user.CurrentUserService; import de.itvsh.goofy.common.user.CurrentUserService;
import de.itvsh.goofy.common.user.UserProfileController; import de.itvsh.goofy.common.user.UserProfileController;
import de.itvsh.goofy.common.user.UserRole; import de.itvsh.goofy.common.user.UserRole;
...@@ -24,12 +30,17 @@ public class RootController { ...@@ -24,12 +30,17 @@ public class RootController {
static final String REL_VORGAENGE = "vorgaenge"; static final String REL_VORGAENGE = "vorgaenge";
static final String REL_SEARCH = "search"; static final String REL_SEARCH = "search";
static final String REL_SEARCH_USER = "search-user-profiles"; static final String REL_SEARCH_USER = "search-user-profiles";
static final String REL_DOWNLOAD_TOKEN = "downloadToken";
static final String PARAM_DOWNLOAD_TOKEN = "downloadToken";
@Autowired(required = false) @Autowired(required = false)
public BuildProperties buildProperties; public BuildProperties buildProperties;
@Autowired @Autowired
private CurrentUserService userService; private CurrentUserService userService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@GetMapping @GetMapping
public EntityModel<RootResource> getRootResource() { public EntityModel<RootResource> getRootResource() {
...@@ -39,6 +50,7 @@ public class RootController { ...@@ -39,6 +50,7 @@ public class RootController {
.addLink(linkTo(methodOn(UserProfileController.class).findUsers(null)).withRel(REL_SEARCH_USER)) .addLink(linkTo(methodOn(UserProfileController.class).findUsers(null)).withRel(REL_SEARCH_USER))
.ifMatch(this::isEinheitlicherAnsprechpartner) .ifMatch(this::isEinheitlicherAnsprechpartner)
.addLink(() -> linkTo(methodOn(VorgangController.class).searchVorgangList(0, null)).withRel(REL_SEARCH)) .addLink(() -> linkTo(methodOn(VorgangController.class).searchVorgangList(0, null)).withRel(REL_SEARCH))
.addLink(linkTo(RootController.class).withRel(REL_DOWNLOAD_TOKEN))
.buildModel(); .buildModel();
} }
...@@ -46,6 +58,30 @@ public class RootController { ...@@ -46,6 +58,30 @@ public class RootController {
return userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER); return userService.hasRole(UserRole.EINHEITLICHER_ANSPRECHPARTNER);
} }
@PostMapping
public ResponseEntity<Void> downloadToken(@RequestBody String resourceUri) {
return buildResponse(generateToken(getIdFromUri(resourceUri)));
}
private String generateToken(String resourceId) {
var user = userService.getUser();
return jwtTokenUtil.generateToken(resourceId, user.getFirstName(), user.getLastName(), user.getAuthorities());
}
private ResponseEntity<Void> buildResponse(String token) {
var uriString = linkTo(OzgFileDataController.class).slash("downloadToken").toUri().toString() + "?" + PARAM_DOWNLOAD_TOKEN + "=" + token;
return ResponseEntity.created(URI.create(uriString)).build();
}
@GetMapping("/downloadToken")
public ResponseEntity<String> getDownloadToken(@RequestParam String downloadToken) {
return ResponseEntity.ok(downloadToken);
}
private String getIdFromUri(String uri) {
return uri.substring(uri.lastIndexOf("/") + 1, uri.length());
}
class RootResource { class RootResource {
public String getVersion() { public String getVersion() {
......
...@@ -2,11 +2,11 @@ package de.itvsh.goofy.common.file; ...@@ -2,11 +2,11 @@ package de.itvsh.goofy.common.file;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*; import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.*;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.springframework.hateoas.CollectionModel; import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel; import org.springframework.hateoas.EntityModel;
import org.springframework.hateoas.LinkRelation;
import org.springframework.hateoas.server.RepresentationModelAssembler; import org.springframework.hateoas.server.RepresentationModelAssembler;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
...@@ -15,7 +15,7 @@ import de.itvsh.goofy.common.ModelBuilder; ...@@ -15,7 +15,7 @@ import de.itvsh.goofy.common.ModelBuilder;
@Component @Component
public class OzgFileModelAssembler implements RepresentationModelAssembler<OzgFile, EntityModel<OzgFile>> { public class OzgFileModelAssembler implements RepresentationModelAssembler<OzgFile, EntityModel<OzgFile>> {
static final LinkRelation REL_DOWNLOAD = LinkRelation.of("download"); static final String REL_DOWNLOAD = "download";
@Override @Override
public EntityModel<OzgFile> toModel(OzgFile file) { public EntityModel<OzgFile> toModel(OzgFile file) {
...@@ -27,7 +27,7 @@ public class OzgFileModelAssembler implements RepresentationModelAssembler<OzgFi ...@@ -27,7 +27,7 @@ public class OzgFileModelAssembler implements RepresentationModelAssembler<OzgFi
} }
public CollectionModel<EntityModel<OzgFile>> toCollectionModel(Stream<OzgFile> entities) { public CollectionModel<EntityModel<OzgFile>> toCollectionModel(Stream<OzgFile> entities) {
return CollectionModel.of(entities.map(this::toModel).toList(), return CollectionModel.of(entities.map(this::toModel).collect(Collectors.toList()),
linkTo(OzgFileDataController.class).withSelfRel()); linkTo(OzgFileDataController.class).withSelfRel());
} }
} }
\ No newline at end of file
...@@ -6,6 +6,7 @@ import java.util.HashSet; ...@@ -6,6 +6,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import org.keycloak.KeycloakPrincipal; import org.keycloak.KeycloakPrincipal;
import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken;
...@@ -51,7 +52,7 @@ public class CurrentUserService { ...@@ -51,7 +52,7 @@ public class CurrentUserService {
List<String> getOrganisationseinheitId(Map<String, Object> claims) { List<String> getOrganisationseinheitId(Map<String, Object> claims) {
return Optional.ofNullable(claims.get(USER_ATTRIBUTE_ORGANISATIONSEINHEIT_ID)) return Optional.ofNullable(claims.get(USER_ATTRIBUTE_ORGANISATIONSEINHEIT_ID))
.map(col -> (Collection<?>) col).orElse(Collections.emptyList()) // NOSONAR - Collection.class::cast has type-safty issue .map(col -> (Collection<?>) col).orElse(Collections.emptyList()) // NOSONAR - Collection.class::cast has type-safty issue
.stream().map(Object::toString).toList(); .stream().map(Object::toString).collect(Collectors.toList());
} }
private Optional<AccessToken> getCurrentSecurityToken() { private Optional<AccessToken> getCurrentSecurityToken() {
......
...@@ -59,3 +59,6 @@ keycloak: ...@@ -59,3 +59,6 @@ keycloak:
resource: goofy resource: goofy
public-client: true public-client: true
kop:
auth:
token-secret: XPPWagXn3rDwKG6Ywoir
\ No newline at end of file
package de.itvsh.goofy;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
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.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
@SpringBootTest
@AutoConfigureMockMvc
@WithMockUser
class JwtAuthenticationFilterITCase {
@SpyBean
private JwtAuthenticationFilter filter;
@Autowired
private MockMvc mockMvc;
@Test
void shouldCallFilter() throws Exception {
performRequest("/");
verify(filter).doFilterInternal(any(), any(), any());
}
void performRequest(String path) throws Exception {
mockMvc.perform(get(path));
}
}
\ No newline at end of file
package de.itvsh.goofy;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import java.io.IOException;
import java.util.UUID;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import com.auth0.jwt.exceptions.JWTVerificationException;
import de.itvsh.goofy.common.errorhandling.TechnicalException;
class JwtAuthenticationFilterTest {
@Spy
@InjectMocks
private JwtAuthenticationFilter filter;
@Mock
private FilterChain filterChain;
@Mock
private HttpServletRequest request;
@Mock
private HttpServletResponse response;
@Mock
private JwtTokenUtil jwtTokenUtil;
@Test
void shouldCallDoFilter() throws IOException, ServletException {
doFilterInternal();
verify(filterChain).doFilter(request, response);
}
@Test
void shouldNotCallJwtTokenUtil() throws Exception {
doFilterInternal();
verifyNoInteractions(jwtTokenUtil);
}
@Nested
class TestWithParamter {
final String TOKEN = UUID.randomUUID().toString();
@BeforeEach
void mockRequestParameter() {
when(request.getParameter(anyString())).thenReturn(TOKEN);
}
@Test
void shouldVerifyTokenIfPresent() throws Exception {
doFilterInternal();
verify(jwtTokenUtil).verifyToken(TOKEN);
}
@Test
void shouldThrowExceptionOnInvalidToken() throws Exception {
doThrow(JWTVerificationException.class).when(jwtTokenUtil).verifyToken(anyString());
assertThrows(TechnicalException.class, () -> doFilterInternal());
}
@Test
void shouldNotFilterOnInvalidToken() throws Exception {
doThrow(JWTVerificationException.class).when(jwtTokenUtil).verifyToken(anyString());
assertThrows(TechnicalException.class, () -> doFilterInternal());
verify(filter, times(0)).doFilter(any(), any(), any());
}
}
private void doFilterInternal() throws ServletException, IOException {
filter.doFilterInternal(request, response, filterChain);
}
}
\ No newline at end of file
package de.itvsh.goofy;
import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.mockito.Spy;
import com.auth0.jwt.exceptions.JWTVerificationException;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
class JwtTokenUtilTest {
@Spy
private JwtTokenUtil jwtTokenUtil;
private static final String TOKEN_SECRET = "t0pS3cr3t";
@BeforeEach
public void initTest() throws Exception {
Field tokenSecretField = JwtTokenUtil.class.getDeclaredField("secret");
tokenSecretField.setAccessible(true);
tokenSecretField.set(jwtTokenUtil, TOKEN_SECRET);
}
@Nested
@DisplayName("Verify token generation")
class TestGenerateToken {
private String generatedToken;
@BeforeEach
void initTest() {
generatedToken = jwtTokenUtil.generateToken(SecurityTestFactory.SUBJECT.toString(), SecurityTestFactory.USER_FIRSTNAME,
SecurityTestFactory.USER_LASTNAME, SecurityTestFactory.AUTHORITIES);
}
@Test
void userId() {
var userId = getParsedBody().getSubject();
assertThat(userId).isEqualTo(SecurityTestFactory.SUBJECT.toString());
}
@Test
void expirationDate() {
var before = new Date();
var expirationDate = getParsedBody().getExpiration();
var after = new Date(System.currentTimeMillis() + 900000);
assertThat(expirationDate).isAfter(before).isBefore(after);
}
private Claims getParsedBody() {
return Jwts.parser().setSigningKey(TOKEN_SECRET.getBytes()).parseClaimsJws(generatedToken).getBody();
}
}
@Nested
class TestVerifyToken {
@Test
void shouldDoNotThrowExcepionOnValidToken() {
var token = buildToken(UUID.randomUUID().toString(), TOKEN_SECRET, 60000);
jwtTokenUtil.verifyToken(token);
}
@Test
void shouldThrowExceptionOnInvalidToken() {
var token = buildToken(UUID.randomUUID().toString(), "invalid_token", 60000);
assertThrows(JWTVerificationException.class, () -> jwtTokenUtil.verifyToken(token));
}
@Test
void shouldThrowExceptionOnTimeExpired() {
var token = buildToken(UUID.randomUUID().toString(), TOKEN_SECRET, -1000);
assertThrows(JWTVerificationException.class, () -> jwtTokenUtil.verifyToken(token));
}
private String buildToken(String subject, String token, int expiredTime) {
Map<String, Object> claims = new HashMap<>();
claims.put(JwtTokenUtil.FIRSTNAME_CLAIM, SecurityTestFactory.USER_FIRSTNAME);
claims.put(JwtTokenUtil.LASTNAME_CLAIM, SecurityTestFactory.USER_LASTNAME);
claims.put(JwtTokenUtil.ROLE_CLAIM, SecurityTestFactory.AUTHORITIES);
return Jwts.builder()//
.setClaims(claims)//
.setSubject(subject)//
.setHeaderParam("typ", JwtTokenUtil.TOKEN_TYPE)//
.setIssuer(JwtTokenUtil.TOKEN_ISSUER).setIssuedAt(new Date(System.currentTimeMillis()))//
.setExpiration(new Date(System.currentTimeMillis() + expiredTime))//
.setAudience(JwtTokenUtil.TOKEN_AUDIENCE)//
.signWith(SignatureAlgorithm.HS512, token.getBytes())//
.compact();
}
}
}
\ No newline at end of file
...@@ -4,10 +4,12 @@ import static org.assertj.core.api.Assertions.*; ...@@ -4,10 +4,12 @@ import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*; import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.DisplayName;
...@@ -23,6 +25,7 @@ import org.springframework.test.web.servlet.ResultActions; ...@@ -23,6 +25,7 @@ import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import de.itvsh.goofy.common.user.CurrentUserService; import de.itvsh.goofy.common.user.CurrentUserService;
import de.itvsh.goofy.common.user.UserTestFactory;
class RootControllerTest { class RootControllerTest {
...@@ -34,6 +37,8 @@ class RootControllerTest { ...@@ -34,6 +37,8 @@ class RootControllerTest {
private BuildProperties properties; private BuildProperties properties;
@Mock @Mock
private CurrentUserService userService; private CurrentUserService userService;
@Mock
private JwtTokenUtil jwtTokenUtil;
private MockMvc mockMvc; private MockMvc mockMvc;
...@@ -90,6 +95,14 @@ class RootControllerTest { ...@@ -90,6 +95,14 @@ class RootControllerTest {
.isEqualTo("/api/userProfiles?searchBy={searchBy}"); .isEqualTo("/api/userProfiles?searchBy={searchBy}");
} }
@Test
void shoulHaveDownloadTokenLink() {
var model = controller.getRootResource();
assertThat(model.getLink(RootController.REL_DOWNLOAD_TOKEN)).isPresent().get().extracting(Link::getHref)
.isEqualTo("/api");
}
@Test @Test
void shouldHaveJavaVersion() throws Exception { void shouldHaveJavaVersion() throws Exception {
callEndpoint().andExpect(jsonPath("$.javaVersion").exists()); callEndpoint().andExpect(jsonPath("$.javaVersion").exists());
...@@ -112,4 +125,55 @@ class RootControllerTest { ...@@ -112,4 +125,55 @@ class RootControllerTest {
private ResultActions callEndpoint() throws Exception { private ResultActions callEndpoint() throws Exception {
return mockMvc.perform(get(PATH)).andExpect(status().isOk()); return mockMvc.perform(get(PATH)).andExpect(status().isOk());
} }
@Nested
class TestDownloadToken {
@BeforeEach
void mock() {
when(userService.getUser()).thenReturn(UserTestFactory.create());
when(jwtTokenUtil.generateToken(any(), any(), any(), any())).thenReturn(UUID.randomUUID().toString());
}
@Test
void shouldCallCurrentUserService() throws Exception {
callEndpoint();
verify(userService).getUser();
}
@Test
void shouldGenerateToken() throws Exception {
callEndpoint();
verify(jwtTokenUtil).generateToken(any(), any(), any(), any());
}
@Test
void shouldHaveReponse() throws Exception {
callEndpoint().andDo(print());
}
private ResultActions callEndpoint() throws Exception {
return mockMvc.perform(post(PATH).content("/api/service/resourceuri"))
.andExpect(status().isCreated());
}
}
@Nested
class TestGetDownloadToken {
static final String DOWNLOAD_TOKEN = "TestDownloadToken";
@Test
void shouldReturnDownloadToken() throws Exception {
callEndpoint().andExpect(content().string(DOWNLOAD_TOKEN));
}
private ResultActions callEndpoint() throws Exception {
return mockMvc.perform(get(PATH + "/downloadToken").param(RootController.PARAM_DOWNLOAD_TOKEN, DOWNLOAD_TOKEN))
.andExpect(status().isOk());
}
}
} }
\ No newline at end of file
package de.itvsh.goofy;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
public class SecurityTestFactory {
static final String SUBJECT = UUID.randomUUID().toString();
static final String USER_FIRSTNAME = "Tim";
static final String USER_LASTNAME = "Tester";
static final String ROLE = "Testrolle";
static final List<SimpleGrantedAuthority> AUTHORITIES = Arrays.asList(new SimpleGrantedAuthority(ROLE));
}
...@@ -22,7 +22,7 @@ class OzgFileModelAssemblerTest { ...@@ -22,7 +22,7 @@ class OzgFileModelAssemblerTest {
@Test @Test
void shouldHaveDownloadLink() throws Exception { void shouldHaveDownloadLink() throws Exception {
var link = getLinkFromModel(OzgFileTestFactory.create(), OzgFileModelAssembler.REL_DOWNLOAD); var link = getLinkFromModel(OzgFileTestFactory.create(), LinkRelation.of(OzgFileModelAssembler.REL_DOWNLOAD));
assertThat(link).isPresent(); assertThat(link).isPresent();
assertThat(link.get().getHref()).isEqualTo(PATH + OzgFileTestFactory.ID); assertThat(link.get().getHref()).isEqualTo(PATH + OzgFileTestFactory.ID);
......
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<groupId>de.itvsh.ozg</groupId> <groupId>de.itvsh.ozg</groupId>
<artifactId>goofy</artifactId> <artifactId>goofy</artifactId>
<version>0.16.0-SNAPSHOT</version> <version>0.16.0-SNAPSHOT</version>
...@@ -34,6 +34,9 @@ ...@@ -34,6 +34,9 @@
<lorem.version>2.1</lorem.version> <lorem.version>2.1</lorem.version>
<java-jwt.version>3.18.2</java-jwt.version>
<jjwt.version>0.9.1</jjwt.version>
<!-- plugins --> <!-- plugins -->
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version> <maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
<resources.plugin.version>3.1.0</resources.plugin.version> <resources.plugin.version>3.1.0</resources.plugin.version>
...@@ -117,6 +120,18 @@ ...@@ -117,6 +120,18 @@
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<version>${lombok.version}</version> <version>${lombok.version}</version>
</dependency> </dependency>
<!-- jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>${java-jwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment