From 5d012628228a9ca2945cd0670a8299c09547145b Mon Sep 17 00:00:00 2001 From: Felix Haller Date: Thu, 15 Jul 2021 15:51:39 +0200 Subject: [PATCH 1/4] add revocation list controller v2 placeholder --- .../verifier/model/RevocationResponse.java | 6 + .../pom.xml | 2 +- .../verifier/ws/config/WsBaseConfig.java | 6 + .../RevocationListControllerV2.java | 104 ++++++++++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/RevocationListControllerV2.java diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/RevocationResponse.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/RevocationResponse.java index 7b078066..e0d733d5 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/RevocationResponse.java +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/RevocationResponse.java @@ -15,6 +15,12 @@ public class RevocationResponse { example = "172800000") private Duration validDuration = Duration.ofHours(48); + public RevocationResponse() {} + + public RevocationResponse(List revokedCerts) { + this.revokedCerts = revokedCerts; + } + public List getRevokedCerts() { return revokedCerts; } diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/pom.xml b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/pom.xml index 88f4f37c..da39a077 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/pom.xml +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/pom.xml @@ -237,7 +237,7 @@ ch.admin.bag.covidcertificate.backend.verifier.ws.controller.KeyControllerV2 - ch.admin.bag.covidcertificate.backend.verifier.ws.controller.RevocationListController + ch.admin.bag.covidcertificate.backend.verifier.ws.controller.RevocationListControllerV2 ch.admin.bag.covidcertificate.backend.verifier.ws.controller.VerificationRulesController diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java index 269b657c..b6bcf580 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java @@ -17,6 +17,7 @@ import ch.admin.bag.covidcertificate.backend.verifier.ws.controller.KeyController; import ch.admin.bag.covidcertificate.backend.verifier.ws.controller.KeyControllerV2; import ch.admin.bag.covidcertificate.backend.verifier.ws.controller.RevocationListController; +import ch.admin.bag.covidcertificate.backend.verifier.ws.controller.RevocationListControllerV2; import ch.admin.bag.covidcertificate.backend.verifier.ws.controller.ValueSetsController; import ch.admin.bag.covidcertificate.backend.verifier.ws.controller.VerificationRulesController; import ch.admin.bag.covidcertificate.backend.verifier.ws.interceptor.HeaderInjector; @@ -156,6 +157,11 @@ public RevocationListController revocationListController() { return new RevocationListController(revokedCertsBaseUrl); } + @Bean + public RevocationListControllerV2 revocationListControllerV2() { + return new RevocationListControllerV2(revokedCertsBaseUrl); + } + @Bean public VerificationRulesController verificationRulesController() throws IOException, NoSuchAlgorithmException { diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/RevocationListControllerV2.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/RevocationListControllerV2.java new file mode 100644 index 00000000..d9fd440b --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/RevocationListControllerV2.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2021 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package ch.admin.bag.covidcertificate.backend.verifier.ws.controller; + +import ch.admin.bag.covidcertificate.backend.verifier.model.RevocationResponse; +import ch.admin.bag.covidcertificate.backend.verifier.ws.utils.CacheUtil; +import ch.ubique.openapi.docannotations.Documentation; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.CacheControl; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.CrossOrigin; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +@Controller +@RequestMapping("trust/v2") +@Documentation(description = "Endpoint to obtain the list of revoked certificates") +public class RevocationListControllerV2 { + + private static final Logger logger = LoggerFactory.getLogger(RevocationListControllerV2.class); + + private static final String NEXT_SINCE_HEADER = "X-Next-Since"; + private static final String UP_TO_DATE_HEADER = "up-to-date"; + + private final String baseurl; + private final String endpoint = "/v1/revocation-list"; + @Autowired RestTemplate rt; + + public RevocationListControllerV2(String revokedCertsBaseUrl) { + logger.info("Instantiated controller with baseurl: {}", revokedCertsBaseUrl); + this.baseurl = revokedCertsBaseUrl; + } + + @Documentation( + description = "get list of revoked certificates", + responses = {"200 => next batch of revoked certificates"}, + responseHeaders = { + "X-Next-Since:`since` to set for next request:string", + "up-to-date:set to 'true' when no more certs to fetch:string" + }) + @CrossOrigin(origins = {"https://editor.swagger.io"}) + @GetMapping(value = "/revocationList") + public @ResponseBody ResponseEntity getRevokedCerts( + @RequestParam(required = false) String since) throws HttpStatusCodeException { + // TODO implement with BIT paging and return correct headers + List revokedCerts = getRevokedCerts(); + return ResponseEntity.ok() + .header(NEXT_SINCE_HEADER, "1000") + .header(UP_TO_DATE_HEADER, "true") + .cacheControl(CacheControl.maxAge(CacheUtil.REVOCATION_LIST_MAX_AGE)) + .body(new RevocationResponse(revokedCerts)); + } + + private List getRevokedCerts() { + final List certs = new ArrayList<>(); + final var requestEndpoint = baseurl + endpoint; + final var uri = UriComponentsBuilder.fromHttpUrl(requestEndpoint).build().toUri(); + final RequestEntity requestEntity = + RequestEntity.get(uri).headers(createDownloadHeaders()).build(); + final var responseEntity = rt.exchange(requestEntity, String[].class); + final var body = responseEntity.getBody(); + if (body != null) { + certs.addAll(Arrays.asList(body)); + } + return certs; + } + + @ExceptionHandler({HttpStatusCodeException.class}) + @ResponseStatus(HttpStatus.BAD_GATEWAY) + public ResponseEntity requestFailed(HttpStatusCodeException e) { + logger.error("{} returned non-2xx status code: {}", endpoint, e.getStatusCode(), e); + return ResponseEntity.status(HttpStatus.BAD_GATEWAY).build(); + } + + private HttpHeaders createDownloadHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.ACCEPT, "application/json"); + return headers; + } +} From 15134a1291a648199845a82c815f83f2803e2163 Mon Sep 17 00:00:00 2001 From: Felix Haller Date: Thu, 15 Jul 2021 16:14:00 +0200 Subject: [PATCH 2/4] add dev controller that mocks endpoints not locally available --- .../verifier/ws/config/WsLocalConfig.java | 30 ++++++++++++ .../ws/controller/dev/DevController.java | 46 +++++++++++++++++++ .../resources/application-local.properties | 4 +- .../src/main/resources/application.properties | 2 - .../src/test/resources/http/verifier-ws.http | 3 +- 5 files changed, 80 insertions(+), 5 deletions(-) create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsLocalConfig.java create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/dev/DevController.java diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsLocalConfig.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsLocalConfig.java new file mode 100644 index 00000000..1e494633 --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsLocalConfig.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2021 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package ch.admin.bag.covidcertificate.backend.verifier.ws.config; + +import ch.admin.bag.covidcertificate.backend.verifier.ws.controller.dev.DevController; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; +import java.util.Properties; +import javax.sql.DataSource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Configuration +@Profile("local") +public class WsLocalConfig { + + @Bean + public DevController devController() { + return new DevController(); + } +} diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/dev/DevController.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/dev/DevController.java new file mode 100644 index 00000000..f5437657 --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/dev/DevController.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package ch.admin.bag.covidcertificate.backend.verifier.ws.controller.dev; + +import ch.admin.bag.covidcertificate.backend.verifier.ws.utils.CacheUtil; +import ch.ubique.openapi.docannotations.Documentation; +import java.util.ArrayList; +import java.util.List; +import org.springframework.http.CacheControl; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +@RequestMapping("") +@Documentation(description = "mocks endpoints that are not available during local development") +public class DevController { + + private static final String NEXT_SINCE_HEADER = "X-Next-Since"; + private static final String UP_TO_DATE_HEADER = "up-to-date"; + + @GetMapping(value = "/v1/revocation-list") + public @ResponseBody ResponseEntity> getMockRevokedCerts( + @RequestParam(required = false) String since) { + List response = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + response.add("urn:uvci:01:CH:MOCK" + i); + } + return ResponseEntity.ok() + .header(NEXT_SINCE_HEADER, "1000") + .header(UP_TO_DATE_HEADER, "true") + .cacheControl(CacheControl.maxAge(CacheUtil.REVOCATION_LIST_MAX_AGE)) + .body(response); + } +} diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application-local.properties b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application-local.properties index 9b544c20..1507f6b4 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application-local.properties +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application-local.properties @@ -7,4 +7,6 @@ ws.authentication.apiKeys.local=4d1d5663-b4ef-46a5-85b6-3d1d376429da ws.monitor.prometheus.user=prometheus ws.monitor.prometheus.password=prometheus -server.port=8081 \ No newline at end of file +server.port=8081 + +revocationList.baseurl=http://localhost:8081 \ No newline at end of file diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application.properties b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application.properties index d2ed3d44..a9e765cd 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application.properties +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application.properties @@ -25,5 +25,3 @@ datasource.maximumPoolSize=5 datasource.maxLifetime=1700000 datasource.idleTimeout=600000 datasource.connectionTimeout=30000 - -revocationList.baseurl= diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/verifier-ws.http b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/verifier-ws.http index 41445a77..a264d4fd 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/verifier-ws.http +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/verifier-ws.http @@ -14,9 +14,8 @@ If-None-Match: "-720957702" Authorization: Bearer {{apiKey}} ### get revocation list -GET {{baseUrl}}/v1/revocationList +GET {{baseUrl}}/v2/revocationList Accept: application/json -If-None-Match: "1089905096" Authorization: Bearer {{apiKey}} ### get verification rules From 3189c8b202e04386ff19e05a821f7162974ee023 Mon Sep 17 00:00:00 2001 From: Felix Haller Date: Wed, 21 Jul 2021 07:30:09 +0200 Subject: [PATCH 3/4] sync revoked cert list and store in db. serve in batches --- .../verifier/data/RevokedCertDataService.java | 27 +++++ .../impl/JdbcRevokedCertDataServiceImpl.java | 113 ++++++++++++++++++ .../data/mapper/RevokedCertRowMapper.java | 27 +++++ .../migration/pgsql/V0_5__revoked_certs.sql | 12 ++ .../pgsql_cluster/V0_5__revoked_certs.sql | 12 ++ .../backend/verifier/model/DbRevokedCert.java | 32 +++++ .../cert/db/RevokedCertsUpdateResponse.java | 29 +++++ .../ws/client/RevocationListSyncer.java | 74 ++++++++++++ .../verifier/ws/config/SchedulingConfig.java | 37 ++++++ .../verifier/ws/config/WsBaseConfig.java | 19 ++- .../RevocationListControllerV2.java | 65 ++++------ .../resources/application-local.properties | 3 +- .../test/resources/http/http-client.env.json | 1 + .../src/test/resources/http/verifier-ws.http | 2 +- 14 files changed, 405 insertions(+), 48 deletions(-) create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/RevokedCertDataService.java create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/impl/JdbcRevokedCertDataServiceImpl.java create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/mapper/RevokedCertRowMapper.java create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/resources/db/migration/pgsql/V0_5__revoked_certs.sql create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/resources/db/migration/pgsql_cluster/V0_5__revoked_certs.sql create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/DbRevokedCert.java create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/cert/db/RevokedCertsUpdateResponse.java create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/client/RevocationListSyncer.java create mode 100644 ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/SchedulingConfig.java diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/RevokedCertDataService.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/RevokedCertDataService.java new file mode 100644 index 00000000..64ab5eb4 --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/RevokedCertDataService.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package ch.admin.bag.covidcertificate.backend.verifier.data; + +import ch.admin.bag.covidcertificate.backend.verifier.model.DbRevokedCert; +import ch.admin.bag.covidcertificate.backend.verifier.model.cert.db.RevokedCertsUpdateResponse; +import java.util.List; + +public interface RevokedCertDataService { + + /** upserts the given revoked uvcis into the db */ + public RevokedCertsUpdateResponse replaceRevokedCerts(List revokedUvcis); + + /** returns the next batch of revoked certs after `since` */ + public List findRevokedCerts(Long since); + + /** returns the highest revoked cert pk id */ + public long findMaxRevokedCertPkId(); +} diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/impl/JdbcRevokedCertDataServiceImpl.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/impl/JdbcRevokedCertDataServiceImpl.java new file mode 100644 index 00000000..53f245b9 --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/impl/JdbcRevokedCertDataServiceImpl.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2021 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package ch.admin.bag.covidcertificate.backend.verifier.data.impl; + +import ch.admin.bag.covidcertificate.backend.verifier.data.RevokedCertDataService; +import ch.admin.bag.covidcertificate.backend.verifier.data.mapper.RevokedCertRowMapper; +import ch.admin.bag.covidcertificate.backend.verifier.model.DbRevokedCert; +import ch.admin.bag.covidcertificate.backend.verifier.model.cert.db.RevokedCertsUpdateResponse; +import java.util.Arrays; +import java.util.List; +import javax.sql.DataSource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.transaction.annotation.Transactional; + +public class JdbcRevokedCertDataServiceImpl implements RevokedCertDataService { + + private static final Logger logger = + LoggerFactory.getLogger(JdbcRevokedCertDataServiceImpl.class); + + private static final int MAX_REVOKED_CERT_BATCH_COUNT = 1000; + private final NamedParameterJdbcTemplate jt; + + public JdbcRevokedCertDataServiceImpl(DataSource dataSource) { + this.jt = new NamedParameterJdbcTemplate(dataSource); + } + + @Transactional(readOnly = false) + @Override + public RevokedCertsUpdateResponse replaceRevokedCerts(List revokedUvcis) { + int insertCount = upsertRevokedCerts(revokedUvcis); + int removeCount = removeRevokedCertsNotIn(revokedUvcis); + return new RevokedCertsUpdateResponse(insertCount, removeCount); + } + + private int upsertRevokedCerts(List revokedUvcis) { + if (revokedUvcis != null && !revokedUvcis.isEmpty()) { + String sql = + "insert into t_revoked_cert" + + " (uvci)" + + " values (:uvci)" + + " on conflict (uvci)" + + " do nothing"; + int[] updateCounts = jt.batchUpdate(sql, createParams(revokedUvcis)); + return Arrays.stream(updateCounts).sum(); + } else { + return 0; + } + } + + private MapSqlParameterSource[] createParams(List revokedUvcis) { + if (revokedUvcis == null) { + return null; + } + + int size = revokedUvcis.size(); + MapSqlParameterSource[] params = new MapSqlParameterSource[size]; + for (int i = 0; i < size; i++) { + params[i] = new MapSqlParameterSource("uvci", revokedUvcis.get(i)); + } + return params; + } + + private int removeRevokedCertsNotIn(List revokedUvcis) { + String sql = "delete from t_revoked_cert"; + if (revokedUvcis != null && !revokedUvcis.isEmpty()) { + sql += " where uvci not in (:to_keep)"; + } + return jt.update(sql, new MapSqlParameterSource("to_keep", revokedUvcis)); + } + + @Transactional(readOnly = true) + @Override + public List findRevokedCerts(Long since) { + if (since == null) { + since = 0L; + } + String sql = + "select pk_revoked_cert_id, uvci from t_revoked_cert" + + " where pk_revoked_cert_id > :since" + + " order by pk_revoked_cert_id asc" + + " limit :max_batch_count"; + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("since", since); + params.addValue("max_batch_count", MAX_REVOKED_CERT_BATCH_COUNT); + return jt.query(sql, params, new RevokedCertRowMapper()); + } + + @Transactional(readOnly = true) + @Override + public long findMaxRevokedCertPkId() { + try { + String sql = + "select pk_revoked_cert_id from t_revoked_cert" + + " order by pk_revoked_cert_id desc" + + " limit 1"; + return jt.queryForObject(sql, new MapSqlParameterSource(), Long.class); + } catch (EmptyResultDataAccessException e) { + return 0L; + } + } +} diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/mapper/RevokedCertRowMapper.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/mapper/RevokedCertRowMapper.java new file mode 100644 index 00000000..ad6ba5c2 --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/data/mapper/RevokedCertRowMapper.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2021 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package ch.admin.bag.covidcertificate.backend.verifier.data.mapper; + +import ch.admin.bag.covidcertificate.backend.verifier.model.DbRevokedCert; +import java.sql.ResultSet; +import java.sql.SQLException; +import org.springframework.jdbc.core.RowMapper; + +public class RevokedCertRowMapper implements RowMapper { + + @Override + public DbRevokedCert mapRow(ResultSet resultSet, int i) throws SQLException { + var revokedCert = new DbRevokedCert(); + revokedCert.setPkId(resultSet.getLong("pk_revoked_cert_id")); + revokedCert.setUvci(resultSet.getString("uvci")); + return revokedCert; + } +} diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/resources/db/migration/pgsql/V0_5__revoked_certs.sql b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/resources/db/migration/pgsql/V0_5__revoked_certs.sql new file mode 100644 index 00000000..65762304 --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/resources/db/migration/pgsql/V0_5__revoked_certs.sql @@ -0,0 +1,12 @@ +/* + * Created by Ubique Innovation AG + * https://www.ubique.ch + * Copyright (c) 2021. All rights reserved. + */ + +CREATE TABLE t_revoked_cert +( + pk_revoked_cert_id serial NOT NULL, + uvci character varying(50) UNIQUE NOT NULL, + CONSTRAINT pk_t_revoked_cert PRIMARY KEY (pk_revoked_cert_id) +); \ No newline at end of file diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/resources/db/migration/pgsql_cluster/V0_5__revoked_certs.sql b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/resources/db/migration/pgsql_cluster/V0_5__revoked_certs.sql new file mode 100644 index 00000000..65762304 --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-data/src/main/resources/db/migration/pgsql_cluster/V0_5__revoked_certs.sql @@ -0,0 +1,12 @@ +/* + * Created by Ubique Innovation AG + * https://www.ubique.ch + * Copyright (c) 2021. All rights reserved. + */ + +CREATE TABLE t_revoked_cert +( + pk_revoked_cert_id serial NOT NULL, + uvci character varying(50) UNIQUE NOT NULL, + CONSTRAINT pk_t_revoked_cert PRIMARY KEY (pk_revoked_cert_id) +); \ No newline at end of file diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/DbRevokedCert.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/DbRevokedCert.java new file mode 100644 index 00000000..49e3ba9e --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/DbRevokedCert.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2021 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package ch.admin.bag.covidcertificate.backend.verifier.model; + +public class DbRevokedCert { + private Long pkId; + private String uvci; + + public Long getPkId() { + return pkId; + } + + public void setPkId(Long pkId) { + this.pkId = pkId; + } + + public String getUvci() { + return uvci; + } + + public void setUvci(String uvci) { + this.uvci = uvci; + } +} diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/cert/db/RevokedCertsUpdateResponse.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/cert/db/RevokedCertsUpdateResponse.java new file mode 100644 index 00000000..a15636d6 --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-model/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/model/cert/db/RevokedCertsUpdateResponse.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2021 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package ch.admin.bag.covidcertificate.backend.verifier.model.cert.db; + +public class RevokedCertsUpdateResponse { + private final int insertCount; + private final int removeCount; + + public RevokedCertsUpdateResponse(int insertCount, int removeCount) { + this.insertCount = insertCount; + this.removeCount = removeCount; + } + + public int getInsertCount() { + return insertCount; + } + + public int getRemoveCount() { + return removeCount; + } +} diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/client/RevocationListSyncer.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/client/RevocationListSyncer.java new file mode 100644 index 00000000..f64dc653 --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/client/RevocationListSyncer.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2021 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package ch.admin.bag.covidcertificate.backend.verifier.ws.client; + +import ch.admin.bag.covidcertificate.backend.verifier.data.RevokedCertDataService; +import ch.admin.bag.covidcertificate.backend.verifier.model.cert.db.RevokedCertsUpdateResponse; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.RequestEntity; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +public class RevocationListSyncer { + + private static final Logger logger = LoggerFactory.getLogger(RevocationListSyncer.class); + + private final String baseurl; + private final String endpoint = "/v1/revocation-list"; + private final RevokedCertDataService revokedCertDataService; + @Autowired private RestTemplate rt; + + public RevocationListSyncer( + String revokedCertsBaseUrl, RevokedCertDataService revokedCertDataService) { + this.baseurl = revokedCertsBaseUrl; + this.revokedCertDataService = revokedCertDataService; + } + + public void updateRevokedCerts() { + logger.info("updating revoked certs"); + + try { + List revokedCerts = downloadRevokedCerts(); + logger.info("downloaded {} revoked certs", revokedCerts.size()); + + RevokedCertsUpdateResponse updateResponse = + revokedCertDataService.replaceRevokedCerts(revokedCerts); + + logger.info( + "finished updating revoked certs. inserted {}, removed {}", + updateResponse.getInsertCount(), + updateResponse.getRemoveCount()); + } catch (Exception e) { + logger.error("revoked certs update failed", e); + } + } + + private List downloadRevokedCerts() { + final var requestEndpoint = baseurl + endpoint; + final var uri = UriComponentsBuilder.fromHttpUrl(requestEndpoint).build().toUri(); + final RequestEntity requestEntity = + RequestEntity.get(uri).headers(createDownloadHeaders()).build(); + final var response = rt.exchange(requestEntity, String[].class).getBody(); + return new ArrayList<>(Arrays.asList(response)); + } + + private HttpHeaders createDownloadHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.add(HttpHeaders.ACCEPT, "application/json"); + return headers; + } +} diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/SchedulingConfig.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/SchedulingConfig.java new file mode 100644 index 00000000..ca8818c7 --- /dev/null +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/SchedulingConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2021 Ubique Innovation AG + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + * + * SPDX-License-Identifier: MPL-2.0 + */ + +package ch.admin.bag.covidcertificate.backend.verifier.ws.config; + +import ch.admin.bag.covidcertificate.backend.verifier.ws.client.RevocationListSyncer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.scheduling.annotation.Scheduled; + +@Configuration +@EnableScheduling +public class SchedulingConfig { + + private static final Logger logger = LoggerFactory.getLogger(SchedulingConfig.class); + + private final RevocationListSyncer revocationListSyncer; + + public SchedulingConfig(RevocationListSyncer revocationListSyncer) { + this.revocationListSyncer = revocationListSyncer; + } + + // Sync revocation list every full hour (default) + @Scheduled(cron = "${revocationList.sync.cron:0 0 * ? * *}") + public void syncRevocationList() { + revocationListSyncer.updateRevokedCerts(); + } +} diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java index b6bcf580..e3f0583c 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java @@ -11,9 +11,12 @@ package ch.admin.bag.covidcertificate.backend.verifier.ws.config; import ch.admin.bag.covidcertificate.backend.verifier.data.AppTokenDataService; +import ch.admin.bag.covidcertificate.backend.verifier.data.RevokedCertDataService; import ch.admin.bag.covidcertificate.backend.verifier.data.VerifierDataService; import ch.admin.bag.covidcertificate.backend.verifier.data.impl.JdbcAppTokenDataServiceImpl; +import ch.admin.bag.covidcertificate.backend.verifier.data.impl.JdbcRevokedCertDataServiceImpl; import ch.admin.bag.covidcertificate.backend.verifier.data.impl.JdbcVerifierDataServiceImpl; +import ch.admin.bag.covidcertificate.backend.verifier.ws.client.RevocationListSyncer; import ch.admin.bag.covidcertificate.backend.verifier.ws.controller.KeyController; import ch.admin.bag.covidcertificate.backend.verifier.ws.controller.KeyControllerV2; import ch.admin.bag.covidcertificate.backend.verifier.ws.controller.RevocationListController; @@ -158,8 +161,20 @@ public RevocationListController revocationListController() { } @Bean - public RevocationListControllerV2 revocationListControllerV2() { - return new RevocationListControllerV2(revokedCertsBaseUrl); + public RevocationListControllerV2 revocationListControllerV2( + RevokedCertDataService revokedCertDataService) { + return new RevocationListControllerV2(revokedCertDataService); + } + + @Bean + public RevocationListSyncer revocationListSyncer( + RevokedCertDataService revokedCertDataService) { + return new RevocationListSyncer(revokedCertsBaseUrl, revokedCertDataService); + } + + @Bean + public RevokedCertDataService revokedCertDataService(DataSource dataSource) { + return new JdbcRevokedCertDataServiceImpl(dataSource); } @Bean diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/RevocationListControllerV2.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/RevocationListControllerV2.java index d9fd440b..8bb080d7 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/RevocationListControllerV2.java +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/controller/RevocationListControllerV2.java @@ -10,31 +10,25 @@ package ch.admin.bag.covidcertificate.backend.verifier.ws.controller; +import ch.admin.bag.covidcertificate.backend.verifier.data.RevokedCertDataService; +import ch.admin.bag.covidcertificate.backend.verifier.model.DbRevokedCert; import ch.admin.bag.covidcertificate.backend.verifier.model.RevocationResponse; import ch.admin.bag.covidcertificate.backend.verifier.ws.utils.CacheUtil; import ch.ubique.openapi.docannotations.Documentation; -import java.util.ArrayList; -import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.CacheControl; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.RequestEntity; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CrossOrigin; -import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.client.HttpStatusCodeException; -import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; @Controller @RequestMapping("trust/v2") @@ -46,13 +40,10 @@ public class RevocationListControllerV2 { private static final String NEXT_SINCE_HEADER = "X-Next-Since"; private static final String UP_TO_DATE_HEADER = "up-to-date"; - private final String baseurl; - private final String endpoint = "/v1/revocation-list"; - @Autowired RestTemplate rt; + private final RevokedCertDataService revokedCertDataService; - public RevocationListControllerV2(String revokedCertsBaseUrl) { - logger.info("Instantiated controller with baseurl: {}", revokedCertsBaseUrl); - this.baseurl = revokedCertsBaseUrl; + public RevocationListControllerV2(RevokedCertDataService revokedCertDataService) { + this.revokedCertDataService = revokedCertDataService; } @Documentation( @@ -65,40 +56,26 @@ public RevocationListControllerV2(String revokedCertsBaseUrl) { @CrossOrigin(origins = {"https://editor.swagger.io"}) @GetMapping(value = "/revocationList") public @ResponseBody ResponseEntity getRevokedCerts( - @RequestParam(required = false) String since) throws HttpStatusCodeException { - // TODO implement with BIT paging and return correct headers - List revokedCerts = getRevokedCerts(); + @RequestParam(required = false, defaultValue = "0") Long since) + throws HttpStatusCodeException { + List revokedCerts = revokedCertDataService.findRevokedCerts(since); + List revokedUvcis = + revokedCerts.stream().map(DbRevokedCert::getUvci).collect(Collectors.toList()); return ResponseEntity.ok() - .header(NEXT_SINCE_HEADER, "1000") - .header(UP_TO_DATE_HEADER, "true") + .headers(getRevokedCertsHeaders(revokedCerts)) .cacheControl(CacheControl.maxAge(CacheUtil.REVOCATION_LIST_MAX_AGE)) - .body(new RevocationResponse(revokedCerts)); + .body(new RevocationResponse(revokedUvcis)); } - private List getRevokedCerts() { - final List certs = new ArrayList<>(); - final var requestEndpoint = baseurl + endpoint; - final var uri = UriComponentsBuilder.fromHttpUrl(requestEndpoint).build().toUri(); - final RequestEntity requestEntity = - RequestEntity.get(uri).headers(createDownloadHeaders()).build(); - final var responseEntity = rt.exchange(requestEntity, String[].class); - final var body = responseEntity.getBody(); - if (body != null) { - certs.addAll(Arrays.asList(body)); - } - return certs; - } - - @ExceptionHandler({HttpStatusCodeException.class}) - @ResponseStatus(HttpStatus.BAD_GATEWAY) - public ResponseEntity requestFailed(HttpStatusCodeException e) { - logger.error("{} returned non-2xx status code: {}", endpoint, e.getStatusCode(), e); - return ResponseEntity.status(HttpStatus.BAD_GATEWAY).build(); - } - - private HttpHeaders createDownloadHeaders() { + private HttpHeaders getRevokedCertsHeaders(List revokedCerts) { HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.ACCEPT, "application/json"); + long maxPkId = revokedCertDataService.findMaxRevokedCertPkId(); + Long nextSince = + revokedCerts.stream().mapToLong(DbRevokedCert::getPkId).max().orElse(maxPkId); + headers.add(NEXT_SINCE_HEADER, nextSince.toString()); + if (nextSince >= maxPkId) { + headers.add(UP_TO_DATE_HEADER, "true"); + } return headers; } } diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application-local.properties b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application-local.properties index 1507f6b4..20479054 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application-local.properties +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/resources/application-local.properties @@ -9,4 +9,5 @@ ws.monitor.prometheus.password=prometheus server.port=8081 -revocationList.baseurl=http://localhost:8081 \ No newline at end of file +revocationList.baseurl=http://localhost:8081 +revocationList.sync.cron=0 * * ? * * \ No newline at end of file diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/http-client.env.json b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/http-client.env.json index 281472e1..b986c3a2 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/http-client.env.json +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/http-client.env.json @@ -2,6 +2,7 @@ "local": { "baseUrl": "localhost:8081/trust", "since": 4, + "revokedSince": 4, "upTo": 4, "certFormat": "IOS" }, diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/verifier-ws.http b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/verifier-ws.http index a264d4fd..45fdf6fc 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/verifier-ws.http +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/test/resources/http/verifier-ws.http @@ -14,7 +14,7 @@ If-None-Match: "-720957702" Authorization: Bearer {{apiKey}} ### get revocation list -GET {{baseUrl}}/v2/revocationList +GET {{baseUrl}}/v2/revocationList?since={{revokedSince}} Accept: application/json Authorization: Bearer {{apiKey}} From 310786b5c9b7dc4b98574055d91f7fda4bb0d3ff Mon Sep 17 00:00:00 2001 From: Felix Haller Date: Wed, 21 Jul 2021 10:05:56 +0200 Subject: [PATCH 4/4] add shedlock for revocation list sync --- .../verifier/ws/config/SchedulingConfig.java | 6 ++++++ .../backend/verifier/ws/config/WsBaseConfig.java | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/SchedulingConfig.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/SchedulingConfig.java index ca8818c7..007fcfd0 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/SchedulingConfig.java +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/SchedulingConfig.java @@ -11,6 +11,9 @@ package ch.admin.bag.covidcertificate.backend.verifier.ws.config; import ch.admin.bag.covidcertificate.backend.verifier.ws.client.RevocationListSyncer; +import net.javacrumbs.shedlock.core.LockAssert; +import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; +import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; @@ -19,6 +22,7 @@ @Configuration @EnableScheduling +@EnableSchedulerLock(defaultLockAtMostFor = "PT10M") public class SchedulingConfig { private static final Logger logger = LoggerFactory.getLogger(SchedulingConfig.class); @@ -31,7 +35,9 @@ public SchedulingConfig(RevocationListSyncer revocationListSyncer) { // Sync revocation list every full hour (default) @Scheduled(cron = "${revocationList.sync.cron:0 0 * ? * *}") + @SchedulerLock(name = "revocation_list_sync", lockAtLeastFor = "PT15S") public void syncRevocationList() { + LockAssert.assertLocked(); revocationListSyncer.updateRevokedCerts(); } } diff --git a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java index e3f0583c..7dd7b177 100644 --- a/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java +++ b/ch-covidcertificate-backend-verifier/ch-covidcertificate-backend-verifier-ws/src/main/java/ch/admin/bag/covidcertificate/backend/verifier/ws/config/WsBaseConfig.java @@ -39,6 +39,8 @@ import java.util.List; import java.util.Map; import javax.sql.DataSource; +import net.javacrumbs.shedlock.core.LockProvider; +import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider; import org.flywaydb.core.Flyway; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,6 +48,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -192,4 +195,15 @@ public ValueSetsController valueSetsController() throws IOException, NoSuchAlgor public RestTemplate restTemplate() { return RestTemplateHelper.getRestTemplate(); } + + @Bean + public LockProvider lockProvider(DataSource dataSource) { + return new JdbcTemplateLockProvider( + JdbcTemplateLockProvider.Configuration.builder() + .withTableName("t_shedlock") + .withJdbcTemplate(new JdbcTemplate(dataSource)) + // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2 + .usingDbTime() + .build()); + } }