Commit 733f5d47 authored by Jujube Orange's avatar Jujube Orange
Browse files

WIP

parent e8ab09ef
Pipeline #283805 failed with stages
in 7 minutes and 44 seconds
......@@ -2,13 +2,17 @@ package fr.gouv.clea.integrationtests.cucumber;
import fr.gouv.clea.integrationtests.config.ApplicationProperties;
import fr.gouv.clea.integrationtests.service.ClusterExpositionService;
import fr.gouv.clea.integrationtests.utils.DeepLinkGenerator;
import fr.gouv.clea.integrationtests.service.visitorsimulator.Visitor;
import fr.gouv.clea.integrationtests.utils.LocationBuilder;
import fr.gouv.clea.qr.LocationQrCodeGenerator;
import fr.inria.clea.lsp.Location;
import fr.inria.clea.lsp.exception.CleaCryptoException;
import io.cucumber.spring.ScenarioScope;
import org.bouncycastle.util.encoders.Hex;
import org.springframework.stereotype.Component;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
......@@ -22,9 +26,7 @@ public class ScenarioContext {
private final Map<String, Visitor> visitors = new HashMap<>();
private final Map<String, LocationQrCodeGenerator> locations = new HashMap<>();
private final Map<String, LocationQrCodeGenerator> staffLocations = new HashMap<>();
private final Map<String, DeepLinkGenerator> locations = new HashMap<>();
private final ApplicationProperties applicationProperties;
......@@ -48,115 +50,11 @@ public class ScenarioContext {
return visitors.get(visitorName);
}
private LocationQrCodeGenerator createDynamicLocation(final String locationName, final Instant periodStartTime,
final Integer venueType, final Integer venueCategory1, final Integer venueCategory2,
final Duration qrCodeRenewalInterval, final Integer periodDuration) throws CleaCryptoException {
final var qrCodeRenewalIntervalLong = qrCodeRenewalInterval.getSeconds();
final var qrCodeRenewalIntervalExponentCompact = (int) (Math.log(qrCodeRenewalIntervalLong) / Math.log(2));
return this.createLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
qrCodeRenewalIntervalExponentCompact, periodDuration
);
}
private LocationQrCodeGenerator createStaticLocation(final String locationName, final Instant periodStartTime,
final Integer venueType, final Integer venueCategory1, final Integer venueCategory2,
final Integer periodDuration) throws CleaCryptoException {
final var qrCodeRenewalIntervalExponentCompact = 0x1F;
return this.createLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
qrCodeRenewalIntervalExponentCompact, periodDuration
);
}
private LocationQrCodeGenerator createLocation(final String locationName, final Instant periodStartTime,
final Integer venueType, final Integer venueCategory1, final Integer venueCategory2,
final Integer qrCodeRenewalIntervalExponentCompact, final Integer periodDuration)
throws CleaCryptoException {
final var permanentLocationSecretKey = Hex.toHexString(UUID.randomUUID().toString().getBytes());
final var location = LocationQrCodeGenerator.builder()
.countryCode(250) // France Country Code
.staff(false)
.venueType(venueType)
.venueCategory1(venueCategory1)
.venueCategory2(venueCategory2)
.periodDuration(periodDuration)
.periodStartTime(periodStartTime)
.qrCodeRenewalIntervalExponentCompact(qrCodeRenewalIntervalExponentCompact)
.manualContactTracingAuthorityPublicKey(
applicationProperties.getManualContactTracingAuthorityPublicKey()
)
.serverAuthorityPublicKey(applicationProperties.getServerAuthorityPublicKey())
.permanentLocationSecretKey(permanentLocationSecretKey)
.build();
final var staffLocation = LocationQrCodeGenerator.builder()
.countryCode(250) // France Country Code
.staff(true)
.venueType(venueType)
.venueCategory1(venueCategory1)
.venueCategory2(venueCategory2)
.periodDuration(periodDuration)
.periodStartTime(periodStartTime)
.qrCodeRenewalIntervalExponentCompact(qrCodeRenewalIntervalExponentCompact)
.manualContactTracingAuthorityPublicKey(
applicationProperties.getManualContactTracingAuthorityPublicKey()
)
.serverAuthorityPublicKey(applicationProperties.getServerAuthorityPublicKey())
.permanentLocationSecretKey(permanentLocationSecretKey)
.build();
staffLocations.put(locationName, staffLocation);
locations.put(locationName, location);
return location;
}
private Predicate<VenueConfiguration> matchingConfigurationExists(Integer venueType, Integer venueCategory1,
Integer venueCategory2) {
return config -> config.getVenueType() == venueType &&
config.getVenueCategory1() == venueCategory1 &&
config.getVenueCategory2() == venueCategory2;
}
public LocationQrCodeGenerator getOrCreateDynamicLocation(final String locationName, final Instant periodStartTime,
final Integer venueType, final Integer venueCategory1, final Integer venueCategory2,
final Duration qrCodeRenewalInterval) throws CleaCryptoException {
return this.getOrCreateDynamicLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
qrCodeRenewalInterval, 24
);
}
public LocationQrCodeGenerator getOrCreateDynamicLocation(final String locationName, final Instant periodStartTime,
final Integer venueType, final Integer venueCategory1, final Integer venueCategory2,
final Duration qrCodeRenewalInterval, final Integer periodDuration) throws CleaCryptoException {
return locations.containsKey(locationName) ? locations.get(locationName)
: this.createDynamicLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
qrCodeRenewalInterval, periodDuration
);
}
public LocationQrCodeGenerator getOrCreateStaticLocation(final String locationName, final Instant periodStartTime,
final Integer venueType, final Integer venueCategory1, final Integer venueCategory2,
final Integer periodDuration) throws CleaCryptoException {
return locations.containsKey(locationName) ? locations.get(locationName)
: this.createStaticLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2, periodDuration
);
}
public LocationQrCodeGenerator getOrCreateStaticLocationWithUnlimitedDuration(final String locationName,
final Instant periodStartTime, final Integer venueType, final Integer venueCategory1,
final Integer venueCategory2) throws CleaCryptoException {
return this.getOrCreateStaticLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2, 24
);
}
public LocationQrCodeGenerator getLocation(final String locationName) {
return locations.get(locationName);
public void registerLocation(String locationName, Location location) {
locations.put(locationName, new DeepLinkGenerator(location));
}
public LocationQrCodeGenerator getStaffLocation(final String locationName) {
return staffLocations.get(locationName);
public DeepLinkGenerator getLocation(String locationName) {
return locations.get(locationName);
}
}
package fr.gouv.clea.integrationtests.cucumber.steps;
import fr.gouv.clea.integrationtests.config.ApplicationProperties;
import fr.gouv.clea.integrationtests.cucumber.ScenarioContext;
import fr.inria.clea.lsp.exception.CleaCryptoException;
import fr.gouv.clea.integrationtests.utils.LocationBuilder;
import io.cucumber.java.en.Given;
import lombok.AllArgsConstructor;
import java.time.Duration;
import java.time.Instant;
@AllArgsConstructor
public class CleaQrCodeSteps {
private final ScenarioContext scenarioContext;
// Dynamic Location
private final LocationBuilder defaultLocationBuilder;
public CleaQrCodeSteps(ApplicationProperties applicationProperties, ScenarioContext scenarioContext) {
this.scenarioContext = scenarioContext;
defaultLocationBuilder = new LocationBuilder(
applicationProperties.getServerAuthorityPublicKey(),
applicationProperties.getManualContactTracingAuthorityPublicKey()
);
}
@Given("{string} created a dynamic QRCode at {naturalTime} with VType as {int}, with VCategory1 as {int}, with VCategory2 as {int}, with a renewal time of {duration} and with a periodDuration of {int} hours")
public void dynamic_location_with_a_periodDuration_and_renewalTime(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2, Duration qrCodeRenewalIntervalDuration,
Integer periodDuration) throws CleaCryptoException {
this.scenarioContext.getOrCreateDynamicLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
qrCodeRenewalIntervalDuration,
periodDuration
);
// TODO: add QR id
Integer periodDurationInHours) {
final var locationBuilder = defaultLocationBuilder
.withRandomPermanentLocationSecretKey()
.withPeriodStartTime(periodStartTime)
.withRenewalIntervalInSeconds(qrCodeRenewalIntervalDuration.getSeconds())
.withVenueParameters(venueType, venueCategory1, venueCategory2)
.withPeriodDurationInHours(periodDurationInHours);
scenarioContext.registerLocation(locationName, locationBuilder.buildPublic());
scenarioContext.registerLocation(locationName + " [staff]", locationBuilder.buildStaff());
}
@Given("{string} created a dynamic QRCode at {naturalTime} with VType as {int} and with VCategory1 as {int} and with VCategory2 as {int} and with and with a renewal time of {duration}")
public void dynamic_location_without_periodDuration_with_renewalTime(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2, Duration qrCodeRenewalIntervalDuration)
throws CleaCryptoException {
this.scenarioContext.getOrCreateDynamicLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
qrCodeRenewalIntervalDuration
);
// TODO: add QR id
int venueType, int venueCategory1, int venueCategory2, Duration qrCodeRenewalIntervalDuration) {
final var locationBuilder = defaultLocationBuilder
.withRandomPermanentLocationSecretKey()
.withPeriodStartTime(periodStartTime)
.withRenewalIntervalInSeconds(qrCodeRenewalIntervalDuration.getSeconds())
.withVenueParameters(venueType, venueCategory1, venueCategory2);
scenarioContext.registerLocation(locationName, locationBuilder.buildPublic());
scenarioContext.registerLocation(locationName + " [staff]", locationBuilder.buildStaff());
}
@Given("{string} created a static QRCode at {naturalTime} with VType as {int}, with VCategory1 as {int}, with VCategory2 as {int} and with a periodDuration of {int} hours")
public void static_location_without_renewalTime_with_periodDuration(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2, Integer periodDuration)
throws CleaCryptoException {
this.scenarioContext.getOrCreateStaticLocation(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2,
periodDuration
);
// TODO: add QR id
int venueType, int venueCategory1, int venueCategory2, int periodDurationInHours) {
final var locationBuilder = defaultLocationBuilder
.withRandomPermanentLocationSecretKey()
.withPeriodStartTime(periodStartTime)
.withVenueParameters(venueType, venueCategory1, venueCategory2)
.withPeriodDurationInHours(periodDurationInHours);
scenarioContext.registerLocation(locationName, locationBuilder.buildPublic());
scenarioContext.registerLocation(locationName + " [staff]", locationBuilder.buildStaff());
}
@Given("{string} created a static QRCode at {naturalTime} with VType as {string} and VCategory1 as {int} and with VCategory2 as {int}")
public void static_location_with_default_periodDuration(String locationName, Instant periodStartTime,
Integer venueType, Integer venueCategory1, Integer venueCategory2) throws CleaCryptoException {
this.scenarioContext.getOrCreateStaticLocationWithUnlimitedDuration(
locationName, periodStartTime, venueType, venueCategory1, venueCategory2
);
// TODO: add QR id
int venueType, int venueCategory1, int venueCategory2) {
final var locationBuilder = defaultLocationBuilder
.withRandomPermanentLocationSecretKey()
.withPeriodStartTime(periodStartTime)
.withVenueParameters(venueType, venueCategory1, venueCategory2);
scenarioContext.registerLocation(locationName, locationBuilder.buildPublic());
scenarioContext.registerLocation(locationName + " [staff]", locationBuilder.buildStaff());
}
}
......@@ -21,29 +21,31 @@ public class CleaVisitorUserSteps {
// Visitor scan a QR code at given instant
@Given("{word} recorded a visit to {string} at {naturalTime}")
public void visitor_scans_qrcode_at_given_instant(String visitorName, String locationName, Instant qrCodeScanTime)
throws CleaCryptoException {
final var location = this.scenarioContext.getLocation(locationName);
final var deepLink = location.getQrCodeAt(qrCodeScanTime);
this.scenarioContext.getOrCreateUser(visitorName).registerDeepLink(deepLink.getQrCode(), qrCodeScanTime);
public void visitor_scans_qrcode_at_given_instant(String visitorName, String locationName, Instant qrCodeScanTime) {
final var deepLink = scenarioContext.getLocation(locationName)
.getDeepLink(qrCodeScanTime);
scenarioContext.getVisitor(visitorName)
.registerDeepLink(deepLink, qrCodeScanTime);
}
// Visitor scan a staff QR code at given instant
@Given("{word} recorded a visit as a STAFF to {string} at {naturalTime}")
public void visitor_scans_staff_qrcode_at_given_instant(String visitorName, String locationName,
Instant qrCodeScanTime) throws CleaCryptoException {
final var location = this.scenarioContext.getStaffLocation(locationName);
final var qr = location.getQrCodeAt(qrCodeScanTime);
this.scenarioContext.getOrCreateUser(visitorName).registerDeepLink(qr.getQrCode(), qrCodeScanTime);
Instant qrCodeScanTime) {
final var deepLink = this.scenarioContext.getLocation(locationName + " [staff]")
.getDeepLink(qrCodeScanTime);
this.scenarioContext.getOrCreateUser(visitorName)
.registerDeepLink(deepLink, qrCodeScanTime);
}
// Visitor scan a QR code at a given Instant, but the scanned QR code is valid
// for another Instant
@Given("{word} recorded a visit to {string} at {naturalTime} with a QR code valid for {string}")
public void visitor_scans_qrcode_at_given_instant_but_qr_code_valid_for_another_instant(String visitorName,
String locationName, Instant qrCodeScanTime, Instant qrCodeValidTime) throws CleaCryptoException {
final var location = this.scenarioContext.getLocation(locationName);
final var qr = location.getQrCodeAt(qrCodeValidTime);
this.scenarioContext.getOrCreateUser(visitorName).registerDeepLink(qr.getQrCode(), qrCodeScanTime);
String locationName, Instant qrCodeScanTime, Instant qrCodeValidTime) {
final var deepLink = this.scenarioContext.getLocation(locationName)
.getDeepLink(qrCodeValidTime);
this.scenarioContext.getOrCreateUser(visitorName)
.registerDeepLink(deepLink, qrCodeScanTime);
}
}
......@@ -7,14 +7,15 @@ import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import java.net.URL;
import java.time.Instant;
import java.util.*;
import static fr.inria.clea.lsp.Location.COUNTRY_SPECIFIC_PREFIX;
@RequiredArgsConstructor
public class Visitor {
private static final String DEEPLINK_COUNTRY_PART = "https://tac.gouv.fr?v=0#";
@Getter
private final String name;
......@@ -46,13 +47,13 @@ public class Visitor {
return Optional.ofNullable(lastReportResponse);
}
public void registerDeepLink(final String deepLink, final Instant scanTime) {
public void registerDeepLink(final URL deepLink, final Instant scanTime) {
// check if prefix is present then removes it
if (!deepLink.startsWith(DEEPLINK_COUNTRY_PART)) {
if (!deepLink.toString().startsWith(COUNTRY_SPECIFIC_PREFIX)) {
throw new RuntimeException("Scanned deeplink has wrong prefix");
}
final var encodedInformation = deepLink.substring(DEEPLINK_COUNTRY_PART.length());
final var encodedInformation = deepLink.getRef();
localVisitsList.add(
Visit.builder()
......
package fr.gouv.clea.integrationtests.utils;
import fr.inria.clea.lsp.Location;
import lombok.*;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import static lombok.AccessLevel.PRIVATE;
@RequiredArgsConstructor
public class DeepLinkGenerator {
private final Map<Period, URL> generatedDeepLinks = new HashMap<>();
private final Location location;
public URL getDeepLink(Instant instant) {
final var periodDuration = Duration.ofHours(location.getLocationSpecificPart().getPeriodDuration());
final var renewalInterval = Duration.ofSeconds(location.getLocationSpecificPart().getQrCodeRenewalInterval());
final var period = Period.ofInstant(instant, renewalInterval);
return generatedDeepLinks.computeIfAbsent(period, this::generatedDeepLink);
}
@SneakyThrows
private URL generatedDeepLink(Period period) {
final var locationStartTime = location.getContact().getPeriodStartTime();
final var deepLink = location.newDeepLink(period.startTime, locationStartTime);
return new URL(deepLink);
}
@Value
@AllArgsConstructor(access = PRIVATE)
private static class Period {
Instant startTime;
Duration duration;
public static Period ofInstant(Instant instant, Duration renewalInterval) {
// Assumption : All Periods are contiguous, making auto period-creation easier.
// This is not the real-world case. Could emulate how real-world will be done
// using a period duration such as periodDuration%24 == 0.
final var secondsToRemove = instant.getEpochSecond() % renewalInterval.getSeconds();
final var periodStart = instant.minusSeconds(secondsToRemove);
return new Period(periodStart, renewalInterval);
}
public boolean contains(Instant instant) {
return !(instant.isBefore(startTime) || instant.isAfter(startTime.plus(duration)));
}
}
}
package fr.gouv.clea.integrationtests.utils;
import fr.inria.clea.lsp.Location;
import fr.inria.clea.lsp.LocationContact;
import fr.inria.clea.lsp.LocationSpecificPart;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.With;
import org.bouncycastle.util.encoders.Hex;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Random;
import static fr.inria.clea.lsp.LocationSpecificPart.LOCATION_TEMPORARY_SECRET_KEY_SIZE;
import static java.time.temporal.ChronoUnit.DAYS;
import static java.time.temporal.ChronoUnit.YEARS;
import static java.util.stream.Collectors.joining;
import static lombok.AccessLevel.PRIVATE;
@RequiredArgsConstructor
@AllArgsConstructor(access = PRIVATE)
public class LocationBuilder {
private final Random random = new Random();
private final String serverAuthorityPublicKey;
private final String manualContactTracingAuthorityPublicKey;
private final byte[] permanentLocationSecretKey = new byte[LOCATION_TEMPORARY_SECRET_KEY_SIZE];
@With
private Instant periodStartTime = Instant.now().minus(365, DAYS);
@With
private double renewalIntervalInSeconds = Math.pow(2, 10);
@With
private int periodDurationInHours = 1;
private Integer venueType = 1;
private Integer venueCategory1 = 1;
private Integer venueCategory2 = 1;
public LocationBuilder withRandomPermanentLocationSecretKey() {
random.nextBytes(permanentLocationSecretKey);
return this;
}
public LocationBuilder withVenueParameters(int venueType, int venueCategory1, int venueCategory2) {
this.venueType = venueType;
this.venueCategory1 = venueCategory1;
this.venueCategory2 = venueCategory2;
return this;
}
public Location buildPublic() {
return build(false);
}
public Location buildStaff() {
return build(true);
}
private Location build(boolean staff) {
return Location.builder()
.serverAuthorityPublicKey(serverAuthorityPublicKey)
.manualContactTracingAuthorityPublicKey(manualContactTracingAuthorityPublicKey)
.permanentLocationSecretKey(Hex.toHexString(permanentLocationSecretKey))
.locationSpecificPart(
unlimitedRandomLocationSpecificPart()
.staff(staff)
.build()
)
.contact(
randomContact()
.periodStartTime(periodStartTime)
.build()
)
.build();
}
private LocationSpecificPart.LocationSpecificPartBuilder<?, ?> unlimitedRandomLocationSpecificPart() {
final var renewalIntervalExponentCompact = (int) (Math.log(renewalIntervalInSeconds) / Math.log(2));
return LocationSpecificPart.builder()
.periodDuration(periodDurationInHours)
.qrCodeRenewalIntervalExponentCompact(renewalIntervalExponentCompact)
.venueType(venueType)
.venueCategory1(venueCategory1)
.venueCategory2(venueCategory2);
}
private LocationContact.LocationContactBuilder randomContact() {
final var randomPhoneNumber = random.ints(10, 0, 9)
.mapToObj(String::valueOf)
.collect(joining());
final var randomPinNumber = random.ints(6, 0, 9)
.mapToObj(String::valueOf)
.collect(joining());
return LocationContact.builder()
.locationPhone(randomPhoneNumber)
.locationRegion(0)
.locationPin(randomPinNumber)
.periodStartTime(Instant.now());
}
}
......@@ -7,21 +7,16 @@ import fr.inria.clea.lsp.LocationContact;
import fr.inria.clea.lsp.LocationSpecificPart;
import fr.inria.clea.lsp.exception.CleaCryptoException;
import fr.inria.clea.lsp.exception.CleaEncryptionException;
import fr.inria.clea.lsp.utils.TimeUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import static java.time.temporal.ChronoUnit.HOURS;
@Slf4j
public class LocationQrCodeGenerator {
......@@ -32,34 +27,34 @@ public class LocationQrCodeGenerator {
private final Location location;
private Instant initialPeriodStartTime;
private final Instant initialPeriodStartTime;
private Map<Period, List<QRCode>> generatedQRs;
private final Map<Period, List<QRCode>> generatedDeepLinks = new HashMap<>();
@Builder
private LocationQrCodeGenerator(Instant periodStartTime, int periodDuration,
int qrCodeRenewalIntervalExponentCompact, boolean staff, int countryCode, int venueType, int venueCategory1,
int qrCodeRenewalIntervalExponentCompact, boolean staff, int venueType, int venueCategory1,
int venueCategory2, String manualContactTracingAuthorityPublicKey, String serverAuthorityPublicKey,
String permanentLocationSecretKey, String locationPhone, String locationPin)
throws CleaCryptoException {
String permanentLocationSecretKey, String locationPhone, String locationPin) {
// TODO: check data validity (eg. < periodDuration <= 255)
if (qrCodeRenewalIntervalExponentCompact == 0x1F)
if (qrCodeRenewalIntervalExponentCompact == 0x1F) {
this.qrCodeRenewalInterval = 0;
else