All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.fitko.fitconnect.client.bootstrap.ClientFactory Maven / Gradle / Ivy

Go to download

Library that provides client access to the FIT-Connect api-endpoints for sending, subscribing and routing

There is a newer version: 2.3.5
Show newest version
package dev.fitko.fitconnect.client.bootstrap;

import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.RSAKey;
import dev.fitko.fitconnect.api.FitConnectService;
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.ZBPCertConfig;
import dev.fitko.fitconnect.api.config.chunking.AttachmentChunkingConfig;
import dev.fitko.fitconnect.api.config.defaults.ZBPEnvironment;
import dev.fitko.fitconnect.api.config.http.HttpConfig;
import dev.fitko.fitconnect.api.config.http.ProxyConfig;
import dev.fitko.fitconnect.api.config.http.RetryConfig;
import dev.fitko.fitconnect.api.config.http.Timeouts;
import dev.fitko.fitconnect.api.config.resources.BuildInfo;
import dev.fitko.fitconnect.api.config.resources.CertificateConfig;
import dev.fitko.fitconnect.api.domain.model.reply.Reply;
import dev.fitko.fitconnect.api.domain.model.submission.Submission;
import dev.fitko.fitconnect.api.exceptions.client.FitConnectInitialisationException;
import dev.fitko.fitconnect.api.services.auth.OAuthService;
import dev.fitko.fitconnect.api.services.crypto.CryptoService;
import dev.fitko.fitconnect.api.services.crypto.MessageDigestService;
import dev.fitko.fitconnect.api.services.events.CaseService;
import dev.fitko.fitconnect.api.services.events.EventLogVerificationService;
import dev.fitko.fitconnect.api.services.events.SecurityEventService;
import dev.fitko.fitconnect.api.services.http.HttpClient;
import dev.fitko.fitconnect.api.services.keys.KeyService;
import dev.fitko.fitconnect.api.services.reply.ReplyService;
import dev.fitko.fitconnect.api.services.routing.RoutingService;
import dev.fitko.fitconnect.api.services.schema.SchemaProvider;
import dev.fitko.fitconnect.api.services.submission.SubmissionService;
import dev.fitko.fitconnect.api.services.validation.ValidationService;
import dev.fitko.fitconnect.client.RouterClient;
import dev.fitko.fitconnect.client.SenderClient;
import dev.fitko.fitconnect.client.SubscriberClient;
import dev.fitko.fitconnect.client.ZBPClient;
import dev.fitko.fitconnect.client.zbp.ZBPServiceAdapter;
import dev.fitko.fitconnect.client.attachments.AttachmentPayloadHandler;
import dev.fitko.fitconnect.client.attachments.AttachmentStorageResolver;
import dev.fitko.fitconnect.client.attachments.download.AttachmentDownloader;
import dev.fitko.fitconnect.client.attachments.download.ReplyAttachmentDownloader;
import dev.fitko.fitconnect.client.attachments.download.SubmissionAttachmentDownloader;
import dev.fitko.fitconnect.client.attachments.upload.AttachmentUploader;
import dev.fitko.fitconnect.client.attachments.upload.ReplyAttachmentUploader;
import dev.fitko.fitconnect.client.attachments.upload.SubmissionAttachmentUploader;
import dev.fitko.fitconnect.client.sender.ReplyReceiver;
import dev.fitko.fitconnect.client.sender.SubmissionSender;
import dev.fitko.fitconnect.client.subscriber.ReplySender;
import dev.fitko.fitconnect.client.subscriber.SubmissionReceiver;
import dev.fitko.fitconnect.client.util.DestinationValidator;
import dev.fitko.fitconnect.client.util.ResourceLoadingUtils;
import dev.fitko.fitconnect.core.FitConnectDefaultService;
import dev.fitko.fitconnect.core.auth.DefaultOAuthApiService;
import dev.fitko.fitconnect.core.cases.CaseApiService;
import dev.fitko.fitconnect.core.cases.EventLogVerifier;
import dev.fitko.fitconnect.core.cases.SecurityEventTokenService;
import dev.fitko.fitconnect.core.crypto.HashService;
import dev.fitko.fitconnect.core.crypto.JWECryptoService;
import dev.fitko.fitconnect.core.http.DefaultHttpClient;
import dev.fitko.fitconnect.core.http.interceptors.ApiRequestInterceptor;
import dev.fitko.fitconnect.core.http.interceptors.RetryInterceptor;
import dev.fitko.fitconnect.core.http.interceptors.UserAgentInterceptor;
import dev.fitko.fitconnect.core.io.FileChunker;
import dev.fitko.fitconnect.core.keys.PublicKeyApiService;
import dev.fitko.fitconnect.core.reply.ReplyApiService;
import dev.fitko.fitconnect.core.routing.RouteVerifier;
import dev.fitko.fitconnect.core.routing.RoutingApiService;
import dev.fitko.fitconnect.core.schema.SchemaResourceProvider;
import dev.fitko.fitconnect.core.submission.SubmissionApiService;
import dev.fitko.fitconnect.core.validation.DefaultValidationService;
import dev.fitko.fitconnect.core.zbp.ZBPApiService;
import okhttp3.Interceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static dev.fitko.fitconnect.client.util.ResourceLoadingUtils.loadAllSchemaResources;
import static dev.fitko.fitconnect.client.util.ResourceLoadingUtils.loadYaml;
import static dev.fitko.fitconnect.client.util.ResourceLoadingUtils.readKeyFromPath;
import static java.util.Objects.requireNonNull;

/**
 * Factory that constructs {@link SenderClient}, {@link SubscriberClient} and {@link RouterClient}.
 */
public final class ClientFactory {

    private static final Logger LOGGER = LoggerFactory.getLogger(ClientFactory.class);
    private static final String BUILD_INFO_PATH = "buildinfo.yaml";
    private static final String CERTIFICATE_CONFIG_PATH = "certificate-config.yaml";
    private static final String SCHEMA_CONFIG_PATH = "schema-config.yaml";

    private ClientFactory() {
    }

    /**
     * Create a new {@link SenderClient} to send submissions and receive replies.
     * The client is automatically configured via the provided {@link ApplicationConfig}.
     *
     * @return the sender client
     * @throws FitConnectInitialisationException on errors during the sender client construction
     */
    public static SenderClient createSenderClient(final ApplicationConfig config) throws FitConnectInitialisationException {

        requireNonNull(config, "Application config must not be null");

        LOGGER.info("Initializing sender client ...");
        final SenderConfig senderConfig = config.getSenderConfig();
        final FitConnectService fitConnectService = createFitConnectService(config, senderConfig.getClientId(), senderConfig.getClientSecret());
        final DestinationValidator destinationValidator = new DestinationValidator(fitConnectService);

        final AttachmentChunkingConfig chunkingConfig = config.getAttachmentChunkingConfig();
        final AttachmentStorageResolver attachmentStorageResolver = new AttachmentStorageResolver(chunkingConfig.getAttachmentStoragePath());
        final AttachmentPayloadHandler attachmentPayloadHandler = createPayloadHandler(chunkingConfig, attachmentStorageResolver);
        final AttachmentUploader attachmentUploader = new SubmissionAttachmentUploader(fitConnectService);
        final AttachmentDownloader attachmentDownloader = new ReplyAttachmentDownloader(config, fitConnectService, attachmentPayloadHandler);

        final SubmissionSender submissionSender = new SubmissionSender(config, fitConnectService, destinationValidator, attachmentPayloadHandler, attachmentUploader);
        final ReplyReceiver replyReceiver = new ReplyReceiver(config, fitConnectService, attachmentDownloader);

        return new SenderClient(fitConnectService, submissionSender, replyReceiver, attachmentStorageResolver);
    }

    /**
     * Create a new {@link SubscriberClient} to receive submissions and send replies.
     * The client is automatically configured via the provided {@link ApplicationConfig}.
     *
     * @return the subscriber client
     * @throws FitConnectInitialisationException on errors during the subscriber client construction
     */
    public static SubscriberClient createSubscriberClient(final ApplicationConfig config) throws FitConnectInitialisationException {

        requireNonNull(config, "Application config must not be null");

        LOGGER.info("Initializing subscriber client ...");
        final SubscriberConfig subscriberConfig = config.getSubscriberConfig();

        final RSAKey rsaDecryptionKey = getDecryptionKeyFromConfig(subscriberConfig);
        final RSAKey rsaSigningKey = getSignatureKeyFromConfig(subscriberConfig);

        final FitConnectService fitConnectService = createFitConnectService(config, subscriberConfig.getClientId(), subscriberConfig.getClientSecret(), rsaSigningKey);
        final DestinationValidator destinationValidator = new DestinationValidator(fitConnectService);

        final AttachmentChunkingConfig chunkingConfig = config.getAttachmentChunkingConfig();
        final AttachmentStorageResolver attachmentStorageResolver = new AttachmentStorageResolver(chunkingConfig.getAttachmentStoragePath());
        final AttachmentPayloadHandler attachmentPayloadHandler = createPayloadHandler(chunkingConfig, attachmentStorageResolver);
        final AttachmentUploader attachmentUploader = new ReplyAttachmentUploader(fitConnectService);
        final AttachmentDownloader attachmentDownloader = new SubmissionAttachmentDownloader(config, fitConnectService, attachmentPayloadHandler);

        final SubmissionReceiver submissionReceiver = new SubmissionReceiver(fitConnectService, rsaDecryptionKey, config, attachmentDownloader);
        final ReplySender replySender = new ReplySender(config, fitConnectService, destinationValidator, attachmentPayloadHandler, attachmentUploader);

        return new SubscriberClient(fitConnectService, submissionReceiver, replySender, attachmentStorageResolver);
    }

    /**
     * Create a new {@link RouterClient} to find destinations and services that is automatically configured via a provided {@link ApplicationConfig}.
     *
     * @return the routing client
     * @throws FitConnectInitialisationException on errors during the routing client construction
     */
    public static RouterClient createRouterClient(final ApplicationConfig config) throws FitConnectInitialisationException {

        requireNonNull(config, "Application config must not be null");

        LOGGER.info("Initializing router client ...");
        final HttpClient httpClient = createHttpClient(config);
        final SchemaProvider schemaProvider = createSchemaProvider(httpClient, config);
        final ValidationService validator = createValidationService(config, schemaProvider, new HashService());

        final KeyService keyService = new PublicKeyApiService(config, httpClient, validator);
        final RouteVerifier routeVerifier = new RouteVerifier(keyService, validator);
        final RoutingService routingService = new RoutingApiService(httpClient, config.getRoutingBaseUrl());

        return new RouterClient(routingService, routeVerifier);
    }

    /**
     * Create a new client to send messages and status updates to mailboxes of the ZBP (Zentrales Bürgerpostfach).
     *
     * @param certConfig  the certificate configuration holding the private signing key and the public key
     * @param environment the environment requests are sent to
     * @return the client
     * @throws FitConnectInitialisationException on errors during the zbp client construction
     */
    public static ZBPClient createZBPClient(ZBPCertConfig certConfig, ZBPEnvironment environment) {
        return createZBPClient(certConfig, environment, HttpConfig.builder().build());
    }

    /**
     * Create a new client to send messages and status updates to mailboxes of the ZBP (Zentrales Bürgerpostfach).
     *
     * @param certConfig  the certificate configuration holding the private signing key and the public key
     * @param environment the environment requests are sent to
     * @param httpConfig  HTTP-client configuration to pass custom {@link RetryConfig}, {@link Timeouts} or {@link ProxyConfig} settings
     * @return the client
     * @throws FitConnectInitialisationException on errors during the zbp client construction
     */
    public static ZBPClient createZBPClient(ZBPCertConfig certConfig, ZBPEnvironment environment, HttpConfig httpConfig) {
        LOGGER.info("Initializing ZBP client ...");
        final HttpClient httpClient = createHttpClient(ApplicationConfig.builder().httpConfig(httpConfig).build(), certConfig);
        final ZBPApiService apiService = new ZBPApiService(httpClient, environment.getBaseUrl());
        return new ZBPClient(new ZBPServiceAdapter(apiService, certConfig.getClientPrivateKey()));
    }

    private static FitConnectService createFitConnectService(final ApplicationConfig config, final String clientId, final String clientSecret) {
        return createFitConnectService(config, clientId, clientSecret, null);
    }

    private static FitConnectService createFitConnectService(final ApplicationConfig config, final String clientId, final String clientSecret, final RSAKey signingKey) {
        final HttpClient httpClient = createHttpClient(config);
        final SchemaProvider schemaProvider = createSchemaProvider(httpClient, config);
        final MessageDigestService messageDigestService = new HashService();

        final CryptoService cryptoService = new JWECryptoService(messageDigestService);
        final ValidationService validator = createValidationService(config, schemaProvider, messageDigestService);

        final OAuthService authService = new DefaultOAuthApiService(httpClient, clientId, clientSecret, config.getAuthBaseUrl());

        final SubmissionService submissionService = new SubmissionApiService(authService, httpClient, config.getSubmissionBaseUrl());
        final KeyService keyService = new PublicKeyApiService(config, httpClient, authService, validator);
        final EventLogVerificationService eventLogVerifier = new EventLogVerifier(keyService, validator);
        final CaseService caseService = new CaseApiService(authService, httpClient, eventLogVerifier, config.getSubmissionBaseUrl());
        final ReplyService replyService = new ReplyApiService(authService, httpClient, config.getSubmissionBaseUrl());

        final SecurityEventService setService = new SecurityEventTokenService(config, validator, signingKey);

        return new FitConnectDefaultService(submissionService, caseService, replyService, cryptoService, validator, keyService, setService);
    }

    private static ValidationService createValidationService(final ApplicationConfig config, final SchemaProvider schemaProvider, final MessageDigestService messageDigestService) {
        final List trustedRootCertificates = loadYaml(CERTIFICATE_CONFIG_PATH, CertificateConfig.class)
                .getTrustedRootCertificates().stream()
                .map(ResourceLoadingUtils::readResourceToString)
                .collect(Collectors.toList());
        LOGGER.info("Initialised trusted root certificates");
        return new DefaultValidationService(config, messageDigestService, schemaProvider, trustedRootCertificates);
    }

    private static HttpClient createHttpClient(final ApplicationConfig config) {
        return createHttpClient(config, null);
    }

    private static HttpClient createHttpClient(final ApplicationConfig config, ZBPCertConfig zbpCertConfig) {
        final BuildInfo buildInfo = loadYaml(BUILD_INFO_PATH, BuildInfo.class);
        final HttpConfig httpConfig = config.getHttpConfig();
        final List interceptors = getInterceptors(buildInfo, httpConfig);
        return new DefaultHttpClient(httpConfig, interceptors, zbpCertConfig);
    }

    private static List getInterceptors(BuildInfo buildInfo, HttpConfig httpConfig) {
        final List interceptors = new ArrayList<>();
        interceptors.add(new ApiRequestInterceptor());
        interceptors.add(new UserAgentInterceptor(buildInfo));
        final RetryConfig retryConfig = httpConfig.getRetryConfig();
        if (retryConfig.isAllowRetries()) {
            LOGGER.info("Using {} retries for failed HTTP-calls", retryConfig.getMaxRetryCount());
            interceptors.add(new RetryInterceptor(retryConfig));
        }
        return interceptors;
    }

    private static SchemaProvider createSchemaProvider(final HttpClient httpClient, final ApplicationConfig config) {
        final var schemaResources = loadAllSchemaResources(SCHEMA_CONFIG_PATH, config.getSubmissionDataSchemas());
        return new SchemaResourceProvider(httpClient, schemaResources);
    }

    private static AttachmentPayloadHandler createPayloadHandler(final AttachmentChunkingConfig config, AttachmentStorageResolver attachmentStorageResolver) {
        final FileChunker fileChunker = new FileChunker();
        final MessageDigestService messageDigestService = new HashService();
        return new AttachmentPayloadHandler(config, fileChunker, attachmentStorageResolver, messageDigestService);
    }

    private static RSAKey parseRSAKeyFromString(final String key) {
        try {
            return RSAKey.parse(JWK.parse(key).toJSONObject());
        } catch (final ParseException | NullPointerException e) {
            LOGGER.error("Could not parse key, please check environment (config.yml)");
            throw new FitConnectInitialisationException("Key could not be parsed", e);
        }
    }

    private static String getPrivateDecryptionKeyPathFromSubscriber(final SubscriberConfig subscriberConfig) {
        if (subscriberConfig.getPrivateDecryptionKeyPaths().size() != 1) {
            throw new FitConnectInitialisationException("Currently only one configured private key per subscriber is allowed !");
        }
        return subscriberConfig.getPrivateDecryptionKeyPaths().get(0);
    }

    private static RSAKey getSignatureKeyFromConfig(SubscriberConfig config) {
        LOGGER.info("Initialising private signature key");
        if (config.getSubscriberKeys() != null) {
            return config.getSubscriberKeys().getPrivateSigningKey().toRSAKey();
        }
        return parseRSAKeyFromString(readKeyFromPath(config.getPrivateSigningKeyPath()));
    }

    private static RSAKey getDecryptionKeyFromConfig(SubscriberConfig config) {
        LOGGER.info("Initialising private decryption key");
        if (config.getSubscriberKeys() != null) {
            return config.getSubscriberKeys().getPrivateDecryptionKeys().get(0).toRSAKey();
        }
        final String decryptionKeyPath = getPrivateDecryptionKeyPathFromSubscriber(config);
        final String decryptionKey = readKeyFromPath(decryptionKeyPath);
        return parseRSAKeyFromString(decryptionKey);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy