Support fuzzy matching for identities.

Matching can be done with case insensitive substrings in the identity service, RPC and shell. In future cleverer matching should be possible, e.g. using Lucene or RDBMS free text search features.
This commit is contained in:
Mike Hearn
2017-05-17 23:42:00 +02:00
parent a86c5ba2dd
commit ccf43a8e17
7 changed files with 94 additions and 17 deletions

View File

@ -8,6 +8,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
@ -174,7 +175,8 @@ class CordaRPCOpsImpl(
@Suppress("DEPRECATION")
@Deprecated("Use partyFromX500Name instead")
override fun partyFromName(name: String) = services.identityService.partyFromName(name)
override fun partyFromX500Name(x500Name: X500Name)= services.identityService.partyFromX500Name(x500Name)
override fun partyFromX500Name(x500Name: X500Name) = services.identityService.partyFromX500Name(x500Name)
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> = services.identityService.partiesFromName(query, exactMatch)
override fun registeredFlows(): List<String> = services.rpcFlows.map { it.name }.sorted()

View File

@ -4,7 +4,10 @@ import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.requireThat
import net.corda.core.crypto.subject
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.loggerFor
@ -18,6 +21,7 @@ import java.security.cert.*
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import javax.annotation.concurrent.ThreadSafe
import kotlin.collections.ArrayList
/**
* Simple identity service which caches parties and provides functionality for efficient lookup.
@ -95,6 +99,24 @@ class InMemoryIdentityService(identities: Iterable<PartyAndCertificate>,
return partyFromAnonymous(party) ?: throw IllegalStateException("Could not deanonymise party ${party.owningKey.toStringShort()}")
}
override fun partiesFromName(query: String, exactMatch: Boolean): Set<Party> {
val results = HashSet<Party>()
for ((x500name, partyAndCertificate) in principalToParties) {
val party = partyAndCertificate.party
for (rdn in x500name.rdNs) {
val component = rdn.first.value.toString()
if (exactMatch && component == query) {
results += party
} else if (!exactMatch) {
// We can imagine this being a query over a lucene index in future.
if (component.contains(query, ignoreCase = true))
results += party
}
}
}
return results
}
@Throws(IdentityService.UnknownAnonymousPartyException::class)
override fun assertOwnership(party: Party, anonymousParty: AnonymousParty) {
val path = partyToPath[anonymousParty] ?: throw IdentityService.UnknownAnonymousPartyException("Unknown anonymous party ${anonymousParty.owningKey.toStringShort()}")

View File

@ -11,6 +11,8 @@ import net.corda.testing.ALICE_PUBKEY
import net.corda.testing.BOB_PUBKEY
import org.bouncycastle.asn1.x500.X500Name
import org.junit.Test
import java.security.KeyPair
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@ -53,6 +55,18 @@ class InMemoryIdentityServiceTests {
assertNull(service.partyFromX500Name(ALICE.name))
}
@Test
fun `get identity by substring match`() {
val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate)
service.registerIdentity(ALICE_IDENTITY)
service.registerIdentity(BOB_IDENTITY)
val (_, _, alicente) = createParty(X500Name("O=Alicente Worldwide,L=London,C=UK"))
service.registerIdentity(alicente)
assertEquals(setOf(ALICE, alicente.party), service.partiesFromName("Alice", false))
assertEquals(setOf(ALICE), service.partiesFromName("Alice Corp", true))
assertEquals(setOf(BOB), service.partiesFromName("Bob Plc", true))
}
@Test
fun `get identity by name`() {
val service = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate)
@ -87,12 +101,7 @@ class InMemoryIdentityServiceTests {
*/
@Test
fun `assert ownership`() {
val aliceRootKey = Crypto.generateKeyPair()
val aliceRootCert = X509Utilities.createSelfSignedCACertificate(ALICE.name, aliceRootKey)
val aliceTxKey = Crypto.generateKeyPair()
val aliceTxCert = X509Utilities.createCertificate(CertificateType.IDENTITY, aliceRootCert, aliceRootKey, ALICE.name, aliceTxKey.public)
val aliceCertPath = X509Utilities.createCertificatePath(aliceRootCert, aliceTxCert, revocationEnabled = false)
val alice = PartyAndCertificate(ALICE.name, aliceRootKey.public, aliceRootCert, aliceCertPath)
val (aliceTxKey, aliceCertPath, alice) = createParty(ALICE.name)
val bobRootKey = Crypto.generateKeyPair()
val bobRootCert = X509Utilities.createSelfSignedCACertificate(BOB.name, bobRootKey)
@ -120,6 +129,15 @@ class InMemoryIdentityServiceTests {
}
}
private fun createParty(x500Name: X500Name): Triple<KeyPair, CertPath, PartyAndCertificate> {
val rootKey = Crypto.generateKeyPair()
val rootCert = X509Utilities.createSelfSignedCACertificate(x500Name, rootKey)
val txKey = Crypto.generateKeyPair()
val txCert = X509Utilities.createCertificate(CertificateType.IDENTITY, rootCert, rootKey, x500Name, txKey.public)
val certPath = X509Utilities.createCertificatePath(rootCert, txCert, revocationEnabled = false)
return Triple(txKey, certPath, PartyAndCertificate(x500Name, rootKey.public, rootCert, certPath))
}
/**
* Ensure if we feed in a full identity, we get the same identity back.
*/