Skip to content

Commit

Permalink
Merge pull request #232 from JetBrains/graph-store-configurable-close
Browse files Browse the repository at this point in the history
Graph store configurable close && crop cipher key
  • Loading branch information
leostryuk authored Dec 17, 2024
2 parents 93cb3c4 + 3b887e2 commit b6e84d8
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ package jetbrains.exodus.entitystore.orientdb

import com.orientechnologies.orient.core.db.ODatabaseType
import com.orientechnologies.orient.core.db.OrientDBConfigBuilder
import kotlin.math.min

class ODatabaseConfig private constructor(
val databaseRoot: String,
val connectionConfig: ODatabaseConnectionConfig,
val databaseName: String,
val userName: String,
val password: String,
val databaseType: ODatabaseType,
val closeAfterDelayTimeout: Int,
val cipherKey: ByteArray?,
val closeDatabaseInDbProvider: Boolean,
val tweakConfig: OrientDBConfigBuilder.() -> Unit
) {
companion object {
Expand All @@ -37,34 +37,37 @@ class ODatabaseConfig private constructor(

@Suppress("unused")
class Builder internal constructor() {
var databaseName: String = ""
var databaseRoot: String = ""
var userName: String = ""
var password: String = ""
var databaseType: ODatabaseType = ODatabaseType.MEMORY
var closeAfterDelayTimeout: Int = 10
var cipherKey: ByteArray? = null
var tweakConfig: OrientDBConfigBuilder.() -> Unit = {}
private lateinit var connectionConfig: ODatabaseConnectionConfig
private var databaseName: String = ""
private var databaseType: ODatabaseType? = null
private var closeAfterDelayTimeout: Int? = null
private var cipherKey: ByteArray? = null
private var closeDatabaseInDbProvider = true
private var tweakConfig: OrientDBConfigBuilder.() -> Unit = {}

fun withDatabaseName(databaseName: String) = apply { this.databaseName = databaseName }
fun withDatabaseRoot(databaseURL: String) = apply { this.databaseRoot = databaseURL }
fun withUserName(userName: String) = apply { this.userName = userName }
fun withPassword(password: String) = apply { this.password = password }
fun withConnectionConfig(connectionConfig: ODatabaseConnectionConfig) =
apply { this.connectionConfig = connectionConfig }

fun withDatabaseType(databaseType: ODatabaseType) = apply { this.databaseType = databaseType }
fun withCloseAfterDelayTimeout(closeAfterDelayTimeout: Int) =
apply { this.closeAfterDelayTimeout = closeAfterDelayTimeout }

fun withCloseDatabaseInDbProvider(closeDatabaseInDbProvider: Boolean) =
apply { this.closeDatabaseInDbProvider = closeDatabaseInDbProvider }

fun withCipherKey(cipherKey: ByteArray?) = apply { this.cipherKey = cipherKey }
fun withStringHexAndIV(key: String, IV: Long) = apply {
require(cipherKey == null) { "Cipher is already initialized" }
cipherKey = hexStringToByteArray(key) + longToByteArray(IV)
byteArrayOf()
cipherKey = hexStringToByteArray(key.substring(0, min(16 * 2, key.length))) + longToByteArray(IV)
}

fun tweakConfig(tweakConfig: OrientDBConfigBuilder.() -> Unit) = apply { this.tweakConfig = tweakConfig }

fun build() = ODatabaseConfig(
databaseRoot, databaseName, userName, password, databaseType,
closeAfterDelayTimeout, cipherKey, tweakConfig
connectionConfig, databaseName, databaseType ?: connectionConfig.databaseType, closeAfterDelayTimeout ?: connectionConfig.closeAfterDelayTimeout,
cipherKey, closeDatabaseInDbProvider, tweakConfig
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright ${inceptionYear} - ${year} ${owner}
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.exodus.entitystore.orientdb

import com.orientechnologies.orient.core.db.ODatabaseType

class ODatabaseConnectionConfig private constructor(
val databaseRoot: String,
val userName: String,
val password: String,
val databaseType: ODatabaseType,
val closeAfterDelayTimeout: Int
) {
companion object {
fun builder(): Builder {
return Builder()
}
}

@Suppress("unused")
class Builder internal constructor() {
private lateinit var databaseRoot: String
private lateinit var userName: String
private lateinit var password: String
private var databaseType: ODatabaseType = ODatabaseType.MEMORY
private var closeAfterDelayTimeout: Int = 10

fun withDatabaseRoot(databaseURL: String) = apply { this.databaseRoot = databaseURL }
fun withUserName(userName: String) = apply { this.userName = userName }
fun withPassword(password: String) = apply { this.password = password }
fun withDatabaseType(databaseType: ODatabaseType) = apply { this.databaseType = databaseType }
fun withCloseAfterDelayTimeout(closeAfterDelayTimeout: Int) =
apply { this.closeAfterDelayTimeout = closeAfterDelayTimeout }

fun build() = ODatabaseConnectionConfig(
databaseRoot, userName, password, databaseType,
closeAfterDelayTimeout
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal
import com.orientechnologies.orient.core.db.ODatabaseSession
import com.orientechnologies.orient.core.db.OrientDB
import com.orientechnologies.orient.core.db.OrientDBConfigBuilder
import kotlin.streams.asSequence

interface ODatabaseProvider {
val databaseLocation: String
Expand Down Expand Up @@ -91,7 +90,7 @@ internal fun hasActiveSession(): Boolean {
return db != null
}

fun initOrientDbServer(config: ODatabaseConfig): OrientDB {
fun initOrientDbServer(config: ODatabaseConnectionConfig): OrientDB {
val orientConfig = OrientDBConfigBuilder().apply {
addConfig(OGlobalConfiguration.AUTO_CLOSE_AFTER_DELAY, true)
addConfig(OGlobalConfiguration.AUTO_CLOSE_DELAY, config.closeAfterDelayTimeout)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import com.orientechnologies.orient.core.db.OrientDB
import com.orientechnologies.orient.core.db.OrientDBConfig
import com.orientechnologies.orient.core.db.OrientDBConfigBuilder
import java.io.File
import java.util.Base64
import java.util.*

//username and password are considered to be same for all databases
//todo this params also should be collected in some config entity
Expand Down Expand Up @@ -62,7 +62,7 @@ class ODatabaseProviderImpl(
}

override val databaseLocation: String
get() = File(config.databaseRoot, config.databaseName).absolutePath
get() = File(config.connectionConfig.databaseRoot, config.databaseName).absolutePath

override fun acquireSession(): ODatabaseSession {
return acquireSessionImpl(true)
Expand Down Expand Up @@ -105,12 +105,14 @@ class ODatabaseProviderImpl(
if (checkNoActiveSession) {
requireNoActiveSession()
}
return database.cachedPool(config.databaseName, config.userName, config.password, orientConfig).acquire()
return database.cachedPool(config.databaseName, config.connectionConfig.userName, config.connectionConfig.password, orientConfig).acquire()
}

override fun close() {
// OxygenDB cannot close the database if it is read-only (frozen)
readOnly = false
database.close()
if (config.closeDatabaseInDbProvider){
database.close()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
package jetbrains.exodus.entitystore

import com.orientechnologies.orient.core.db.ODatabaseType
import jetbrains.exodus.entitystore.orientdb.ODatabaseConfig
import jetbrains.exodus.entitystore.orientdb.ODatabaseConnectionConfig
import jetbrains.exodus.entitystore.orientdb.initOrientDbServer
import java.nio.file.Files
import kotlin.io.path.absolutePathString
Expand All @@ -26,11 +26,11 @@ class WrongUsernameTest {

@Test(expected = IllegalArgumentException::class)
fun cannotCreateDatabaseWithWrongUsername() {
val cfg = ODatabaseConfig
val cfg = ODatabaseConnectionConfig
.builder()
.withPassword("hello")
.withUserName(";drop database users")
.withDatabaseType(ODatabaseType.MEMORY)
.withDatabaseName("hello")
.withDatabaseRoot(Files.createTempDirectory("haha").absolutePathString())
.build()
initOrientDbServer(cfg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,23 @@ import mu.KLogging
import org.junit.After
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.lang.AssertionError
import java.nio.file.Files
import java.util.*
import kotlin.io.path.absolutePathString

class EncryptedDBTest {
companion object : KLogging()
@RunWith(Parameterized::class)
class EncryptedDBTest(val number: Int) {
companion object : KLogging() {
@JvmStatic
@Parameterized.Parameters
fun data(): Collection<Array<Any>> {
return Arrays.asList<Array<Any>>(*Array(5) { arrayOf(0) }) // Repeat 20 times
}
}

lateinit var provider: ODatabaseProviderImpl
lateinit var db: OrientDB

Expand All @@ -39,12 +50,17 @@ class EncryptedDBTest {
val username = "admin"
val dbName = "test"

return ODatabaseConfig.builder()
val connConfig = ODatabaseConnectionConfig.builder()
.withPassword(password)
.withUserName(username)
.withDatabaseType(ODatabaseType.PLOCAL)
.withDatabaseRoot(Files.createTempDirectory("oxigenDB_test$number").absolutePathString())
.build()

return ODatabaseConfig.builder()
.withConnectionConfig(connConfig)
.withDatabaseType(ODatabaseType.PLOCAL)
.withDatabaseName(dbName)
.withDatabaseRoot(Files.createTempDirectory("oxigenDB_test").absolutePathString())
.withCipherKey(key)
.build()
}
Expand All @@ -57,11 +73,13 @@ class EncryptedDBTest {
}
val config = createConfig(cipherKey)
val noEncryptionConfig = createConfig(null)
db = initOrientDbServer(config)
logger.info("Connect to db and create test vertex class")
db = initOrientDbServer(config.connectionConfig)
provider = ODatabaseProviderImpl(config, db)
provider.withSession { session ->
session.createVertexClass("TEST")
}
logger.info("Set vertex property")
provider.withSession { session ->
session.executeInTx {
val vertex = session.newVertex("TEST")
Expand All @@ -70,18 +88,22 @@ class EncryptedDBTest {
}
}
db.close()
logger.info("Close the DB")
Thread.sleep(1000)
db = initOrientDbServer(config)
logger.info("Connect to db one more time and read")
db = initOrientDbServer(config.connectionConfig)
provider = ODatabaseProviderImpl(config, db)
provider.withSession { session ->
session.executeInTx {
val vertex = session.query("SELECT FROM TEST").vertexStream().toList()
Assert.assertEquals(1, vertex.size)
}
}
logger.info("Close the DB")
db.close()
Thread.sleep(1000)
db = initOrientDbServer(config)
logger.info("Connect to db one more time without encryption")
db = initOrientDbServer(config.connectionConfig)
try {
ODatabaseProviderImpl(noEncryptionConfig, db).apply {
withSession { session ->
Expand All @@ -94,17 +116,22 @@ class EncryptedDBTest {
}
} catch (_: OStorageException) {
logger.info("As expected DB failed to initialize without key")
} catch (e: AssertionError) {
logger.info("As expected DB failed to initialize without key")
} catch (e: Throwable) {
logger.error("DB failed with unexpected error", e)
Assert.fail("Wrong error")
}
}

@After
fun close() {
db.close()
try {
if (!Orient.instance().isActive){
if (!Orient.instance().isActive) {
Orient.instance().startup()
}
} catch (_: Throwable){
} catch (_: Throwable) {
logger.error("CANNOT REINIT OXIGENDB")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright ${inceptionYear} - ${year} ${owner}
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package jetbrains.exodus.entitystore.orientdb

import org.junit.Assert
import org.junit.Test

class ODatabaseConfigTest {
@Test
fun `cypher key is trunked to 24 bytes from bigger one`() {
val key1 = Array(60) { "aa" }.joinToString(separator = "")

val connConfig = ODatabaseConnectionConfig
.builder()
.withUserName("testUrl")
.withPassword("testPassword")
.withDatabaseRoot("aa")
.build()


val cfg = ODatabaseConfig
.builder()
.withConnectionConfig(connConfig)
.withStringHexAndIV(key1, 10L)
.withDatabaseName("aa")
.build()

Assert.assertEquals(24, cfg.cipherKey?.size)
}

@Test
fun `cypher key is not trunked if key is smaller than 24`() {
val key1 = "aabbccddaabbccdd"

val connConfig = ODatabaseConnectionConfig.builder()
.withUserName("testUrl")
.withPassword("testPassword")
.withDatabaseRoot("aa")
.build()

val cfg = ODatabaseConfig
.builder()
.withConnectionConfig(connConfig)
.withStringHexAndIV(key1, 10L)
.withDatabaseName("aa")

.build()

Assert.assertEquals(16, cfg.cipherKey?.size)
}

}
Loading

0 comments on commit b6e84d8

Please sign in to comment.