mirror of
https://github.com/corda/corda.git
synced 2025-06-23 01:19:00 +00:00
Configurable authorization/authentication data sources [CORDA-827] (#2145)
* Add support for external data source of access control data (RPC/Shell users credential and permissions), with optional in-memory caching. * Support password encoded with Apache Shiro fully reversible Modular Crypt Format. * Introduce 'security' field in Node configuration and related docsite page.
This commit is contained in:
@ -63,8 +63,8 @@ class RPCStabilityTests {
|
||||
val executor = Executors.newScheduledThreadPool(1)
|
||||
fun startAndStop() {
|
||||
rpcDriver {
|
||||
val server = startRpcServer<RPCOps>(ops = DummyOps)
|
||||
startRpcClient<RPCOps>(server.get().broker.hostAndPort!!).get()
|
||||
val server = startRpcServer<RPCOps>(ops = DummyOps).get()
|
||||
startRpcClient<RPCOps>(server.broker.hostAndPort!!).get()
|
||||
}
|
||||
}
|
||||
repeat(5) {
|
||||
|
@ -22,7 +22,10 @@ import net.corda.core.internal.ThreadBox
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.core.utilities.Try
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.nodeapi.ArtemisConsumer
|
||||
import net.corda.nodeapi.ArtemisProducer
|
||||
import net.corda.nodeapi.RPCApi
|
||||
|
@ -1,8 +1,6 @@
|
||||
package net.corda.client.rpc
|
||||
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.node.services.Permissions.Companion.invokeRpc
|
||||
import net.corda.node.services.messaging.rpcContext
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.testing.internal.RPCDriverDSL
|
||||
@ -10,15 +8,12 @@ import net.corda.testing.internal.rpcDriver
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.declaredMemberFunctions
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class RPCPermissionsTests : AbstractRPCTest() {
|
||||
companion object {
|
||||
const val DUMMY_FLOW = "StartFlow.net.corda.flows.DummyFlow"
|
||||
const val OTHER_FLOW = "StartFlow.net.corda.flows.OtherFlow"
|
||||
const val ALL_ALLOWED = "ALL"
|
||||
}
|
||||
|
||||
@ -26,12 +21,21 @@ class RPCPermissionsTests : AbstractRPCTest() {
|
||||
* RPC operation.
|
||||
*/
|
||||
interface TestOps : RPCOps {
|
||||
fun validatePermission(str: String)
|
||||
fun validatePermission(method: String, target: String? = null)
|
||||
}
|
||||
|
||||
class TestOpsImpl : TestOps {
|
||||
override val protocolVersion = 1
|
||||
override fun validatePermission(str: String) { rpcContext().requirePermission(str) }
|
||||
override fun validatePermission(method: String, target: String?) {
|
||||
val authorized = if (target == null) {
|
||||
rpcContext().isPermitted(method)
|
||||
} else {
|
||||
rpcContext().isPermitted(method, target)
|
||||
}
|
||||
if (!authorized) {
|
||||
throw PermissionException("RPC user not authorized")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -46,9 +50,9 @@ class RPCPermissionsTests : AbstractRPCTest() {
|
||||
rpcDriver {
|
||||
val emptyUser = userOf("empty", emptySet())
|
||||
val proxy = testProxyFor(emptyUser)
|
||||
assertFailsWith(PermissionException::class,
|
||||
"User ${emptyUser.username} should not be allowed to use $DUMMY_FLOW.",
|
||||
{ proxy.validatePermission(DUMMY_FLOW) })
|
||||
assertNotAllowed {
|
||||
proxy.validatePermission("startFlowDynamic", "net.corda.flows.DummyFlow")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +61,8 @@ class RPCPermissionsTests : AbstractRPCTest() {
|
||||
rpcDriver {
|
||||
val adminUser = userOf("admin", setOf(ALL_ALLOWED))
|
||||
val proxy = testProxyFor(adminUser)
|
||||
proxy.validatePermission(DUMMY_FLOW)
|
||||
proxy.validatePermission("startFlowDynamic", "net.corda.flows.DummyFlow")
|
||||
proxy.validatePermission("startTrackedFlowDynamic", "net.corda.flows.DummyFlow")
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +71,8 @@ class RPCPermissionsTests : AbstractRPCTest() {
|
||||
rpcDriver {
|
||||
val joeUser = userOf("joe", setOf(DUMMY_FLOW))
|
||||
val proxy = testProxyFor(joeUser)
|
||||
proxy.validatePermission(DUMMY_FLOW)
|
||||
proxy.validatePermission("startFlowDynamic", "net.corda.flows.DummyFlow")
|
||||
proxy.validatePermission("startTrackedFlowDynamic", "net.corda.flows.DummyFlow")
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,36 +81,46 @@ class RPCPermissionsTests : AbstractRPCTest() {
|
||||
rpcDriver {
|
||||
val joeUser = userOf("joe", setOf(DUMMY_FLOW))
|
||||
val proxy = testProxyFor(joeUser)
|
||||
assertFailsWith(PermissionException::class,
|
||||
"User ${joeUser.username} should not be allowed to use $OTHER_FLOW",
|
||||
{ proxy.validatePermission(OTHER_FLOW) })
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `check ALL is implemented the correct way round`() {
|
||||
rpcDriver {
|
||||
val joeUser = userOf("joe", setOf(DUMMY_FLOW))
|
||||
val proxy = testProxyFor(joeUser)
|
||||
assertFailsWith(PermissionException::class,
|
||||
"Permission $ALL_ALLOWED should not do anything for User ${joeUser.username}",
|
||||
{ proxy.validatePermission(ALL_ALLOWED) })
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fine grained permissions are enforced`() {
|
||||
val allPermissions = CordaRPCOps::class.declaredMemberFunctions.filter { it.visibility == KVisibility.PUBLIC }.map { invokeRpc(it) }
|
||||
allPermissions.forEach { permission ->
|
||||
rpcDriver {
|
||||
val user = userOf("Mark", setOf(permission))
|
||||
val proxy = testProxyFor(user)
|
||||
|
||||
proxy.validatePermission(permission)
|
||||
(allPermissions - permission).forEach { notOwnedPermission ->
|
||||
assertFailsWith(PermissionException::class, { proxy.validatePermission(notOwnedPermission) })
|
||||
}
|
||||
assertNotAllowed {
|
||||
proxy.validatePermission("startFlowDynamic", "net.corda.flows.OtherFlow")
|
||||
}
|
||||
assertNotAllowed {
|
||||
proxy.validatePermission("startTrackedFlowDynamic", "net.corda.flows.OtherFlow")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `joe user is not allowed to call other RPC methods`() {
|
||||
rpcDriver {
|
||||
val joeUser = userOf("joe", setOf(DUMMY_FLOW))
|
||||
val proxy = testProxyFor(joeUser)
|
||||
assertNotAllowed {
|
||||
proxy.validatePermission("nodeInfo")
|
||||
}
|
||||
assertNotAllowed {
|
||||
proxy.validatePermission("networkMapFeed")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `checking invokeRpc permissions entitlements`() {
|
||||
rpcDriver {
|
||||
val joeUser = userOf("joe", setOf("InvokeRpc.networkMapFeed"))
|
||||
val proxy = testProxyFor(joeUser)
|
||||
assertNotAllowed {
|
||||
proxy.validatePermission("nodeInfo")
|
||||
}
|
||||
assertNotAllowed {
|
||||
proxy.validatePermission("startTrackedFlowDynamic", "net.corda.flows.OtherFlow")
|
||||
}
|
||||
proxy.validatePermission("networkMapFeed")
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertNotAllowed(action: () -> Unit) {
|
||||
|
||||
assertFailsWith(PermissionException::class, "User should not be allowed to perform this action.", action)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user