Skip to content

Commit

Permalink
Merge pull request #710 from overture-stack/feature/passportVisa
Browse files Browse the repository at this point in the history
Changes related to error status 400 and updates to deletion of visa and implementation of JWT token validation
  • Loading branch information
Azher2Ali authored Jun 6, 2023
2 parents 6eb668d + 4f16469 commit 85cd6c7
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 90 deletions.
11 changes: 10 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,16 @@
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>jwks-rsa</artifactId>
<version>0.22.0</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
Expand Down
49 changes: 25 additions & 24 deletions src/main/java/bio/overture/ego/controller/VisaController.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
package bio.overture.ego.controller;

import static java.lang.String.format;
import static org.springframework.web.bind.annotation.RequestMethod.*;

import bio.overture.ego.model.dto.*;
import bio.overture.ego.model.entity.*;
import bio.overture.ego.model.dto.PageDTO;
import bio.overture.ego.model.dto.VisaPermissionRequest;
import bio.overture.ego.model.dto.VisaRequest;
import bio.overture.ego.model.entity.Visa;
import bio.overture.ego.model.entity.VisaPermission;
import bio.overture.ego.model.exceptions.NotFoundException;
import bio.overture.ego.security.AdminScoped;
import bio.overture.ego.service.*;
import bio.overture.ego.service.VisaPermissionService;
import bio.overture.ego.service.VisaService;
import bio.overture.ego.view.Views;
import com.fasterxml.jackson.annotation.JsonView;
import io.swagger.v3.oas.annotations.Parameter;
Expand Down Expand Up @@ -33,22 +39,11 @@ public class VisaController {

private final VisaPermissionService visaPermissionService;

private final UserPermissionService userPermissionService;
private final GroupPermissionService groupPermissionService;
private final ApplicationPermissionService applicationPermissionService;

@Autowired
public VisaController(
@NonNull VisaService visaService,
@NotNull VisaPermissionService visaPermissionService,
@NonNull UserPermissionService userPermissionService,
@NonNull GroupPermissionService groupPermissionService,
@NonNull ApplicationPermissionService applicationPermissionService) {
@NonNull VisaService visaService, @NotNull VisaPermissionService visaPermissionService) {
this.visaService = visaService;
this.visaPermissionService = visaPermissionService;
this.groupPermissionService = groupPermissionService;
this.userPermissionService = userPermissionService;
this.applicationPermissionService = applicationPermissionService;
}

/*
Expand All @@ -67,7 +62,13 @@ public VisaController(
final String authorization,
@PathVariable(value = "type", required = true) String type,
@PathVariable(value = "value", required = true) String value) {
return visaService.getByTypeAndValue(type, value);
List<Visa> visas = visaService.getByTypeAndValue(type, value);
if (visas != null && !visas.isEmpty()) {
return visas;
} else {
throw new NotFoundException(
format("No Visa exists with type '%s' and value '%s'", type, value));
}
}

/*
Expand Down Expand Up @@ -110,28 +111,28 @@ public VisaController(
* @return Visa visa
*/
@AdminScoped
@RequestMapping(method = PUT, value = "/{id}")
@RequestMapping(method = PUT, value = "/{type}/{value}")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Update Visa")})
public @ResponseBody Visa updateVisa(
public @ResponseBody List<Visa> updateVisa(
@Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true)
final String authorization,
@PathVariable(value = "id", required = true) UUID id,
@RequestBody(required = true) VisaRequest visaRequest) {
return visaService.partialUpdate(id, visaRequest);
return visaService.partialUpdate(visaRequest);
}

/*
* This method is used to delete visa using visa id
* @param visaId UUID
*/
@AdminScoped
@RequestMapping(method = DELETE, value = "/{id}")
@RequestMapping(method = DELETE, value = "/{type}/{value}")
@ResponseStatus(value = HttpStatus.OK)
public void deleteVisa(
@Parameter(hidden = true) @RequestHeader(value = "Authorization", required = true)
final String authorization,
@PathVariable(value = "id", required = true) UUID id) {
visaService.delete(id);
@PathVariable(value = "type", required = true) String type,
@PathVariable(value = "value", required = true) String value) {
visaService.delete(type, value);
}

/*
Expand Down Expand Up @@ -192,7 +193,7 @@ public void deleteVisa(
* @param visaPermissionRequest VisaPermissionRequest
*/
@AdminScoped
@RequestMapping(method = DELETE, value = "/permissions/{policyId}/{visaId}")
@RequestMapping(method = DELETE, value = "/permissions/{policyId}/{type}/{value}")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "Remove VisaPermission")})
@JsonView(Views.REST.class)
public @ResponseBody void removePermissions(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package bio.overture.ego.model.exceptions;

import static java.lang.String.format;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.NOT_FOUND;
import static org.springframework.http.HttpStatus.*;

import bio.overture.ego.utils.Joiners;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -13,6 +12,7 @@
import lombok.val;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MissingRequestHeaderException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

Expand All @@ -35,6 +35,21 @@ public ResponseEntity<Object> handleConstraintViolationException(
NOT_FOUND);
}

@ExceptionHandler(MissingRequestHeaderException.class)
public ResponseEntity<Object> handleMissingRequestHeaderException(
HttpServletRequest req, MissingRequestHeaderException ex) {
val message = ex.getMessage();
log.error(message);
return new ResponseEntity<Object>(
Map.of(
"message", ex.getMessage(),
"timestamp", new Date(),
"path", req.getServletPath(),
"error", UNAUTHORIZED.getReasonPhrase()),
new HttpHeaders(),
UNAUTHORIZED);
}

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<Object> handleConstraintViolationException(
HttpServletRequest req, ConstraintViolationException ex) {
Expand Down
53 changes: 24 additions & 29 deletions src/main/java/bio/overture/ego/service/PassportService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@
import bio.overture.ego.model.dto.PassportVisa;
import bio.overture.ego.model.entity.Visa;
import bio.overture.ego.model.entity.VisaPermission;
import bio.overture.ego.model.exceptions.InvalidTokenException;
import bio.overture.ego.utils.CacheUtil;
import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import java.security.interfaces.RSAPublicKey;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -41,11 +45,10 @@ public PassportService(
this.visaPermissionService = visaPermissionService;
}

public List<VisaPermission> getPermissions(String authToken) throws JsonProcessingException {
public List<VisaPermission> getPermissions(String authToken)
throws JsonProcessingException, ParseException, JwkException {
// Validates passport auth token
if (!isValidPassport(authToken)) {
throw new InvalidTokenException("The passport token received from broker is invalid");
}
isValidPassport(authToken);
// Parses passport JWT token
Passport parsedPassport = parsePassport(authToken);
// Fetches visas for parsed passport
Expand All @@ -58,21 +61,12 @@ public List<VisaPermission> getPermissions(String authToken) throws JsonProcessi
}

// Validates passport token based on public key
private boolean isValidPassport(@NonNull String authToken) {
Claims claims;
try {
claims =
Jwts.parser()
.setSigningKey(cacheUtil.getPassportBrokerPublicKey())
.parseClaimsJws(authToken)
.getBody();
if (claims != null) {
return true;
}
} catch (Exception exception) {
throw new InvalidTokenException("The passport token received from broker is invalid");
}
return false;
private void isValidPassport(@NonNull String authToken)
throws ParseException, JwkException, JsonProcessingException {
DecodedJWT jwt = JWT.decode(authToken);
Jwk jwk = cacheUtil.getPassportBrokerPublicKey().get(jwt.getKeyId());
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
algorithm.verify(jwt);
}

// Extracts Visas from parsed passport object
Expand All @@ -82,13 +76,12 @@ private List<PassportVisa> getVisas(Passport passport) {
.forEach(
visaJwt -> {
try {
if (visaService.isValidVisa(visaJwt)) {
PassportVisa visa = visaService.parseVisa(visaJwt);
if (visa != null) {
visas.add(visa);
}
visaService.isValidVisa(visaJwt);
PassportVisa visa = visaService.parseVisa(visaJwt);
if (visa != null) {
visas.add(visa);
}
} catch (JsonProcessingException e) {
} catch (JsonProcessingException | JwkException e) {
e.printStackTrace();
}
});
Expand All @@ -105,7 +98,9 @@ private List<VisaPermission> getVisaPermissions(List<PassportVisa> visas) {
List<Visa> visaEntities =
visaService.getByTypeAndValue(
visa.getGa4ghVisaV1().getType(), visa.getGa4ghVisaV1().getValue());
visaPermissions.addAll(visaPermissionService.getPermissionsForVisa(visaEntities));
if (visaEntities != null && !visaEntities.isEmpty()) {
visaPermissions.addAll(visaPermissionService.getPermissionsForVisa(visaEntities));
}
});
return visaPermissions;
}
Expand Down
63 changes: 36 additions & 27 deletions src/main/java/bio/overture/ego/service/VisaService.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@
import bio.overture.ego.model.dto.PassportVisa;
import bio.overture.ego.model.dto.VisaRequest;
import bio.overture.ego.model.entity.Visa;
import bio.overture.ego.model.exceptions.InvalidTokenException;
import bio.overture.ego.model.exceptions.NotFoundException;
import bio.overture.ego.repository.VisaRepository;
import bio.overture.ego.utils.CacheUtil;
import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkException;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import jakarta.validation.constraints.NotNull;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
Expand Down Expand Up @@ -75,17 +79,20 @@ public List<Visa> getByTypeAndValue(@NonNull String type, @NotNull String value)
val result = visaRepository.getByTypeAndValue(type, value);
if (!result.isEmpty()) {
return result;
}
return null;
}

public void delete(@NonNull String type, @NotNull String value) {
List<Visa> visas = getByTypeAndValue(type, value);
if (visas != null && !visas.isEmpty()) {
visas.stream().forEach(visa -> visaRepository.delete(visa));
} else {
throw new NotFoundException(
format("No Visa exists with type '%s' and value '%s'", type, value));
}
}

public void delete(@NonNull UUID id) {
checkExistence(id);
super.delete(id);
}

// Parses Visa JWT token to convert into Visa Object
public PassportVisa parseVisa(@NonNull String visaJwtToken) throws JsonProcessingException {
String[] split_string = visaJwtToken.split("\\.");
Expand All @@ -99,31 +106,33 @@ public PassportVisa parseVisa(@NonNull String visaJwtToken) throws JsonProcessin
}

// Checks if the visa is a valid visa
public boolean isValidVisa(@NonNull String authToken) {
Claims claims;
try {
claims =
Jwts.parser()
.setSigningKey(cacheUtil.getPassportBrokerPublicKey())
.parseClaimsJws(authToken)
.getBody();
if (claims != null) {
return true;
}
} catch (Exception exception) {
throw new InvalidTokenException("The visa token received from broker is invalid");
}
return false;
public void isValidVisa(@NonNull String authToken) throws JwkException, JsonProcessingException {
DecodedJWT jwt = JWT.decode(authToken);
Jwk jwk = cacheUtil.getPassportBrokerPublicKey().get(jwt.getKeyId());
Algorithm algorithm = Algorithm.RSA256((RSAPublicKey) jwk.getPublicKey(), null);
algorithm.verify(jwt);
}

public Page<Visa> listVisa(@NonNull Pageable pageable) {
return visaRepository.findAll(pageable);
}

public Visa partialUpdate(@NotNull UUID id, @NonNull VisaRequest updateRequest) {
val visa = getById(id);
VISA_CONVERTER.updateVisa(updateRequest, visa);
return getRepository().save(visa);
public List<Visa> partialUpdate(@NonNull VisaRequest updateRequest) {
List<Visa> updatedVisas = new ArrayList<>();
List<Visa> visas = getByTypeAndValue(updateRequest.getType(), updateRequest.getValue());
if (visas != null && !visas.isEmpty()) {
for (Visa visa : visas) {
visa.setBy(updateRequest.getBy());
visa.setSource(updateRequest.getSource());
updatedVisas.add(getRepository().save(visa));
}
} else {
throw new NotFoundException(
format(
"No Visa exists with type '%s' and value '%s'",
updateRequest.getType(), updateRequest.getValue()));
}
return updatedVisas;
}

@Mapper(
Expand Down
Loading

0 comments on commit 85cd6c7

Please sign in to comment.