Commit 76031011 authored by Deniro StopCovid's avatar Deniro StopCovid

Merge branch 'release/v1.7.0' into 'develop'

Merge Back Release/v1.7.0

See merge request !107
parents e9eb6a4d 9c45d333
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
<groupId>fr.gouv.stopc</groupId> <groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId> <artifactId>robert-server</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.8.0-SNAPSHOT</version>
<name>robert-server</name> <name>robert-server</name>
<packaging>pom</packaging> <packaging>pom</packaging>
<description>Projet principal</description> <description>Projet principal</description>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<parent> <parent>
<groupId>fr.gouv.stopc</groupId> <groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId> <artifactId>robert-server</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.8.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>robert-crypto-grpc-server-messaging</artifactId> <artifactId>robert-crypto-grpc-server-messaging</artifactId>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<parent> <parent>
<groupId>fr.gouv.stopc</groupId> <groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId> <artifactId>robert-server</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.8.0-SNAPSHOT</version>
</parent> </parent>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<parent> <parent>
<groupId>fr.gouv.stopc</groupId> <groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId> <artifactId>robert-server</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.8.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>robert-crypto-grpc-server</artifactId> <artifactId>robert-crypto-grpc-server</artifactId>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<parent> <parent>
<groupId>fr.gouv.stopc</groupId> <groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId> <artifactId>robert-server</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.8.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>robert-server-batch</artifactId> <artifactId>robert-server-batch</artifactId>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<parent> <parent>
<groupId>fr.gouv.stopc</groupId> <groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId> <artifactId>robert-server</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.8.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>robert-server-common</artifactId> <artifactId>robert-server-common</artifactId>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<parent> <parent>
<groupId>fr.gouv.stopc</groupId> <groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId> <artifactId>robert-server</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.8.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>robert-server-crypto</artifactId> <artifactId>robert-server-crypto</artifactId>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<parent> <parent>
<groupId>fr.gouv.stopc</groupId> <groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId> <artifactId>robert-server</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.8.0-SNAPSHOT</version>
</parent> </parent>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<parent> <parent>
<groupId>fr.gouv.stopc</groupId> <groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId> <artifactId>robert-server</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.8.0-SNAPSHOT</version>
</parent> </parent>
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<parent> <parent>
<groupId>fr.gouv.stopc</groupId> <groupId>fr.gouv.stopc</groupId>
<artifactId>robert-server</artifactId> <artifactId>robert-server</artifactId>
<version>1.7.0-SNAPSHOT</version> <version>1.8.0-SNAPSHOT</version>
</parent> </parent>
<artifactId>robert-server-ws-rest</artifactId> <artifactId>robert-server-ws-rest</artifactId>
...@@ -134,6 +134,24 @@ ...@@ -134,6 +134,24 @@
<artifactId>micrometer-registry-prometheus</artifactId> <artifactId>micrometer-registry-prometheus</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
......
...@@ -16,10 +16,18 @@ import lombok.ToString; ...@@ -16,10 +16,18 @@ import lombok.ToString;
@RefreshScope @RefreshScope
public class WsServerConfiguration { public class WsServerConfiguration {
@Value("${robert.epoch-bundle-duration-in-days}") @Value("${robert.epoch-bundle-duration-in-days}")
private Integer epochBundleDurationInDays; private Integer epochBundleDurationInDays;
@Value("${robert.server.status-request-minimum-epoch-gap}") @Value("${robert.server.status-request-minimum-epoch-gap}")
private Integer statusRequestMinimumEpochGap; private Integer statusRequestMinimumEpochGap;
@Value("${robert.jwt.privatekey}")
private String jwtPrivateKey;
@Value("${robert.jwt.lifetime}")
private int jwtLifeTime;
@Value("${robert.jwt.use-transient-key}")
private Boolean jwtUseTransientKey;
} }
...@@ -17,8 +17,8 @@ import fr.gouv.stopc.robertserver.ws.utils.UriConstants; ...@@ -17,8 +17,8 @@ import fr.gouv.stopc.robertserver.ws.utils.UriConstants;
import fr.gouv.stopc.robertserver.ws.vo.DeleteHistoryRequestVo; import fr.gouv.stopc.robertserver.ws.vo.DeleteHistoryRequestVo;
@RestController @RestController
@RequestMapping(value = {"${controller.path.prefix}" + UriConstants.API_V2, @RequestMapping(value = {"${controller.path.prefix}" + UriConstants.API_V1, "${controller.path.prefix}" + UriConstants.API_V2,
"${controller.path.prefix}" + UriConstants.API_V3 }) "${controller.path.prefix}" + UriConstants.API_V3, "${controller.path.prefix}" + UriConstants.API_V4})
@Consumes(MediaType.APPLICATION_JSON_VALUE) @Consumes(MediaType.APPLICATION_JSON_VALUE)
@Produces(MediaType.APPLICATION_JSON_VALUE) @Produces(MediaType.APPLICATION_JSON_VALUE)
public interface IDeleteHistoryController { public interface IDeleteHistoryController {
......
...@@ -17,7 +17,8 @@ import fr.gouv.stopc.robertserver.ws.exception.RobertServerException; ...@@ -17,7 +17,8 @@ import fr.gouv.stopc.robertserver.ws.exception.RobertServerException;
import fr.gouv.stopc.robertserver.ws.utils.UriConstants; import fr.gouv.stopc.robertserver.ws.utils.UriConstants;
@RestController @RestController
@RequestMapping(value = {"${controller.path.prefix}" + UriConstants.API_V2, "${controller.path.prefix}" + UriConstants.API_V3}) @RequestMapping(value = {"${controller.path.prefix}" + UriConstants.API_V2, "${controller.path.prefix}" + UriConstants.API_V3,
"${controller.path.prefix}" + UriConstants.API_V4})
@Consumes(MediaType.APPLICATION_JSON_VALUE) @Consumes(MediaType.APPLICATION_JSON_VALUE)
@Produces(MediaType.APPLICATION_JSON_VALUE) @Produces(MediaType.APPLICATION_JSON_VALUE)
public interface IRegisterController { public interface IRegisterController {
......
...@@ -24,7 +24,8 @@ import fr.gouv.stopc.robertserver.ws.vo.ReportBatchRequestVo; ...@@ -24,7 +24,8 @@ import fr.gouv.stopc.robertserver.ws.vo.ReportBatchRequestVo;
public interface IReportController { public interface IReportController {
@PostMapping(value = UriConstants.REPORT) @PostMapping(value = UriConstants.REPORT)
ResponseEntity<ReportBatchResponseDto> reportContactHistory(@Valid @RequestBody(required = true) ReportBatchRequestVo reportBatchRequestVo) ResponseEntity<ReportBatchResponseDto> reportContactHistory(
@Valid @RequestBody(required = true) ReportBatchRequestVo reportBatchRequestVo)
throws RobertServerException; throws RobertServerException;
} }
package fr.gouv.stopc.robertserver.ws.controller;
import javax.validation.Valid;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
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.RestController;
import fr.gouv.stopc.robertserver.ws.dto.ReportBatchResponseV4Dto;
import fr.gouv.stopc.robertserver.ws.exception.RobertServerException;
import fr.gouv.stopc.robertserver.ws.utils.UriConstants;
import fr.gouv.stopc.robertserver.ws.vo.ReportBatchRequestVo;
@RestController
@RequestMapping(value = "${controller.path.prefix}" + UriConstants.API_V4,
consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
public interface IReportControllerV4 {
@PostMapping(value = UriConstants.REPORT)
ResponseEntity<ReportBatchResponseV4Dto> reportContactHistory(@Valid @RequestBody(required = true) ReportBatchRequestVo reportBatchRequestVo)
throws RobertServerException;
}
...@@ -18,7 +18,7 @@ import fr.gouv.stopc.robertserver.ws.vo.StatusVo; ...@@ -18,7 +18,7 @@ import fr.gouv.stopc.robertserver.ws.vo.StatusVo;
@RestController @RestController
@RequestMapping(value = {"${controller.path.prefix}" + UriConstants.API_V1, "${controller.path.prefix}" + UriConstants.API_V2, @RequestMapping(value = {"${controller.path.prefix}" + UriConstants.API_V1, "${controller.path.prefix}" + UriConstants.API_V2,
"${controller.path.prefix}" + UriConstants.API_V3}) "${controller.path.prefix}" + UriConstants.API_V3, "${controller.path.prefix}" + UriConstants.API_V4})
@Consumes(MediaType.APPLICATION_JSON_VALUE) @Consumes(MediaType.APPLICATION_JSON_VALUE)
@Produces(MediaType.APPLICATION_JSON_VALUE) @Produces(MediaType.APPLICATION_JSON_VALUE)
public interface IStatusController { public interface IStatusController {
......
...@@ -16,8 +16,8 @@ import fr.gouv.stopc.robertserver.ws.utils.UriConstants; ...@@ -16,8 +16,8 @@ import fr.gouv.stopc.robertserver.ws.utils.UriConstants;
import fr.gouv.stopc.robertserver.ws.vo.UnregisterRequestVo; import fr.gouv.stopc.robertserver.ws.vo.UnregisterRequestVo;
@RestController @RestController
@RequestMapping(value = {"${controller.path.prefix}" + UriConstants.API_V2, @RequestMapping(value = {"${controller.path.prefix}" + UriConstants.API_V1, "${controller.path.prefix}" + UriConstants.API_V2,
"${controller.path.prefix}" + UriConstants.API_V3}) "${controller.path.prefix}" + UriConstants.API_V3, "${controller.path.prefix}" + UriConstants.API_V4})
@Consumes(MediaType.APPLICATION_JSON_VALUE) @Consumes(MediaType.APPLICATION_JSON_VALUE)
@Produces(MediaType.APPLICATION_JSON_VALUE) @Produces(MediaType.APPLICATION_JSON_VALUE)
public interface IUnregisterController { public interface IUnregisterController {
......
package fr.gouv.stopc.robertserver.ws.controller.impl;
import java.util.Objects;
import java.util.Optional;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import fr.gouv.stopc.robertserver.ws.dto.VerifyResponseDto;
import fr.gouv.stopc.robertserver.ws.exception.RobertServerBadRequestException;
import fr.gouv.stopc.robertserver.ws.exception.RobertServerException;
import fr.gouv.stopc.robertserver.ws.exception.RobertServerUnauthorizedException;
import fr.gouv.stopc.robertserver.ws.service.IRestApiService;
import fr.gouv.stopc.robertserver.ws.utils.MessageConstants;
import fr.gouv.stopc.robertserver.ws.vo.ReportBatchRequestVo;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class ReportControllerDelegate {
private IRestApiService restApiService;
public ReportControllerDelegate(final IRestApiService restApiService) {
this.restApiService = restApiService;
}
public boolean isReportRequestValid(ReportBatchRequestVo reportBatchRequestVo) throws RobertServerException {
if (this.areBothFieldsPresent(reportBatchRequestVo)) {
log.warn("Contacts and ContactsAsBinary are both present");
return false;
} else if (Objects.isNull(reportBatchRequestVo.getContacts())) {
log.warn("Contacts are null. They could be empty([]) but not null");
return false;
} else if (this.areBothFieldsAbsent(reportBatchRequestVo)) {
log.warn("Contacts and ContactsAsBinary are both absent");
return false;
}
this.checkValidityToken(reportBatchRequestVo.getToken());
return true;
}
private boolean areBothFieldsPresent(ReportBatchRequestVo reportBatchRequestVo) {
return !CollectionUtils.isEmpty(reportBatchRequestVo.getContacts())
&& StringUtils.isNotEmpty(reportBatchRequestVo.getContactsAsBinary());
}
private boolean areBothFieldsAbsent(ReportBatchRequestVo reportBatchRequestVo) {
return Objects.isNull(reportBatchRequestVo.getContacts())
&& StringUtils.isEmpty(reportBatchRequestVo.getContactsAsBinary());
}
private void checkValidityToken(String token) throws RobertServerException {
if (StringUtils.isEmpty(token)) {
log.warn("No token provided");
throw new RobertServerBadRequestException(MessageConstants.INVALID_DATA.getValue());
}
if (token.length() != 6 && token.length() != 36) {
log.warn("Token size is incorrect");
throw new RobertServerBadRequestException(MessageConstants.INVALID_DATA.getValue());
}
Optional<VerifyResponseDto> response = this.restApiService.verifyReportToken(token, getCodeType(token));
if (!response.isPresent() || !response.get().isValid()) {
log.warn("Verifying the token failed");
throw new RobertServerUnauthorizedException(MessageConstants.INVALID_AUTHENTICATION.getValue());
}
log.info("Verifying the token succeeded");
}
private String getCodeType(String token) {
// TODO: create enum for long and short codes
return token.length() == 6 ? "2" : "1";
}
}
package fr.gouv.stopc.robertserver.ws.controller.impl; package fr.gouv.stopc.robertserver.ws.controller.impl;
import java.util.Optional;
import javax.inject.Inject;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import fr.gouv.stopc.robertserver.ws.controller.IReportController; import fr.gouv.stopc.robertserver.ws.controller.IReportController;
import fr.gouv.stopc.robertserver.ws.dto.ReportBatchResponseDto; import fr.gouv.stopc.robertserver.ws.dto.ReportBatchResponseDto;
import fr.gouv.stopc.robertserver.ws.dto.VerifyResponseDto;
import fr.gouv.stopc.robertserver.ws.exception.RobertServerBadRequestException;
import fr.gouv.stopc.robertserver.ws.exception.RobertServerException; import fr.gouv.stopc.robertserver.ws.exception.RobertServerException;
import fr.gouv.stopc.robertserver.ws.exception.RobertServerUnauthorizedException;
import fr.gouv.stopc.robertserver.ws.service.ContactDtoService; import fr.gouv.stopc.robertserver.ws.service.ContactDtoService;
import fr.gouv.stopc.robertserver.ws.service.IRestApiService; import fr.gouv.stopc.robertserver.ws.service.IRestApiService;
import fr.gouv.stopc.robertserver.ws.utils.MessageConstants; import fr.gouv.stopc.robertserver.ws.utils.MessageConstants;
import fr.gouv.stopc.robertserver.ws.vo.ReportBatchRequestVo; import fr.gouv.stopc.robertserver.ws.vo.ReportBatchRequestVo;
import io.micrometer.core.instrument.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import java.util.Objects;
@Service @Service
@Slf4j
public class ReportControllerImpl implements IReportController { public class ReportControllerImpl implements IReportController {
private ReportControllerDelegate delegate;
private final ContactDtoService contactDtoService; private ContactDtoService contactDtoService;
private final IRestApiService restApiService;
@Inject public ReportControllerImpl(final ContactDtoService contactDtoService,
public ReportControllerImpl(final ContactDtoService contactDtoService, final IRestApiService restApiService) { final IRestApiService restApiService, final ReportControllerDelegate delegate) {
this.contactDtoService = contactDtoService; this.contactDtoService = contactDtoService;
this.restApiService = restApiService; this.delegate = delegate;
}
private boolean areBothFieldsPresent(ReportBatchRequestVo reportBatchRequestVo) {
return !CollectionUtils.isEmpty(reportBatchRequestVo.getContacts())
&& StringUtils.isNotEmpty(reportBatchRequestVo.getContactsAsBinary());
}
private boolean areBothFieldsAbsent(ReportBatchRequestVo reportBatchRequestVo) {
return Objects.isNull(reportBatchRequestVo.getContacts())
&& StringUtils.isEmpty(reportBatchRequestVo.getContactsAsBinary());
} }
@Override @Override
public ResponseEntity<ReportBatchResponseDto> reportContactHistory(ReportBatchRequestVo reportBatchRequestVo) throws RobertServerException { public ResponseEntity<ReportBatchResponseDto> reportContactHistory(ReportBatchRequestVo reportBatchRequestVo) throws RobertServerException {
if ( ! delegate.isReportRequestValid(reportBatchRequestVo) )
if (areBothFieldsPresent(reportBatchRequestVo)) { return ResponseEntity.badRequest().build();
log.warn("Contacts and ContactsAsBinary are both present");
return ResponseEntity.badRequest().build();
} else if (Objects.isNull(reportBatchRequestVo.getContacts())) {
log.warn("Contacts are null. They could be empty([]) but not null");
return ResponseEntity.badRequest().build();
}else if (areBothFieldsAbsent(reportBatchRequestVo)) {
log.warn("Contacts and ContactsAsBinary are both absent");
return ResponseEntity.badRequest().build();
}
checkValidityToken(reportBatchRequestVo.getToken());
contactDtoService.saveContacts(reportBatchRequestVo.getContacts()); contactDtoService.saveContacts(reportBatchRequestVo.getContacts());
ReportBatchResponseDto reportBatchResponseDto = ReportBatchResponseDto.builder()
.message(MessageConstants.SUCCESSFUL_OPERATION.getValue())
.success(Boolean.TRUE)
.build();
ReportBatchResponseDto reportBatchResponseDto = ReportBatchResponseDto.builder().message(MessageConstants.SUCCESSFUL_OPERATION.getValue()).success(Boolean.TRUE).build(); return ResponseEntity.ok(reportBatchResponseDto);
return ResponseEntity.ok(reportBatchResponseDto);
}
private void checkValidityToken(String token) throws RobertServerException {
if (StringUtils.isEmpty(token)) {
log.warn("No token provided");
throw new RobertServerBadRequestException(MessageConstants.INVALID_DATA.getValue());
}
if (token.length() != 6 && token.length() != 36) {
log.warn("Token size is incorrect");
throw new RobertServerBadRequestException(MessageConstants.INVALID_DATA.getValue());
}
Optional<VerifyResponseDto> response = this.restApiService.verifyReportToken(token, getCodeType(token));
if (!response.isPresent() || !response.get().isValid()) {
log.warn("Verifying the token failed");
throw new RobertServerUnauthorizedException(MessageConstants.INVALID_AUTHENTICATION.getValue());
}
log.info("Verifying the token succeeded");
}
private String getCodeType(String token) {
// TODO: create enum for long and short codes
return token.length() == 6 ? "2" : "1";
} }
} }
package fr.gouv.stopc.robertserver.ws.controller.impl;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Date;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import fr.gouv.stopc.robertserver.ws.config.WsServerConfiguration;
import fr.gouv.stopc.robertserver.ws.controller.IReportControllerV4;
import fr.gouv.stopc.robertserver.ws.dto.ReportBatchResponseV4Dto;
import fr.gouv.stopc.robertserver.ws.exception.RobertServerException;
import fr.gouv.stopc.robertserver.ws.service.ContactDtoService;
import fr.gouv.stopc.robertserver.ws.utils.MessageConstants;
import fr.gouv.stopc.robertserver.ws.vo.ReportBatchRequestVo;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class ReportControllerV4Impl implements IReportControllerV4 {
public static final SignatureAlgorithm signatureAlgo = SignatureAlgorithm.RS256;
private final WsServerConfiguration wsServerConfiguration;
private final ReportControllerDelegate delegate;