aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/dev/msfjarvis/aps/data/password
diff options
context:
space:
mode:
authorHarsh Shandilya <me@msfjarvis.dev>2020-12-05 06:07:18 +0530
committerGitHub <noreply@github.com>2020-12-05 06:07:18 +0530
commit5e66d99c852ea67a88b650c03b0e8d55e83eccde (patch)
treeaee323a08253fe7b863976eff92a1bd412bd1430 /app/src/main/java/dev/msfjarvis/aps/data/password
parent8eb55f18a11d6b2f155c7c9d48ca833313ee13db (diff)
downloadHardenedPass-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.kt139
-rw-r--r--app/src/main/java/dev/msfjarvis/aps/data/password/PasswordItem.kt86
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)
+ }
+ }
+}