mirror of
https://github.com/corda/corda.git
synced 2025-01-14 16:59:52 +00:00
Handle rejected jira issues in doorman (#371)
* handle reject status from jira - WIP * fix up after rebase * address PR issue and fix build error after rebase
This commit is contained in:
parent
3094e44115
commit
43604ed212
@ -65,13 +65,15 @@ The doorman service can use JIRA to manage the certificate signing request appro
|
||||
projectCode = "TD"
|
||||
username = "username"
|
||||
password = "password"
|
||||
doneTransitionCode = 41
|
||||
}
|
||||
.
|
||||
.
|
||||
.
|
||||
}
|
||||
```
|
||||
#### JIRA project configuration
|
||||
* The JIRA project should setup as "Business Project" with "Task" workflow.
|
||||
* Custom text field input "Request ID", and "Reject Reason" should be created in JIRA, doorman will exit with error without these custom fields.
|
||||
|
||||
### Auto approval
|
||||
When `approveAll` is set to `true`, the doorman will approve all requests on receive. (*This should only be enabled in a test environment)
|
||||
@ -118,7 +120,6 @@ doormanConfig {
|
||||
projectCode = "TD"
|
||||
username = "username"
|
||||
password = "password"
|
||||
doneTransitionCode = 41
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ dependencies {
|
||||
testCompile "com.nhaarman:mockito-kotlin:0.6.1"
|
||||
testCompile "com.spotify:docker-client:8.9.1"
|
||||
|
||||
compile('com.atlassian.jira:jira-rest-java-client-core:4.0.0') {
|
||||
compile('com.atlassian.jira:jira-rest-java-client-core:5.0.4') {
|
||||
// The jira client includes jersey-core 1.5 which breaks everything.
|
||||
exclude module: 'jersey-core'
|
||||
}
|
||||
|
@ -23,7 +23,6 @@ doormanConfig{
|
||||
projectCode = "TD"
|
||||
username = "username"
|
||||
password = "password"
|
||||
doneTransitionCode = 41
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,7 +59,7 @@ interface CertificationRequestStorage {
|
||||
* @param rejectedBy authority (its identifier) rejecting this request.
|
||||
* @param rejectReason brief description of the rejection reason
|
||||
*/
|
||||
fun rejectRequest(requestId: String, rejectedBy: String, rejectReason: String)
|
||||
fun rejectRequest(requestId: String, rejectedBy: String, rejectReason: String?)
|
||||
|
||||
/**
|
||||
* Store certificate path with [requestId], this will store the encoded [CertPath] and transit request status to [RequestStatus.SIGNED].
|
||||
|
@ -93,7 +93,7 @@ class PersistentCertificateRequestStorage(private val database: CordaPersistence
|
||||
}
|
||||
}
|
||||
|
||||
override fun rejectRequest(requestId: String, rejectedBy: String, rejectReason: String) {
|
||||
override fun rejectRequest(requestId: String, rejectedBy: String, rejectReason: String?) {
|
||||
database.transaction(TransactionIsolationLevel.SERIALIZABLE) {
|
||||
val request = findRequest(requestId)
|
||||
request ?: throw IllegalArgumentException("Error when rejecting request with id: $requestId. Request does not exist.")
|
||||
|
@ -65,8 +65,7 @@ data class JiraConfig(
|
||||
val address: String,
|
||||
val projectCode: String,
|
||||
val username: String,
|
||||
val password: String,
|
||||
val doneTransitionCode: Int
|
||||
val password: String
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -1,13 +1,15 @@
|
||||
package com.r3.corda.networkmanage.doorman
|
||||
|
||||
import com.atlassian.jira.rest.client.api.IssueRestClient
|
||||
import com.atlassian.jira.rest.client.api.JiraRestClient
|
||||
import com.atlassian.jira.rest.client.api.domain.Comment
|
||||
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
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.bouncycastle.openssl.jcajce.JcaPEMWriter
|
||||
@ -17,15 +19,20 @@ import java.io.StringWriter
|
||||
import java.security.cert.CertPath
|
||||
import javax.security.auth.x500.X500Principal
|
||||
|
||||
class JiraClient(private val restClient: JiraRestClient, private val projectCode: String, private val doneTransitionCode: Int) {
|
||||
class JiraClient(private val restClient: JiraRestClient, private val projectCode: String) {
|
||||
companion object {
|
||||
val logger = loggerFor<JiraClient>()
|
||||
val logger = contextLogger()
|
||||
}
|
||||
|
||||
// The JIRA project must have a Request ID field and the Task issue type.
|
||||
// The JIRA project must have a Request ID and reject reason field, and the Task issue type.
|
||||
private val requestIdField: Field = restClient.metadataClient.fields.claim().find { it.name == "Request ID" } ?: throw IllegalArgumentException("Request ID field not found in JIRA '$projectCode'")
|
||||
private val rejectReasonField: Field = restClient.metadataClient.fields.claim().find { it.name == "Reject Reason" } ?: throw IllegalArgumentException("Reject Reason field not found in JIRA '$projectCode'")
|
||||
private val taskIssueType: IssueType = restClient.metadataClient.issueTypes.claim().find { it.name == "Task" } ?: throw IllegalArgumentException("Task issue type field not found in JIRA '$projectCode'")
|
||||
|
||||
private var doneTransitionId: Int = -1
|
||||
private var canceledTransitionId: Int = -1
|
||||
private var startProgressTransitionId: Int = -1
|
||||
|
||||
fun createRequestTicket(requestId: String, signingRequest: PKCS10CertificationRequest) {
|
||||
// Check there isn't already a ticket for this request.
|
||||
if (getIssueById(requestId) != null) {
|
||||
@ -54,14 +61,26 @@ class JiraClient(private val restClient: JiraRestClient, private val projectCode
|
||||
restClient.issueClient.createIssue(issue.build()).fail { logger.error("Exception when creating JIRA issue.", it) }.claim()
|
||||
}
|
||||
|
||||
fun getApprovedRequests(): List<Pair<String, String>> {
|
||||
fun getApprovedRequests(): List<ApprovedRequest> {
|
||||
val issues = restClient.searchClient.searchJql("project = $projectCode AND status = Approved").claim().issues
|
||||
return issues.map { issue ->
|
||||
issue.getField(requestIdField.id)?.value?.toString().let {
|
||||
val requestId = it ?: throw IllegalArgumentException("RequestId cannot be null.")
|
||||
val approvedBy = issue.assignee?.displayName ?: "Unknown"
|
||||
Pair(requestId, approvedBy)
|
||||
}
|
||||
return issues.mapNotNull { issue ->
|
||||
val requestId = issue.getField(requestIdField.id)?.value?.toString() ?: throw IllegalArgumentException("Error processing request '${issue.key}' : RequestId cannot be null.")
|
||||
// Issue retrieved via search doesn't contain change logs.
|
||||
val fullIssue = restClient.issueClient.getIssue(issue.key, listOf(IssueRestClient.Expandos.CHANGELOG)).claim()
|
||||
val approvedBy = fullIssue.changelog?.last { it.items.any { it.field == "status" && it.toString == "Approved" } }
|
||||
ApprovedRequest(requestId, approvedBy?.author?.displayName ?: "Unknown")
|
||||
}
|
||||
}
|
||||
|
||||
fun getRejectedRequests(): List<RejectedRequest> {
|
||||
val issues = restClient.searchClient.searchJql("project = $projectCode AND status = Rejected").claim().issues
|
||||
return issues.mapNotNull { issue ->
|
||||
val requestId = issue.getField(requestIdField.id)?.value?.toString() ?: throw IllegalArgumentException("Error processing request '${issue.key}' : RequestId cannot be null.")
|
||||
val rejectedReason = issue.getField(rejectReasonField.id)?.value?.toString()
|
||||
// Issue retrieved via search doesn't contain comments.
|
||||
val fullIssue = restClient.issueClient.getIssue(issue.key, listOf(IssueRestClient.Expandos.CHANGELOG)).claim()
|
||||
val rejectedBy = fullIssue.changelog?.last { it.items.any { it.field == "status" && it.toString == "Rejected" } }
|
||||
RejectedRequest(requestId, rejectedBy?.author?.displayName ?: "Unknown", rejectedReason)
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,16 +88,41 @@ class JiraClient(private val restClient: JiraRestClient, private val projectCode
|
||||
// Retrieving certificates for signed CSRs to attach to the jira tasks.
|
||||
signedRequests.forEach { (id, certPath) ->
|
||||
val certificate = certPath.certificates.first()
|
||||
// Jira only support ~ (contains) search for custom textfield.
|
||||
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")
|
||||
.fail { logger.error("Exception when uploading attachment to JIRA.", it) }.claim()
|
||||
if (doneTransitionId == -1) {
|
||||
doneTransitionId = restClient.issueClient.getTransitions(issue.transitionsUri).claim().single { it.name == "Done" }.id
|
||||
}
|
||||
restClient.issueClient.transition(issue, TransitionInput(doneTransitionId)).fail { logger.error("Exception when transiting JIRA status.", it) }.claim()
|
||||
restClient.issueClient.addAttachment(issue.attachmentsUri, certificate.encoded.inputStream(), "${X509Utilities.CORDA_CLIENT_CA}.cer")
|
||||
.fail { logger.error("Error processing request '${issue.key}' : Exception when uploading attachment to JIRA.", it) }.claim()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIssueById(requestId: String): Issue? =
|
||||
restClient.searchClient.searchJql("'Request ID' ~ $requestId").claim().issues.firstOrNull()
|
||||
fun updateRejectedRequests(rejectedRequests: List<String>) {
|
||||
rejectedRequests.mapNotNull { getIssueById(it) }
|
||||
.forEach { issue ->
|
||||
// Move status to in progress.
|
||||
if (startProgressTransitionId == -1) {
|
||||
startProgressTransitionId = restClient.issueClient.getTransitions(issue.transitionsUri).claim().single { it.name == "Start Progress" }.id
|
||||
}
|
||||
restClient.issueClient.transition(issue, TransitionInput(startProgressTransitionId)).fail { logger.error("Error processing request '${issue.key}' : Exception when transiting JIRA status.", it) }.claim()
|
||||
// Move status to cancelled.
|
||||
if (canceledTransitionId == -1) {
|
||||
canceledTransitionId = restClient.issueClient.getTransitions(issue.transitionsUri).claim().single { it.name == "Stop Progress" }.id
|
||||
}
|
||||
restClient.issueClient.transition(issue, TransitionInput(canceledTransitionId)).fail { logger.error("Error processing request '${issue.key}' : Exception when transiting JIRA status.", it) }.claim()
|
||||
restClient.issueClient.addComment(issue.commentsUri, Comment.valueOf("Request cancelled by doorman.")).claim()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIssueById(requestId: String): Issue? {
|
||||
// Jira only support ~ (contains) search for custom textfield.
|
||||
return restClient.searchClient.searchJql("'Request ID' ~ $requestId").claim().issues.firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
data class ApprovedRequest(val requestId: String, val approvedBy: String)
|
||||
|
||||
data class RejectedRequest(val requestId: String, val rejectedBy: String, val reason: String?)
|
||||
|
@ -91,7 +91,7 @@ class NetworkManagementServer : Closeable {
|
||||
val jiraConfig = config.jiraConfig
|
||||
val requestProcessor = if (jiraConfig != null) {
|
||||
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI(jiraConfig.address), jiraConfig.username, jiraConfig.password)
|
||||
val jiraClient = JiraClient(jiraWebAPI, jiraConfig.projectCode, jiraConfig.doneTransitionCode)
|
||||
val jiraClient = JiraClient(jiraWebAPI, jiraConfig.projectCode)
|
||||
JiraCsrHandler(jiraClient, requestService, DefaultCsrHandler(requestService, csrCertPathAndKey))
|
||||
} else {
|
||||
DefaultCsrHandler(requestService, csrCertPathAndKey)
|
||||
@ -101,10 +101,8 @@ class NetworkManagementServer : Closeable {
|
||||
val approvalThread = Runnable {
|
||||
try {
|
||||
serverStatus.lastRequestCheckTime = Instant.now()
|
||||
// Create tickets for requests which don't have one yet.
|
||||
requestProcessor.createTickets()
|
||||
// Process Jira approved tickets.
|
||||
requestProcessor.processApprovedRequests()
|
||||
requestProcessor.processRequests()
|
||||
} catch (e: Exception) {
|
||||
// Log the error and carry on.
|
||||
logger.error("Error encountered when approving request.", e)
|
||||
|
@ -18,25 +18,23 @@ import javax.security.auth.x500.X500Principal
|
||||
|
||||
interface CsrHandler {
|
||||
fun saveRequest(rawRequest: PKCS10CertificationRequest): String
|
||||
fun createTickets()
|
||||
fun processApprovedRequests()
|
||||
fun processRequests()
|
||||
fun getResponse(requestId: String): CertificateResponse
|
||||
}
|
||||
|
||||
class DefaultCsrHandler(private val storage: CertificationRequestStorage,
|
||||
private val csrCertPathAndKey: CertPathAndKey?) : CsrHandler {
|
||||
|
||||
override fun processApprovedRequests() {
|
||||
override fun processRequests() {
|
||||
if (csrCertPathAndKey == null) return
|
||||
storage.getRequests(RequestStatus.APPROVED).forEach {
|
||||
val nodeCertPath = createSignedNodeCertificate(it.request, csrCertPathAndKey)
|
||||
// Since Doorman is deployed in the auto-signing mode, we use DOORMAN_SIGNATURE as the signer.
|
||||
// Since Doorman is deployed in the auto-signing mode (i.e. signer != null),
|
||||
// we use DOORMAN_SIGNATURE as the signer.
|
||||
storage.putCertificatePath(it.requestId, nodeCertPath, listOf(DOORMAN_SIGNATURE))
|
||||
}
|
||||
}
|
||||
|
||||
override fun createTickets() {}
|
||||
|
||||
override fun saveRequest(rawRequest: PKCS10CertificationRequest): String = storage.saveRequest(rawRequest)
|
||||
|
||||
override fun getResponse(requestId: String): CertificateResponse {
|
||||
|
@ -4,7 +4,9 @@ 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.ApprovedRequest
|
||||
import com.r3.corda.networkmanage.doorman.JiraClient
|
||||
import com.r3.corda.networkmanage.doorman.RejectedRequest
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import org.bouncycastle.pkcs.PKCS10CertificationRequest
|
||||
@ -29,21 +31,34 @@ class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: Ce
|
||||
}
|
||||
}
|
||||
|
||||
override fun processApprovedRequests() {
|
||||
override fun processRequests() {
|
||||
createTickets()
|
||||
val (approvedRequests, rejectedRequests) = updateRequestStatus()
|
||||
delegate.processRequests()
|
||||
updateJiraTickets(approvedRequests, rejectedRequests)
|
||||
}
|
||||
|
||||
private fun updateRequestStatus(): Pair<List<ApprovedRequest>, List<RejectedRequest>> {
|
||||
// Update local request statuses.
|
||||
val approvedRequest = jiraClient.getApprovedRequests()
|
||||
approvedRequest.forEach { (id, approvedBy) -> storage.approveRequest(id, approvedBy) }
|
||||
delegate.processApprovedRequests()
|
||||
val rejectedRequest = jiraClient.getRejectedRequests()
|
||||
rejectedRequest.forEach { (id, rejectedBy, reason) -> storage.rejectRequest(id, rejectedBy, reason) }
|
||||
return Pair(approvedRequest, rejectedRequest)
|
||||
}
|
||||
|
||||
val signedRequests = approvedRequest.mapNotNull { (id, _) ->
|
||||
val request = storage.getRequest(id)
|
||||
|
||||
if (request != null && request.status == RequestStatus.SIGNED) {
|
||||
request.certData?.certPath?.let { certs -> id to certs }
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}.toMap()
|
||||
private fun updateJiraTickets(approvedRequest: List<ApprovedRequest>, rejectedRequest: List<RejectedRequest>) {
|
||||
// Reconfirm request status and update jira status
|
||||
val signedRequests = approvedRequest.mapNotNull { storage.getRequest(it.requestId) }
|
||||
.filter { it.status == RequestStatus.SIGNED && it.certData != null }
|
||||
.associateBy { it.requestId }
|
||||
.mapValues { it.value.certData!!.certPath }
|
||||
jiraClient.updateSignedRequests(signedRequests)
|
||||
|
||||
val rejectedRequestIDs = rejectedRequest.mapNotNull { storage.getRequest(it.requestId) }
|
||||
.filter { it.status == RequestStatus.REJECTED }
|
||||
.map { it.requestId }
|
||||
jiraClient.updateRejectedRequests(rejectedRequestIDs)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,13 +67,13 @@ class JiraCsrHandler(private val jiraClient: JiraClient, private val storage: Ce
|
||||
* 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)
|
||||
private fun createTickets() {
|
||||
storage.getRequests(RequestStatus.NEW).forEach {
|
||||
try {
|
||||
createTicket(it)
|
||||
} catch (e: Exception) {
|
||||
log.warn("There were errors while creating Jira tickets for request '${it.requestId}'", e)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log.warn("There were errors while creating Jira tickets", e)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,6 @@ class NetworkMapWebService(private val nodeInfoStorage: NodeInfoStorage,
|
||||
@GET
|
||||
@Path("my-ip")
|
||||
fun myIp(@Context request: HttpServletRequest): Response {
|
||||
// TODO: Verify this returns IP correctly.
|
||||
return ok(request.getHeader("X-Forwarded-For")?.split(",")?.first() ?: "${request.remoteHost}:${request.remotePort}").build()
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,5 @@ class DoormanParametersTest {
|
||||
assertEquals("TD", parameter.jiraConfig?.projectCode)
|
||||
assertEquals("username", parameter.jiraConfig?.username)
|
||||
assertEquals("password", parameter.jiraConfig?.password)
|
||||
assertEquals(41, parameter.jiraConfig?.doneTransitionCode)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,55 @@
|
||||
package com.r3.corda.networkmanage.doorman
|
||||
|
||||
import com.atlassian.jira.rest.client.internal.async.AsynchronousJiraRestClientFactory
|
||||
import com.r3.corda.networkmanage.common.utils.buildCertPath
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import org.junit.Before
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.net.URI
|
||||
|
||||
@Ignore
|
||||
// This is manual test for testing Jira API.
|
||||
class JiraClientTest {
|
||||
private lateinit var jiraClient: JiraClient
|
||||
@Before
|
||||
fun init() {
|
||||
val jiraWebAPI = AsynchronousJiraRestClientFactory().createWithBasicHttpAuthentication(URI("http://jira.url.com"), "username", "password")
|
||||
jiraClient = JiraClient(jiraWebAPI, "DOOR")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun createRequestTicket() {
|
||||
val request = X509Utilities.createCertificateSigningRequest(CordaX500Name("JiraAPITest", "R3 Ltd 3", "London", "GB").x500Principal, "test@test.com", Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||
jiraClient.createRequestTicket(SecureHash.randomSHA256().toString(), request)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getApprovedRequests() {
|
||||
jiraClient.getApprovedRequests().forEach { println(it) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getRejectedRequests() {
|
||||
val requests = jiraClient.getRejectedRequests()
|
||||
requests.forEach { println(it) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateSignedRequests() {
|
||||
val requests = jiraClient.getApprovedRequests()
|
||||
val selfSignedCA = X509Utilities.createSelfSignedCACertificate(CordaX500Name("test", "london", "GB").x500Principal, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
|
||||
jiraClient.updateSignedRequests(requests.map { it.requestId to buildCertPath(selfSignedCA) }.toMap())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun updateRejectedRequests() {
|
||||
val requests = jiraClient.getRejectedRequests()
|
||||
jiraClient.updateRejectedRequests(requests.map { it.requestId })
|
||||
|
||||
assert(jiraClient.getRejectedRequests().isEmpty())
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ class DefaultCsrHandlerTest : TestBase() {
|
||||
val csrCertPathAndKey = CertPathAndKey(listOf(csrCa.certificate, rootCa.certificate), csrCa.keyPair.private)
|
||||
val requestProcessor = DefaultCsrHandler(requestStorage, csrCertPathAndKey)
|
||||
|
||||
requestProcessor.processApprovedRequests()
|
||||
requestProcessor.processRequests()
|
||||
|
||||
val certPathCapture = argumentCaptor<CertPath>()
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
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.common.persistence.*
|
||||
import com.r3.corda.networkmanage.doorman.ApprovedRequest
|
||||
import com.r3.corda.networkmanage.doorman.JiraClient
|
||||
import com.r3.corda.networkmanage.doorman.RejectedRequest
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||
import org.junit.Before
|
||||
@ -16,9 +16,9 @@ import org.mockito.Mock
|
||||
import org.mockito.junit.MockitoJUnit
|
||||
import org.mockito.junit.MockitoRule
|
||||
import java.security.cert.CertPath
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class JiraCsrHandlerTest {
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val mockitoRule: MockitoRule = MockitoJUnit.rule()
|
||||
@ -80,9 +80,67 @@ class JiraCsrHandlerTest {
|
||||
whenever(certificationRequestStorage.getRequests(RequestStatus.NEW)).thenReturn(listOf(csr))
|
||||
|
||||
// Test
|
||||
jiraCsrHandler.createTickets()
|
||||
jiraCsrHandler.processRequests()
|
||||
|
||||
verify(jiraClient).createRequestTicket(requestId, csr.request)
|
||||
verify(certificationRequestStorage).markRequestTicketCreated(requestId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sync tickets status`() {
|
||||
val id1 = SecureHash.randomSHA256().toString()
|
||||
val id2 = SecureHash.randomSHA256().toString()
|
||||
val csr1 = CertificateSigningRequest(id1, "name1", RequestStatus.NEW, pkcS10CertificationRequest, null, emptyList(), null)
|
||||
val csr2 = CertificateSigningRequest(id2, "name2", RequestStatus.NEW, pkcS10CertificationRequest, null, emptyList(), null)
|
||||
|
||||
val requests = mutableMapOf(id1 to csr1, id2 to csr2)
|
||||
|
||||
// Mocking storage behaviour.
|
||||
whenever(certificationRequestStorage.getRequests(RequestStatus.NEW)).thenReturn(requests.values.filter { it.status == RequestStatus.NEW })
|
||||
whenever(certificationRequestStorage.getRequest(any())).thenAnswer { requests[it.getArgument(0)] }
|
||||
whenever(certificationRequestStorage.approveRequest(any(), any())).then {
|
||||
val id = it.getArgument<String>(0)
|
||||
if (requests[id]?.status == RequestStatus.NEW) {
|
||||
requests[id] = requests[id]!!.copy(status = RequestStatus.APPROVED, modifiedBy = listOf(it.getArgument(1)))
|
||||
}
|
||||
null
|
||||
}
|
||||
whenever(certificationRequestStorage.rejectRequest(any(), any(), any())).then {
|
||||
val id = it.getArgument<String>(0)
|
||||
requests[id] = requests[id]!!.copy(status = RequestStatus.REJECTED, modifiedBy = listOf(it.getArgument(1)), remark = it.getArgument(2))
|
||||
null
|
||||
}
|
||||
|
||||
// Status change from jira.
|
||||
whenever(jiraClient.getApprovedRequests()).thenReturn(listOf(ApprovedRequest(id1, "Me")))
|
||||
whenever(jiraClient.getRejectedRequests()).thenReturn(listOf(RejectedRequest(id2, "Me", "Test reject")))
|
||||
|
||||
// Test.
|
||||
jiraCsrHandler.processRequests()
|
||||
|
||||
verify(jiraClient).createRequestTicket(id1, csr1.request)
|
||||
verify(jiraClient).createRequestTicket(id2, csr2.request)
|
||||
|
||||
verify(certificationRequestStorage).markRequestTicketCreated(id1)
|
||||
verify(certificationRequestStorage).markRequestTicketCreated(id2)
|
||||
|
||||
// Verify request has the correct status in DB.
|
||||
assertEquals(RequestStatus.APPROVED, requests[id1]!!.status)
|
||||
assertEquals(RequestStatus.REJECTED, requests[id2]!!.status)
|
||||
|
||||
// Verify jira client get the correct call.
|
||||
verify(jiraClient).updateRejectedRequests(listOf(id2))
|
||||
verify(jiraClient).updateSignedRequests(emptyMap())
|
||||
|
||||
// Sign request 1
|
||||
val certPath = mock<CertPath>()
|
||||
val certData = CertificateData("", CertificateStatus.VALID, certPath)
|
||||
requests[id1] = requests[id1]!!.copy(status = RequestStatus.SIGNED, certData = certData)
|
||||
|
||||
// Process request again.
|
||||
jiraCsrHandler.processRequests()
|
||||
|
||||
// Update signed request should be called.
|
||||
verify(jiraClient).updateSignedRequests(mapOf(id1 to certPath))
|
||||
}
|
||||
}
|
@ -100,7 +100,7 @@ class RegistrationWebServiceTest : TestBase() {
|
||||
CertificateResponse.Ready(it)
|
||||
} ?: CertificateResponse.NotReady
|
||||
}
|
||||
on { processApprovedRequests() }.then {
|
||||
on { processRequests() }.then {
|
||||
val request = X509Utilities.createCertificateSigningRequest(subject, "my@mail.com", keyPair)
|
||||
certificateStore[id] = JcaPKCS10CertificationRequest(request).run {
|
||||
val tlsCert = X509Utilities.createCertificate(
|
||||
@ -118,7 +118,7 @@ class RegistrationWebServiceTest : TestBase() {
|
||||
startSigningServer(requestProcessor)
|
||||
assertThat(pollForResponse(id)).isEqualTo(PollResponse.NotReady)
|
||||
|
||||
requestProcessor.processApprovedRequests()
|
||||
requestProcessor.processRequests()
|
||||
|
||||
val certificates = (pollForResponse(id) as PollResponse.Ready).certChain
|
||||
verify(requestProcessor, times(2)).getResponse(any())
|
||||
@ -141,7 +141,7 @@ class RegistrationWebServiceTest : TestBase() {
|
||||
CertificateResponse.Ready(it)
|
||||
} ?: CertificateResponse.NotReady
|
||||
}
|
||||
on { processApprovedRequests() }.then {
|
||||
on { processRequests() }.then {
|
||||
val request = X509Utilities.createCertificateSigningRequest(
|
||||
CordaX500Name(locality = "London", organisation = "Legal Name", country = "GB").x500Principal,
|
||||
"my@mail.com",
|
||||
@ -165,7 +165,7 @@ class RegistrationWebServiceTest : TestBase() {
|
||||
|
||||
startSigningServer(storage)
|
||||
assertThat(pollForResponse(id)).isEqualTo(PollResponse.NotReady)
|
||||
storage.processApprovedRequests()
|
||||
storage.processRequests()
|
||||
|
||||
val certificates = (pollForResponse(id) as PollResponse.Ready).certChain
|
||||
verify(storage, times(2)).getResponse(any())
|
||||
|
Loading…
Reference in New Issue
Block a user