diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fb8d042..d7583289 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Dropping a requirement of a major version of a dependency is a new contract. [Unreleased]: https://github.com/atlassian/infrastructure/compare/release-4.18.0...master ### Added - `JiraUserPasswordOverridingDatabase` to support providing custom admin password during database setup [JPERF-729] +- Add static `Database.overridePassword` (a Kotlin extension). [JPERF-729]: https://ecosystem.atlassian.net/browse/JPERF-729 diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordEncryptor.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordEncryptor.kt deleted file mode 100644 index ffa84a5a..00000000 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordEncryptor.kt +++ /dev/null @@ -1,40 +0,0 @@ -package com.atlassian.performance.tools.infrastructure.api.database.passwordoverride - -import com.atlassian.performance.tools.infrastructure.database.SshSqlClient -import com.atlassian.performance.tools.ssh.api.SshConnection -import java.util.function.Function - -interface JiraUserPasswordEncryptor { - fun getEncryptedPassword(plainTextPassword: String): String -} - -interface JiraUserPasswordEncryptorProvider { - fun getEncryptor(ssh: SshConnection, sqlClient: SshSqlClient): JiraUserPasswordEncryptor -} - -class DefaultJiraUserPasswordEncryptorProvider( - private val jiraDatabaseSchemaName: String, - private val plainTextPasswordEncryptor: JiraUserPasswordEncryptor, - private val encryptedPasswordEncryptor: JiraUserPasswordEncryptor -) : JiraUserPasswordEncryptorProvider { - - override fun getEncryptor(ssh: SshConnection, sqlClient: SshSqlClient): JiraUserPasswordEncryptor { - val sqlResult = - sqlClient.runSql(ssh, "select attribute_value from ${jiraDatabaseSchemaName}.cwd_directory_attribute where attribute_name = 'user_encryption_method';").output - return when { - sqlResult.contains("plaintext") -> plainTextPasswordEncryptor - sqlResult.contains("atlassian-security") -> encryptedPasswordEncryptor - else -> throw RuntimeException("Unknown jira user password encryption type") - } - } -} - -class EncryptedJiraUserPasswordEncryptor( - private val passwordEncryptFunction: Function -) : JiraUserPasswordEncryptor { - override fun getEncryptedPassword(plainTextPassword: String) = passwordEncryptFunction.apply(plainTextPassword) -} - -class PlainTextJiraUserPasswordEncryptor : JiraUserPasswordEncryptor { - override fun getEncryptedPassword(plainTextPassword: String) = plainTextPassword -} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordOverridingDatabase.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordOverridingDatabase.kt index b0ac37d8..1363b761 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordOverridingDatabase.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordOverridingDatabase.kt @@ -9,13 +9,13 @@ import org.apache.logging.log4j.Logger import java.net.URI import java.util.function.Function -class JiraUserPasswordOverridingDatabase internal constructor( +class JiraUserPasswordOverridingDatabase private constructor( private val databaseDelegate: Database, - private val sqlClient: SshSqlClient, private val username: String, - private val jiraDatabaseSchemaName: String, - private val userPasswordPlainText: String, - private val userPasswordEncryptorProvider: JiraUserPasswordEncryptorProvider + private val plainTextPassword: String, + private val passwordEncryption: Function, + private val sqlClient: SshSqlClient, + private val schema: String ) : Database { private val logger: Logger = LogManager.getLogger(this::class.java) @@ -26,61 +26,57 @@ class JiraUserPasswordOverridingDatabase internal constructor( ssh: SshConnection ) { databaseDelegate.start(jira, ssh) - val userPasswordEncryptor = userPasswordEncryptorProvider.getEncryptor(ssh, sqlClient) - val password = userPasswordEncryptor.getEncryptedPassword(userPasswordPlainText) - sqlClient.runSql(ssh, "UPDATE ${jiraDatabaseSchemaName}.cwd_user SET credential='$password' WHERE user_name='$username';") - logger.debug("Password for user '$username' updated to '${userPasswordPlainText}'") + val methodSelect = "SELECT attribute_value FROM $schema.cwd_directory_attribute" + + " WHERE attribute_name = 'user_encryption_method';" + val encryptionMethod = sqlClient.runSql(ssh, methodSelect).output + val password = when { + encryptionMethod.contains("plaintext") -> plainTextPassword + encryptionMethod.contains("atlassian-security") -> passwordEncryption.apply(plainTextPassword) + else -> throw RuntimeException("Unknown jira user password encryption type") + } + val passwordUpdate = "UPDATE $schema.cwd_user SET credential='$password'" + + " WHERE user_name='$username';" + sqlClient.runSql(ssh, passwordUpdate) + logger.debug("Password for user '$username' updated to '$plainTextPassword'") } - class Builder( private var databaseDelegate: Database, - private var userPasswordPlainText: String, - private var userPasswordEncryptorProvider: JiraUserPasswordEncryptorProvider + private var passwordEncryption: Function ) { private var sqlClient: SshSqlClient = SshMysqlClient() - private var jiraDatabaseSchemaName: String = "jiradb" + private var schema: String = "jiradb" private var username: String = "admin" + private var plainTextPassword: String = "admin" fun databaseDelegate(databaseDelegate: Database) = apply { this.databaseDelegate = databaseDelegate } fun username(username: String) = apply { this.username = username } - fun userPasswordPlainText(userPassword: String) = apply { this.userPasswordPlainText = userPassword } + fun plainTextPassword(passwordPlainText: String) = apply { this.plainTextPassword = passwordPlainText } fun sqlClient(sqlClient: SshSqlClient) = apply { this.sqlClient = sqlClient } - fun jiraDatabaseSchemaName(jiraDatabaseSchemaName: String) = apply { this.jiraDatabaseSchemaName = jiraDatabaseSchemaName } - fun userPasswordEncryptorProvider(userPasswordEncryptorProvider: JiraUserPasswordEncryptorProvider) = - apply { this.userPasswordEncryptorProvider = userPasswordEncryptorProvider } + fun schema(jiraDatabaseSchemaName: String) = apply { this.schema = jiraDatabaseSchemaName } + fun passwordEncryption(passwordEncryption: Function) = + apply { this.passwordEncryption = passwordEncryption } fun build() = JiraUserPasswordOverridingDatabase( databaseDelegate = databaseDelegate, sqlClient = sqlClient, username = username, - userPasswordPlainText = userPasswordPlainText, - jiraDatabaseSchemaName = jiraDatabaseSchemaName, - userPasswordEncryptorProvider = userPasswordEncryptorProvider + plainTextPassword = plainTextPassword, + passwordEncryption = passwordEncryption, + schema = schema ) } } /** - * @param passwordEncryptFunction Based on [retrieving-the-jira-administrator](https://confluence.atlassian.com/jira/retrieving-the-jira-administrator-192836.html) + * @param passwordEncryption Based on [retrieving-the-jira-administrator](https://confluence.atlassian.com/jira/retrieving-the-jira-administrator-192836.html) * to encode the password in Jira format use [com.atlassian.crowd.password.encoder.AtlassianSecurityPasswordEncoder](https://docs.atlassian.com/atlassian-crowd/4.2.2/com/atlassian/crowd/password/encoder/AtlassianSecurityPasswordEncoder.html) * from the [com.atlassian.crowd.crowd-password-encoders](https://mvnrepository.com/artifact/com.atlassian.crowd/crowd-password-encoders/4.2.2). - * */ -fun Database.withAdminPassword(adminPasswordPlainText: String, passwordEncryptFunction: Function): Database { - val jiraDatabaseSchemaName = "jiradb" - val sqlClient = SshMysqlClient() - return JiraUserPasswordOverridingDatabase.Builder( - databaseDelegate = this, - userPasswordPlainText = adminPasswordPlainText, - userPasswordEncryptorProvider = DefaultJiraUserPasswordEncryptorProvider( - jiraDatabaseSchemaName = jiraDatabaseSchemaName, - plainTextPasswordEncryptor = PlainTextJiraUserPasswordEncryptor(), - encryptedPasswordEncryptor = EncryptedJiraUserPasswordEncryptor(passwordEncryptFunction) - ) - ) - .jiraDatabaseSchemaName(jiraDatabaseSchemaName) - .sqlClient(sqlClient) - .build() -} \ No newline at end of file +fun Database.overridePassword( + passwordEncryption: Function +): JiraUserPasswordOverridingDatabase.Builder = JiraUserPasswordOverridingDatabase.Builder( + databaseDelegate = this, + passwordEncryption = passwordEncryption +) \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/DefaultJiraUserPasswordEncryptionTypeServiceTest.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/DefaultJiraUserPasswordEncryptionTypeServiceTest.kt deleted file mode 100644 index 3f3acf17..00000000 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/DefaultJiraUserPasswordEncryptionTypeServiceTest.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.atlassian.performance.tools.infrastructure.api.database.passwordoverride - -import com.atlassian.performance.tools.infrastructure.mock.MockSshSqlClient -import com.atlassian.performance.tools.infrastructure.mock.RememberingSshConnection -import com.atlassian.performance.tools.ssh.api.SshConnection -import org.assertj.core.api.Assertions.assertThat -import org.junit.Before -import org.junit.Test -import java.util.function.Function - -class DefaultJiraUserPasswordEncryptionTypeServiceTest { - private lateinit var sqlClient: MockSshSqlClient - private lateinit var sshConnection: RememberingSshConnection - private lateinit var tested: JiraUserPasswordEncryptorProvider - private val plainTextPasswordEncryptor: JiraUserPasswordEncryptor = PlainTextJiraUserPasswordEncryptor() - private val encryptedPasswordEncryptor: JiraUserPasswordEncryptor = EncryptedJiraUserPasswordEncryptor(Function { "" }) - - @Before - fun setup() { - sqlClient = MockSshSqlClient() - sshConnection = RememberingSshConnection() - tested = DefaultJiraUserPasswordEncryptorProvider( - jiraDatabaseSchemaName = "jiradb", - plainTextPasswordEncryptor = plainTextPasswordEncryptor, - encryptedPasswordEncryptor = encryptedPasswordEncryptor - ) - } - - @Test - fun shouldQueryEncryptionMethod() { - // given - sqlClient.queueReturnedSqlCommandResult( - SshConnection.SshResult( - exitStatus = 0, - output = """attribute_value - atlassian-security -""".trimMargin(), - errorOutput = "" - ) - ) - // when - tested.getEncryptor(sshConnection, sqlClient) - // then - assertThat(sqlClient.getLog()) - .`as`("sql queries executed") - .containsExactly( - "select attribute_value from jiradb.cwd_directory_attribute where attribute_name = 'user_encryption_method';" - ) - } - - @Test - fun shouldThrowExceptionWhenUnknownEncryption() { - // when - var exception: RuntimeException? = null - try { - tested.getEncryptor(sshConnection, sqlClient) - } catch (e: RuntimeException) { - exception = e - } - // then - assertThat(exception).isNotNull() - assertThat(exception!!.message).isEqualTo("Unknown jira user password encryption type") - } - - @Test - fun shouldReturnEncrypted() { - // given - sqlClient.queueReturnedSqlCommandResult( - SshConnection.SshResult( - exitStatus = 0, - output = """attribute_value - atlassian-security -""".trimMargin(), - errorOutput = "" - ) - ) - // when - val passwordEncryptor = tested.getEncryptor(sshConnection, sqlClient) - // then - assertThat(passwordEncryptor).isEqualTo(encryptedPasswordEncryptor) - } - - @Test - fun shouldReturnPlainText() { - // given - sqlClient.queueReturnedSqlCommandResult( - SshConnection.SshResult( - exitStatus = 0, - output = """attribute_value - plaintext -""".trimMargin(), - errorOutput = "" - ) - ) - // when - val passwordEncryptor = tested.getEncryptor(sshConnection, sqlClient) - // then - assertThat(passwordEncryptor).isEqualTo(plainTextPasswordEncryptor) - } - -} \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordOverridingDatabaseTest.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordOverridingDatabaseTest.kt index 05fb5514..7f8d377a 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordOverridingDatabaseTest.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/passwordoverride/JiraUserPasswordOverridingDatabaseTest.kt @@ -1,7 +1,6 @@ package com.atlassian.performance.tools.infrastructure.api.database.passwordoverride import com.atlassian.performance.tools.infrastructure.api.database.Database -import com.atlassian.performance.tools.infrastructure.database.SshSqlClient import com.atlassian.performance.tools.infrastructure.mock.MockSshSqlClient import com.atlassian.performance.tools.infrastructure.mock.RememberingDatabase import com.atlassian.performance.tools.infrastructure.mock.RememberingSshConnection @@ -10,6 +9,7 @@ import org.assertj.core.api.Assertions.assertThat import org.junit.Before import org.junit.Test import java.net.URI +import java.util.function.Function class JiraUserPasswordOverridingDatabaseTest { @@ -27,21 +27,18 @@ class JiraUserPasswordOverridingDatabaseTest { underlyingDatabase = RememberingDatabase() sshConnection = RememberingSshConnection() sqlClient = MockSshSqlClient() - database = JiraUserPasswordOverridingDatabase - .Builder( - databaseDelegate = underlyingDatabase, - userPasswordPlainText = samplePassword, - userPasswordEncryptorProvider = object : JiraUserPasswordEncryptorProvider { - override fun getEncryptor(ssh: SshConnection, sqlClient: SshSqlClient): JiraUserPasswordEncryptor { - return object : JiraUserPasswordEncryptor { - override fun getEncryptedPassword(plainTextPassword: String) = expectedEncryptedPassword - } - } - } - ) + database = underlyingDatabase.overridePassword(Function { expectedEncryptedPassword }) + .plainTextPassword(samplePassword) .sqlClient(sqlClient) - .jiraDatabaseSchemaName("jira") + .schema("jira") .build() + sqlClient.queueReturnedSqlCommandResult( + SshConnection.SshResult( + exitStatus = 0, + output = "atlassian-security", + errorOutput = "" + ) + ) } @Test @@ -74,7 +71,7 @@ class JiraUserPasswordOverridingDatabaseTest { // then assertThat(sqlClient.getLog()) .`as`("sql queries executed") - .containsExactly( + .contains( "UPDATE jira.cwd_user SET credential='${expectedEncryptedPassword}' WHERE user_name='admin';" ) }