diff --git a/.gitignore b/.gitignore index 05adbc0..bf22659 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ lefthook-local.yml ### macOS ### .DS_Store -fitConnectConfig.yaml -!fitConnectConfig.yaml.example -!**/test/resources/fitConnectConfig.yaml +### fit-connect ### +signing_key.json +decryption_key.json +application-local.yaml diff --git a/src/main/java/de/bund/digitalservice/a2j/config/FitConnectConfig.java b/src/main/java/de/bund/digitalservice/a2j/config/FitConnectConfig.java index 0eb85ad..eb67a2c 100644 --- a/src/main/java/de/bund/digitalservice/a2j/config/FitConnectConfig.java +++ b/src/main/java/de/bund/digitalservice/a2j/config/FitConnectConfig.java @@ -1,28 +1,81 @@ package de.bund.digitalservice.a2j.config; +import com.nimbusds.jose.jwk.JWK; import dev.fitko.fitconnect.api.config.ApplicationConfig; +import dev.fitko.fitconnect.api.config.SenderConfig; +import dev.fitko.fitconnect.api.config.SubscriberConfig; +import dev.fitko.fitconnect.api.config.defaults.Environments; import dev.fitko.fitconnect.client.SenderClient; -import dev.fitko.fitconnect.client.bootstrap.ApplicationConfigLoader; +import dev.fitko.fitconnect.client.SubscriberClient; import dev.fitko.fitconnect.client.bootstrap.ClientFactory; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.text.ParseException; +import java.util.List; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.core.io.ResourceLoader; +import org.springframework.core.io.ClassPathResource; @Configuration public class FitConnectConfig { - private final ResourceLoader resourceLoader; + @Value("${fitConnect.sender.clientId}") + String senderClientId; - public FitConnectConfig(ResourceLoader resourceLoader) { - this.resourceLoader = resourceLoader; - } + @Value("${fitConnect.sender.clientSecret}") + String senderClientSecret; + + @Value("${fitConnect.subscriber.clientId}") + String subscriberClientId; + + @Value("${fitConnect.subscriber.clientSecret}") + String subscriberClientSecret; + + @Value("${fitConnect.subscriber.privateDecryptionKeyPath}") + String subscriberPrivateDecryptionKeyPath; + + @Value("${fitConnect.subscriber.privateSigningKeyPath}") + String subscriberPrivateSigningKeyPath; @Bean - public SenderClient senderClient() throws IOException { + public SenderClient senderClient() { + SenderConfig senderConfig = + SenderConfig.builder().clientId(senderClientId).clientSecret(senderClientSecret).build(); + ApplicationConfig config = - ApplicationConfigLoader.loadConfigFromPath( - Path.of(resourceLoader.getResource("classpath:fitConnectConfig.yaml").getURI())); + ApplicationConfig.builder() + .senderConfig(senderConfig) + .activeEnvironment(Environments.TEST.getEnvironmentName()) + .build(); + return ClientFactory.createSenderClient(config); } + + @Bean + public SubscriberClient subscriberClient() throws IOException, ParseException { + SubscriberConfig subscriberConfig = + SubscriberConfig.builder() + .clientId(subscriberClientId) + .clientSecret(subscriberClientSecret) + .privateDecryptionKeys(List.of(loadKey(subscriberPrivateDecryptionKeyPath))) + .privateSigningKey(loadKey(subscriberPrivateSigningKeyPath)) + .build(); + + ApplicationConfig config = + ApplicationConfig.builder() + .subscriberConfig(subscriberConfig) + .activeEnvironment(Environments.TEST.getEnvironmentName()) + .build(); + + return ClientFactory.createSubscriberClient(config); + } + + private JWK loadKey(String filePath) throws IOException, ParseException { + Path path = Paths.get(new ClassPathResource(filePath).getURI()); + String jwkContent = Files.readString(path); + + return JWK.parse(jwkContent); + } } diff --git a/src/main/java/de/bund/digitalservice/a2j/config/SecurityConfig.java b/src/main/java/de/bund/digitalservice/a2j/config/SecurityConfig.java index 9a20263..f4ec5f6 100644 --- a/src/main/java/de/bund/digitalservice/a2j/config/SecurityConfig.java +++ b/src/main/java/de/bund/digitalservice/a2j/config/SecurityConfig.java @@ -1,6 +1,6 @@ package de.bund.digitalservice.a2j.config; -import de.bund.digitalservice.a2j.service.receiver.CallbackVerificationFilter; +import de.bund.digitalservice.a2j.service.subscriber.CallbackVerificationFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; diff --git a/src/main/java/de/bund/digitalservice/a2j/controller/ReceiverController.java b/src/main/java/de/bund/digitalservice/a2j/controller/ReceiverController.java deleted file mode 100644 index 5c02339..0000000 --- a/src/main/java/de/bund/digitalservice/a2j/controller/ReceiverController.java +++ /dev/null @@ -1,16 +0,0 @@ -package de.bund.digitalservice.a2j.controller; - -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class ReceiverController { - - public ReceiverController() {} - ; - - @PostMapping("callbacks/fit-connect") - public void newSubmission() { - System.out.println("new Submission via callback!"); - } -} diff --git a/src/main/java/de/bund/digitalservice/a2j/controller/SubscriberController.java b/src/main/java/de/bund/digitalservice/a2j/controller/SubscriberController.java new file mode 100644 index 0000000..06ef851 --- /dev/null +++ b/src/main/java/de/bund/digitalservice/a2j/controller/SubscriberController.java @@ -0,0 +1,22 @@ +package de.bund.digitalservice.a2j.controller; + +import de.bund.digitalservice.a2j.service.subscriber.SubscriberService; +import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SubscriberController { + + private final SubscriberService service; + + public SubscriberController(SubscriberService service) { + this.service = service; + } + + @PostMapping("callbacks/fit-connect") + public void newSubmission(@RequestBody SubmissionForPickup submission) { + service.pickUpSubmission(submission); + } +} diff --git a/src/main/java/de/bund/digitalservice/a2j/service/sender/FitConnectSenderService.java b/src/main/java/de/bund/digitalservice/a2j/service/sender/FitConnectSenderService.java index 36853f8..1f4a038 100644 --- a/src/main/java/de/bund/digitalservice/a2j/service/sender/FitConnectSenderService.java +++ b/src/main/java/de/bund/digitalservice/a2j/service/sender/FitConnectSenderService.java @@ -6,6 +6,8 @@ import dev.fitko.fitconnect.client.SenderClient; import java.net.URI; import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -16,13 +18,14 @@ public class FitConnectSenderService implements SenderService { private final String serviceUrn; private final String serviceName; private final String jsonUri; + private final Logger logger = LoggerFactory.getLogger(FitConnectSenderService.class); public FitConnectSenderService( SenderClient senderClient, - @Value("${submission.destination}") String destinationUuid, - @Value("${submission.serviceType.urn}") String serviceUrn, - @Value("${submission.serviceType.name}") String serviceName, - @Value("${submission.jsonUri}") String jsonUri) { + @Value("${fitConnect.submission.destination}") String destinationUuid, + @Value("${fitConnect.submission.serviceType.urn}") String serviceUrn, + @Value("${fitConnect.submission.serviceType.name}") String serviceName, + @Value("${fitConnect.submission.jsonUri}") String jsonUri) { this.client = senderClient; this.destinationUuid = destinationUuid; this.serviceUrn = serviceUrn; @@ -42,8 +45,10 @@ public String submit(SubmitRequest submitRequest) { try { SentSubmission sentSubmission = client.send(submission); + logger.info("Submission sent, caseId: " + sentSubmission.getCaseId()); return "submission sent, caseId: " + sentSubmission.getCaseId(); } catch (FitConnectSenderException e) { + logger.error("failed to submit request: " + e.getMessage()); return "failed to submit: " + e.getMessage(); } } diff --git a/src/main/java/de/bund/digitalservice/a2j/service/receiver/CallbackVerificationFilter.java b/src/main/java/de/bund/digitalservice/a2j/service/subscriber/CallbackVerificationFilter.java similarity index 80% rename from src/main/java/de/bund/digitalservice/a2j/service/receiver/CallbackVerificationFilter.java rename to src/main/java/de/bund/digitalservice/a2j/service/subscriber/CallbackVerificationFilter.java index c1dc5b8..fbee8ec 100644 --- a/src/main/java/de/bund/digitalservice/a2j/service/receiver/CallbackVerificationFilter.java +++ b/src/main/java/de/bund/digitalservice/a2j/service/subscriber/CallbackVerificationFilter.java @@ -1,4 +1,4 @@ -package de.bund.digitalservice.a2j.service.receiver; +package de.bund.digitalservice.a2j.service.subscriber; import dev.fitko.fitconnect.client.SenderClient; import jakarta.servlet.FilterChain; @@ -7,6 +7,8 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -15,9 +17,10 @@ public class CallbackVerificationFilter extends OncePerRequestFilter { private final SenderClient senderClient; private final String callbackSecret; + private final Logger logger = LoggerFactory.getLogger(CallbackVerificationFilter.class); public CallbackVerificationFilter( - SenderClient senderClient, @Value("${callbackSecret}") String callbackSecret) { + SenderClient senderClient, @Value("${fitConnect.callbackSecret}") String callbackSecret) { this.senderClient = senderClient; this.callbackSecret = callbackSecret; } @@ -40,7 +43,7 @@ protected void doFilterInternal( callbackSecret) .isValid()) { - System.out.println("Received fit-connect callback, returned 401"); + logger.info("Received invalid fit-connect callback"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } diff --git a/src/main/java/de/bund/digitalservice/a2j/service/subscriber/FitConnectSubscriberService.java b/src/main/java/de/bund/digitalservice/a2j/service/subscriber/FitConnectSubscriberService.java new file mode 100644 index 0000000..f62ef39 --- /dev/null +++ b/src/main/java/de/bund/digitalservice/a2j/service/subscriber/FitConnectSubscriberService.java @@ -0,0 +1,27 @@ +package de.bund.digitalservice.a2j.service.subscriber; + +import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; +import dev.fitko.fitconnect.api.domain.subscriber.ReceivedSubmission; +import dev.fitko.fitconnect.client.SubscriberClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +@Service +public class FitConnectSubscriberService implements SubscriberService { + + private final SubscriberClient client; + private static final Logger logger = LoggerFactory.getLogger(FitConnectSubscriberService.class); + + public FitConnectSubscriberService(SubscriberClient client) { + this.client = client; + } + + public void pickUpSubmission(SubmissionForPickup submissionForPickup) { + ReceivedSubmission receivedSubmission = client.requestSubmission(submissionForPickup); + logger.info("Submission requested. SubmissionId: " + submissionForPickup.getSubmissionId()); + + receivedSubmission.acceptSubmission(); + logger.info("Submission accepted. CaseId: " + receivedSubmission.getCaseId()); + } +} diff --git a/src/main/java/de/bund/digitalservice/a2j/service/subscriber/SubscriberService.java b/src/main/java/de/bund/digitalservice/a2j/service/subscriber/SubscriberService.java new file mode 100644 index 0000000..b712735 --- /dev/null +++ b/src/main/java/de/bund/digitalservice/a2j/service/subscriber/SubscriberService.java @@ -0,0 +1,8 @@ +package de.bund.digitalservice.a2j.service.subscriber; + +import dev.fitko.fitconnect.api.domain.model.submission.SubmissionForPickup; + +public interface SubscriberService { + + void pickUpSubmission(SubmissionForPickup submissionForPickup); +} diff --git a/src/main/resources/application-local.yaml b/src/main/resources/application-local.yaml deleted file mode 100644 index 7ccbdbd..0000000 --- a/src/main/resources/application-local.yaml +++ /dev/null @@ -1,8 +0,0 @@ -submission: - destination: d0fe1fad-d6f1-4c67-b4e7-9d0ca6663bf3 - serviceType: - urn: urn:de:fim:leika:leistung:99400048079000 - name: Simple Dummy Service - jsonUri: https://schema.fitko.de/fim/s17000717_1.0.schema.json - -callbackSecret: s3cr3t diff --git a/src/main/resources/application-local.yaml.example b/src/main/resources/application-local.yaml.example new file mode 100644 index 0000000..b81afdc --- /dev/null +++ b/src/main/resources/application-local.yaml.example @@ -0,0 +1,17 @@ +fitConnect: + sender: + clientId: senderClientId + clientSecret: s3cr3t + subscriber: + clientId: subscriberClientId + clientSecret: s3cr3t + privateDecryptionKeyPath: decryption_key.json + privateSigningKeyPath: signing_key.json + activeEnvironment: TEST + submission: + destination: destinationId + serviceType: + urn: urn:de:fim:leika:leistung:99400048079000 + name: Simple Dummy Service + jsonUri: https://schema.fitko.de/fim/s17000717_1.0.schema.json + callbackSecret: s3cr3t diff --git a/src/main/resources/fitConnectConfig.yaml.example b/src/main/resources/fitConnectConfig.yaml.example deleted file mode 100644 index 9482ef5..0000000 --- a/src/main/resources/fitConnectConfig.yaml.example +++ /dev/null @@ -1,5 +0,0 @@ -senderConfig: - clientId: "client" - clientSecret: "s3cr3t" - -activeEnvironment: TEST diff --git a/src/test/java/de/bund/digitalservice/a2j/ArchitectureFitnessTest.java b/src/test/java/de/bund/digitalservice/a2j/ArchitectureFitnessTest.java index 7c64da6..c519deb 100644 --- a/src/test/java/de/bund/digitalservice/a2j/ArchitectureFitnessTest.java +++ b/src/test/java/de/bund/digitalservice/a2j/ArchitectureFitnessTest.java @@ -38,12 +38,12 @@ void senderAndReceiverClassesShouldBeIndependent() { .resideInAPackage("de.bund.digitalservice.a2j.service.sender..") .should() .dependOnClassesThat() - .resideInAnyPackage("de.bund.digitalservice.a2j.service.receiver..") + .resideInAnyPackage("de.bund.digitalservice.a2j.service.subscriber..") .check(classes); ArchRuleDefinition.noClasses() .that() - .resideInAPackage("de.bund.digitalservice.a2j.service.receiver..") + .resideInAPackage("de.bund.digitalservice.a2j.service.subscriber..") .should() .dependOnClassesThat() .resideInAnyPackage("de.bund.digitalservice.a2j.service.sender..") diff --git a/src/test/java/de/bund/digitalservice/a2j/integration/ReceiverIntegrationTest.java b/src/test/java/de/bund/digitalservice/a2j/integration/ReceiverIntegrationTest.java index d80eb10..8cac52a 100644 --- a/src/test/java/de/bund/digitalservice/a2j/integration/ReceiverIntegrationTest.java +++ b/src/test/java/de/bund/digitalservice/a2j/integration/ReceiverIntegrationTest.java @@ -18,7 +18,7 @@ public class ReceiverIntegrationTest { @Autowired private MockMvc mockMvc; @Test - void shouldExposeCallback() throws Exception { - mockMvc.perform(post("/callbacks/fit-connect")).andExpect(status().isOk()); + void shouldSecureCallbackWithoutAuthorization() throws Exception { + mockMvc.perform(post("/callbacks/fit-connect")).andExpect(status().is4xxClientError()); } } diff --git a/src/test/resources/application-test.yaml b/src/test/resources/application-test.yaml index af05cd2..b81afdc 100644 --- a/src/test/resources/application-test.yaml +++ b/src/test/resources/application-test.yaml @@ -1,8 +1,17 @@ -submission: - destination: d0fe1fad-erf3-4c67-123i-9d0ca6663bf3 - serviceType: - urn: urn:de:fim:leika:leistung:99400048079112 - name: Simple Dummy Service - jsonUri: https://schema.fitko.de/fim/s170007g_1.0.schema.json - -callbackSecret: s3cr3t +fitConnect: + sender: + clientId: senderClientId + clientSecret: s3cr3t + subscriber: + clientId: subscriberClientId + clientSecret: s3cr3t + privateDecryptionKeyPath: decryption_key.json + privateSigningKeyPath: signing_key.json + activeEnvironment: TEST + submission: + destination: destinationId + serviceType: + urn: urn:de:fim:leika:leistung:99400048079000 + name: Simple Dummy Service + jsonUri: https://schema.fitko.de/fim/s17000717_1.0.schema.json + callbackSecret: s3cr3t