mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
Moved the RPC user config out of the properties file and into the main config file
This commit is contained in:
parent
f4925c0fa9
commit
c326a9ae46
12
.idea/modules.xml
generated
12
.idea/modules.xml
generated
@ -15,12 +15,9 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/core/core.iml" filepath="$PROJECT_DIR$/.idea/modules/core/core.iml" group="core" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/core/core_main.iml" filepath="$PROJECT_DIR$/.idea/modules/core/core_main.iml" group="core" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/core/core_test.iml" filepath="$PROJECT_DIR$/.idea/modules/core/core_test.iml" group="core" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/docs.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/docs.iml" group="docs" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/docs_main.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/docs_main.iml" group="docs" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/docs_test.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/docs_test.iml" group="docs" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/source/example-code/example-code.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/source/example-code/example-code.iml" group="docs/source/example-code" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/source/example-code/example-code_main.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/source/example-code/example-code_main.iml" group="docs/source/example-code" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/source/example-code/example-code_test.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/source/example-code/example-code_test.iml" group="docs/source/example-code" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/source/example-code/docs_source_example-code.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/source/example-code/docs_source_example-code.iml" group="docs/source/example-code" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/source/example-code/docs_source_example-code_main.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/source/example-code/docs_source_example-code_main.iml" group="docs/source/example-code" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/source/example-code/docs_source_example-code_test.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/source/example-code/docs_source_example-code_test.iml" group="docs/source/example-code" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/experimental/experimental.iml" filepath="$PROJECT_DIR$/.idea/modules/experimental/experimental.iml" group="experimental" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/experimental/experimental_main.iml" filepath="$PROJECT_DIR$/.idea/modules/experimental/experimental_main.iml" group="experimental" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/experimental/experimental_test.iml" filepath="$PROJECT_DIR$/.idea/modules/experimental/experimental_test.iml" group="experimental" />
|
||||
@ -49,9 +46,6 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping_main.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping_main.iml" group="r3prototyping" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/r3prototyping_test.iml" filepath="$PROJECT_DIR$/.idea/modules/r3prototyping_test.iml" group="r3prototyping" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/source/source.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/source/source.iml" group="docs/source" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/source/source_main.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/source/source_main.iml" group="docs/source" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/docs/source/source_test.iml" filepath="$PROJECT_DIR$/.idea/modules/docs/source/source_test.iml" group="docs/source" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/test-utils/test-utils.iml" filepath="$PROJECT_DIR$/.idea/modules/test-utils/test-utils.iml" group="test-utils" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/test-utils/test-utils_main.iml" filepath="$PROJECT_DIR$/.idea/modules/test-utils/test-utils_main.iml" group="test-utils" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/modules/test-utils/test-utils_test.iml" filepath="$PROJECT_DIR$/.idea/modules/test-utils/test-utils_test.iml" group="test-utils" />
|
||||
|
@ -22,8 +22,10 @@ import java.nio.file.*
|
||||
import java.nio.file.attribute.FileAttribute
|
||||
import java.time.Duration
|
||||
import java.time.temporal.Temporal
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.ExecutionException
|
||||
import java.util.concurrent.Executor
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.stream.Stream
|
||||
import java.util.zip.ZipInputStream
|
||||
@ -58,6 +60,9 @@ infix fun Long.checkedAdd(b: Long) = Math.addExact(this, b)
|
||||
*/
|
||||
fun random63BitValue(): Long = Math.abs(newSecureRandom().nextLong())
|
||||
|
||||
// TODO Convert the CompletableFuture into a ListenableFuture
|
||||
fun <T> future(block: () -> T): Future<T> = CompletableFuture.supplyAsync(block)
|
||||
|
||||
// Some utilities for working with Guava listenable futures.
|
||||
fun <T> ListenableFuture<T>.then(executor: Executor, body: () -> Unit) = addListener(Runnable(body), executor)
|
||||
|
||||
|
@ -21,9 +21,8 @@ Security
|
||||
--------
|
||||
|
||||
Users wanting to use the RPC library are first required to authenticate themselves with the node using a valid username
|
||||
and password. These are kept in ``rpc-users.properties`` in the node base directory. This file also specifies
|
||||
permissions for each user, which the RPC implementation can use to control access. The file format is described in
|
||||
:doc:`corda-configuration-files`.
|
||||
and password. These are specified in the configuration file. Each user can be configured with a set of permissions which
|
||||
the RPC can use for fine-grain access control.
|
||||
|
||||
Observables
|
||||
-----------
|
||||
|
@ -41,6 +41,9 @@ General node configuration file for hosting the IRSDemo services.
|
||||
extraAdvertisedServiceIds: "corda.interest_rates"
|
||||
networkMapAddress : "localhost:12345"
|
||||
useHTTPS : false
|
||||
rpcUsers : [
|
||||
{ user=user1, password=letmein, permissions=[ cash ] }
|
||||
]
|
||||
|
||||
NetworkMapService plus Simple Notary configuration file.
|
||||
|
||||
@ -97,20 +100,12 @@ Configuration File Fields
|
||||
|
||||
:useHTTPS: If false the node's web server will be plain HTTP. If true the node will use the same certificate and private key from the ``<workspace>/certificates/sslkeystore.jks`` file as the ArtemisMQ port for HTTPS. If HTTPS is enabled then unencrypted HTTP traffic to the node's **webAddress** port is not supported.
|
||||
|
||||
RPC Users File
|
||||
==============
|
||||
:rpcUsers:
|
||||
A list of users who are authorised to access the RPC system. Each user in the list is a config object with the
|
||||
following fields:
|
||||
|
||||
Corda also uses the ``rpc-users.properties`` file, found in the base directory, to control access to the RPC subsystem.
|
||||
This is a Java properties file (details can be found in the `Javadocs <https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html#load-java.io.Reader->`_)
|
||||
which specifies a list of users with their password and list of permissions they're enabled for.
|
||||
:user: Username consisting only of word characters (a-z, A-Z, 0-9 and _)
|
||||
:password: The password
|
||||
:permissions: A list of permission strings which RPC methods can use to control access
|
||||
|
||||
.. code-block:: text
|
||||
:caption: Sample
|
||||
|
||||
admin=notsecure,ADMIN
|
||||
user1=letmein,CASH,PAPER
|
||||
|
||||
In this example ``user1`` has password ``letmein`` and has permissions for ``CASH`` and ``PAPER``. The permissions are
|
||||
free-form strings which can be used by the RPC methods to control access.
|
||||
|
||||
If ``rpc-users.properties`` is empty or doesn't exist then the RPC subsystem is effectively locked down.
|
||||
If this field is absent or an empty list then RPC is effectively locked down.
|
||||
|
@ -8,10 +8,10 @@ import com.typesafe.config.ConfigRenderOptions
|
||||
import net.corda.core.ThreadBox
|
||||
import net.corda.core.crypto.Party
|
||||
import net.corda.core.div
|
||||
import net.corda.core.future
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.write
|
||||
import net.corda.node.services.User
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.FullNodeConfiguration
|
||||
@ -28,7 +28,9 @@ import java.time.Instant
|
||||
import java.time.ZoneOffset.UTC
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
import java.util.concurrent.*
|
||||
import java.util.concurrent.Future
|
||||
import java.util.concurrent.TimeUnit.SECONDS
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
/**
|
||||
* This file defines a small "Driver" DSL for starting up nodes that is only intended for development, demos and tests.
|
||||
@ -246,11 +248,11 @@ open class DriverDSL(
|
||||
registeredProcesses.forEach(Process::destroy)
|
||||
}
|
||||
/** Wait 5 seconds, then [Process.destroyForcibly] */
|
||||
val finishedFuture = Executors.newSingleThreadExecutor().submit {
|
||||
val finishedFuture = future {
|
||||
waitForAllNodesToFinish()
|
||||
}
|
||||
try {
|
||||
finishedFuture.get(5, TimeUnit.SECONDS)
|
||||
finishedFuture.get(5, SECONDS)
|
||||
} catch (exception: TimeoutException) {
|
||||
finishedFuture.cancel(true)
|
||||
state.locked {
|
||||
@ -306,22 +308,19 @@ open class DriverDSL(
|
||||
"webAddress" to apiAddress.toString(),
|
||||
"extraAdvertisedServiceIds" to advertisedServices.joinToString(","),
|
||||
"networkMapAddress" to networkMapAddress.toString(),
|
||||
"useTestClock" to useTestClock
|
||||
"useTestClock" to useTestClock,
|
||||
"rpcUsers" to rpcUsers.map { mapOf(
|
||||
"user" to it.username,
|
||||
"password" to it.password,
|
||||
"permissions" to it.permissions)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
val nodeConfig = FullNodeConfiguration(config)
|
||||
|
||||
nodeConfig.rpcUsersFile.write(createDirs = true) {
|
||||
rpcUsers.map { it.username to "${it.password},${it.permissions.joinToString(",")}" }
|
||||
.toMap(Properties())
|
||||
.store(it, null)
|
||||
}
|
||||
|
||||
return Executors.newSingleThreadExecutor().submit(Callable<NodeInfoAndConfig> {
|
||||
registerProcess(DriverDSL.startNode(nodeConfig, quasarJarPath, debugPort))
|
||||
return future {
|
||||
registerProcess(DriverDSL.startNode(FullNodeConfiguration(config), quasarJarPath, debugPort))
|
||||
NodeInfoAndConfig(queryNodeInfo(apiAddress)!!, config)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun start() {
|
||||
|
@ -8,8 +8,8 @@ import net.corda.core.node.services.ServiceInfo
|
||||
import net.corda.core.then
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.node.serialization.NodeClock
|
||||
import net.corda.node.services.PropertiesFileRPCUserService
|
||||
import net.corda.node.services.RPCUserService
|
||||
import net.corda.node.services.RPCUserServiceImpl
|
||||
import net.corda.node.services.api.MessagingServiceInternal
|
||||
import net.corda.node.services.config.FullNodeConfiguration
|
||||
import net.corda.node.services.messaging.ArtemisMessagingServer
|
||||
@ -118,7 +118,7 @@ class Node(override val configuration: FullNodeConfiguration, networkMapAddress:
|
||||
private lateinit var userService: RPCUserService
|
||||
|
||||
override fun makeMessagingService(): MessagingServiceInternal {
|
||||
userService = PropertiesFileRPCUserService(configuration.rpcUsersFile)
|
||||
userService = RPCUserServiceImpl(configuration.config)
|
||||
val serverAddr = with(configuration) {
|
||||
messagingServerAddress ?: {
|
||||
messageBroker = ArtemisMessagingServer(this, artemisAddress, services.networkMapCache, userService)
|
||||
|
@ -1,9 +1,7 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import net.corda.core.exists
|
||||
import net.corda.core.read
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import com.typesafe.config.Config
|
||||
import net.corda.node.services.config.getListOrElse
|
||||
|
||||
/**
|
||||
* Service for retrieving [User] objects representing RPC users who are authorised to use the RPC system. A [User]
|
||||
@ -15,30 +13,22 @@ interface RPCUserService {
|
||||
val users: List<User>
|
||||
}
|
||||
|
||||
// TODO If this sticks around then change it to use HOCON ...
|
||||
// TODO ... and also store passwords as salted hashes.
|
||||
// TODO Otherwise consider something like Apache Shiro
|
||||
class PropertiesFileRPCUserService(file: Path) : RPCUserService {
|
||||
// TODO Store passwords as salted hashes
|
||||
// TODO Or ditch this and consider something like Apache Shiro
|
||||
class RPCUserServiceImpl(config: Config) : RPCUserService {
|
||||
|
||||
private val _users: Map<String, User>
|
||||
|
||||
init {
|
||||
_users = if (file.exists()) {
|
||||
val properties = Properties()
|
||||
file.read {
|
||||
properties.load(it)
|
||||
}
|
||||
properties.map {
|
||||
val parts = it.value.toString().split(delimiters = ",")
|
||||
val username = it.key.toString()
|
||||
require(!username.contains("""\.|\*|#""".toRegex())) { """Usernames cannot have the following characters: * . # """ }
|
||||
val password = parts[0]
|
||||
val permissions = parts.drop(1).map(String::toUpperCase).toSet()
|
||||
User(username, password, permissions)
|
||||
}.associateBy(User::username)
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
_users = config.getListOrElse<Config>("rpcUsers") { emptyList() }
|
||||
.map {
|
||||
val username = it.getString("user")
|
||||
require(username.matches("\\w+".toRegex())) { "Username $username contains invalid characters" }
|
||||
val password = it.getString("password")
|
||||
val permissions = it.getListOrElse<String>("permissions") { emptyList() }.map(String::toUpperCase).toSet()
|
||||
User(username, password, permissions)
|
||||
}
|
||||
.associateBy(User::username)
|
||||
}
|
||||
|
||||
override fun getUser(usename: String): User? = _users[usename]
|
||||
|
@ -87,6 +87,15 @@ fun Config.getProperties(path: String): Properties {
|
||||
return props
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
inline fun <reified T : Any> Config.getListOrElse(path: String, default: Config.() -> List<T>): List<T> {
|
||||
return if (hasPath(path)) {
|
||||
(if (T::class == String::class) getStringList(path) else getConfigList(path)) as List<T>
|
||||
} else {
|
||||
this.default()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Strictly for dev only automatically construct a server certificate/private key signed from
|
||||
* the CA certs in Node resources. Then provision KeyStores into certificates folder under node path.
|
||||
|
@ -24,7 +24,6 @@ interface NodeSSLConfiguration {
|
||||
interface NodeConfiguration : NodeSSLConfiguration {
|
||||
val basedir: Path
|
||||
override val certificatesPath: Path get() = basedir / "certificates"
|
||||
val rpcUsersFile: Path get() = basedir / "rpc-users.properties"
|
||||
val myLegalName: String
|
||||
val nearestCity: String
|
||||
val emailAddress: String
|
||||
|
@ -0,0 +1,75 @@
|
||||
package com.r3corda.node.services
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.node.services.RPCUserServiceImpl
|
||||
import net.corda.node.services.User
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Test
|
||||
|
||||
class RPCUserServiceImplTest {
|
||||
|
||||
@Test
|
||||
fun `missing config`() {
|
||||
val service = loadWithContents("{}")
|
||||
assertThat(service.getUser("user")).isNull()
|
||||
assertThat(service.users).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no users`() {
|
||||
val service = loadWithContents("rpcUsers : []")
|
||||
assertThat(service.getUser("user")).isNull()
|
||||
assertThat(service.users).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no permissions`() {
|
||||
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein }]")
|
||||
val expectedUser = User("user1", "letmein", permissions = emptySet())
|
||||
assertThat(service.getUser("user1")).isEqualTo(expectedUser)
|
||||
assertThat(service.users).containsOnly(expectedUser)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single permission, which is in lower case`() {
|
||||
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein, permissions=[cash] }]")
|
||||
assertThat(service.getUser("user1")?.permissions).containsOnly("CASH")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two permissions, which are upper case`() {
|
||||
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein, permissions=[CASH, ADMIN] }]")
|
||||
assertThat(service.getUser("user1")?.permissions).containsOnly("CASH", "ADMIN")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two users`() {
|
||||
val service = loadWithContents("""rpcUsers : [
|
||||
{ user=user, password=password, permissions=[ADMIN] }
|
||||
{ user=user2, password=password2, permissions=[] }
|
||||
]""")
|
||||
val user1 = User("user", "password", permissions = setOf("ADMIN"))
|
||||
val user2 = User("user2", "password2", permissions = emptySet())
|
||||
assertThat(service.getUser("user")).isEqualTo(user1)
|
||||
assertThat(service.getUser("user2")).isEqualTo(user2)
|
||||
assertThat(service.users).containsOnly(user1, user2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unknown user`() {
|
||||
val service = loadWithContents("rpcUsers : [{ user=user1, password=letmein }]")
|
||||
assertThat(service.getUser("test")).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Artemis special characters not permitted in usernames`() {
|
||||
assertThatThrownBy { loadWithContents("rpcUsers : [{ user=user.1, password=letmein }]") }.hasMessageContaining(".")
|
||||
assertThatThrownBy { loadWithContents("rpcUsers : [{ user=user*1, password=letmein }]") }.hasMessageContaining("*")
|
||||
assertThatThrownBy { loadWithContents("""rpcUsers : [{ user="user#1", password=letmein }]""") }.hasMessageContaining("#")
|
||||
}
|
||||
|
||||
private fun loadWithContents(configString: String): RPCUserServiceImpl {
|
||||
return RPCUserServiceImpl(ConfigFactory.parseString(configString))
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.node.utilities.databaseTransaction
|
||||
import net.corda.testing.freeLocalHostAndPort
|
||||
import net.corda.testing.node.makeTestDataSourceProperties
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.junit.After
|
||||
@ -59,7 +60,7 @@ class ArtemisMessagingTests {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
userService = PropertiesFileRPCUserService(temporaryFolder.newFile().toPath())
|
||||
userService = RPCUserServiceImpl(ConfigFactory.empty())
|
||||
// TODO: create a base class that provides a default implementation
|
||||
config = object : NodeConfiguration {
|
||||
override val basedir: Path = temporaryFolder.newFolder().toPath()
|
||||
|
@ -1,81 +0,0 @@
|
||||
package net.corda.node.services
|
||||
|
||||
import com.google.common.jimfs.Configuration.unix
|
||||
import com.google.common.jimfs.Jimfs
|
||||
import net.corda.core.writeLines
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
|
||||
class PropertiesFileRPCUserServiceTest {
|
||||
|
||||
private val fileSystem = Jimfs.newFileSystem(unix())
|
||||
private val file = fileSystem.getPath("users.properties")
|
||||
|
||||
@After
|
||||
fun cleanUp() {
|
||||
fileSystem.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `file doesn't exist`() {
|
||||
val service = PropertiesFileRPCUserService(file)
|
||||
assertThat(service.getUser("user")).isNull()
|
||||
assertThat(service.users).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty file`() {
|
||||
val service = loadWithContents()
|
||||
assertThat(service.getUser("user")).isNull()
|
||||
assertThat(service.users).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `no permissions`() {
|
||||
val service = loadWithContents("user=password")
|
||||
assertThat(service.getUser("user")).isEqualTo(User("user", "password", permissions = emptySet()))
|
||||
assertThat(service.users).containsOnly(User("user", "password", permissions = emptySet()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single permission, which is in lower case`() {
|
||||
val service = loadWithContents("user=password,cash")
|
||||
assertThat(service.getUser("user")?.permissions).containsOnly("CASH")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two permissions, which are upper case`() {
|
||||
val service = loadWithContents("user=password,CASH,ADMIN")
|
||||
assertThat(service.getUser("user")?.permissions).containsOnly("CASH", "ADMIN")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two users`() {
|
||||
val service = loadWithContents("user=password,ADMIN", "user2=password2")
|
||||
assertThat(service.getUser("user")).isNotNull()
|
||||
assertThat(service.getUser("user2")).isNotNull()
|
||||
assertThat(service.users).containsOnly(
|
||||
User("user", "password", permissions = setOf("ADMIN")),
|
||||
User("user2", "password2", permissions = emptySet()))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unknown user`() {
|
||||
val service = loadWithContents("user=password")
|
||||
assertThat(service.getUser("test")).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Artemis special characters not permitted in usernames`() {
|
||||
assertThatThrownBy { loadWithContents("user.name=password") }
|
||||
assertThatThrownBy { loadWithContents("user*name=password") }
|
||||
assertThatThrownBy { loadWithContents("user#name=password") }
|
||||
}
|
||||
|
||||
private fun loadWithContents(vararg lines: String): PropertiesFileRPCUserService {
|
||||
file.writeLines(lines.asList())
|
||||
return PropertiesFileRPCUserService(file)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user