Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JPERF-729: Create a way to configure Jira admin user password during database setup #107

Merged
merged 26 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a5f1e36
JPERF-729: Create a way to configure Jira admin user password during …
pczuj Jun 28, 2021
761f912
JPERF-729 Log dataset password update
mgrzaslewicz Nov 16, 2021
e71d277
JPERF-729 Fix unit test
mgrzaslewicz Nov 16, 2021
64b388b
JPERF-729 Handle both plain text and encrypted admin password dataset…
mgrzaslewicz Nov 19, 2021
8e2bd06
JPERF-729 Add changelog entry, deprecate Database.setup(ssh) instead …
mgrzaslewicz Nov 24, 2021
9e9fbc3
JPERF-729 Move changelog entry to unreleased, remove all API breaking…
mgrzaslewicz Nov 24, 2021
341818c
JPERF-729 Apply suggestions from PR comments
mgrzaslewicz Nov 24, 2021
9b8ae41
JPERF-729 Move username out of extension function to builder with def…
mgrzaslewicz Nov 25, 2021
10f9d35
JPERF-729 Move username out of extension function to builder with def…
mgrzaslewicz Nov 25, 2021
26aeb0e
JPERF-729 Make loggers non-static again to have the same approach eve…
mgrzaslewicz Nov 25, 2021
881f9b2
JPERF-729 Add link to issue in changelog
mgrzaslewicz Nov 25, 2021
fb2be73
JPERF-729 Unify logger creation
mgrzaslewicz Nov 25, 2021
56ad890
JPERF-729 Change log level to debug for password update
mgrzaslewicz Nov 25, 2021
0471a2a
JPERF-729 Extract password encryptor to follow single responsibility …
mgrzaslewicz Nov 25, 2021
13bcd56
JPERF-729 Fix typo in test method name
mgrzaslewicz Nov 25, 2021
4c49b61
JPERF-729 Refactor JiraUserPasswordOverridingDatabase
mgrzaslewicz Nov 29, 2021
bfc5e2e
JPERF-729 Change withAdminPassword extension function return type to …
mgrzaslewicz Dec 2, 2021
f5fb3b8
JPERF-729 Add .idea folder to gitignore
mgrzaslewicz Jan 5, 2022
64c957f
JPERF-729 Use java.util.Function instead of kotlin interface - for ja…
mgrzaslewicz Jan 11, 2022
614475f
JPERF-729 Simplify password encryptor API
mgrzaslewicz Jan 11, 2022
6009d11
JPERF-729 Simplify password encryptor API even more
mgrzaslewicz Jan 11, 2022
148c9ab
JPERF-729 Remove duplication in extension function creating builder
mgrzaslewicz Jan 11, 2022
9be53d5
JPERF-729 Use shorter names in builder
mgrzaslewicz Jan 11, 2022
16425ea
JPERF-729 Rename file containing JiraUserEncryptedPasswordProvider class
mgrzaslewicz Jan 11, 2022
bab7c59
JPERF-729 Move CrowdEncryptedPasswordProvider to a separate file
mgrzaslewicz Jan 12, 2022
9b2fbec
JPERF-729 Explicitly setup test context in JiraUserPasswordOverriding…
mgrzaslewicz Jan 12, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ class MySqlDatabase(
private val source: DatasetPackage,
private val maxConnections: Int
) : Database {
companion object {
private val logger: Logger = LogManager.getLogger(MySqlDatabase::class.java)
}

private val logger: Logger = LogManager.getLogger(this::class.java)

private val image: DockerImage = DockerImage(
name = "mysql:5.7.32",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,56 +2,39 @@ package com.atlassian.performance.tools.infrastructure.api.database.passwordover

import com.atlassian.performance.tools.infrastructure.database.SshSqlClient
import com.atlassian.performance.tools.ssh.api.SshConnection

enum class JiraUserPasswordEncryptionType {
dagguh marked this conversation as resolved.
Show resolved Hide resolved
PLAIN_TEXT, ENCRYPTED
}
import java.util.function.Function

interface JiraUserPasswordEncryptor {
fun getEncryptedPassword(plainTextPassword: String): String
}

interface JiraUserPasswordEncryptorProvider {
fun get(jiraUserPasswordEncryptionType: JiraUserPasswordEncryptionType): JiraUserPasswordEncryptor
}


interface JiraUserPasswordEncryptionTypeService {
fun getEncryptionType(ssh: SshConnection, sqlClient: SshSqlClient): JiraUserPasswordEncryptionType
fun getEncryptor(ssh: SshConnection, sqlClient: SshSqlClient): JiraUserPasswordEncryptor
}

class DefaultJiraUserPasswordEncryptionTypeService(
private val jiraDatabaseSchemaName: String
) : JiraUserPasswordEncryptionTypeService {
class DefaultJiraUserPasswordEncryptorProvider(
mgrzaslewicz marked this conversation as resolved.
Show resolved Hide resolved
private val jiraDatabaseSchemaName: String,
private val plainTextPasswordEncryptor: JiraUserPasswordEncryptor,
private val encryptedPasswordEncryptor: JiraUserPasswordEncryptor
) : JiraUserPasswordEncryptorProvider {

override fun getEncryptionType(ssh: SshConnection, sqlClient: SshSqlClient): JiraUserPasswordEncryptionType {
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") -> JiraUserPasswordEncryptionType.PLAIN_TEXT
sqlResult.contains("atlassian-security") -> JiraUserPasswordEncryptionType.ENCRYPTED
sqlResult.contains("plaintext") -> plainTextPasswordEncryptor
sqlResult.contains("atlassian-security") -> encryptedPasswordEncryptor
else -> throw RuntimeException("Unknown jira user password encryption type")
}
}
}

class EncryptedJiraUserPasswordEncryptor(
private val passwordEncryptFunction: (String) -> String
private val passwordEncryptFunction: Function<String, String>
) : JiraUserPasswordEncryptor {
override fun getEncryptedPassword(plainTextPassword: String) = passwordEncryptFunction(plainTextPassword)
override fun getEncryptedPassword(plainTextPassword: String) = passwordEncryptFunction.apply(plainTextPassword)
}

class PlainTextJiraUserPasswordEncryptor : JiraUserPasswordEncryptor {
override fun getEncryptedPassword(plainTextPassword: String) = plainTextPassword
}


class DefaultJiraUserPasswordEncryptorProvider(passwordEncryptFunction: (String) -> String) : JiraUserPasswordEncryptorProvider {
private val encryptors = mapOf(
JiraUserPasswordEncryptionType.PLAIN_TEXT to PlainTextJiraUserPasswordEncryptor(),
JiraUserPasswordEncryptionType.ENCRYPTED to EncryptedJiraUserPasswordEncryptor(passwordEncryptFunction)
)

override fun get(jiraUserPasswordEncryptionType: JiraUserPasswordEncryptionType) = encryptors[jiraUserPasswordEncryptionType]!!

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import com.atlassian.performance.tools.ssh.api.SshConnection
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.Logger
import java.net.URI
import java.util.function.Function

class JiraUserPasswordOverridingDatabase internal constructor(
private val databaseDelegate: Database,
private val sqlClient: SshSqlClient,
private val username: String,
private val jiraDatabaseSchemaName: String,
private val userPasswordPlainText: String,
private val userPasswordEncryptionTypeService: JiraUserPasswordEncryptionTypeService,
private val userPasswordEncryptorProvider: JiraUserPasswordEncryptorProvider
) : Database {
private val logger: Logger = LogManager.getLogger(this::class.java)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Logging responsibility could be split to a decorator on this class.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the benefit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are multiple benefits:

  • Decoupling logging from a class ensures that your code is observable
    • It's possible to create code that will replace logging with something easier to understand by machine, e.g. event system.
    • It's possible to write unit tests on the same signals as would trigger log lines seen by the user, i.e. you don't need to mock logger to use same signals.
  • You could easily remove the logger code from the execution path by removing the decorator from the initialisation. It could be done dynamically.
  • You could unit test the logger responsibility.
  • It spreads the foundation of the good practice of keeping things SRP.

Again I'm not insisting, especially with this one, because our practice was so far to not decouple logger from other business logic. Still I wanted to point it out to see your thoughts on it. I personally would prefer us to follow the decoupling practices, because I see how not following it damages the flexibility of our implementations. I don't believe in popular thinking that everything can be rewritten to match the new requirements - from my experience it never happens. However we can write code that is modular and later one be able to initialise it differently to achieve new requirements.

Copy link
Contributor

@mgrzaslewicz mgrzaslewicz Jan 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand all the benefits, but do we really need them?
Was there a single time in this library we had to test what's being logged or a need to make it configurable?
I don't feel adding functionality because it might be needed some day is a good thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@pczuj I think these are interesting dev experiments, they deserve to be explored. However, we shouldn't mix them inside a big and overdue feature PR.

Expand All @@ -26,8 +26,7 @@ class JiraUserPasswordOverridingDatabase internal constructor(
ssh: SshConnection
) {
databaseDelegate.start(jira, ssh)
val userPasswordEncryptionType = userPasswordEncryptionTypeService.getEncryptionType(ssh, sqlClient)
val userPasswordEncryptor = userPasswordEncryptorProvider.get(userPasswordEncryptionType)
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}'")
Expand All @@ -37,7 +36,6 @@ class JiraUserPasswordOverridingDatabase internal constructor(
class Builder(
private var databaseDelegate: Database,
private var userPasswordPlainText: String,
private var userPasswordEncryptionTypeService: JiraUserPasswordEncryptionTypeService,
private var userPasswordEncryptorProvider: JiraUserPasswordEncryptorProvider
) {
private var sqlClient: SshSqlClient = SshMysqlClient()
Expand All @@ -49,9 +47,6 @@ class JiraUserPasswordOverridingDatabase internal constructor(
fun userPasswordPlainText(userPassword: String) = apply { this.userPasswordPlainText = userPassword }
fun sqlClient(sqlClient: SshSqlClient) = apply { this.sqlClient = sqlClient }
fun jiraDatabaseSchemaName(jiraDatabaseSchemaName: String) = apply { this.jiraDatabaseSchemaName = jiraDatabaseSchemaName }
fun userPasswordEncryptionTypeService(userPasswordEncryptionTypeService: JiraUserPasswordEncryptionTypeService) =
apply { this.userPasswordEncryptionTypeService = userPasswordEncryptionTypeService }

fun userPasswordEncryptorProvider(userPasswordEncryptorProvider: JiraUserPasswordEncryptorProvider) =
apply { this.userPasswordEncryptorProvider = userPasswordEncryptorProvider }

Expand All @@ -61,7 +56,6 @@ class JiraUserPasswordOverridingDatabase internal constructor(
username = username,
userPasswordPlainText = userPasswordPlainText,
jiraDatabaseSchemaName = jiraDatabaseSchemaName,
userPasswordEncryptionTypeService = userPasswordEncryptionTypeService,
userPasswordEncryptorProvider = userPasswordEncryptorProvider
)
}
Expand All @@ -74,17 +68,16 @@ class JiraUserPasswordOverridingDatabase internal constructor(
* 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: (String) -> String): Database {
fun Database.withAdminPassword(adminPasswordPlainText: String, passwordEncryptFunction: Function<String, String>): Database {
val jiraDatabaseSchemaName = "jiradb"
val sqlClient = SshMysqlClient()
return JiraUserPasswordOverridingDatabase.Builder(
databaseDelegate = this,
userPasswordPlainText = adminPasswordPlainText,
userPasswordEncryptionTypeService = DefaultJiraUserPasswordEncryptionTypeService(
jiraDatabaseSchemaName = jiraDatabaseSchemaName
),
userPasswordEncryptorProvider = DefaultJiraUserPasswordEncryptorProvider(
passwordEncryptFunction = passwordEncryptFunction
jiraDatabaseSchemaName = jiraDatabaseSchemaName,
plainTextPasswordEncryptor = PlainTextJiraUserPasswordEncryptor(),
encryptedPasswordEncryptor = EncryptedJiraUserPasswordEncryptor(passwordEncryptFunction)
)
)
.jiraDatabaseSchemaName(jiraDatabaseSchemaName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,23 @@ 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: JiraUserPasswordEncryptionTypeService
private lateinit var tested: JiraUserPasswordEncryptorProvider
private val plainTextPasswordEncryptor: JiraUserPasswordEncryptor = PlainTextJiraUserPasswordEncryptor()
private val encryptedPasswordEncryptor: JiraUserPasswordEncryptor = EncryptedJiraUserPasswordEncryptor(Function { "" })

@Before
fun setup() {
sqlClient = MockSshSqlClient()
sshConnection = RememberingSshConnection()
tested = DefaultJiraUserPasswordEncryptionTypeService(
jiraDatabaseSchemaName = "jiradb"
tested = DefaultJiraUserPasswordEncryptorProvider(
jiraDatabaseSchemaName = "jiradb",
plainTextPasswordEncryptor = plainTextPasswordEncryptor,
encryptedPasswordEncryptor = encryptedPasswordEncryptor
)
}

Expand All @@ -34,7 +39,7 @@ class DefaultJiraUserPasswordEncryptionTypeServiceTest {
)
)
// when
tested.getEncryptionType(sshConnection, sqlClient)
tested.getEncryptor(sshConnection, sqlClient)
// then
assertThat(sqlClient.getLog())
.`as`("sql queries executed")
Expand All @@ -48,9 +53,9 @@ class DefaultJiraUserPasswordEncryptionTypeServiceTest {
// when
var exception: RuntimeException? = null
try {
tested.getEncryptionType(sshConnection, sqlClient)
tested.getEncryptor(sshConnection, sqlClient)
} catch (e: RuntimeException) {
exception = e
exception = e
}
// then
assertThat(exception).isNotNull()
Expand All @@ -70,9 +75,9 @@ class DefaultJiraUserPasswordEncryptionTypeServiceTest {
)
)
// when
val encryptionType = tested.getEncryptionType(sshConnection, sqlClient)
val passwordEncryptor = tested.getEncryptor(sshConnection, sqlClient)
// then
assertThat(encryptionType).isEqualTo(JiraUserPasswordEncryptionType.ENCRYPTED)
assertThat(passwordEncryptor).isEqualTo(encryptedPasswordEncryptor)
}

@Test
Expand All @@ -88,9 +93,9 @@ class DefaultJiraUserPasswordEncryptionTypeServiceTest {
)
)
// when
val encryptionType = tested.getEncryptionType(sshConnection, sqlClient)
val passwordEncryptor = tested.getEncryptor(sshConnection, sqlClient)
// then
assertThat(encryptionType).isEqualTo(JiraUserPasswordEncryptionType.PLAIN_TEXT)
assertThat(passwordEncryptor).isEqualTo(plainTextPasswordEncryptor)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ class JiraUserPasswordOverridingDatabaseTest {
.Builder(
databaseDelegate = underlyingDatabase,
userPasswordPlainText = samplePassword,
userPasswordEncryptorProvider = DefaultJiraUserPasswordEncryptorProvider(passwordEncryptFunction = { _ -> expectedEncryptedPassword }),
userPasswordEncryptionTypeService = object : JiraUserPasswordEncryptionTypeService {
override fun getEncryptionType(ssh: SshConnection, sqlClient: SshSqlClient) = JiraUserPasswordEncryptionType.ENCRYPTED
userPasswordEncryptorProvider = object : JiraUserPasswordEncryptorProvider {
override fun getEncryptor(ssh: SshConnection, sqlClient: SshSqlClient): JiraUserPasswordEncryptor {
return object : JiraUserPasswordEncryptor {
override fun getEncryptedPassword(plainTextPassword: String) = expectedEncryptedPassword
}
}
}
)
.sqlClient(sqlClient)
Expand Down