diff options
Diffstat (limited to 'app/src/main/java/dev/msfjarvis/aps/data/password')
3 files changed, 217 insertions, 237 deletions
diff --git a/app/src/main/java/dev/msfjarvis/aps/data/password/FieldItem.kt b/app/src/main/java/dev/msfjarvis/aps/data/password/FieldItem.kt index 2a3b52cd..5b4931e7 100644 --- a/app/src/main/java/dev/msfjarvis/aps/data/password/FieldItem.kt +++ b/app/src/main/java/dev/msfjarvis/aps/data/password/FieldItem.kt @@ -6,27 +6,30 @@ package dev.msfjarvis.aps.data.password class FieldItem(val key: String, val value: String, val action: ActionType) { - enum class ActionType { - COPY, HIDE - } + enum class ActionType { + COPY, + HIDE + } - enum class ItemType(val type: String) { - USERNAME("Username"), PASSWORD("Password"), OTP("OTP") - } + enum class ItemType(val type: String) { + USERNAME("Username"), + PASSWORD("Password"), + OTP("OTP") + } - companion object { + companion object { - // Extra helper methods - fun createOtpField(otp: String): FieldItem { - return FieldItem(ItemType.OTP.type, otp, ActionType.COPY) - } + // Extra helper methods + fun createOtpField(otp: String): FieldItem { + return FieldItem(ItemType.OTP.type, otp, ActionType.COPY) + } - fun createPasswordField(password: String): FieldItem { - return FieldItem(ItemType.PASSWORD.type, password, ActionType.HIDE) - } + fun createPasswordField(password: String): FieldItem { + return FieldItem(ItemType.PASSWORD.type, password, ActionType.HIDE) + } - fun createUsernameField(username: String): FieldItem { - return FieldItem(ItemType.USERNAME.type, username, ActionType.COPY) - } + fun createUsernameField(username: String): FieldItem { + return FieldItem(ItemType.USERNAME.type, username, ActionType.COPY) } + } } diff --git a/app/src/main/java/dev/msfjarvis/aps/data/password/PasswordEntry.kt b/app/src/main/java/dev/msfjarvis/aps/data/password/PasswordEntry.kt index d6f3c15f..8a2ca3c6 100644 --- a/app/src/main/java/dev/msfjarvis/aps/data/password/PasswordEntry.kt +++ b/app/src/main/java/dev/msfjarvis/aps/data/password/PasswordEntry.kt @@ -18,178 +18,178 @@ import java.util.Date */ class PasswordEntry(content: String, private val totpFinder: TotpFinder = UriTotpFinder()) { - val password: String - val username: String? - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - val digits: String - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - val totpSecret: String? - val totpPeriod: Long - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - val totpAlgorithm: String - val extraContent: String - val extraContentWithoutAuthData: String - val extraContentMap: Map<String, String> - - constructor(os: ByteArrayOutputStream) : this(os.toString(Charsets.UTF_8.name()), UriTotpFinder()) - - init { - val (foundPassword, passContent) = findAndStripPassword(content.split("\n".toRegex())) - password = foundPassword - extraContent = passContent.joinToString("\n") - extraContentWithoutAuthData = generateExtraContentWithoutAuthData() - extraContentMap = generateExtraContentPairs() - username = findUsername() - digits = findOtpDigits(content) - totpSecret = findTotpSecret(content) - totpPeriod = findTotpPeriod(content) - totpAlgorithm = findTotpAlgorithm(content) - } - - fun hasExtraContent(): Boolean { - return extraContent.isNotEmpty() - } - - fun hasExtraContentWithoutAuthData(): Boolean { - return extraContentWithoutAuthData.isNotEmpty() - } - - fun hasTotp(): Boolean { - return totpSecret != null + val password: String + val username: String? + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) val digits: String + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) val totpSecret: String? + val totpPeriod: Long + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) val totpAlgorithm: String + val extraContent: String + val extraContentWithoutAuthData: String + val extraContentMap: Map<String, String> + + constructor(os: ByteArrayOutputStream) : this(os.toString(Charsets.UTF_8.name()), UriTotpFinder()) + + init { + val (foundPassword, passContent) = findAndStripPassword(content.split("\n".toRegex())) + password = foundPassword + extraContent = passContent.joinToString("\n") + extraContentWithoutAuthData = generateExtraContentWithoutAuthData() + extraContentMap = generateExtraContentPairs() + username = findUsername() + digits = findOtpDigits(content) + totpSecret = findTotpSecret(content) + totpPeriod = findTotpPeriod(content) + totpAlgorithm = findTotpAlgorithm(content) + } + + fun hasExtraContent(): Boolean { + return extraContent.isNotEmpty() + } + + fun hasExtraContentWithoutAuthData(): Boolean { + return extraContentWithoutAuthData.isNotEmpty() + } + + fun hasTotp(): Boolean { + return totpSecret != null + } + + fun hasUsername(): Boolean { + return username != null + } + + fun calculateTotpCode(): String? { + if (totpSecret == null) return null + return Otp.calculateCode(totpSecret, Date().time / (1000 * totpPeriod), totpAlgorithm, digits).get() + } + + private fun generateExtraContentWithoutAuthData(): String { + var foundUsername = false + return extraContent + .lineSequence() + .filter { line -> + return@filter when { + USERNAME_FIELDS.any { prefix -> line.startsWith(prefix, ignoreCase = true) } && !foundUsername -> { + foundUsername = true + false + } + line.startsWith("otpauth://", ignoreCase = true) || line.startsWith("totp:", ignoreCase = true) -> { + false + } + else -> { + true + } + } + } + .joinToString(separator = "\n") + } + + private fun generateExtraContentPairs(): Map<String, String> { + fun MutableMap<String, String>.putOrAppend(key: String, value: String) { + if (value.isEmpty()) return + val existing = this[key] + this[key] = + if (existing == null) { + value + } else { + "$existing\n$value" + } } - fun hasUsername(): Boolean { - return username != null + val items = mutableMapOf<String, String>() + // Take extraContentWithoutAuthData and onEach line perform the following tasks + extraContentWithoutAuthData.lines().forEach { line -> + // Split the line on ':' and save all the parts into an array + // "ABC : DEF:GHI" --> ["ABC", "DEF", "GHI"] + val splitArray = line.split(":") + // Take the first element of the array. This will be the key for the key-value pair. + // ["ABC ", " DEF", "GHI"] -> key = "ABC" + val key = splitArray.first().trimEnd() + // Remove the first element from the array and join the rest of the string again with + // ':' as separator. + // ["ABC ", " DEF", "GHI"] -> value = "DEF:GHI" + val value = splitArray.drop(1).joinToString(":").trimStart() + + if (key.isNotEmpty() && value.isNotEmpty()) { + // If both key and value are not empty, we can form a pair with this so add it to + // the map. + // key = "ABC", value = "DEF:GHI" + items[key] = value + } else { + // If either key or value is empty, we were not able to form proper key-value pair. + // So append the original line into an "EXTRA CONTENT" map entry + items.putOrAppend(EXTRA_CONTENT, line) + } } - fun calculateTotpCode(): String? { - if (totpSecret == null) - return null - return Otp.calculateCode(totpSecret, Date().time / (1000 * totpPeriod), totpAlgorithm, digits).get() - } + return items + } - private fun generateExtraContentWithoutAuthData(): String { - var foundUsername = false - return extraContent - .lineSequence() - .filter { line -> - return@filter when { - USERNAME_FIELDS.any { prefix -> line.startsWith(prefix, ignoreCase = true) } && !foundUsername -> { - foundUsername = true - false - } - line.startsWith("otpauth://", ignoreCase = true) || - line.startsWith("totp:", ignoreCase = true) -> { - false - } - else -> { - true - } - } - }.joinToString(separator = "\n") + private fun findUsername(): String? { + extraContent.splitToSequence("\n").forEach { line -> + for (prefix in USERNAME_FIELDS) { + if (line.startsWith(prefix, ignoreCase = true)) return line.substring(prefix.length).trimStart() + } } - - private fun generateExtraContentPairs(): Map<String, String> { - fun MutableMap<String, String>.putOrAppend(key: String, value: String) { - if (value.isEmpty()) return - val existing = this[key] - this[key] = if (existing == null) { - value - } else { - "$existing\n$value" - } + return null + } + + private fun findAndStripPassword(passContent: List<String>): Pair<String, List<String>> { + if (UriTotpFinder.TOTP_FIELDS.any { passContent[0].startsWith(it) }) return Pair("", passContent) + for (line in passContent) { + for (prefix in PASSWORD_FIELDS) { + if (line.startsWith(prefix, ignoreCase = true)) { + return Pair(line.substring(prefix.length).trimStart(), passContent.minus(line)) } - - val items = mutableMapOf<String, String>() - // Take extraContentWithoutAuthData and onEach line perform the following tasks - extraContentWithoutAuthData.lines().forEach { line -> - // Split the line on ':' and save all the parts into an array - // "ABC : DEF:GHI" --> ["ABC", "DEF", "GHI"] - val splitArray = line.split(":") - // Take the first element of the array. This will be the key for the key-value pair. - // ["ABC ", " DEF", "GHI"] -> key = "ABC" - val key = splitArray.first().trimEnd() - // Remove the first element from the array and join the rest of the string again with ':' as separator. - // ["ABC ", " DEF", "GHI"] -> value = "DEF:GHI" - val value = splitArray.drop(1).joinToString(":").trimStart() - - if (key.isNotEmpty() && value.isNotEmpty()) { - // If both key and value are not empty, we can form a pair with this so add it to the map. - // key = "ABC", value = "DEF:GHI" - items[key] = value - } else { - // If either key or value is empty, we were not able to form proper key-value pair. - // So append the original line into an "EXTRA CONTENT" map entry - items.putOrAppend(EXTRA_CONTENT, line) - } - } - - return items + } } + return Pair(passContent[0], passContent.minus(passContent[0])) + } - private fun findUsername(): String? { - extraContent.splitToSequence("\n").forEach { line -> - for (prefix in USERNAME_FIELDS) { - if (line.startsWith(prefix, ignoreCase = true)) - return line.substring(prefix.length).trimStart() - } - } - return null - } + private fun findTotpSecret(decryptedContent: String): String? { + return totpFinder.findSecret(decryptedContent) + } - private fun findAndStripPassword(passContent: List<String>): Pair<String, List<String>> { - if (UriTotpFinder.TOTP_FIELDS.any { passContent[0].startsWith(it) }) return Pair("", passContent) - for (line in passContent) { - for (prefix in PASSWORD_FIELDS) { - if (line.startsWith(prefix, ignoreCase = true)) { - return Pair(line.substring(prefix.length).trimStart(), passContent.minus(line)) - } - } - } - return Pair(passContent[0], passContent.minus(passContent[0])) - } + private fun findOtpDigits(decryptedContent: String): String { + return totpFinder.findDigits(decryptedContent) + } - private fun findTotpSecret(decryptedContent: String): String? { - return totpFinder.findSecret(decryptedContent) - } + private fun findTotpPeriod(decryptedContent: String): Long { + return totpFinder.findPeriod(decryptedContent) + } - private fun findOtpDigits(decryptedContent: String): String { - return totpFinder.findDigits(decryptedContent) - } + private fun findTotpAlgorithm(decryptedContent: String): String { + return totpFinder.findAlgorithm(decryptedContent) + } - private fun findTotpPeriod(decryptedContent: String): Long { - return totpFinder.findPeriod(decryptedContent) - } + companion object { - private fun findTotpAlgorithm(decryptedContent: String): String { - return totpFinder.findAlgorithm(decryptedContent) - } + private const val EXTRA_CONTENT = "Extra Content" - companion object { - - private const val EXTRA_CONTENT = "Extra Content" - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - val USERNAME_FIELDS = arrayOf( - "login:", - "username:", - "user:", - "account:", - "email:", - "name:", - "handle:", - "id:", - "identity:", - ) - - @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) - val PASSWORD_FIELDS = arrayOf( - "password:", - "secret:", - "pass:", - ) - } + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + val USERNAME_FIELDS = + arrayOf( + "login:", + "username:", + "user:", + "account:", + "email:", + "name:", + "handle:", + "id:", + "identity:", + ) + + @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) + val PASSWORD_FIELDS = + arrayOf( + "password:", + "secret:", + "pass:", + ) + } } diff --git a/app/src/main/java/dev/msfjarvis/aps/data/password/PasswordItem.kt b/app/src/main/java/dev/msfjarvis/aps/data/password/PasswordItem.kt index 60889150..89923dff 100644 --- a/app/src/main/java/dev/msfjarvis/aps/data/password/PasswordItem.kt +++ b/app/src/main/java/dev/msfjarvis/aps/data/password/PasswordItem.kt @@ -8,79 +8,56 @@ import dev.msfjarvis.aps.ui.crypto.BasePgpActivity import java.io.File data class PasswordItem( - val name: String, - val parent: PasswordItem? = null, - val type: Char, - val file: File, - val rootDir: File + val name: String, + val parent: PasswordItem? = null, + val type: Char, + val file: File, + val rootDir: File ) : Comparable<PasswordItem> { - val fullPathToParent = file.absolutePath - .replace(rootDir.absolutePath, "") - .replace(file.name, "") + val fullPathToParent = file.absolutePath.replace(rootDir.absolutePath, "").replace(file.name, "") - val longName = BasePgpActivity.getLongName( - fullPathToParent, - rootDir.absolutePath, - toString()) + val longName = BasePgpActivity.getLongName(fullPathToParent, rootDir.absolutePath, toString()) - override fun equals(other: Any?): Boolean { - return (other is PasswordItem) && (other.file == file) - } + override fun equals(other: Any?): Boolean { + return (other is PasswordItem) && (other.file == file) + } - override fun compareTo(other: PasswordItem): Int { - return (type + name).compareTo(other.type + other.name, ignoreCase = true) - } + override fun compareTo(other: PasswordItem): Int { + return (type + name).compareTo(other.type + other.name, ignoreCase = true) + } - override fun toString(): String { - return name.replace("\\.gpg$".toRegex(), "") - } + override fun toString(): String { + return name.replace("\\.gpg$".toRegex(), "") + } - override fun hashCode(): Int { - return 0 - } + override fun hashCode(): Int { + return 0 + } - companion object { + companion object { - const val TYPE_CATEGORY = 'c' - const val TYPE_PASSWORD = 'p' + const val TYPE_CATEGORY = 'c' + const val TYPE_PASSWORD = 'p' - @JvmStatic - fun newCategory( - name: String, - file: File, - parent: PasswordItem, - rootDir: File - ): PasswordItem { - return PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir) - } + @JvmStatic + fun newCategory(name: String, file: File, parent: PasswordItem, rootDir: File): PasswordItem { + return PasswordItem(name, parent, TYPE_CATEGORY, file, rootDir) + } - @JvmStatic - fun newCategory( - name: String, - file: File, - rootDir: File - ): PasswordItem { - return PasswordItem(name, null, TYPE_CATEGORY, file, rootDir) - } + @JvmStatic + fun newCategory(name: String, file: File, rootDir: File): PasswordItem { + return PasswordItem(name, null, TYPE_CATEGORY, file, rootDir) + } - @JvmStatic - fun newPassword( - name: String, - file: File, - parent: PasswordItem, - rootDir: File - ): PasswordItem { - return PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir) - } + @JvmStatic + fun newPassword(name: String, file: File, parent: PasswordItem, rootDir: File): PasswordItem { + return PasswordItem(name, parent, TYPE_PASSWORD, file, rootDir) + } - @JvmStatic - fun newPassword( - name: String, - file: File, - rootDir: File - ): PasswordItem { - return PasswordItem(name, null, TYPE_PASSWORD, file, rootDir) - } + @JvmStatic + fun newPassword(name: String, file: File, rootDir: File): PasswordItem { + return PasswordItem(name, null, TYPE_PASSWORD, file, rootDir) } + } } |
