RandomPasswordProvider.kt
package de.pflugradts.passbird.domain.service.password.provider
import de.pflugradts.passbird.domain.model.egg.PasswordRequirements
import de.pflugradts.passbird.domain.model.shell.MAX_ASCII_VALUE
import de.pflugradts.passbird.domain.model.shell.MIN_ASCII_VALUE
import de.pflugradts.passbird.domain.model.shell.PlainValue
import de.pflugradts.passbird.domain.model.shell.PlainValue.Companion.plainValueOf
import de.pflugradts.passbird.domain.model.shell.Shell
import de.pflugradts.passbird.domain.model.shell.Shell.Companion.emptyShell
import de.pflugradts.passbird.domain.model.shell.Shell.Companion.shellOf
import java.security.SecureRandom
import kotlin.reflect.KProperty1
private val random = SecureRandom()
class RandomPasswordProvider(
private val passwordCandidateProvider: (PasswordRequirements) -> Shell = ::randomPassword,
) : PasswordProvider {
override fun createNewPassword(passwordRequirements: PasswordRequirements): Shell {
var passwordShell = emptyShell()
while (!isStrong(passwordShell, passwordRequirements)) {
passwordShell.scramble()
passwordShell = passwordCandidateProvider(passwordRequirements)
}
return passwordShell
}
private fun isStrong(passwordShell: Shell, passwordRequirements: PasswordRequirements) =
(!passwordRequirements.hasNumbers || passwordShell.anyMatch(PlainValue::isDigit)) &&
(!passwordRequirements.hasUppercaseLetters || passwordShell.anyMatch(PlainValue::isUppercaseCharacter)) &&
(!passwordRequirements.hasLowercaseLetters || passwordShell.anyMatch(PlainValue::isLowercaseCharacter)) &&
(!passwordRequirements.hasSpecialCharacters || passwordShell.anyMatch(PlainValue::isSymbol))
private fun Shell.anyMatch(property: KProperty1<PlainValue, Boolean>): Boolean {
for (index in 0 until size) {
if (property.get(plainValueOf(getByte(index)))) return true
}
return false
}
}
private fun randomPassword(passwordRequirements: PasswordRequirements) =
shellOf((0..<passwordRequirements.length).map { randomByte(passwordRequirements) }.toList())
private fun randomByte(passwordRequirements: PasswordRequirements): Byte {
val getRandom = { (random.nextInt(MAX_ASCII_VALUE - MIN_ASCII_VALUE) + MIN_ASCII_VALUE).toByte() }
var result: Byte
do result = getRandom() while (!result.satisfies(passwordRequirements))
return result
}
private fun Byte.matches(property: KProperty1<PlainValue, Boolean>) = property.get(plainValueOf(this))
private fun Byte.satisfies(passwordRequirements: PasswordRequirements): Boolean {
if (passwordRequirements.hasSpecialCharacters && this.toInt().toChar() in passwordRequirements.unusedSpecialCharacters) return false
if (!passwordRequirements.hasSpecialCharacters && matches(PlainValue::isSymbol)) return false
if (!passwordRequirements.hasNumbers && passwordRequirements.isValid() && matches(PlainValue::isDigit)) return false
if (!passwordRequirements.hasLowercaseLetters && matches(PlainValue::isLowercaseCharacter)) return false
if (!passwordRequirements.hasUppercaseLetters && matches(PlainValue::isUppercaseCharacter)) return false
return true
}