From f784425549c2ed19b25dbf42640ec4da701f193b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Gst=C3=B6hl?= Date: Tue, 1 Mar 2022 10:17:15 +0100 Subject: [PATCH] rule sync refactoring --- .../verifier/sync/syncer/DgcRulesClient.java | 139 ++++++++++-------- .../verifier/sync/syncer/SigningClient.java | 24 ++- .../backend/verifier/sync/utils/CmsUtil.java | 11 +- 3 files changed, 103 insertions(+), 71 deletions(-) diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/syncer/DgcRulesClient.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/syncer/DgcRulesClient.java index 4c584a9a..1ccc9807 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/syncer/DgcRulesClient.java +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/syncer/DgcRulesClient.java @@ -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; @@ -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; @@ -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 download() { logger.info("[DgcRulesClient] Downloading rules"); Map rules = new HashMap<>(); - ResponseEntity response = this.dgcRT.exchange(RequestEntity.get(dgcBaseUrl+String.format(DOWNLOAD_PATH, "CH")).headers(CmsUtil.createCmsTextUploadHeaders()).build(), - String.class); + ResponseEntity 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); } @@ -73,52 +81,63 @@ public Map 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 identifiers){ + public RulesSyncResult delete(Collection identifiers) { logger.info("Deleting {} rules", identifiers); List deletedRuleIds = new ArrayList<>(); List 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 rules = download(); + Set ruleIds = rules.keySet(); + return delete(ruleIds); + } /** * uploads Swiss rules @@ -126,7 +145,9 @@ public RulesSyncResult deleteRules(Collection identifiers){ * @return successfully uploaded rule ids */ public RulesSyncResult upload(JsonNode rules) { - Set existingRules = download().keySet(); + // Download all remote rules and later remove the ones that should not be deleted + Set rulesToDelete = download().keySet(); + logger.info("Uploading Swiss rules"); List uploadedRuleIds = new ArrayList<>(); List failedRuleIds = new ArrayList<>(); @@ -137,40 +158,32 @@ public RulesSyncResult upload(JsonNode rules) { Entry 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); @@ -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); diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/syncer/SigningClient.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/syncer/SigningClient.java index 845cb156..f21b3ce5 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/syncer/SigningClient.java +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/syncer/SigningClient.java @@ -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( @@ -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); + } + } } diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/utils/CmsUtil.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/utils/CmsUtil.java index c96a03c4..b4953df8 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/utils/CmsUtil.java +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-sync/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/sync/utils/CmsUtil.java @@ -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; @@ -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");