diff options
| author | Harsh Shandilya <me@msfjarvis.dev> | 2020-12-05 06:07:18 +0530 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-12-05 06:07:18 +0530 |
| commit | 5e66d99c852ea67a88b650c03b0e8d55e83eccde (patch) | |
| tree | aee323a08253fe7b863976eff92a1bd412bd1430 /app/src/main/java/dev/msfjarvis/aps/data/password | |
| parent | 8eb55f18a11d6b2f155c7c9d48ca833313ee13db (diff) | |
| download | HardenedPass-5e66d99c852ea67a88b650c03b0e8d55e83eccde.tar.gz HardenedPass-5e66d99c852ea67a88b650c03b0e8d55e83eccde.zip | |
Refactor package structure (#1233)
* idea: default test runner to Gradle
* Kick off package structure revamp
* Reparent all classes under dev.msfjarvis.aps
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
Diffstat (limited to 'app/src/main/java/dev/msfjarvis/aps/data/password')
| -rw-r--r-- | app/src/main/java/dev/msfjarvis/aps/data/password/PasswordEntry.kt | 139 | ||||
| -rw-r--r-- | app/src/main/java/dev/msfjarvis/aps/data/password/PasswordItem.kt | 86 |
2 files changed, 225 insertions, 0 deletions
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 new file mode 100644 index 00000000..3a6d9e2c --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/data/password/PasswordEntry.kt @@ -0,0 +1,139 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package dev.msfjarvis.aps.data.password + +import com.github.michaelbull.result.get +import dev.msfjarvis.aps.util.totp.Otp +import dev.msfjarvis.aps.util.totp.TotpFinder +import dev.msfjarvis.aps.util.totp.UriTotpFinder +import java.io.ByteArrayOutputStream +import java.io.UnsupportedEncodingException +import java.util.Date + +/** + * A single entry in password store. [totpFinder] is an implementation of [TotpFinder] that let's us + * abstract out the Android-specific part and continue testing the class in the JVM. + */ +class PasswordEntry(content: String, private val totpFinder: TotpFinder = UriTotpFinder()) { + + val password: String + val username: String? + val digits: String + val totpSecret: String? + val totpPeriod: Long + val totpAlgorithm: String + var extraContent: String + private set + + @Throws(UnsupportedEncodingException::class) + constructor(os: ByteArrayOutputStream) : this(os.toString("UTF-8"), UriTotpFinder()) + + init { + val (foundPassword, passContent) = findAndStripPassword(content.split("\n".toRegex())) + password = foundPassword + extraContent = passContent.joinToString("\n") + username = findUsername() + digits = findOtpDigits(content) + totpSecret = findTotpSecret(content) + totpPeriod = findTotpPeriod(content) + totpAlgorithm = findTotpAlgorithm(content) + } + + fun hasExtraContent(): Boolean { + return extraContent.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() + } + + val extraContentWithoutAuthData by lazy(LazyThreadSafetyMode.NONE) { + var foundUsername = false + extraContent.splitToSequence("\n").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() + } + } + 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)) + } + } + } + return Pair(passContent[0], passContent.minus(passContent[0])) + } + + private fun findTotpSecret(decryptedContent: String): String? { + return totpFinder.findSecret(decryptedContent) + } + + private fun findOtpDigits(decryptedContent: String): String { + return totpFinder.findDigits(decryptedContent) + } + + private fun findTotpPeriod(decryptedContent: String): Long { + return totpFinder.findPeriod(decryptedContent) + } + + private fun findTotpAlgorithm(decryptedContent: String): String { + return totpFinder.findAlgorithm(decryptedContent) + } + + companion object { + + val USERNAME_FIELDS = arrayOf( + "login:", + "username:", + "user:", + "account:", + "email:", + "name:", + "handle:", + "id:", + "identity:" + ) + + 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 new file mode 100644 index 00000000..75fc475b --- /dev/null +++ b/app/src/main/java/dev/msfjarvis/aps/data/password/PasswordItem.kt @@ -0,0 +1,86 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ +package dev.msfjarvis.aps.data.password + +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 +) : Comparable<PasswordItem> { + + val fullPathToParent = file.absolutePath + .replace(rootDir.absolutePath, "") + .replace(file.name, "") + + val longName = BasePgpActivity.getLongName( + fullPathToParent, + rootDir.absolutePath, + toString()) + + 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 toString(): String { + return name.replace("\\.gpg$".toRegex(), "") + } + + override fun hashCode(): Int { + return 0 + } + + companion object { + + 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, + 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, + rootDir: File + ): PasswordItem { + return PasswordItem(name, null, TYPE_PASSWORD, file, rootDir) + } + } +} |
