Skip to content

Commit

Permalink
add debug methods foor debug controller
Browse files Browse the repository at this point in the history
  • Loading branch information
ubamrein committed Sep 14, 2020
1 parent 2efe45c commit ce3d8e9
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@

package org.dpppt.backend.sdk.data.gaen;

import java.time.Duration;
import java.util.List;
import java.util.Map;
import org.dpppt.backend.sdk.model.gaen.GaenKey;
import org.dpppt.backend.sdk.utils.UTCInstant;

public interface DebugGAENDataService {

Expand All @@ -32,5 +34,5 @@ public interface DebugGAENDataService {
* @return all exposed keys for the given batch from the debug store
*/
Map<String, List<GaenKey>> getSortedExposedForBatchReleaseTime(
Long batchReleaseTime, long releaseBucketDuration);
UTCInstant batchReleaseTime, Duration releaseBucketDuration);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

package org.dpppt.backend.sdk.data.gaen;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -71,15 +72,14 @@ public void upsertExposees(String deviceName, List<GaenKey> gaenKeys) {
@Override
@Transactional(readOnly = true)
public Map<String, List<GaenKey>> getSortedExposedForBatchReleaseTime(
Long batchReleaseTime, long releaseBucketDuration) {
UTCInstant batchReleaseTime, Duration releaseBucketDuration) {
String sql =
"select pk_exposed_id, device_name, key, rolling_start_number, rolling_period,"
+ " transmission_risk_level from t_debug_gaen_exposed where received_at >= :startBatch"
+ " and received_at < :batchReleaseTime order by pk_exposed_id desc";
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("batchReleaseTime", UTCInstant.ofEpochMillis(batchReleaseTime).getDate());
params.addValue(
"startBatch", UTCInstant.ofEpochMillis(batchReleaseTime - releaseBucketDuration).getDate());
params.addValue("batchReleaseTime", batchReleaseTime.getDate());
params.addValue("startBatch", batchReleaseTime.minus(releaseBucketDuration).getDate());
return jt.query(sql, params, new DebugGaenKeyResultSetExtractor());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
import org.dpppt.backend.sdk.data.gaen.DebugGAENDataService;
import org.dpppt.backend.sdk.data.gaen.DebugJDBCGAENDataServiceImpl;
import org.dpppt.backend.sdk.ws.controller.DebugController;
import org.dpppt.backend.sdk.ws.insertmanager.InsertManager;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.AssertKeyFormat;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.EnforceRetentionPeriod;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.EnforceValidRollingPeriod;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.RemoveFakeKeys;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.RemoveKeysFromFuture;
import org.dpppt.backend.sdk.ws.security.KeyVault;
import org.dpppt.backend.sdk.ws.security.KeyVault.PrivateKeyNoSuitableEncodingFoundException;
import org.dpppt.backend.sdk.ws.security.KeyVault.PublicKeyNoSuitableEncodingFoundException;
Expand Down Expand Up @@ -175,13 +181,24 @@ DebugGAENDataService dataService() {
return new DebugJDBCGAENDataServiceImpl(dbType, dataSource);
}

@Bean
public InsertManager insertManagerDebug() {
var manager = InsertManager.getDebugInsertManager(dataService(), gaenValidationUtils);
manager.addFilter(new AssertKeyFormat(gaenValidationUtils));
manager.addFilter(new RemoveKeysFromFuture());
manager.addFilter(new EnforceRetentionPeriod(gaenValidationUtils));
manager.addFilter(new RemoveFakeKeys());
manager.addFilter(new EnforceValidRollingPeriod());
return manager;
}

@Bean
DebugController debugController() {
return new DebugController(
dataService(),
gaenSigner,
backupValidator,
gaenValidationUtils,
insertManagerDebug(),
Duration.ofMillis(releaseBucketDuration),
Duration.ofMillis(requestTime));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,50 @@
package org.dpppt.backend.sdk.ws.controller;

import com.fasterxml.jackson.core.JsonProcessingException;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.List;
import javax.validation.Valid;
import org.dpppt.backend.sdk.data.gaen.DebugGAENDataService;
import org.dpppt.backend.sdk.model.gaen.DayBuckets;
import org.dpppt.backend.sdk.model.gaen.GaenKey;
import org.dpppt.backend.sdk.model.gaen.GaenRequest;
import org.dpppt.backend.sdk.utils.UTCInstant;
import org.dpppt.backend.sdk.ws.insertmanager.InsertException;
import org.dpppt.backend.sdk.ws.insertmanager.InsertManager;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.AssertKeyFormat.KeyFormatException;
import org.dpppt.backend.sdk.ws.security.ValidateRequest;
import org.dpppt.backend.sdk.ws.security.ValidateRequest.ClaimIsBeforeOnsetException;
import org.dpppt.backend.sdk.ws.security.ValidateRequest.InvalidDateException;
import org.dpppt.backend.sdk.ws.security.ValidateRequest.WrongScopeException;
import org.dpppt.backend.sdk.ws.security.signature.ProtoSignature;
import org.dpppt.backend.sdk.ws.util.ValidationUtils;
import org.dpppt.backend.sdk.ws.util.ValidationUtils.BadBatchReleaseTimeException;
import org.dpppt.backend.sdk.ws.util.ValidationUtils.DelayedKeyDateClaimIsMissing;
import org.dpppt.backend.sdk.ws.util.ValidationUtils.DelayedKeyDateIsInvalid;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;

@Controller
@RequestMapping("/v1/debug")
public class DebugController {
private final ValidateRequest validateRequest;
private final ValidationUtils validationUtils;
private final InsertManager insertManager;
private final Duration releaseBucketDuration;
private final Duration requestTime;
private final ProtoSignature gaenSigner;
Expand All @@ -48,15 +54,15 @@ public DebugController(
DebugGAENDataService dataService,
ProtoSignature gaenSigner,
ValidateRequest validateRequest,
ValidationUtils validationUtils,
InsertManager insertManager,
Duration releaseBucketDuration,
Duration requestTime) {
this.validateRequest = validateRequest;
this.validationUtils = validationUtils;
this.releaseBucketDuration = releaseBucketDuration;
this.requestTime = requestTime;
this.gaenSigner = gaenSigner;
this.dataService = dataService;
this.insertManager = insertManager;
}

@PostMapping(value = "/exposed")
Expand All @@ -65,32 +71,15 @@ public DebugController(
@RequestHeader(value = "User-Agent", required = true) String userAgent,
@RequestHeader(value = "X-Device-Name", required = true) String deviceName,
@AuthenticationPrincipal Object principal)
throws InvalidDateException, ClaimIsBeforeOnsetException, WrongScopeException {
throws InvalidDateException, ClaimIsBeforeOnsetException, WrongScopeException,
InsertException {
var now = UTCInstant.now();
this.validateRequest.isValid(principal);

List<GaenKey> nonFakeKeys = new ArrayList<>();
for (var key : gaenRequest.getGaenKeys()) {
if (!validationUtils.isValidKeyFormat(key.getKeyData())) {
return new ResponseEntity<>("No valid base64 key", HttpStatus.BAD_REQUEST);
}
this.validateRequest.validateKeyDate(now, principal, key);
if (this.validateRequest.isFakeRequest(principal, key)) {
continue;
} else {
nonFakeKeys.add(key);
}
}
if (principal instanceof Jwt
&& ((Jwt) principal).containsClaim("fake")
&& ((Jwt) principal).getClaim("fake").equals("1")
&& !nonFakeKeys.isEmpty()) {
return ResponseEntity.badRequest().body("Claim is fake but list contains non fake keys");
}
if (!nonFakeKeys.isEmpty()) {
dataService.upsertExposees(deviceName, nonFakeKeys);
}

// Filter out non valid keys and insert them into the database (c.f. InsertManager and
// configured Filters in the WSBaseConfig)
insertManager.insertIntoDatabaseDEBUG(
deviceName, gaenRequest.getGaenKeys(), userAgent, principal, now);
var responseBuilder = ResponseEntity.ok();

normalizeRequestTime(now.getTimestamp());
Expand All @@ -104,13 +93,14 @@ public DebugController(
NoSuchAlgorithmException, SignatureException {

var batchReleaseTimeDuration = Duration.ofMillis(batchReleaseTime);

if (batchReleaseTime % releaseBucketDuration.toMillis() != 0) {
return ResponseEntity.notFound().build();
}

var exposedKeys =
dataService.getSortedExposedForBatchReleaseTime(
batchReleaseTimeDuration.toMillis(), releaseBucketDuration.toMillis());
UTCInstant.ofEpochMillis(batchReleaseTime), releaseBucketDuration);

byte[] payload = gaenSigner.getPayload(exposedKeys);

Expand Down Expand Up @@ -150,4 +140,38 @@ private void normalizeRequestTime(long now) {

}
}

@ExceptionHandler({DelayedKeyDateClaimIsMissing.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<String> delayedClaimIsWrong() {
return ResponseEntity.badRequest().body("DelayedKeyDateClaim is wrong");
}

@ExceptionHandler({DelayedKeyDateIsInvalid.class})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<String> delayedKeyDateIsInvalid() {
return ResponseEntity.badRequest()
.body("DelayedKeyDate must be between yesterday and tomorrow");
}

@ExceptionHandler({
IllegalArgumentException.class,
InvalidDateException.class,
JsonProcessingException.class,
MethodArgumentNotValidException.class,
BadBatchReleaseTimeException.class,
DateTimeParseException.class,
ClaimIsBeforeOnsetException.class,
KeyFormatException.class
})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<Object> invalidArguments() {
return ResponseEntity.badRequest().build();
}

@ExceptionHandler({WrongScopeException.class})
@ResponseStatus(HttpStatus.FORBIDDEN)
public ResponseEntity<Object> forbidden() {
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.ArrayList;
import java.util.List;
import org.dpppt.backend.sdk.data.gaen.DebugGAENDataService;
import org.dpppt.backend.sdk.data.gaen.GAENDataService;
import org.dpppt.backend.sdk.model.gaen.GaenKey;
import org.dpppt.backend.sdk.semver.Version;
Expand All @@ -28,11 +29,25 @@ public class InsertManager {
private final GAENDataService dataService;
private final ValidationUtils validationUtils;

private DebugGAENDataService debugDataService;

private static final Logger logger = LoggerFactory.getLogger(InsertManager.class);

public InsertManager(GAENDataService dataService, ValidationUtils validationUtils) {
this.dataService = dataService;
this.validationUtils = validationUtils;
this.debugDataService = null;
}

private InsertManager(DebugGAENDataService debugDataService, ValidationUtils validationUtils) {
this.debugDataService = debugDataService;
this.validationUtils = validationUtils;
this.dataService = null;
}

public static InsertManager getDebugInsertManager(
DebugGAENDataService debugDataService, ValidationUtils validationUtils) {
return new InsertManager(debugDataService, validationUtils);
}

public void addFilter(KeyInsertionFilter filter) {
Expand Down Expand Up @@ -60,7 +75,37 @@ public void insertIntoDatabase(
if (keys == null || keys.isEmpty()) {
return;
}
var internalKeys = filterAndModify(keys, header, principal, now);
// if no keys remain or this is a fake request, just return. Else, insert the
// remaining keys.
if (internalKeys.isEmpty() || validationUtils.jwtIsFake(principal)) {
return;
} else {
dataService.upsertExposees(internalKeys, now);
}
}

public void insertIntoDatabaseDEBUG(
String deviceName, List<GaenKey> keys, String header, Object principal, UTCInstant now)
throws InsertException {
if (keys == null || keys.isEmpty()) {
return;
}
var internalKeys = filterAndModify(keys, header, principal, now);
// if no keys remain or this is a fake request, just return. Else, insert the
// remaining keys.
if (internalKeys.isEmpty() || validationUtils.jwtIsFake(principal)) {
return;
} else {
debugDataService.upsertExposees(deviceName, internalKeys);
}
}

private List<GaenKey> filterAndModify(
List<GaenKey> keys, String header, Object principal, UTCInstant now) throws InsertException {
if (debugDataService != null) {
logger.warn("DebugDataService is not null, don't use this in production!");
}
var internalKeys = keys;
var headerParts = header.split(";");
if (headerParts.length != 5) {
Expand All @@ -83,14 +128,7 @@ public void insertIntoDatabase(
for (KeyInsertionFilter filter : filterList) {
internalKeys = filter.filter(now, internalKeys, osType, osVersion, appVersion, principal);
}

// if no keys remain or this is a fake request, just return. Else, insert the
// remaining keys.
if (internalKeys.isEmpty() || validationUtils.jwtIsFake(principal)) {
return;
} else {
dataService.upsertExposees(internalKeys, now);
}
return internalKeys;
}

/**
Expand Down

0 comments on commit ce3d8e9

Please sign in to comment.