diff --git a/backend/app/.env.properties b/backend/app/.env.properties index c6f5749..d7a12ca 100644 --- a/backend/app/.env.properties +++ b/backend/app/.env.properties @@ -32,3 +32,9 @@ VALTIMO_OPENKLANT_SECRET=e09b8bc5-5831-4618-ab28-41411304309d #org.openapitools.client.baseUrl=${BASEURL} org.openapitools.client.baseUrl=wdwwer +ROTTERDAM_XENTIAL_ACCOUNT_NAME=gzac +ROTTERDAM_XENTIAL_ACCOUNT_PASSWORD=12345 + +ROTTERDAM_ESB_SERVER_CERTIFICATE_FILENAME=/Users/paul/Documents/rotterdam/xential/test.gemeente-rotterdam.gzac.cloud_20241023.pem +ROTTERDAM_ESB_CLIENT_PRIVATE_KEY_FILENAME=/Users/paul/Documents/rotterdam/xential/private.key +ROTTERDAM_ESB_CLIENT_CERTIFICATE_FILENAME=/Users/paul/Documents/rotterdam/xential/test.gemeente-rotterdam.gzac.cloud_20241023.pem diff --git a/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/autoconfiguration/XentialAutoConfiguration.kt b/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/autoconfiguration/XentialAutoConfiguration.kt index a2afdcb..7ccf68b 100644 --- a/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/autoconfiguration/XentialAutoConfiguration.kt +++ b/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/autoconfiguration/XentialAutoConfiguration.kt @@ -18,6 +18,7 @@ package com.ritense.valtimo.xential.autoconfiguration import com.ritense.documentenapi.client.DocumentenApiClient import com.ritense.plugin.service.PluginService +import com.ritense.valtimo.contract.authentication.UserManagementService import com.ritense.valtimo.contract.config.LiquibaseMasterChangeLogLocation import com.ritense.valtimo.xential.plugin.XentialPluginFactory import com.ritense.valtimo.xential.repository.XentialTokenRepository @@ -25,17 +26,13 @@ import com.ritense.valtimo.xential.service.DocumentGenerationService import com.ritense.valueresolver.ValueResolverService import com.ritense.zakenapi.ZaakUrlProvider import com.ritense.zakenapi.client.ZakenApiClient -import com.rotterdam.xential.api.DefaultApi import org.camunda.bpm.engine.RuntimeService -import org.openapitools.client.infrastructure.ApiClient -import org.springframework.beans.factory.annotation.Value import org.springframework.boot.autoconfigure.AutoConfiguration import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.domain.EntityScan import org.springframework.context.ApplicationEventPublisher import org.springframework.context.annotation.Bean import org.springframework.data.jpa.repository.config.EnableJpaRepositories -import org.springframework.web.reactive.function.client.WebClient @AutoConfiguration @EnableJpaRepositories(basePackages = ["com.ritense.valtimo.xential.repository"]) @@ -60,23 +57,9 @@ class XentialAutoConfiguration { return LiquibaseMasterChangeLogLocation("config/liquibase/xential-plugin-master.xml") } - @Bean - @ConditionalOnMissingBean - fun defaultApi(): DefaultApi { - return DefaultApi() - } - -// @Value("\${plugin.xential.baseurl: }") baseUrl: String - - @Bean - @ConditionalOnMissingBean - fun apiClient( - ) = ApiClient("http://localhost:1080") - @Bean @ConditionalOnMissingBean fun documentGenerationService( - defaultApi: DefaultApi, xentialTokenRepository: XentialTokenRepository, pluginService: PluginService, documentenApiClient: DocumentenApiClient, @@ -84,9 +67,9 @@ class XentialAutoConfiguration { zaakUrlProvider: ZaakUrlProvider, zakenApiClient: ZakenApiClient, runtimeService: RuntimeService, - valueResolverService: ValueResolverService + valueResolverService: ValueResolverService, + userManagementService: UserManagementService ) = DocumentGenerationService( - defaultApi, xentialTokenRepository, pluginService, documentenApiClient, @@ -94,7 +77,7 @@ class XentialAutoConfiguration { zaakUrlProvider, zakenApiClient, runtimeService, - valueResolverService + valueResolverService, + userManagementService ) - } diff --git a/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/domain/HttpClientProperties.kt b/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/domain/HttpClientProperties.kt new file mode 100644 index 0000000..17f8388 --- /dev/null +++ b/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/domain/HttpClientProperties.kt @@ -0,0 +1,13 @@ +package com.ritense.valtimo.xential.domain + +import java.io.File +import java.net.URI + +data class HttpClientProperties( + val applicationName: String, + val applicationPassword: String, + val baseUrl: URI, + val serverCertificateFilename: File, + val clientPrivateKeyFilename: File?, + val clientCertFile: File? +) diff --git a/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/domain/XentialToken.kt b/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/domain/XentialToken.kt index dd9eabd..c050c87 100644 --- a/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/domain/XentialToken.kt +++ b/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/domain/XentialToken.kt @@ -2,21 +2,41 @@ package com.ritense.valtimo.xential.domain import jakarta.persistence.Column import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType import jakarta.persistence.Id import jakarta.persistence.Table -import java.net.URI import java.util.UUID @Entity @Table(name = "xential_tokens") data class XentialToken ( + @Id - @Column(name = "token", nullable = false, updatable = false) - val token: UUID, - @Column(name = "process_id", nullable = false, updatable = false) - val processId: UUID, - @Column(name = "message_name", nullable = false, updatable = false) - val messageName: String, - @Column(name = "resume_url", nullable = true, updatable = false) - val resumeUrl: URI?, + @GeneratedValue(strategy = GenerationType.AUTO) + @Column(name = "token") + val token: UUID = UUID.randomUUID(), + + @Column(name = "external_token") + val externalToken: String = "", + + @Column(name = "process_id") + val processId: UUID = UUID.randomUUID(), + + @Column(name = "message_name") + val messageName: String = "", + + @Column(name = "resume_url") + val resumeUrl: String = "" + +// @Id +// @Column(name = "token", nullable = false, updatable = false) +// val token: UUID, +// @Column(name = "process_id", nullable = false, updatable = false) +// val processId: UUID, +// @Column(name = "message_name", nullable = false, updatable = false) +// val messageName: String, +// @Column(name = "resume_url", nullable = true, updatable = false) +// val resumeUrl: URI?, @Id +// @Column(name = "token", nullable = false, updatable = false) ) diff --git a/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/plugin/XentialPlugin.kt b/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/plugin/XentialPlugin.kt index 5b34cb2..0f9da86 100644 --- a/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/plugin/XentialPlugin.kt +++ b/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/plugin/XentialPlugin.kt @@ -22,28 +22,35 @@ import com.ritense.plugin.annotation.PluginAction import com.ritense.plugin.annotation.PluginActionProperty import com.ritense.plugin.annotation.PluginProperty import com.ritense.processlink.domain.ActivityTypeWithEventName +import com.ritense.valtimo.xential.domain.HttpClientProperties import com.ritense.valtimo.xential.domain.FileFormat import com.ritense.valtimo.xential.domain.GenerateDocumentProperties import com.ritense.valtimo.xential.plugin.XentialPlugin.Companion.PLUGIN_KEY import com.ritense.valtimo.xential.service.DocumentGenerationService import com.ritense.zakenapi.ZakenApiPlugin import org.camunda.bpm.engine.delegate.DelegateExecution +import java.io.File +import java.net.URI import java.util.UUID @Plugin( key = PLUGIN_KEY, title = "Xential Plugin", - description = "" + description = "handle xentail requests" ) +@Suppress("UNUSED") class XentialPlugin( - val documentGenerationService: DocumentGenerationService + private val documentGenerationService: DocumentGenerationService ) { - @PluginProperty(key = "clientId", secret = false) - private lateinit var clientId: String + @PluginProperty(key = "applicationName", secret = false, required = true) + private lateinit var applicationName: String - @PluginProperty(key = "clientPassword", secret = true) - private lateinit var clientPassword: String + @PluginProperty(key = "applicationPassword", secret = true, required = true) + private lateinit var applicationPassword: String + + @PluginProperty(key = "baseUrl", secret = false, required = true) + lateinit var baseUrl: URI @PluginProperty(key = "documentenApiPluginConfiguration", secret = false) lateinit var documentenApiPluginConfiguration: DocumentenApiPlugin @@ -51,6 +58,14 @@ class XentialPlugin( @PluginProperty(key = "zakenApiPluginConfiguration", secret = false) lateinit var zakenApiPluginConfiguration: ZakenApiPlugin + @PluginProperty(key = "serverCertificateFilename", secret = false, required = true) + private lateinit var serverCertificateFilename: String + + @PluginProperty(key = "clientPrivateKeyFilename", secret = false, required = false) + var clientPrivateKeyFilename: String? = null + + @PluginProperty(key = "clientCertificateFilename", secret = false, required = false) + var clientCertificateFilename: String? = null @PluginAction( key = "generate-document", @@ -73,11 +88,20 @@ class XentialPlugin( messageName, templateData ) + + val httpClientProperties = HttpClientProperties( + applicationName, + applicationPassword, + baseUrl, + File(serverCertificateFilename), + clientPrivateKeyFilename?.let{File(it)}, + clientCertificateFilename?.let {File(it)} + ) + documentGenerationService.generateDocument( + httpClientProperties, UUID.fromString(execution.processInstanceId), generateDocumentProperties, - clientId, - clientPassword, execution ) } diff --git a/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/service/DocumentGenerationService.kt b/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/service/DocumentGenerationService.kt index 505b3b3..7156ff3 100644 --- a/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/service/DocumentGenerationService.kt +++ b/backend/plugin/src/main/kotlin/com/ritense/valtimo/xential/service/DocumentGenerationService.kt @@ -5,6 +5,8 @@ import com.ritense.documentenapi.client.DocumentStatusType import com.ritense.documentenapi.client.DocumentenApiClient import com.ritense.documentenapi.event.DocumentCreated import com.ritense.plugin.service.PluginService +import com.ritense.valtimo.contract.authentication.UserManagementService +import com.ritense.valtimo.xential.domain.HttpClientProperties import com.ritense.valtimo.xential.domain.DocumentCreatedMessage import com.ritense.valtimo.xential.domain.GenerateDocumentProperties import com.ritense.valtimo.xential.domain.XentialToken @@ -17,42 +19,52 @@ import com.ritense.zakenapi.client.LinkDocumentRequest import com.ritense.zakenapi.client.ZakenApiClient import com.rotterdam.xential.api.DefaultApi import com.rotterdam.xential.model.Sjabloondata +import mu.KotlinLogging +import okhttp3.Credentials +import okhttp3.OkHttpClient import org.camunda.bpm.engine.RuntimeService import org.camunda.bpm.engine.delegate.DelegateExecution -import org.openapitools.client.infrastructure.ApiClient import org.springframework.context.ApplicationEventPublisher import java.io.ByteArrayInputStream +import java.io.File +import java.io.FileInputStream +import java.security.KeyFactory +import java.security.KeyStore +import java.security.cert.CertificateFactory +import java.security.spec.PKCS8EncodedKeySpec import java.time.LocalDate -import java.util.Base64 -import java.util.UUID +import java.util.* +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory +import javax.net.ssl.X509TrustManager + class DocumentGenerationService( - val defaultApi: DefaultApi, - val xentialTokenRepository: XentialTokenRepository, - val pluginService: PluginService, - val documentenApiClient: DocumentenApiClient, - val applicationEventPublisher: ApplicationEventPublisher, - val zaakUrlProvider: ZaakUrlProvider, - val zakenApiClient: ZakenApiClient, - val runtimeService: RuntimeService, - val valueResolverService: ValueResolverService - ) { + private val xentialTokenRepository: XentialTokenRepository, + private val pluginService: PluginService, + private val documentenApiClient: DocumentenApiClient, + private val applicationEventPublisher: ApplicationEventPublisher, + private val zaakUrlProvider: ZaakUrlProvider, + private val zakenApiClient: ZakenApiClient, + private val runtimeService: RuntimeService, + private val valueResolverService: ValueResolverService, + private val userManagementService: UserManagementService +) { fun generateDocument( + httpClientProperties: HttpClientProperties, processId: UUID, generateDocumentProperties: GenerateDocumentProperties, - clientId: String, - clientPassword: String, execution: DelegateExecution, ) { + logger.info { "current userid: ${userManagementService.currentUserId}" } - val resolvedMap = resolveTemplateData(generateDocumentProperties.templateData,execution) + val api = configureClient(httpClientProperties) + val resolvedMap = resolveTemplateData(generateDocumentProperties.templateData, execution) val sjabloonVulData = resolvedMap.map { "<${it.key}>${it.value}" }.joinToString() - - ApiClient.username = clientId - ApiClient.password = clientPassword - val result = defaultApi.creeerDocument( - gebruikersId = clientId, + val result = api.creeerDocument( + gebruikersId = userManagementService.currentUserId, accepteerOnbekend = false, sjabloondata = Sjabloondata( sjabloonId = generateDocumentProperties.templateId.toString(), @@ -61,15 +73,89 @@ class DocumentGenerationService( sjabloonVulData = "$sjabloonVulData" ) ) - + logger.info { "found something: $result" } val xentialToken = XentialToken( token = UUID.fromString(result.documentCreatieSessieId), + externalToken = result.documentCreatieSessieId, processId = processId, messageName = generateDocumentProperties.messageName, - resumeUrl = result.resumeUrl + resumeUrl = result.resumeUrl.toString() ) - + logger.info { "token: ${xentialToken.token}" } xentialTokenRepository.save(xentialToken) + logger.info { "ready" } + } + + private fun trustManagerFactory(certFile: File): TrustManagerFactory { + + // Load the server certificate + val certificateFactory = CertificateFactory.getInstance("X.509") + val serverCert = certificateFactory.generateCertificate(FileInputStream(certFile)) + + // Create a KeyStore with the server certificate + val trustStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply { + load(null, null) + setCertificateEntry("server", serverCert) + } + + // Configure the TrustManager + val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + trustManagerFactory.init(trustStore) + return trustManagerFactory + } + + private fun keyManagerFactory(privateKeyFile: File?, clientCertFile: File?): KeyManagerFactory? { + return if (privateKeyFile != null && clientCertFile != null) { + val certificateFactory = CertificateFactory.getInstance("X.509") + val clientCert = certificateFactory.generateCertificate(FileInputStream(clientCertFile)) + val privateKey = loadPrivateKey(privateKeyFile) + + // Create a KeyStore with the client certificate and private key + val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply { + load(null, null) + setKeyEntry("client", privateKey, null, arrayOf(clientCert)) + } + + KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply { + init(keyStore, null) + } + } else null + } + + private fun configureClient(properties: HttpClientProperties): DefaultApi { + val trustManagerFactory = trustManagerFactory(properties.serverCertificateFilename) + val keyManagerFactory = keyManagerFactory( + properties.clientPrivateKeyFilename, + properties.clientCertFile + ) + + val sslContext = SSLContext.getInstance("TLS").apply { + init(keyManagerFactory?.keyManagers, trustManagerFactory.trustManagers, null) + } + + val credentials = Credentials.basic(properties.applicationName, properties.applicationPassword) + val customClient = OkHttpClient.Builder() + .addInterceptor { chain -> + val request = chain.request().newBuilder() + .header("Authorization", credentials) + .build() + chain.proceed(request) + } + .sslSocketFactory(sslContext.socketFactory, trustManagerFactory.trustManagers[0] as X509TrustManager) + .build() + + return DefaultApi(properties.baseUrl.toString(), customClient) + } + + private fun loadPrivateKey(file: File): java.security.PrivateKey { + val keyBytes = file.readText() + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replace("\\s".toRegex(), "") + .let { Base64.getDecoder().decode(it) } + + val keySpec = PKCS8EncodedKeySpec(keyBytes) + return KeyFactory.getInstance("RSA").generatePrivate(keySpec) } fun onDocumentGenerated(message: DocumentCreatedMessage) { @@ -147,8 +233,12 @@ class DocumentGenerationService( private fun getXentialPlugin(message: DocumentCreatedMessage): XentialPlugin { //FIXME needs a way of determining the right plugin - val pluginConfig = pluginService.findPluginConfiguration(XentialPlugin.PLUGIN_KEY) { _ -> true} + val pluginConfig = pluginService.findPluginConfiguration(XentialPlugin.PLUGIN_KEY) { _ -> true } ?: throw NoSuchElementException("Could not find Xential plugin") return pluginService.createInstance(pluginConfig) as XentialPlugin } + + companion object { + private val logger = KotlinLogging.logger {} + } } diff --git a/backend/plugin/src/main/resources/config/liquibase/changelog/20241010-add-xential_tokens.xml b/backend/plugin/src/main/resources/config/liquibase/changelog/20241010-add-xential_tokens.xml index 1c6e952..246dbca 100644 --- a/backend/plugin/src/main/resources/config/liquibase/changelog/20241010-add-xential_tokens.xml +++ b/backend/plugin/src/main/resources/config/liquibase/changelog/20241010-add-xential_tokens.xml @@ -19,12 +19,16 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.8.xsd"> - - - - + + + + + + + + diff --git a/backend/plugin/src/main/resources/config/plugin/common.pluginconfig.json b/backend/plugin/src/main/resources/config/plugin/common.pluginconfig.json index 2d3d8cf..a889e59 100644 --- a/backend/plugin/src/main/resources/config/plugin/common.pluginconfig.json +++ b/backend/plugin/src/main/resources/config/plugin/common.pluginconfig.json @@ -3,10 +3,14 @@ "id": "6e034748-8ed5-43de-a5f2-f80e1a4b60dd", "title": "Xential", "properties": { - "clientId": "test", - "clientPassword": "pwd", + "applicationName": "${ROTTERDAM_XENTIAL_ACCOUNT_NAME}", + "applicationPassword": "${ROTTERDAM_XENTIAL_ACCOUNT_PASSWORD}", + "baseUrl": "http://localhost:1080", "documentenApiPluginConfiguration": "5474fe57-532a-4050-8d89-32e62ca3e895", - "zakenApiPluginConfiguration": "3079d6fe-42e3-4f8f-a9db-52ce2507b7ee" + "zakenApiPluginConfiguration": "3079d6fe-42e3-4f8f-a9db-52ce2507b7ee", + "serverCertificateFilename": "${ROTTERDAM_ESB_SERVER_CERTIFICATE_FILENAME}", + "clientPrivateKeyFilename": "${ROTTERDAM_ESB_CLIENT_PRIVATE_KEY_FILENAME}", + "clientCertificateFilename": "${ROTTERDAM_ESB_CLIENT_CERTIFICATE_FILENAME}" }, "pluginDefinitionKey": "xential" } diff --git a/frontend/projects/valtimo/xential/src/lib/components/xential-configuration/xential-configuration.component.html b/frontend/projects/valtimo/xential/src/lib/components/xential-configuration/xential-configuration.component.html index 2a4c57c..4a8b39d 100644 --- a/frontend/projects/valtimo/xential/src/lib/components/xential-configuration/xential-configuration.component.html +++ b/frontend/projects/valtimo/xential/src/lib/components/xential-configuration/xential-configuration.component.html @@ -31,18 +31,64 @@ > + name="applicationName" + [title]="'applicationName' | pluginTranslate: pluginId | async" + [margin]="true" + [fullWidth]="true" + [defaultValue]="obs.prefill?.applicationName" + [disabled]="obs.disabled" + [required]="true" + > + + +
+ name="serverCertificateFilename" + [title]="'serverCertificateFilename' | pluginTranslate: pluginId | async" + [margin]="true" + [fullWidth]="true" + [defaultValue]="obs.prefill?.serverCertificateFilename" + [disabled]="obs.disabled" + [required]="true" + > + + + +
; -} - export {XentialConfig}; diff --git a/frontend/projects/valtimo/xential/src/lib/models/generate-document-config.ts b/frontend/projects/valtimo/xential/src/lib/models/generate-document-config.ts index 9e4dd7f..358330e 100644 --- a/frontend/projects/valtimo/xential/src/lib/models/generate-document-config.ts +++ b/frontend/projects/valtimo/xential/src/lib/models/generate-document-config.ts @@ -2,6 +2,7 @@ interface GenerateDocumentConfig { templateId: string; fileFormat: FileFormat; documentId: string; + messageName: "sfsefef" templateData: Array<{key: string; value: string}> }