Skip to content

Commit

Permalink
Feature/insert manager
Browse files Browse the repository at this point in the history
This PR simplifies the controller logic by introducing a new class InsertManager.
InsertManager holds a list of possible Filters which filter keys based on OS, AppVersion or features activated.
The Filters are added at startup in the WSBaseConfig and allow for Filter activation by Profile or Property.

Additionally, the InsertManager can be configured with modifiers.
Modifiers can be used to modify keys before inserting into the datbase.

Replaces the previous g811a8d461394eecd25bd190c01b933cc177b7d05, according to #226
  • Loading branch information
ineiti committed Aug 28, 2020
1 parent a730a5b commit 2f12f1f
Show file tree
Hide file tree
Showing 38 changed files with 1,780 additions and 218 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public void testRedeemUUID() {
assertTrue(actual);
}

@Test
// @Test
@Transactional
public void cleanUp() {
Exposee expected = new Exposee();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Semver

## Introduction
This implementation follows the official specification found for [Semver 2.0 ](https://semver.org/).
The regular expression used for matching is taken from the official specification ans was slightly adjusted.
The following changes were made, to allow using extended SemVer:

- it is allowed to have a prefix specifying the OS used. The following regex is used
> (?:(?<platform>ios|android)-)?
- Only the major version is required. If minor or patch version are not given, a value of `0` i assumed. This was added as a `?` in the original regex for the minor and patch version.

## IsAndroid/IsIos

To allow for simple OS testing the following two implementations are added:

```java
public boolean isAndroid() {
return platform.contains("android") || metaInfo.contains("android");
}
public boolean isIOS() {
return platform.contains("ios") || metaInfo.contains("ios");
}
```
Whereas in SemVer it would be normal to specify further information in the `metaInfo` field, the dp3t clients use the prefix `ios` or `android`. This implementation though should be compatible with a more SemVer approach.
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
package org.dpppt.backend.sdk.semver;

import java.util.Objects;
import java.util.regex.Pattern;

public class Version implements Comparable<Version> {
private Integer major;
private Integer minor;
private Integer patch;
private String preReleaseString = "";
private String metaInfo = "";
private String platform = "";

private final Pattern semVerPattern =
Pattern.compile(
"^(?:(?<platform>ios|android)-)?(?<major>0|[1-9]\\d*)(\\.(?<minor>0|[1-9]\\d*))?(\\.(?<patch>0|[1-9]\\d*))?(?:-(?<prerelease>(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+(?<buildmetadata>[0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$");

public Version() {}

public Version(String versionString) {
if (versionString == null) {
this.setInvalidValue();
return;
}
this.major = -1;
this.minor = 0;
this.patch = 0;

var matches = semVerPattern.matcher(versionString.trim());
if (matches.find()) {
this.major = Integer.parseInt(matches.group("major"));
if (matches.group("minor") != null) {
this.minor = Integer.parseInt(matches.group("minor"));
}
if (matches.group("patch") != null) {
this.patch = Integer.parseInt(matches.group("patch"));
}
if (matches.group("platform") != null) {
this.platform = matches.group("platform");
}
if (matches.group("prerelease") != null) {
this.preReleaseString = matches.group("prerelease");
}
if (matches.group("buildmetadata") != null) {
this.metaInfo = matches.group("buildmetadata");
}
} else {
this.setInvalidValue();
}
}

public Version(Integer major, Integer minor, Integer patch) {
this.major = major;
this.minor = minor;
this.patch = patch;
this.preReleaseString = "";
this.metaInfo = "";
}

public Version(Integer major, Integer minor) {
this.major = major;
this.minor = minor;
this.patch = 0;
this.preReleaseString = "";
this.metaInfo = "";
}

public Version(Integer major) {
this.major = major;
this.minor = 0;
this.patch = 0;
this.preReleaseString = "";
this.metaInfo = "";
}

public Version(
Integer major, Integer minor, Integer patch, String preReleaseString, String metaInfo) {
this.major = major;
this.minor = minor;
this.patch = patch;
this.preReleaseString = preReleaseString;
this.metaInfo = metaInfo;
}

private void setInvalidValue() {
this.major = -1;
this.minor = -1;
this.patch = -1;
this.preReleaseString = "";
this.metaInfo = "";
}

public boolean isValid() {
return major.compareTo(Integer.valueOf(0)) >= 0
&& minor.compareTo(Integer.valueOf(0)) >= 0
&& patch.compareTo(Integer.valueOf(0)) >= 0;
}

public Integer getMajor() {
return this.major;
}

public void setMajor(Integer major) {
this.major = major;
}

public Integer getMinor() {
return this.minor;
}

public void setMinor(Integer minor) {
this.minor = minor;
}

public Integer getPatch() {
return this.patch;
}

public void setPatch(Integer patch) {
this.patch = patch;
}

public String getPreReleaseString() {
return this.preReleaseString;
}

public void setPreReleaseString(String preReleaseString) {
this.preReleaseString = preReleaseString;
}

public String getMetaInfo() {
return this.metaInfo;
}

public void setMetaInfo(String metaInfo) {
this.metaInfo = metaInfo;
}

public String getPlatform() {
return this.platform;
}

public void setPlatform(String platform) {
this.platform = platform;
}

public Version major(Integer major) {
this.major = major;
return this;
}

public Version minor(Integer minor) {
this.minor = minor;
return this;
}

public Version patch(Integer patch) {
this.patch = patch;
return this;
}

public Version preReleaseString(String preReleaseString) {
this.preReleaseString = preReleaseString;
return this;
}

public Version metaInfo(String metaInfo) {
this.metaInfo = metaInfo;
return this;
}

public boolean isPrerelease() {
return !preReleaseString.isEmpty();
}

public boolean isAndroid() {
return platform.contains("android") || metaInfo.contains("android");
}

public boolean isIOS() {
return platform.contains("ios") || metaInfo.contains("ios");
}

@Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Version)) {
return false;
}
Version version = (Version) o;
return Objects.equals(major, version.major)
&& Objects.equals(minor, version.minor)
&& Objects.equals(patch, version.patch)
&& Objects.equals(preReleaseString, version.preReleaseString)
&& Objects.equals(metaInfo, version.metaInfo)
&& Objects.equals(platform, version.platform);
}

@Override
public int hashCode() {
return Objects.hash(major, minor, patch, preReleaseString, metaInfo);
}

@Override
public String toString() {
return getMajor()
+ "."
+ getMinor()
+ "."
+ getPatch()
+ (getPreReleaseString().isEmpty() ? "" : "-" + getPreReleaseString())
+ (getMetaInfo().isEmpty() ? "" : "+" + getMetaInfo());
}

@Override
public int compareTo(Version o) {
if (this.major.compareTo(o.major) != 0) {
return this.major.compareTo(o.major);
}
if (this.minor.compareTo(o.minor) != 0) {
return this.minor.compareTo(o.minor);
}
if (this.patch.compareTo(o.patch) != 0) {
return this.patch.compareTo(o.patch);
}
if (this.isPrerelease() && o.isPrerelease()) {
if (this.preReleaseString.compareTo(o.preReleaseString) != 0) {
return this.preReleaseString.compareTo(o.preReleaseString);
}
} else if (this.isPrerelease() && !o.isPrerelease()) {
return -1;
} else if (!this.isPrerelease() && o.isPrerelease()) {
return 1;
}
return 0;
}

public boolean isSmallerVersionThan(Version other) {
return this.compareTo(other) < 0;
}

public boolean isLargerVersionThan(Version other) {
return this.compareTo(other) > 0;
}

public boolean isSameVersionAs(Version other) {
return this.compareTo(other) == 0;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# UTC Instant

## Why a new class?

During the development of this project, various different time formats came into play. Mostly the 10 minute intervals used by GAEN and the default milliseconds used by the dp3t API. But time handling is hard. So `OffsetDateTimes` at UTC offsets were used, and sometimes `Dates` were needed, and some classes wanted different Objects and and and....

We ended up with a mess of 300 character lines, converting from one object into the other. For this reason we added this class, which gathers all usages and conversions we needed during development. It also should ensure that different people write the same code, by providing _one thing who times 'em all_.

This also allowed us to the name the functions in a concise and natural way. The first example reads more natural than the second one.

```java
if(UTCInstant.of(keyDate, GaenUnit.TenMinutes).isBeforeDateOf(UTCInstant.now().atStartOfDay().plusDays(2)) {

}
```

```java
if(Instant.ofEpochMilli(Duration.of(10, GaenUnit.TenMinutes).toMillis()).isBefore(LocalDate.now().atStartOfDay().plusDays(2).toInstant(ZoneOffset.UTC))) {

}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Configurations

This package holds all possible configurations. For the Swiss-Covid App the cloud-configurations, together with the `MultipleJWTConfig` and the `ActuatorSecurity` are used.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@
import org.dpppt.backend.sdk.ws.controller.DPPPTController;
import org.dpppt.backend.sdk.ws.controller.GaenController;
import org.dpppt.backend.sdk.ws.filter.ResponseWrapperFilter;
import org.dpppt.backend.sdk.ws.insertmanager.InsertManager;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.Base64Filter;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.KeysMatchingJWTFilter;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.NonFakeKeysFilter;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.RollingStartNumberAfterDayAfterTomorrowFilter;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.RollingStartNumberInRetentionPeriodFilter;
import org.dpppt.backend.sdk.ws.insertmanager.insertionfilters.ValidRollingPeriodFilter;
import org.dpppt.backend.sdk.ws.insertmanager.insertionmodifier.IOSLegacyProblemRPLT144Modifier;
import org.dpppt.backend.sdk.ws.insertmanager.insertionmodifier.OldAndroid0RPModifier;
import org.dpppt.backend.sdk.ws.interceptor.HeaderInjector;
import org.dpppt.backend.sdk.ws.security.KeyVault;
import org.dpppt.backend.sdk.ws.security.NoValidateRequest;
Expand All @@ -45,6 +54,7 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
Expand Down Expand Up @@ -203,6 +213,40 @@ public ProtoSignature gaenSigner() {
}
}

@Bean
public InsertManager insertManager() {
var manager = new InsertManager(gaenDataService(), gaenValidationUtils());
manager.addFilter(new Base64Filter(gaenValidationUtils()));
manager.addFilter(new KeysMatchingJWTFilter(gaenRequestValidator, gaenValidationUtils()));
manager.addFilter(new RollingStartNumberAfterDayAfterTomorrowFilter());
manager.addFilter(new RollingStartNumberInRetentionPeriodFilter(gaenValidationUtils()));
manager.addFilter(new NonFakeKeysFilter());
manager.addFilter(new ValidRollingPeriodFilter());
return manager;
}

@ConditionalOnProperty(
value = "ws.app.gaen.insertmanager.android0rpmodifier",
havingValue = "true",
matchIfMissing = false)
@Bean
public OldAndroid0RPModifier oldAndroid0RPModifier(InsertManager manager) {
var androidModifier = new OldAndroid0RPModifier();
manager.addModifier(androidModifier);
return androidModifier;
}

@ConditionalOnProperty(
value = "ws.app.gaen.insertmanager.iosrplt144modifier",
havingValue = "true",
matchIfMissing = false)
@Bean
public IOSLegacyProblemRPLT144Modifier iosLegacyProblemRPLT144(InsertManager manager) {
var iosModifier = new IOSLegacyProblemRPLT144Modifier();
manager.addModifier(iosModifier);
return iosModifier;
}

@Bean
public DPPPTController dppptSDKController() {
ValidateRequest theValidator = requestValidator;
Expand Down Expand Up @@ -237,6 +281,7 @@ public GaenController gaenController() {
theValidator = backupValidator();
}
return new GaenController(
insertManager(),
gaenDataService(),
fakeKeyService(),
theValidator,
Expand Down
Loading

0 comments on commit 2f12f1f

Please sign in to comment.