Skip to content
This repository has been archived by the owner on Sep 15, 2023. It is now read-only.

Commit

Permalink
rule sync refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
gstoehld committed Mar 1, 2022
1 parent f065e04 commit f784425
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package ch.admin.bag.covidcertificate.backend.verifier.sync.syncer;

import ch.admin.bag.covidcertificate.backend.verifier.model.sync.SigningPayload;
import ch.admin.bag.covidcertificate.backend.verifier.sync.syncer.SigningClient.SigningException;
import ch.admin.bag.covidcertificate.backend.verifier.sync.syncer.model.RulesSyncResult;
import ch.admin.bag.covidcertificate.backend.verifier.sync.utils.CmsUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
Expand All @@ -25,7 +26,6 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
Expand All @@ -52,17 +52,25 @@ public DgcRulesClient(String dgcBaseUrl, RestTemplate dgcRT, SigningClient signi
/**
* downloads rules for Switzerland
*
* @return rules for Switezrland
* @return rules for Switzerland
*/
public Map<String, JsonNode> download() {
logger.info("[DgcRulesClient] Downloading rules");
Map<String, JsonNode> rules = new HashMap<>();
ResponseEntity<String> response = this.dgcRT.exchange(RequestEntity.get(dgcBaseUrl+String.format(DOWNLOAD_PATH, "CH")).headers(CmsUtil.createCmsTextUploadHeaders()).build(),
String.class);
ResponseEntity<String> response =
this.dgcRT.exchange(
RequestEntity.get(dgcBaseUrl + String.format(DOWNLOAD_PATH, "CH"))
.headers(CmsUtil.createRuleExchangeHeaders())
.build(),
String.class);
try {
new ObjectMapper().readTree(response.getBody()).fields().forEachRemaining( field -> {
rules.put(field.getKey(), field.getValue());
});
new ObjectMapper()
.readTree(response.getBody())
.fields()
.forEachRemaining(
field -> {
rules.put(field.getKey(), field.getValue());
});
} catch (JsonProcessingException e) {
logger.error("[DgcRulesClient] Failed to deserialize downloaded rules", e);
}
Expand All @@ -73,60 +81,73 @@ public Map<String, JsonNode> download() {

/**
* deletes the rules with the given IDs
*
* @param identifiers IDs of the rules to delete
* @return successfully deleted rule IDs
*/
public RulesSyncResult deleteRules(Collection<String> identifiers){
public RulesSyncResult delete(Collection<String> identifiers) {
logger.info("Deleting {} rules", identifiers);
List<String> deletedRuleIds = new ArrayList<>();
List<String> failedRuleIds = new ArrayList<>();
identifiers.forEach(ruleId -> {
try {
// sign payload
identifiers.forEach(
ruleId -> {
String cms = null;
try {
// sign payload

logger.info("signing rule ID {}", ruleId);
var base64encoded = Base64.getEncoder().encodeToString(ruleId.getBytes(
StandardCharsets.UTF_8));
var payloadObject = new SigningPayload(base64encoded);
cms = signingClient.sign(payloadObject);
} catch (Exception e) {
logger.error("Signing rule ID {} failed", ruleId, e);
return;
}
// upload to gateway
logger.info("Deleting rule {}", ruleId);
try {
SigningPayload payload =
CmsUtil.encodePayload(ruleId.getBytes(StandardCharsets.UTF_8));
cms = signingClient.sign(payload);

// upload to gateway
logger.info("Deleting rule {}", ruleId);
this.dgcRT.exchange(
RequestEntity.post(dgcBaseUrl + RULE_DELETE_PATH)
.headers(CmsUtil.createCmsTextUploadHeaders())
.headers(CmsUtil.createRuleExchangeHeaders())
.body(cms),
String.class);
logger.info("All versions of rule {} deleted", ruleId);
deletedRuleIds.add(ruleId);
} catch (SigningException e) {
logger.error("Failed to sign rule ID for {}", ruleId);
} catch (HttpStatusCodeException e) {
failedRuleIds.add(ruleId);
logger.error("[FAILED CMS] {}", cms);
logger.error("Deletion of rule {} failed", ruleId, e);
} catch (Exception ex) {
failedRuleIds.add(ruleId);
logger.error("Failed to delete rule {}", ruleId, ex);
}
} catch (Exception ex) {
failedRuleIds.add(ruleId);
logger.error("Failed to delete rule {}", ruleId, ex);
}

});
logger.info("Finished deleting rules. {} succeeded, {} failed", deletedRuleIds.size(), failedRuleIds.size());
});
logger.info(
"Finished deleting rules. {} succeeded, {} failed",
deletedRuleIds.size(),
failedRuleIds.size());
return new RulesSyncResult(deletedRuleIds, failedRuleIds);
}

/**
* deletes all rules for Switzerland
*
* @return successfully deleted rule IDs
*/
public RulesSyncResult deleteAll() {
logger.info("Deleting all Swiss rules");
Map<String, JsonNode> rules = download();
Set<String> ruleIds = rules.keySet();
return delete(ruleIds);
}

/**
* uploads Swiss rules
*
* @return successfully uploaded rule ids
*/
public RulesSyncResult upload(JsonNode rules) {
Set<String> existingRules = download().keySet();
// Download all remote rules and later remove the ones that should not be deleted
Set<String> rulesToDelete = download().keySet();

logger.info("Uploading Swiss rules");
List<String> uploadedRuleIds = new ArrayList<>();
List<String> failedRuleIds = new ArrayList<>();
Expand All @@ -137,40 +158,32 @@ public RulesSyncResult upload(JsonNode rules) {
Entry<String, JsonNode> ruleArray = fieldIterator.next();
for (var rule : ruleArray.getValue()) {
String ruleId = ruleArray.getKey();
existingRules.remove(ruleId);
String cms = null;
try {
rulesToDelete.remove(ruleId);
// sign payload
String cms = null;
try {
logger.info("signing rule {}", ruleId);
var serializeObject = mapper.writeValueAsBytes(rule);
var base64encoded = Base64.getEncoder().encodeToString(serializeObject);
var payloadObject = new SigningPayload(base64encoded);
cms = signingClient.sign(payloadObject);
} catch (Exception e) {
logger.error("Signing rule {} failed", ruleId, e);
continue;
}
logger.info("signing rule {}", ruleId);
var payloadObject = CmsUtil.encodePayload(mapper.writeValueAsBytes(rule));
cms = signingClient.sign(payloadObject);

// upload to gateway
logger.info("Uploading rule {} to {}", ruleId, dgcBaseUrl + RULE_UPLOAD_PATH);
try {
this.dgcRT.exchange(
RequestEntity.post(dgcBaseUrl + RULE_UPLOAD_PATH)
.headers(CmsUtil.createCmsTextUploadHeaders())
.body(cms),
String.class);
logger.info("New version of rule {} uploaded", ruleId);
uploadedRuleIds.add(ruleId);
} catch (HttpStatusCodeException e) {
failedRuleIds.add(ruleId);
if (e.getStatusCode().equals(HttpStatus.CONFLICT)) {
logger.info(
">= version of rule {} has already been uploaded", ruleId, e);
} else {
logger.error("[FAILED CMS] {}", cms);
logger.error("Upload of rule {} failed", ruleId, e);
}
this.dgcRT.exchange(
RequestEntity.post(dgcBaseUrl + RULE_UPLOAD_PATH)
.headers(CmsUtil.createRuleExchangeHeaders())
.body(cms),
String.class);
logger.info("New version of rule {} uploaded", ruleId);
uploadedRuleIds.add(ruleId);
} catch (SigningException e) {
logger.error("Signing of rule {} failed", ruleId, e);
} catch (HttpStatusCodeException e) {
failedRuleIds.add(ruleId);
if (e.getStatusCode().equals(HttpStatus.CONFLICT)) {
logger.info(">= version of rule {} has already been uploaded", ruleId, e);
} else {
logger.error("[FAILED CMS] {}", cms);
logger.error("Upload of rule {} failed", ruleId, e);
}
} catch (Exception ex) {
failedRuleIds.add(ruleId);
Expand All @@ -181,9 +194,9 @@ public RulesSyncResult upload(JsonNode rules) {
logger.info("Finished uploading Swiss rules");
if (failedRuleIds.isEmpty()) {
logger.info(
"Deleting {} remote rules that no longer exist locally", existingRules.size());
deleteRules(existingRules);
}else{
"Deleting {} remote rules that no longer exist locally", rulesToDelete.size());
delete(rulesToDelete);
} else {
logger.warn("There were upload failures. Skipping rule deletion");
}
return new RulesSyncResult(uploadedRuleIds, failedRuleIds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,19 @@ public SigningClient(RestTemplate rt, String signBaseUrl) {
this.signBaseUrl = signBaseUrl;
}

public String sign(SigningPayload toSign) {
String url = signBaseUrl + SIGNING_PATH;
logger.info("Requesting signed cms at {}", url);
return rt.exchange(RequestEntity.post(url).body(toSign), CmsResponse.class)
.getBody()
.getCms();
public String sign(SigningPayload toSign) throws SigningException {
try {
String url = signBaseUrl + SIGNING_PATH;
logger.info("Requesting signed cms at {}", url);
return rt.exchange(RequestEntity.post(url).body(toSign), CmsResponse.class)
.getBody()
.getCms();
} catch (Exception e) {
throw new SigningException(e);
}
}

public String getCmsForAlias(String alias) {
public String getCmsForAlias(String alias) throws SigningException {
String url = signBaseUrl + String.format(ALIAS_PATH, alias);
logger.info("Requesting cms alias {} at {}", alias, url);
return rt.exchange(
Expand All @@ -55,4 +59,10 @@ public static HttpHeaders acceptJsonHeaders() {
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
return headers;
}

public static class SigningException extends Exception {
public SigningException(Exception e) {
super(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

import ch.admin.bag.covidcertificate.backend.verifier.model.cert.db.DbDsc;
import ch.admin.bag.covidcertificate.backend.verifier.model.exception.InvalidSignatureException;
import ch.admin.bag.covidcertificate.backend.verifier.model.sync.SigningPayload;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.security.MessageDigest;
Expand All @@ -38,13 +39,21 @@ public class CmsUtil {

private CmsUtil() {}

public static HttpHeaders createCmsTextUploadHeaders() {
public static HttpHeaders createRuleExchangeHeaders() {
var headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, "application/json");
headers.add(HttpHeaders.CONTENT_TYPE, "application/cms-text");
return headers;
}

public static SigningPayload encodePayload(byte[] payload){
var base64encoded =
Base64.getEncoder()
.encodeToString(payload);
return new SigningPayload(base64encoded);
}


public static HttpHeaders createCmsUploadHeaders() {
var headers = new HttpHeaders();
headers.add(HttpHeaders.ACCEPT, "application/json");
Expand Down

0 comments on commit f784425

Please sign in to comment.