Skip to content

Commit 7b9ef52

Browse files
authored
Feat: Connector for Revocation List Upload
2 parents 299e51f + 9a4ff9a commit 7b9ef52

35 files changed

+1231
-9
lines changed

src/main/java/eu/europa/ec/dgc/gateway/connector/DgcGatewayDownloadConnectorBuilder.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
/*-
2+
* ---license-start
3+
* EU Digital Green Certificate Gateway Service / dgc-lib
4+
* ---
5+
* Copyright (C) 2021 - 2022 T-Systems International GmbH and all other contributors
6+
* ---
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ---license-end
19+
*/
20+
121
package eu.europa.ec.dgc.gateway.connector;
222

323
import eu.europa.ec.dgc.gateway.connector.client.DgcGatewayConnectorRestClient;

src/main/java/eu/europa/ec/dgc/gateway/connector/DgcGatewayRevocationListDownloadConnector.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
/*-
2+
* ---license-start
3+
* EU Digital Green Certificate Gateway Service / dgc-lib
4+
* ---
5+
* Copyright (C) 2021 - 2022 T-Systems International GmbH and all other contributors
6+
* ---
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ---license-end
19+
*/
20+
121
package eu.europa.ec.dgc.gateway.connector;
222

323

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
/*-
2+
* ---license-start
3+
* EU Digital Green Certificate Gateway Service / dgc-lib
4+
* ---
5+
* Copyright (C) 2021 T-Systems International GmbH and all other contributors
6+
* ---
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ---license-end
19+
*/
20+
21+
package eu.europa.ec.dgc.gateway.connector;
22+
23+
import com.fasterxml.jackson.core.JsonProcessingException;
24+
import com.fasterxml.jackson.databind.ObjectMapper;
25+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
26+
import eu.europa.ec.dgc.gateway.connector.client.DgcGatewayConnectorRestClient;
27+
import eu.europa.ec.dgc.gateway.connector.config.DgcGatewayConnectorConfigProperties;
28+
import eu.europa.ec.dgc.gateway.connector.dto.ProblemReportDto;
29+
import eu.europa.ec.dgc.gateway.connector.dto.RevocationBatchDeleteRequestDto;
30+
import eu.europa.ec.dgc.gateway.connector.dto.RevocationBatchDto;
31+
import eu.europa.ec.dgc.signing.SignedStringMessageBuilder;
32+
import eu.europa.ec.dgc.utils.CertificateUtils;
33+
import feign.FeignException;
34+
import java.io.IOException;
35+
import java.security.KeyStore;
36+
import java.security.KeyStoreException;
37+
import java.security.NoSuchAlgorithmException;
38+
import java.security.PrivateKey;
39+
import java.security.UnrecoverableKeyException;
40+
import java.security.cert.CertificateEncodingException;
41+
import java.security.cert.X509Certificate;
42+
import javax.annotation.PostConstruct;
43+
import lombok.Getter;
44+
import lombok.RequiredArgsConstructor;
45+
import lombok.extern.slf4j.Slf4j;
46+
import org.bouncycastle.cert.X509CertificateHolder;
47+
import org.springframework.beans.factory.annotation.Qualifier;
48+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
49+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
50+
import org.springframework.context.annotation.Lazy;
51+
import org.springframework.context.annotation.Scope;
52+
import org.springframework.http.HttpStatus;
53+
import org.springframework.http.ResponseEntity;
54+
import org.springframework.scheduling.annotation.EnableScheduling;
55+
import org.springframework.stereotype.Service;
56+
57+
58+
@ConditionalOnProperty("dgc.gateway.connector.enabled")
59+
@Lazy
60+
@Service
61+
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
62+
@RequiredArgsConstructor
63+
@EnableScheduling
64+
@Slf4j
65+
public class DgcGatewayRevocationListUploadConnector {
66+
67+
private final DgcGatewayConnectorRestClient dgcGatewayConnectorRestClient;
68+
69+
private final DgcGatewayConnectorConfigProperties properties;
70+
71+
private final ObjectMapper objectMapper;
72+
73+
private final CertificateUtils certificateUtils;
74+
75+
@Qualifier("upload")
76+
private final KeyStore uploadKeyStore;
77+
78+
private X509CertificateHolder uploadCertificateHolder;
79+
80+
private PrivateKey uploadCertificatePrivateKey;
81+
82+
@PostConstruct
83+
void init() throws KeyStoreException, CertificateEncodingException, IOException {
84+
String uploadCertAlias = properties.getUploadKeyStore().getAlias();
85+
X509Certificate uploadCertificate = (X509Certificate) uploadKeyStore.getCertificate(uploadCertAlias);
86+
87+
try {
88+
uploadCertificatePrivateKey =
89+
(PrivateKey) uploadKeyStore.getKey(uploadCertAlias, properties.getUploadKeyStore().getPassword());
90+
} catch (NoSuchAlgorithmException | UnrecoverableKeyException e) {
91+
log.error("Failed to load PrivateKey from KeyStore");
92+
}
93+
94+
if (uploadCertificatePrivateKey == null) {
95+
log.error("Could not find UploadCertificate PrivateKey in Keystore");
96+
throw new KeyStoreException("Could not find UploadCertificate PrivateKey in Keystore");
97+
}
98+
99+
if (uploadCertificate == null) {
100+
log.error("Could not find UploadCertificate in Keystore");
101+
throw new KeyStoreException("Could not find UploadCertificate in Keystore");
102+
}
103+
104+
uploadCertificateHolder = certificateUtils.convertCertificate(uploadCertificate);
105+
}
106+
107+
/**
108+
* Uploads a JSON-File as RevocationBatch to DGC Gateway.
109+
*
110+
* @param revocationBatchDto the RevocationBatchDto to upload.
111+
* @throws DgcRevocationBatchUploadException with detailed information why the upload has failed.
112+
*/
113+
public String uploadRevocationBatch(RevocationBatchDto revocationBatchDto)
114+
throws DgcRevocationBatchUploadException, JsonProcessingException {
115+
116+
objectMapper.registerModule(new JavaTimeModule());
117+
String jsonString = objectMapper.writeValueAsString(revocationBatchDto);
118+
String payload = new SignedStringMessageBuilder().withPayload(jsonString)
119+
.withSigningCertificate(uploadCertificateHolder, uploadCertificatePrivateKey).buildAsString();
120+
121+
try {
122+
ResponseEntity<Void> response = dgcGatewayConnectorRestClient.uploadBatch(payload);
123+
if (response.getStatusCode() == HttpStatus.CREATED) {
124+
log.info("Successfully uploaded RevocationBatch");
125+
return response.getHeaders().getETag();
126+
}
127+
} catch (FeignException e) {
128+
if (e.status() == HttpStatus.BAD_REQUEST.value()) {
129+
handleBadRequest(e);
130+
} else if (e.status() == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
131+
throw new DgcRevocationBatchUploadException(DgcRevocationBatchUploadException.Reason.SERVER_ERROR);
132+
} else if (e.status() == HttpStatus.UNAUTHORIZED.value() || e.status() == HttpStatus.FORBIDDEN.value()) {
133+
log.error("Client is not authorized. (Invalid Client Certificate)");
134+
throw new DgcRevocationBatchUploadException(
135+
DgcRevocationBatchUploadException.Reason.INVALID_AUTHORIZATION);
136+
}
137+
}
138+
return null;
139+
}
140+
141+
/**
142+
* Deletes a RevocationBatch with given ID from DGC Gateway.
143+
*
144+
* @param batchId The ID of the batch to be deleted.
145+
* @throws DgcRevocationBatchUploadException with detailed information why the delete has failed.
146+
*/
147+
public void deleteRevocationBatch(String batchId) throws DgcRevocationBatchUploadException,
148+
JsonProcessingException {
149+
150+
RevocationBatchDeleteRequestDto deleteRequest = new RevocationBatchDeleteRequestDto();
151+
deleteRequest.setBatchId(batchId);
152+
String jsonString = objectMapper.writeValueAsString(deleteRequest);
153+
154+
String payload = new SignedStringMessageBuilder().withPayload(jsonString)
155+
.withSigningCertificate(uploadCertificateHolder, uploadCertificatePrivateKey).buildAsString();
156+
157+
try {
158+
ResponseEntity<Void> response = dgcGatewayConnectorRestClient.deleteBatch(payload);
159+
if (response.getStatusCode() == HttpStatus.NO_CONTENT) {
160+
log.info("Successfully deleted ValidationRule");
161+
}
162+
} catch (FeignException e) {
163+
if (e.status() == HttpStatus.BAD_REQUEST.value()) {
164+
handleBadRequest(e);
165+
} else if (e.status() == HttpStatus.INTERNAL_SERVER_ERROR.value()) {
166+
throw new DgcRevocationBatchUploadException(DgcRevocationBatchUploadException.Reason.SERVER_ERROR);
167+
} else if (e.status() == HttpStatus.UNAUTHORIZED.value() || e.status() == HttpStatus.FORBIDDEN.value()) {
168+
log.error("Client is not authorized. (Invalid Client Certificate)");
169+
throw new DgcRevocationBatchUploadException(
170+
DgcRevocationBatchUploadException.Reason.INVALID_AUTHORIZATION);
171+
172+
} else if (e.status() == HttpStatus.NOT_FOUND.value()) {
173+
log.info("ValidationRules with ID {} does not exists on DGCG", batchId);
174+
}
175+
}
176+
}
177+
178+
private void handleBadRequest(FeignException e) throws DgcRevocationBatchUploadException {
179+
if (e.responseBody().isPresent()) {
180+
try {
181+
ProblemReportDto problemReport = objectMapper.readValue(e.contentUTF8(), ProblemReportDto.class);
182+
183+
throw new DgcRevocationBatchUploadException(DgcRevocationBatchUploadException.Reason.INVALID_BATCH,
184+
String.format("%s: %s, %s", problemReport.getCode(), problemReport.getProblem(),
185+
problemReport.getDetails()));
186+
} catch (JsonProcessingException jsonException) {
187+
throw new DgcRevocationBatchUploadException(DgcRevocationBatchUploadException.Reason.UNKNOWN_ERROR);
188+
}
189+
}
190+
}
191+
192+
public static class DgcRevocationBatchUploadException extends Exception {
193+
194+
@Getter
195+
private final Reason reason;
196+
197+
public DgcRevocationBatchUploadException(Reason reason) {
198+
super();
199+
this.reason = reason;
200+
}
201+
202+
public DgcRevocationBatchUploadException(Reason reason, String message) {
203+
super(message);
204+
this.reason = reason;
205+
}
206+
207+
public enum Reason {
208+
UNKNOWN_ERROR, INVALID_AUTHORIZATION, INVALID_UPLOAD_CERT, INVALID_BATCH, SERVER_ERROR
209+
}
210+
}
211+
212+
}

src/main/java/eu/europa/ec/dgc/gateway/connector/client/DgcGatewayConnectorRestClient.java

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,9 @@
4040

4141
@ConditionalOnProperty("dgc.gateway.connector.enabled")
4242
@FeignClient(
43-
name = "dgc-gateway-connector",
44-
url = "${dgc.gateway.connector.endpoint}",
45-
configuration = DgcGatewayConnectorRestClientConfig.class
43+
name = "dgc-gateway-connector",
44+
url = "${dgc.gateway.connector.endpoint}",
45+
configuration = DgcGatewayConnectorRestClientConfig.class
4646
)
4747
public interface DgcGatewayConnectorRestClient {
4848

@@ -124,12 +124,11 @@ public interface DgcGatewayConnectorRestClient {
124124

125125

126126
/**
127-
* Downloads a batch list from the revocation list.
128-
*
127+
* Downloads a batch list from the revocation list.
129128
*/
130129
@GetMapping(value = "/revocation-list", produces = MediaType.APPLICATION_JSON_VALUE)
131130
ResponseEntity<RevocationBatchListDto> downloadRevocationList(
132-
@RequestHeader(HttpHeaders.IF_MODIFIED_SINCE) String lastUpdate);
131+
@RequestHeader(HttpHeaders.IF_MODIFIED_SINCE) String lastUpdate);
133132

134133
/**
135134
* Downloads a batch of the revocation list.
@@ -140,4 +139,19 @@ ResponseEntity<RevocationBatchListDto> downloadRevocationList(
140139
@GetMapping(value = "/revocation-list/{batchId}", produces = {"application/cms-text"})
141140
ResponseEntity<String> downloadBatch(@PathVariable("batchId") String batchId);
142141

142+
/**
143+
* Uploads a batch to the revocation list.
144+
*
145+
* @param batch the CMS signed Batch JSON.
146+
*/
147+
@PostMapping(value = "/revocation-list", consumes = "application/cms-text")
148+
ResponseEntity<Void> uploadBatch(@RequestBody String batch);
149+
150+
/**
151+
* Deletes a batch from the revocation list.
152+
*
153+
* @param batch the CMS signed Batch Identifier.
154+
*/
155+
@DeleteMapping(value = "/revocation-list", consumes = "application/cms-text")
156+
ResponseEntity<Void> deleteBatch(@RequestBody String batch);
143157
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*-
2+
* ---license-start
3+
* eu-digital-green-certificates / dgc-lib
4+
* ---
5+
* Copyright (C) 2022 T-Systems International GmbH and all other contributors
6+
* ---
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ---license-end
19+
*/
20+
21+
package eu.europa.ec.dgc.gateway.connector.dto;
22+
23+
import lombok.AllArgsConstructor;
24+
import lombok.Data;
25+
import lombok.NoArgsConstructor;
26+
27+
28+
@Data
29+
@NoArgsConstructor
30+
@AllArgsConstructor
31+
public class RevocationBatchDeleteRequestDto {
32+
33+
private String batchId;
34+
35+
}

src/main/java/eu/europa/ec/dgc/gateway/connector/exception/RevocationBatchDownloadException.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
/*-
2+
* ---license-start
3+
* EU Digital Green Certificate Gateway Service / dgc-lib
4+
* ---
5+
* Copyright (C) 2021 - 2022 T-Systems International GmbH and all other contributors
6+
* ---
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ---license-end
19+
*/
20+
121
package eu.europa.ec.dgc.gateway.connector.exception;
222

323
import lombok.Getter;

src/main/java/eu/europa/ec/dgc/gateway/connector/exception/RevocationBatchGoneException.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,23 @@
1+
/*-
2+
* ---license-start
3+
* EU Digital Green Certificate Gateway Service / dgc-lib
4+
* ---
5+
* Copyright (C) 2021 - 2022 T-Systems International GmbH and all other contributors
6+
* ---
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
* ---license-end
19+
*/
20+
121
package eu.europa.ec.dgc.gateway.connector.exception;
222

323
import lombok.Getter;

0 commit comments

Comments
 (0)