BIT-653: Adding data source for passphrase generation (#273)

This commit is contained in:
joshua-livefront
2023-11-22 18:22:56 -05:00
committed by Álison Fernandes
parent 3a07bbd3da
commit 1f337e94f0
14 changed files with 266 additions and 72 deletions

View File

@@ -1,6 +1,6 @@
package com.x8bit.bitwarden.data.tools.generator.datasource.disk
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasswordGenerationOptions
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
/**
* Primary access point for disk information related to generation.
@@ -8,12 +8,12 @@ import com.x8bit.bitwarden.data.tools.generator.repository.model.PasswordGenerat
interface GeneratorDiskSource {
/**
* Retrieves a user's password generation options using a [userId].
* Retrieves a user's passcode generation options using a [userId].
*/
fun getPasswordGenerationOptions(userId: String): PasswordGenerationOptions?
fun getPasscodeGenerationOptions(userId: String): PasscodeGenerationOptions?
/**
* Stores a user's password generation options using a [userId].
* Stores a user's passcode generation options using a [userId].
*/
fun storePasswordGenerationOptions(userId: String, options: PasswordGenerationOptions?)
fun storePasscodeGenerationOptions(userId: String, options: PasscodeGenerationOptions?)
}

View File

@@ -2,7 +2,7 @@ package com.x8bit.bitwarden.data.tools.generator.datasource.disk
import android.content.SharedPreferences
import com.x8bit.bitwarden.data.platform.datasource.disk.BaseDiskSource
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasswordGenerationOptions
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@@ -17,14 +17,14 @@ class GeneratorDiskSourceImpl(
) : BaseDiskSource(sharedPreferences),
GeneratorDiskSource {
override fun getPasswordGenerationOptions(userId: String): PasswordGenerationOptions? {
override fun getPasscodeGenerationOptions(userId: String): PasscodeGenerationOptions? {
val key = getPasswordGenerationOptionsKey(userId)
return getString(key)?.let { json.decodeFromString(it) }
}
override fun storePasswordGenerationOptions(
override fun storePasscodeGenerationOptions(
userId: String,
options: PasswordGenerationOptions?,
options: PasscodeGenerationOptions?,
) {
val key = getPasswordGenerationOptionsKey(userId)
putString(

View File

@@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.tools.generator.datasource.sdk
import com.bitwarden.core.PassphraseGeneratorRequest
import com.bitwarden.core.PasswordGeneratorRequest
/**
@@ -11,4 +12,9 @@ interface GeneratorSdkSource {
* Generates a password returning a [String] wrapped in a [Result].
*/
suspend fun generatePassword(request: PasswordGeneratorRequest): Result<String>
/**
* Generates a passphrase returning a [String] wrapped in a [Result].
*/
suspend fun generatePassphrase(request: PassphraseGeneratorRequest): Result<String>
}

View File

@@ -1,5 +1,6 @@
package com.x8bit.bitwarden.data.tools.generator.datasource.sdk
import com.bitwarden.core.PassphraseGeneratorRequest
import com.bitwarden.core.PasswordGeneratorRequest
import com.bitwarden.sdk.ClientGenerators
@@ -17,4 +18,10 @@ class GeneratorSdkSourceImpl(
): Result<String> = runCatching {
clientGenerator.password(request)
}
override suspend fun generatePassphrase(
request: PassphraseGeneratorRequest,
): Result<String> = runCatching {
clientGenerator.passphrase(request)
}
}

View File

@@ -1,8 +1,10 @@
package com.x8bit.bitwarden.data.tools.generator.repository
import com.bitwarden.core.PassphraseGeneratorRequest
import com.bitwarden.core.PasswordGeneratorRequest
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassphraseResult
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasswordGenerationOptions
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
/**
* Responsible for managing generator data.
@@ -17,12 +19,19 @@ interface GeneratorRepository {
): GeneratedPasswordResult
/**
* Get the [PasswordGenerationOptions] for the current user.
* Attempt to generate a passphrase.
*/
fun getPasswordGenerationOptions(): PasswordGenerationOptions?
suspend fun generatePassphrase(
passphraseGeneratorRequest: PassphraseGeneratorRequest,
): GeneratedPassphraseResult
/**
* Save the [PasswordGenerationOptions] for the current user.
* Get the [PasscodeGenerationOptions] for the current user.
*/
fun savePasswordGenerationOptions(options: PasswordGenerationOptions)
fun getPasscodeGenerationOptions(): PasscodeGenerationOptions?
/**
* Save the [PasscodeGenerationOptions] for the current user.
*/
fun savePasscodeGenerationOptions(options: PasscodeGenerationOptions)
}

View File

@@ -1,11 +1,13 @@
package com.x8bit.bitwarden.data.tools.generator.repository
import com.bitwarden.core.PassphraseGeneratorRequest
import com.bitwarden.core.PasswordGeneratorRequest
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.tools.generator.datasource.disk.GeneratorDiskSource
import com.x8bit.bitwarden.data.tools.generator.datasource.sdk.GeneratorSdkSource
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPassphraseResult
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasswordGenerationOptions
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
import javax.inject.Singleton
/**
@@ -28,13 +30,23 @@ class GeneratorRepositoryImpl constructor(
onFailure = { GeneratedPasswordResult.InvalidRequest },
)
override fun getPasswordGenerationOptions(): PasswordGenerationOptions? {
override suspend fun generatePassphrase(
passphraseGeneratorRequest: PassphraseGeneratorRequest,
): GeneratedPassphraseResult =
generatorSdkSource
.generatePassphrase(passphraseGeneratorRequest)
.fold(
onSuccess = { GeneratedPassphraseResult.Success(it) },
onFailure = { GeneratedPassphraseResult.InvalidRequest },
)
override fun getPasscodeGenerationOptions(): PasscodeGenerationOptions? {
val userId = authDiskSource.userState?.activeUserId
return userId?.let { generatorDiskSource.getPasswordGenerationOptions(it) }
return userId?.let { generatorDiskSource.getPasscodeGenerationOptions(it) }
}
override fun savePasswordGenerationOptions(options: PasswordGenerationOptions) {
override fun savePasscodeGenerationOptions(options: PasscodeGenerationOptions) {
val userId = authDiskSource.userState?.activeUserId
userId?.let { generatorDiskSource.storePasswordGenerationOptions(it, options) }
userId?.let { generatorDiskSource.storePasscodeGenerationOptions(it, options) }
}
}

View File

@@ -0,0 +1,16 @@
package com.x8bit.bitwarden.data.tools.generator.repository.model
/**
* Represents the outcome of a generator operation.
*/
sealed class GeneratedPassphraseResult {
/**
* Operation succeeded with a value.
*/
data class Success(val generatedString: String) : GeneratedPassphraseResult()
/**
* There was an error during the operation.
*/
data object InvalidRequest : GeneratedPassphraseResult()
}

View File

@@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* A data class representing the configuration options for password generation.
* A data class representing the configuration options for both password and passphrase generation.
*
* @property length The total length of the generated password.
* @property allowAmbiguousChar Indicates whether ambiguous characters are allowed in the password.
@@ -16,9 +16,16 @@ import kotlinx.serialization.Serializable
* @property minLowercase The minimum number of lowercase characters required in the password.
* @property allowSpecial Indicates whether special characters are allowed in the password.
* @property minSpecial The minimum number of special characters required in the password.
* @property numWords The number of words in the generated passphrase.
* @property wordSeparator The character used to separate words in the passphrase.
* @property allowCapitalize Indicates whether to use capitals in the passphrase.
* @property allowIncludeNumber Indicates whether to include numbers in the passphrase.
*/
@Serializable
data class PasswordGenerationOptions(
data class PasscodeGenerationOptions(
// Password-specific options
@SerialName("length")
val length: Int,
@@ -35,17 +42,31 @@ data class PasswordGenerationOptions(
val hasUppercase: Boolean,
@SerialName("minUppercase")
val minUppercase: Int?,
val minUppercase: Int? = null,
@SerialName("lowercase")
val hasLowercase: Boolean,
@SerialName("minLowercase")
val minLowercase: Int?,
val minLowercase: Int? = null,
@SerialName("special")
val allowSpecial: Boolean,
@SerialName("minSpecial")
val minSpecial: Int,
// Passphrase-specific options
@SerialName("numWords")
val numWords: Int,
@SerialName("wordSeparator")
val wordSeparator: String,
@SerialName("capitalize")
val allowCapitalize: Boolean,
@SerialName("includeNumber")
val allowIncludeNumber: Boolean,
)

View File

@@ -9,7 +9,7 @@ import com.bitwarden.core.PasswordGeneratorRequest
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
import com.x8bit.bitwarden.data.tools.generator.repository.model.GeneratedPasswordResult
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasswordGenerationOptions
import com.x8bit.bitwarden.data.tools.generator.repository.model.PasscodeGenerationOptions
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
@@ -104,7 +104,7 @@ class GeneratorViewModel @Inject constructor(
}
is Password -> {
val options = generatorRepository.getPasswordGenerationOptions()
val options = generatorRepository.getPasscodeGenerationOptions()
val password = if (options != null) {
Password(
length = options.length,
@@ -134,19 +134,39 @@ class GeneratorViewModel @Inject constructor(
}
private fun savePasswordOptionsToDisk(password: Password) {
val options = PasswordGenerationOptions(
val options = generatorRepository
.getPasscodeGenerationOptions() ?: generatePasscodeDefaultOptions()
val newOptions = options.copy(
length = password.length,
allowAmbiguousChar = password.avoidAmbiguousChars,
hasNumbers = password.useNumbers,
minNumber = password.minNumbers,
hasUppercase = password.useCapitals,
minUppercase = null,
hasLowercase = password.useLowercase,
minLowercase = null,
allowSpecial = password.useSpecialChars,
minSpecial = password.minSpecial,
)
generatorRepository.savePasswordGenerationOptions(options)
generatorRepository.savePasscodeGenerationOptions(newOptions)
}
private fun generatePasscodeDefaultOptions(): PasscodeGenerationOptions {
val defaultPassword = Password()
val defaultPassphrase = Passphrase()
return PasscodeGenerationOptions(
length = defaultPassword.length,
allowAmbiguousChar = defaultPassword.avoidAmbiguousChars,
hasNumbers = defaultPassword.useNumbers,
minNumber = defaultPassword.minNumbers,
hasUppercase = defaultPassword.useCapitals,
hasLowercase = defaultPassword.useLowercase,
allowSpecial = defaultPassword.useSpecialChars,
minSpecial = defaultPassword.minSpecial,
allowCapitalize = defaultPassphrase.capitalize,
allowIncludeNumber = defaultPassphrase.includeNumber,
wordSeparator = defaultPassphrase.wordSeparator.toString(),
numWords = defaultPassphrase.numWords,
)
}
private suspend fun generatePassword(password: Password) {