diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificationRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificationRequestStorage.kt index d041fd0888..b1115ff1df 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificationRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/CertificationRequestStorage.kt @@ -40,6 +40,11 @@ interface CertificationRequestStorage { */ fun getRequests(requestStatus: RequestStatus): List + /** + * Persist the fact that a ticket has been created for the given [requestId]. + */ + fun markRequestTicketCreated(requestId: String) + /** * Approve the given request if it has not already been approved. Otherwise do nothing. * @param requestId id of the certificate signing request @@ -72,7 +77,30 @@ sealed class CertificateResponse { } enum class RequestStatus { - NEW, APPROVED, REJECTED, SIGNED + /** + * The request has been received, this is the initial state in which a request has been created. + */ + NEW, + + /** + * A ticket has been created but has not yet been approved nor rejected. + */ + TICKET_CREATED, + + /** + * The request has been approved, but not yet signed. + */ + APPROVED, + + /** + * The request has been rejected, this is a terminal state, once a request gets in this state it won't change anymore. + */ + REJECTED, + + /** + * The request has been signed, this is a terminal state, once a request gets in this state it won't change anymore. + */ + SIGNED } enum class CertificateStatus { diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRequestStorage.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRequestStorage.kt index 960e31ffd1..3e3c7a8f4c 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRequestStorage.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/common/persistence/PersistentCertificateRequestStorage.kt @@ -7,6 +7,7 @@ import net.corda.core.crypto.SecureHash import net.corda.core.identity.CordaX500Name import net.corda.core.internal.x500Name import net.corda.node.utilities.CordaPersistence +import net.corda.node.utilities.DatabaseTransaction import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.hibernate.Session @@ -57,13 +58,34 @@ class PersistentCertificateRequestStorage(private val database: CordaPersistence return requestId } + private fun DatabaseTransaction.findRequest(requestId: String, + requestStatus: RequestStatus? = null): CertificateSigningRequestEntity? { + return singleRequestWhere(CertificateSigningRequestEntity::class.java) { builder, path -> + val idClause = builder.equal(path.get(CertificateSigningRequestEntity::requestId.name), requestId) + if (requestStatus == null) { + idClause + } else { + val statusClause = builder.equal(path.get(CertificateSigningRequestEntity::status.name), requestStatus) + builder.and(idClause, statusClause) + } + } + } + + override fun markRequestTicketCreated(requestId: String) { + return database.transaction(Connection.TRANSACTION_SERIALIZABLE) { + val request = findRequest(requestId, RequestStatus.NEW) + request ?: throw IllegalArgumentException("Error when creating request ticket with id: $requestId. Request does not exist or its status is not NEW.") + val update = request.copy( + modifiedAt = Instant.now(), + status = RequestStatus.TICKET_CREATED) + session.merge(update) + } + } + override fun approveRequest(requestId: String, approvedBy: String) { return database.transaction(Connection.TRANSACTION_SERIALIZABLE) { - val request = singleRequestWhere(CertificateSigningRequestEntity::class.java) { builder, path -> - builder.and(builder.equal(path.get(CertificateSigningRequestEntity::requestId.name), requestId), - builder.equal(path.get(CertificateSigningRequestEntity::status.name), RequestStatus.NEW)) - } - request ?: throw IllegalArgumentException("Error when approving request with id: $requestId. Request does not exist or its status is not NEW.") + val request = findRequest(requestId, RequestStatus.TICKET_CREATED) + request ?: throw IllegalArgumentException("Error when approving request with id: $requestId. Request does not exist or its status is not TICKET_CREATED.") val update = request.copy( modifiedBy = listOf(approvedBy), modifiedAt = Instant.now(), @@ -74,9 +96,7 @@ class PersistentCertificateRequestStorage(private val database: CordaPersistence override fun rejectRequest(requestId: String, rejectedBy: String, rejectReason: String) { database.transaction(Connection.TRANSACTION_SERIALIZABLE) { - val request = singleRequestWhere(CertificateSigningRequestEntity::class.java) { builder, path -> - builder.equal(path.get(CertificateSigningRequestEntity::requestId.name), requestId) - } + val request = findRequest(requestId) request ?: throw IllegalArgumentException("Error when rejecting request with id: $requestId. Request does not exist.") val update = request.copy( modifiedBy = listOf(rejectedBy), @@ -90,9 +110,7 @@ class PersistentCertificateRequestStorage(private val database: CordaPersistence override fun getRequest(requestId: String): CertificateSigningRequest? { return database.transaction { - singleRequestWhere(CertificateSigningRequestEntity::class.java) { builder, path -> - builder.equal(path.get(CertificateSigningRequestEntity::requestId.name), requestId) - }?.toCertificateSigningRequest() + findRequest(requestId)?.toCertificateSigningRequest() } } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/JiraCient.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/JiraCient.kt index 35f339fb96..16e82e2ba0 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/JiraCient.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/JiraCient.kt @@ -2,6 +2,7 @@ package com.r3.corda.networkmanage.doorman import com.atlassian.jira.rest.client.api.JiraRestClient import com.atlassian.jira.rest.client.api.domain.Field +import com.atlassian.jira.rest.client.api.domain.Issue import com.atlassian.jira.rest.client.api.domain.IssueType import com.atlassian.jira.rest.client.api.domain.input.IssueInputBuilder import com.atlassian.jira.rest.client.api.domain.input.TransitionInput @@ -27,6 +28,12 @@ class JiraClient(private val restClient: JiraRestClient, private val projectCode private val taskIssueType: IssueType = restClient.metadataClient.issueTypes.claim().find { it.name == "Task" } ?: throw IllegalArgumentException("Task issue type field not found in JIRA '$projectCode'") fun createRequestTicket(requestId: String, signingRequest: PKCS10CertificationRequest) { + // Check there isn't already a ticket for this request. + if (getIssueById(requestId) != null) { + logger.warn("There is already a ticket corresponding to request Id $requestId, not creating a new one.") + return + } + // Make sure request has been accepted. val request = StringWriter() JcaPEMWriter(request).use { @@ -63,7 +70,7 @@ class JiraClient(private val restClient: JiraRestClient, private val projectCode signedRequests.forEach { (id, certPath) -> val certificate = certPath.certificates.first() // Jira only support ~ (contains) search for custom textfield. - val issue = restClient.searchClient.searchJql("'Request ID' ~ $id").claim().issues.firstOrNull() + val issue = getIssueById(id) if (issue != null) { restClient.issueClient.transition(issue, TransitionInput(doneTransitionCode)).fail { logger.error("Exception when transiting JIRA status.", it) }.claim() restClient.issueClient.addAttachment(issue.attachmentsUri, certificate?.encoded?.inputStream(), "${X509Utilities.CORDA_CLIENT_CA}.cer") @@ -71,4 +78,7 @@ class JiraClient(private val restClient: JiraRestClient, private val projectCode } } } + + private fun getIssueById(requestId: String): Issue? = + restClient.searchClient.searchJql("'Request ID' ~ $requestId").claim().issues.firstOrNull() } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt index b179ac3151..a73a6564ea 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/Main.kt @@ -194,6 +194,9 @@ fun startDoorman(hostAndPort: NetworkHostAndPort, val approvalThread = Runnable { try { DoormanServer.serverStatus.lastRequestCheckTime = Instant.now() + // Create tickets for requests which don't have one yet. + requestProcessor.createTickets() + // Process Jira approved tickets. requestProcessor.processApprovedRequests() } catch (e: Exception) { // Log the error and carry on. @@ -238,6 +241,7 @@ private fun buildLocalSigner(parameters: DoormanParameters): LocalSigner? { private class ApproveAllCertificateRequestStorage(private val delegate: CertificationRequestStorage) : CertificationRequestStorage by delegate { override fun saveRequest(request: PKCS10CertificationRequest): String { val requestId = delegate.saveRequest(request) + delegate.markRequestTicketCreated(requestId) approveRequest(requestId, DOORMAN_SIGNATURE) return requestId } diff --git a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/CsrHandler.kt b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/CsrHandler.kt index 6176a49337..923143b4a1 100644 --- a/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/CsrHandler.kt +++ b/network-management/src/main/kotlin/com/r3/corda/networkmanage/doorman/signer/CsrHandler.kt @@ -1,14 +1,17 @@ package com.r3.corda.networkmanage.doorman.signer import com.r3.corda.networkmanage.common.persistence.CertificateResponse +import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage.Companion.DOORMAN_SIGNATURE import com.r3.corda.networkmanage.common.persistence.RequestStatus import com.r3.corda.networkmanage.doorman.JiraClient +import net.corda.core.utilities.loggerFor import org.bouncycastle.pkcs.PKCS10CertificationRequest interface CsrHandler { fun saveRequest(rawRequest: PKCS10CertificationRequest): String + fun createTickets() fun processApprovedRequests() fun getResponse(requestId: String): CertificateResponse } @@ -19,6 +22,8 @@ class DefaultCsrHandler(private val storage: CertificationRequestStorage, privat .forEach { processRequest(it.requestId, it.request) } } + override fun createTickets() { } + private fun processRequest(requestId: String, request: PKCS10CertificationRequest) { if (signer != null) { val certs = signer.createSignedClientCertificate(request) @@ -35,7 +40,7 @@ class DefaultCsrHandler(private val storage: CertificationRequestStorage, privat override fun getResponse(requestId: String): CertificateResponse { val response = storage.getRequest(requestId) return when (response?.status) { - RequestStatus.NEW, RequestStatus.APPROVED, null -> CertificateResponse.NotReady + RequestStatus.NEW, RequestStatus.APPROVED, RequestStatus.TICKET_CREATED, null -> CertificateResponse.NotReady RequestStatus.REJECTED -> CertificateResponse.Unauthorised(response.remark ?: "Unknown reason") RequestStatus.SIGNED -> CertificateResponse.Ready(response.certData?.certPath ?: throw IllegalArgumentException("Certificate should not be null.")) } @@ -43,13 +48,23 @@ class DefaultCsrHandler(private val storage: CertificationRequestStorage, privat } class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: CertificationRequestStorage, private val delegate: CsrHandler) : CsrHandler by delegate { + private companion object { + val log = loggerFor() + } + override fun saveRequest(rawRequest: PKCS10CertificationRequest): String { val requestId = delegate.saveRequest(rawRequest) // Make sure request has been accepted. - if (delegate.getResponse(requestId) !is CertificateResponse.Unauthorised) { - jiraClient.createRequestTicket(requestId, rawRequest) + try { + if (delegate.getResponse(requestId) !is CertificateResponse.Unauthorised) { + jiraClient.createRequestTicket(requestId, rawRequest) + storage.markRequestTicketCreated(requestId) + } + } catch (e: Exception) { + log.warn("There was an error while creating Jira tickets", e) + } finally { + return requestId } - return requestId } override fun processApprovedRequests() { @@ -60,4 +75,25 @@ class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: Ce }.toMap() jiraClient.updateSignedRequests(signedRequests) } + + /** + * Creates Jira tickets for all request in [RequestStatus.NEW] state. + * + * Usually requests are expected to move to the [RequestStatus.TICKET_CREATED] state immediately, + * they might be left in the [RequestStatus.NEW] state if Jira is down. + */ + override fun createTickets() { + try { + for (signingRequest in storage.getRequests(RequestStatus.NEW)) { + createTicket(signingRequest) + } + } catch (e: Exception) { + log.warn("There were errors while creating Jira tickets", e) + } + } + + private fun createTicket(signingRequest: CertificateSigningRequest) { + jiraClient.createRequestTicket(signingRequest.requestId, signingRequest.request) + storage.markRequestTicketCreated(signingRequest.requestId) + } } diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBCertificateRequestStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBCertificateRequestStorageTest.kt index d5caf38029..ae2ec1bed3 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBCertificateRequestStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBCertificateRequestStorageTest.kt @@ -58,6 +58,8 @@ class DBCertificateRequestStorageTest : TestBase() { assertEquals(1, storage.getRequests(RequestStatus.NEW).size) // Certificate should be empty. assertNull(storage.getRequest(requestId)!!.certData) + // Signal that a ticket has been created for the request. + storage.markRequestTicketCreated(requestId) // Store certificate to DB. storage.approveRequest(requestId, DOORMAN_SIGNATURE) // Check request is not ready yet. @@ -72,6 +74,7 @@ class DBCertificateRequestStorageTest : TestBase() { val (request, _) = createRequest("LegalName") // Add request to DB. val requestId = storage.saveRequest(request) + storage.markRequestTicketCreated(requestId) storage.approveRequest(requestId, "ApproverA") var thrown: Exception? = null @@ -94,6 +97,7 @@ class DBCertificateRequestStorageTest : TestBase() { assertEquals(1, storage.getRequests(RequestStatus.NEW).size) // Certificate should be empty. assertNull(storage.getRequest(requestId)!!.certData) + storage.markRequestTicketCreated(requestId) // Store certificate to DB. storage.approveRequest(requestId, DOORMAN_SIGNATURE) // Check request is not ready yet. @@ -119,6 +123,7 @@ class DBCertificateRequestStorageTest : TestBase() { // Add request to DB. val requestId = storage.saveRequest(csr) // Store certificate to DB. + storage.markRequestTicketCreated(requestId) storage.approveRequest(requestId, DOORMAN_SIGNATURE) storage.putCertificatePath(requestId, JcaPKCS10CertificationRequest(csr).run { val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) @@ -159,6 +164,7 @@ class DBCertificateRequestStorageTest : TestBase() { assertEquals(RequestStatus.REJECTED, storage.getRequest(requestId2)!!.status) assertThat(storage.getRequest(requestId2)!!.remark).containsIgnoringCase("duplicate") // Make sure the first request is processed properly + storage.markRequestTicketCreated(requestId1) storage.approveRequest(requestId1, DOORMAN_SIGNATURE) assertThat(storage.getRequest(requestId1)!!.status).isEqualTo(RequestStatus.APPROVED) } @@ -166,6 +172,7 @@ class DBCertificateRequestStorageTest : TestBase() { @Test fun `request with the same legal name as a previously approved request`() { val requestId1 = storage.saveRequest(createRequest("BankA").first) + storage.markRequestTicketCreated(requestId1) storage.approveRequest(requestId1, DOORMAN_SIGNATURE) val requestId2 = storage.saveRequest(createRequest("BankA").first) assertThat(storage.getRequest(requestId2)!!.remark).containsIgnoringCase("duplicate") @@ -177,6 +184,7 @@ class DBCertificateRequestStorageTest : TestBase() { storage.rejectRequest(requestId1, DOORMAN_SIGNATURE, "Because I said so!") val requestId2 = storage.saveRequest(createRequest("BankA").first) assertThat(storage.getRequests(RequestStatus.NEW).map { it.requestId }).containsOnly(requestId2) + storage.markRequestTicketCreated(requestId2) storage.approveRequest(requestId2, DOORMAN_SIGNATURE) assertThat(storage.getRequest(requestId2)!!.status).isEqualTo(RequestStatus.APPROVED) } @@ -188,6 +196,7 @@ class DBCertificateRequestStorageTest : TestBase() { // when val requestId = storage.saveRequest(createRequest("BankA").first) + storage.markRequestTicketCreated(requestId) storage.approveRequest(requestId, approver) // then @@ -196,7 +205,12 @@ class DBCertificateRequestStorageTest : TestBase() { val newRevision = auditReader.find(CertificateSigningRequestEntity::class.java, requestId, 1) assertEquals(RequestStatus.NEW, newRevision.status) assertTrue(newRevision.modifiedBy.isEmpty()) - val approvedRevision = auditReader.find(CertificateSigningRequestEntity::class.java, requestId, 2) + + val ticketCreatedRevision = auditReader.find(CertificateSigningRequestEntity::class.java, requestId, 2) + assertEquals(RequestStatus.TICKET_CREATED, ticketCreatedRevision.status) + assertTrue(ticketCreatedRevision.modifiedBy.isEmpty()) + + val approvedRevision = auditReader.find(CertificateSigningRequestEntity::class.java, requestId, 3) assertEquals(RequestStatus.APPROVED, approvedRevision.status) assertEquals(approver, approvedRevision.modifiedBy.first()) } diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt index cc5d252a02..bfd313125b 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/DBNetworkMapStorageTest.kt @@ -56,6 +56,7 @@ class DBNetworkMapStorageTest : TestBase() { // Create node info. val organisation = "Test" val requestId = requestStorage.saveRequest(createRequest(organisation).first) + requestStorage.markRequestTicketCreated(requestId) requestStorage.approveRequest(requestId, "TestUser") val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val clientCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public) @@ -126,6 +127,7 @@ class DBNetworkMapStorageTest : TestBase() { // Create node info. val organisationA = "TestA" val requestIdA = requestStorage.saveRequest(createRequest(organisationA).first) + requestStorage.markRequestTicketCreated(requestIdA) requestStorage.approveRequest(requestIdA, "TestUser") val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val clientCertA = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisationA, locality = "London", country = "GB"), keyPair.public) @@ -133,6 +135,7 @@ class DBNetworkMapStorageTest : TestBase() { requestStorage.putCertificatePath(requestIdA, certPathA, emptyList()) val organisationB = "TestB" val requestIdB = requestStorage.saveRequest(createRequest(organisationB).first) + requestStorage.markRequestTicketCreated(requestIdB) requestStorage.approveRequest(requestIdB, "TestUser") val clientCertB = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisationB, locality = "London", country = "GB"), Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME).public) val certPathB = buildCertPath(clientCertB.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt index 1bcc4e922d..9229573296 100644 --- a/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/common/persistence/PersitenceNodeInfoStorageTest.kt @@ -57,6 +57,7 @@ class PersitenceNodeInfoStorageTest : TestBase() { val request = X509Utilities.createCertificateSigningRequest(nodeInfo.legalIdentities.first().name, "my@mail.com", keyPair) val requestId = requestStorage.saveRequest(request) + requestStorage.markRequestTicketCreated(requestId) requestStorage.approveRequest(requestId, CertificationRequestStorage.DOORMAN_SIGNATURE) assertNull(nodeInfoStorage.getCertificatePath(SecureHash.parse(keyPair.public.hashString()))) @@ -74,6 +75,7 @@ class PersitenceNodeInfoStorageTest : TestBase() { // given val organisationA = "TestA" val requestIdA = requestStorage.saveRequest(createRequest(organisationA).first) + requestStorage.markRequestTicketCreated(requestIdA) requestStorage.approveRequest(requestIdA, "TestUser") val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val clientCertA = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisationA, locality = "London", country = "GB"), keyPair.public) @@ -81,6 +83,7 @@ class PersitenceNodeInfoStorageTest : TestBase() { requestStorage.putCertificatePath(requestIdA, certPathA, emptyList()) val organisationB = "TestB" val requestIdB = requestStorage.saveRequest(createRequest(organisationB).first) + requestStorage.markRequestTicketCreated(requestIdB) requestStorage.approveRequest(requestIdB, "TestUser") val clientCertB = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisationB, locality = "London", country = "GB"), Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME).public) val certPathB = buildCertPath(clientCertB.toX509Certificate(), intermediateCACert.toX509Certificate(), rootCACert.toX509Certificate()) @@ -110,6 +113,7 @@ class PersitenceNodeInfoStorageTest : TestBase() { // Create node info. val organisation = "Test" val requestId = requestStorage.saveRequest(createRequest(organisation).first) + requestStorage.markRequestTicketCreated(requestId) requestStorage.approveRequest(requestId, "TestUser") val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val clientCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public) @@ -137,6 +141,7 @@ class PersitenceNodeInfoStorageTest : TestBase() { // Create node info. val organisation = "Test" val requestId = requestStorage.saveRequest(createRequest(organisation).first) + requestStorage.markRequestTicketCreated(requestId) requestStorage.approveRequest(requestId, "TestUser") val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val clientCert = X509Utilities.createCertificate(CertificateType.CLIENT_CA, intermediateCACert, intermediateCAKey, CordaX500Name(organisation = organisation, locality = "London", country = "GB"), keyPair.public) diff --git a/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/signer/CsrHandlerTest.kt b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/signer/CsrHandlerTest.kt new file mode 100644 index 0000000000..854a749845 --- /dev/null +++ b/network-management/src/test/kotlin/com/r3/corda/networkmanage/doorman/signer/CsrHandlerTest.kt @@ -0,0 +1,84 @@ +package com.r3.corda.networkmanage.doorman.signer + +import com.nhaarman.mockito_kotlin.* +import com.r3.corda.networkmanage.common.persistence.CertificateResponse +import com.r3.corda.networkmanage.common.persistence.CertificateSigningRequest +import com.r3.corda.networkmanage.common.persistence.CertificationRequestStorage +import com.r3.corda.networkmanage.common.persistence.RequestStatus +import com.r3.corda.networkmanage.doorman.JiraClient +import net.corda.core.crypto.Crypto +import net.corda.core.identity.CordaX500Name +import net.corda.node.utilities.X509Utilities +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import java.security.cert.CertPath + +class JiraCsrHandlerTest { + + @Rule + @JvmField + val mockitoRule = MockitoJUnit.rule() + + @Mock + lateinit var jiraClient: JiraClient + + @Mock + lateinit var certificationRequestStorage: CertificationRequestStorage + + @Mock + lateinit var defaultCsrHandler: DefaultCsrHandler + + @Mock + var certPath : CertPath = mock() + + private lateinit var jiraCsrHandler : JiraCsrHandler + private val requestId = "id" + private lateinit var certificateResponse : CertificateResponse.Ready + + private val keyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) + private val pkcS10CertificationRequest = X509Utilities.createCertificateSigningRequest(CordaX500Name(locality = "London", organisation = "LegalName", country = "GB"), "my@mail.com", keyPair) + + @Before + fun setup() { + jiraCsrHandler = JiraCsrHandler(jiraClient, certificationRequestStorage, defaultCsrHandler) + certificateResponse = CertificateResponse.Ready(certPath) + } + + @Test + fun `If jira connection fails we don't mark the ticket as created`() { + whenever(defaultCsrHandler.saveRequest(any())).thenReturn(requestId) + whenever(defaultCsrHandler.getResponse(requestId)).thenReturn(certificateResponse) + whenever(jiraClient.createRequestTicket(eq(requestId), any())).thenThrow(IllegalStateException("something broke")) + + // Test + jiraCsrHandler.saveRequest(pkcS10CertificationRequest) + + verify(certificationRequestStorage, never()).markRequestTicketCreated(requestId) + } + + @Test + fun `If jira connection works we mark the ticket as created`() { + whenever(defaultCsrHandler.saveRequest(any())).thenReturn(requestId) + whenever(defaultCsrHandler.getResponse(requestId)).thenReturn(certificateResponse) + + // Test + jiraCsrHandler.saveRequest(pkcS10CertificationRequest) + + verify(certificationRequestStorage, times(1)).markRequestTicketCreated(requestId) + } + + @Test + fun `create tickets`() { + val csr = CertificateSigningRequest(requestId, "name", RequestStatus.NEW, pkcS10CertificationRequest, null, emptyList(), null) + whenever(certificationRequestStorage.getRequests(RequestStatus.NEW)).thenReturn(listOf(csr)) + + // Test + jiraCsrHandler.createTickets() + + verify(jiraClient).createRequestTicket(requestId, csr.request) + verify(certificationRequestStorage).markRequestTicketCreated(requestId) + } +} \ No newline at end of file