From 5e66d99c852ea67a88b650c03b0e8d55e83eccde Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Sat, 5 Dec 2020 06:07:18 +0530 Subject: 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 --- .../com/zeapo/pwdstore/utils/AndroidExtensions.kt | 178 --------------- .../zeapo/pwdstore/utils/BiometricAuthenticator.kt | 78 ------- .../java/com/zeapo/pwdstore/utils/Extensions.kt | 89 -------- .../com/zeapo/pwdstore/utils/FragmentExtensions.kt | 36 ---- .../pwdstore/utils/FragmentViewBindingDelegate.kt | 65 ------ app/src/main/java/com/zeapo/pwdstore/utils/Otp.kt | 73 ------- .../java/com/zeapo/pwdstore/utils/PasswordItem.kt | 86 -------- .../com/zeapo/pwdstore/utils/PasswordRepository.kt | 238 --------------------- .../com/zeapo/pwdstore/utils/PasswordSortOrder.kt | 46 ---- .../com/zeapo/pwdstore/utils/PreferenceKeys.kt | 85 -------- .../java/com/zeapo/pwdstore/utils/ProxyUtils.kt | 66 ------ .../java/com/zeapo/pwdstore/utils/TotpFinder.kt | 32 --- .../java/com/zeapo/pwdstore/utils/UriTotpFinder.kt | 66 ------ 13 files changed, 1138 deletions(-) delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/FragmentExtensions.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/Otp.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/PasswordSortOrder.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/TotpFinder.kt delete mode 100644 app/src/main/java/com/zeapo/pwdstore/utils/UriTotpFinder.kt (limited to 'app/src/main/java/com/zeapo/pwdstore/utils') diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt deleted file mode 100644 index 408e9d5e..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package com.zeapo.pwdstore.utils - -import android.app.KeyguardManager -import android.content.ClipboardManager -import android.content.Context -import android.content.SharedPreferences -import android.content.pm.PackageManager -import android.os.Build -import android.util.Base64 -import android.util.TypedValue -import android.view.View -import android.view.autofill.AutofillManager -import android.view.inputmethod.InputMethodManager -import androidx.annotation.IdRes -import androidx.annotation.RequiresApi -import androidx.appcompat.app.AlertDialog -import androidx.core.content.ContextCompat -import androidx.core.content.getSystemService -import androidx.fragment.app.FragmentActivity -import androidx.preference.PreferenceManager -import androidx.security.crypto.EncryptedSharedPreferences -import androidx.security.crypto.MasterKey -import com.github.ajalt.timberkt.d -import com.github.michaelbull.result.Ok -import com.github.michaelbull.result.Result -import com.google.android.material.snackbar.Snackbar -import com.zeapo.pwdstore.R -import com.zeapo.pwdstore.git.operation.GitOperation - -/** - * Extension function for [AlertDialog] that requests focus for the - * view whose id is [id]. Solution based on a StackOverflow - * answer: https://stackoverflow.com/a/13056259/297261 - */ -fun AlertDialog.requestInputFocusOnView(@IdRes id: Int) { - setOnShowListener { - findViewById(id)?.apply { - setOnFocusChangeListener { v, _ -> - v.post { - context.getSystemService() - ?.showSoftInput(v, InputMethodManager.SHOW_IMPLICIT) - } - } - requestFocus() - } - } -} - -/** - * Get an instance of [AutofillManager]. Only - * available on Android Oreo and above - */ -val Context.autofillManager: AutofillManager? - @RequiresApi(Build.VERSION_CODES.O) - get() = getSystemService() - -/** - * Get an instance of [ClipboardManager] - */ -val Context.clipboard - get() = getSystemService() - -/** - * Wrapper for [getEncryptedPrefs] to avoid open-coding the file name at - * each call site - */ -fun Context.getEncryptedGitPrefs() = getEncryptedPrefs("git_operation") - -/** - * Wrapper for [getEncryptedPrefs] to get the encrypted preference set for the HTTP - * proxy. - */ -fun Context.getEncryptedProxyPrefs() = getEncryptedPrefs("http_proxy") - -/** - * Get an instance of [EncryptedSharedPreferences] with the given [fileName] - */ -private fun Context.getEncryptedPrefs(fileName: String): SharedPreferences { - val masterKeyAlias = MasterKey.Builder(applicationContext) - .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - .build() - return EncryptedSharedPreferences.create( - applicationContext, - fileName, - masterKeyAlias, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) -} - -/** - * Get an instance of [KeyguardManager] - */ -val Context.keyguardManager: KeyguardManager - get() = getSystemService()!! - -/** - * Get the default [SharedPreferences] instance - */ -val Context.sharedPrefs: SharedPreferences - get() = PreferenceManager.getDefaultSharedPreferences(applicationContext) - - -/** - * Resolve [attr] from the [Context]'s theme - */ -fun Context.resolveAttribute(attr: Int): Int { - val typedValue = TypedValue() - this.theme.resolveAttribute(attr, typedValue, true) - return typedValue.data -} - -/** - * Commit changes to the store from a [FragmentActivity] using - * a custom implementation of [GitOperation] - */ -suspend fun FragmentActivity.commitChange( - message: String, -): Result { - if (!PasswordRepository.isGitRepo()) { - return Ok(Unit) - } - return object : GitOperation(this@commitChange) { - override val commands = arrayOf( - // Stage all files - git.add().addFilepattern("."), - // Populate the changed files count - git.status(), - // Commit everything! If anything changed, that is. - git.commit().setAll(true).setMessage(message), - ) - - override fun preExecute(): Boolean { - d { "Committing with message: '$message'" } - return true - } - }.execute() -} - -/** - * Check if [permission] has been granted to the app. - */ -fun FragmentActivity.isPermissionGranted(permission: String): Boolean { - return ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED -} - -/** - * Show a [Snackbar] in a [FragmentActivity] and correctly - * anchor it to a [com.google.android.material.floatingactionbutton.FloatingActionButton] - * if one exists in the [view] - */ -fun FragmentActivity.snackbar( - view: View = findViewById(android.R.id.content), - message: String, - length: Int = Snackbar.LENGTH_SHORT, -): Snackbar { - val snackbar = Snackbar.make(view, message, length) - snackbar.anchorView = findViewById(R.id.fab) - snackbar.show() - return snackbar -} - -/** - * Simplifies the common `getString(key, null) ?: defaultValue` case slightly - */ -fun SharedPreferences.getString(key: String): String? = getString(key, null) - -/** - * Convert this [String] to its [Base64] representation - */ -fun String.base64(): String { - return Base64.encodeToString(encodeToByteArray(), Base64.NO_WRAP) -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt deleted file mode 100644 index 12ba84f1..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.utils - -import android.app.KeyguardManager -import androidx.annotation.StringRes -import androidx.biometric.BiometricManager -import androidx.biometric.BiometricManager.Authenticators -import androidx.biometric.BiometricPrompt -import androidx.core.content.ContextCompat -import androidx.core.content.getSystemService -import androidx.fragment.app.FragmentActivity -import com.github.ajalt.timberkt.Timber.tag -import com.github.ajalt.timberkt.d -import com.zeapo.pwdstore.R - -object BiometricAuthenticator { - - private const val TAG = "BiometricAuthenticator" - private const val validAuthenticators = Authenticators.DEVICE_CREDENTIAL or Authenticators.BIOMETRIC_WEAK - - sealed class Result { - data class Success(val cryptoObject: BiometricPrompt.CryptoObject?) : Result() - data class Failure(val code: Int?, val message: CharSequence) : Result() - object HardwareUnavailableOrDisabled : Result() - object Cancelled : Result() - } - - fun canAuthenticate(activity: FragmentActivity): Boolean { - return BiometricManager.from(activity).canAuthenticate(validAuthenticators) == BiometricManager.BIOMETRIC_SUCCESS - } - - fun authenticate( - activity: FragmentActivity, - @StringRes dialogTitleRes: Int = R.string.biometric_prompt_title, - callback: (Result) -> Unit - ) { - val authCallback = object : BiometricPrompt.AuthenticationCallback() { - override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - super.onAuthenticationError(errorCode, errString) - tag(TAG).d { "BiometricAuthentication error: errorCode=$errorCode, msg=$errString" } - callback(when (errorCode) { - BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED, - BiometricPrompt.ERROR_NEGATIVE_BUTTON -> { - Result.Cancelled - } - BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE, - BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> { - Result.HardwareUnavailableOrDisabled - } - else -> Result.Failure(errorCode, activity.getString(R.string.biometric_auth_error_reason, errString)) - }) - } - - override fun onAuthenticationFailed() { - super.onAuthenticationFailed() - callback(Result.Failure(null, activity.getString(R.string.biometric_auth_error))) - } - - override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { - super.onAuthenticationSucceeded(result) - callback(Result.Success(result.cryptoObject)) - } - } - val deviceHasKeyguard = activity.getSystemService()?.isDeviceSecure == true - if (canAuthenticate(activity) || deviceHasKeyguard) { - val promptInfo = BiometricPrompt.PromptInfo.Builder() - .setTitle(activity.getString(dialogTitleRes)) - .setAllowedAuthenticators(validAuthenticators) - .build() - BiometricPrompt(activity, ContextCompat.getMainExecutor(activity.applicationContext), authCallback).authenticate(promptInfo) - } else { - callback(Result.HardwareUnavailableOrDisabled) - } - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt deleted file mode 100644 index 7a439e94..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/Extensions.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.utils - -import com.github.michaelbull.result.getOrElse -import com.github.michaelbull.result.runCatching -import java.io.File -import java.util.Date -import org.eclipse.jgit.lib.ObjectId -import org.eclipse.jgit.revwalk.RevCommit - -/** - * The default OpenPGP provider for the app - */ -const val OPENPGP_PROVIDER = "org.sufficientlysecure.keychain" - -/** - * Clears the given [flag] from the value of this [Int] - */ -fun Int.clearFlag(flag: Int): Int { - return this and flag.inv() -} - -/** - * Checks if this [Int] contains the given [flag] - */ -infix fun Int.hasFlag(flag: Int): Boolean { - return this and flag == flag -} - -/** - * Checks whether this [File] is a directory that contains [other]. - */ -fun File.contains(other: File): Boolean { - if (!isDirectory) - return false - if (!other.exists()) - return false - val relativePath = runCatching { - other.relativeTo(this) - }.getOrElse { - return false - } - // Direct containment is equivalent to the relative path being equal to the filename. - return relativePath.path == other.name -} - -/** - * Checks if this [File] is in the password repository directory as given - * by [PasswordRepository.getRepositoryDirectory] - */ -fun File.isInsideRepository(): Boolean { - return canonicalPath.contains(PasswordRepository.getRepositoryDirectory().canonicalPath) -} - -/** - * Recursively lists the files in this [File], skipping any directories it encounters. - */ -fun File.listFilesRecursively() = walkTopDown().filter { !it.isDirectory }.toList() - -/** - * Unique SHA-1 hash of this commit as hexadecimal string. - * - * @see RevCommit.getId - */ -val RevCommit.hash: String - get() = ObjectId.toString(id) - -/** - * Time this commit was made with second precision. - * - * @see RevCommit.commitTime - */ -val RevCommit.time: Date - get() { - val epochSeconds = commitTime.toLong() - val epochMilliseconds = epochSeconds * 1000 - return Date(epochMilliseconds) - } - -/** - * Splits this [String] into an [Array] of [String]s, split on the UNIX LF line ending - * and stripped of any empty lines. - */ -fun String.splitLines(): Array { - return split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/FragmentExtensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/FragmentExtensions.kt deleted file mode 100644 index 251f5259..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/FragmentExtensions.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.zeapo.pwdstore.utils - -import androidx.annotation.IdRes -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.commit -import com.zeapo.pwdstore.R - -/** - * Check if [permission] is granted to the app. Aliases to [isPermissionGranted] internally. - */ -fun Fragment.isPermissionGranted(permission: String): Boolean { - return requireActivity().isPermissionGranted(permission) -} - -/** - * Calls `finish()` on the enclosing [androidx.fragment.app.FragmentActivity] - */ -fun Fragment.finish() = requireActivity().finish() - -/** - * Perform a [commit] on this [FragmentManager] with custom animations and adding the [destinationFragment] - * to the fragment backstack - */ -fun FragmentManager.performTransactionWithBackStack(destinationFragment: Fragment, @IdRes containerViewId: Int = android.R.id.content) { - commit { - beginTransaction() - addToBackStack(destinationFragment.tag) - setCustomAnimations( - R.animator.slide_in_left, - R.animator.slide_out_left, - R.animator.slide_in_right, - R.animator.slide_out_right) - replace(containerViewId, destinationFragment) - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt b/app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt deleted file mode 100644 index 55d654e0..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/FragmentViewBindingDelegate.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package com.zeapo.pwdstore.utils - - -import android.view.LayoutInflater -import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.fragment.app.Fragment -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.viewbinding.ViewBinding -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty - -/** - * Imported from https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c - */ -class FragmentViewBindingDelegate( - val fragment: Fragment, - val viewBindingFactory: (View) -> T -) : ReadOnlyProperty { - - private var binding: T? = null - - init { - fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { - override fun onCreate(owner: LifecycleOwner) { - fragment.viewLifecycleOwnerLiveData.observe(fragment) { viewLifecycleOwner -> - viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { - override fun onDestroy(owner: LifecycleOwner) { - binding = null - } - }) - } - } - }) - } - - override fun getValue(thisRef: Fragment, property: KProperty<*>): T { - val binding = binding - if (binding != null) { - return binding - } - - val lifecycle = fragment.viewLifecycleOwner.lifecycle - if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { - throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.") - } - - return viewBindingFactory(thisRef.requireView()).also { this.binding = it } - } -} - -fun Fragment.viewBinding(viewBindingFactory: (View) -> T) = - FragmentViewBindingDelegate(this, viewBindingFactory) - -inline fun AppCompatActivity.viewBinding(crossinline bindingInflater: (LayoutInflater) -> T) = - lazy(LazyThreadSafetyMode.NONE) { - bindingInflater.invoke(layoutInflater) - } diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/Otp.kt b/app/src/main/java/com/zeapo/pwdstore/utils/Otp.kt deleted file mode 100644 index 994b4d82..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/Otp.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package com.zeapo.pwdstore.utils - -import com.github.michaelbull.result.Err -import com.github.michaelbull.result.runCatching -import java.nio.ByteBuffer -import java.util.Locale -import javax.crypto.Mac -import javax.crypto.spec.SecretKeySpec -import kotlin.experimental.and -import org.apache.commons.codec.binary.Base32 - -object Otp { - - private val BASE_32 = Base32() - private val STEAM_ALPHABET = "23456789BCDFGHJKMNPQRTVWXY".toCharArray() - - init { - check(STEAM_ALPHABET.size == 26) - } - - fun calculateCode(secret: String, counter: Long, algorithm: String, digits: String) = runCatching { - val algo = "Hmac${algorithm.toUpperCase(Locale.ROOT)}" - val decodedSecret = BASE_32.decode(secret) - val secretKey = SecretKeySpec(decodedSecret, algo) - val digest = Mac.getInstance(algo).run { - init(secretKey) - doFinal(ByteBuffer.allocate(8).putLong(counter).array()) - } - // Least significant 4 bits are used as an offset into the digest. - val offset = (digest.last() and 0xf).toInt() - // Extract 32 bits at the offset and clear the most significant bit. - val code = digest.copyOfRange(offset, offset + 4) - code[0] = (0x7f and code[0].toInt()).toByte() - val codeInt = ByteBuffer.wrap(code).int - check(codeInt > 0) - if (digits == "s") { - // Steam - var remainingCodeInt = codeInt - buildString { - repeat(5) { - append(STEAM_ALPHABET[remainingCodeInt % 26]) - remainingCodeInt /= 26 - } - } - } else { - // Base 10, 6 to 10 digits - val numDigits = digits.toIntOrNull() - when { - numDigits == null -> { - return Err(IllegalArgumentException("Digits specifier has to be either 's' or numeric")) - } - numDigits < 6 -> { - return Err(IllegalArgumentException("TOTP codes have to be at least 6 digits long")) - } - numDigits > 10 -> { - return Err(IllegalArgumentException("TOTP codes can be at most 10 digits long")) - } - else -> { - // 2^31 = 2_147_483_648, so we can extract at most 10 digits with the first one - // always being 0, 1, or 2. Pad with leading zeroes. - val codeStringBase10 = codeInt.toString(10).padStart(10, '0') - check(codeStringBase10.length == 10) - codeStringBase10.takeLast(numDigits) - } - } - } - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt deleted file mode 100644 index ef31324b..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordItem.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.utils - -import com.zeapo.pwdstore.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 { - - 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) - } - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt deleted file mode 100644 index 8b403a56..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ -package com.zeapo.pwdstore.utils - -import android.os.Build -import androidx.annotation.RequiresApi -import androidx.core.content.edit -import com.github.michaelbull.result.getOrElse -import com.github.michaelbull.result.onFailure -import com.github.michaelbull.result.runCatching -import com.zeapo.pwdstore.Application -import java.io.File -import java.io.FileFilter -import java.nio.file.Files -import java.nio.file.LinkOption -import org.eclipse.jgit.api.Git -import org.eclipse.jgit.lib.Repository -import org.eclipse.jgit.storage.file.FileRepositoryBuilder -import org.eclipse.jgit.transport.RefSpec -import org.eclipse.jgit.transport.RemoteConfig -import org.eclipse.jgit.transport.URIish -import org.eclipse.jgit.util.FS -import org.eclipse.jgit.util.FS_POSIX_Java6 - -object PasswordRepository { - - @RequiresApi(Build.VERSION_CODES.O) - private class FS_POSIX_Java6_with_optional_symlinks : FS_POSIX_Java6() { - - override fun supportsSymlinks() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O - - override fun isSymLink(file: File) = Files.isSymbolicLink(file.toPath()) - - override fun readSymLink(file: File) = Files.readSymbolicLink(file.toPath()).toString() - - override fun createSymLink(source: File, target: String) { - val sourcePath = source.toPath() - if (Files.exists(sourcePath, LinkOption.NOFOLLOW_LINKS)) - Files.delete(sourcePath) - Files.createSymbolicLink(sourcePath, File(target).toPath()) - } - } - - @RequiresApi(Build.VERSION_CODES.O) - private class Java7FSFactory : FS.FSFactory() { - - override fun detect(cygwinUsed: Boolean?): FS { - return FS_POSIX_Java6_with_optional_symlinks() - } - } - - private var repository: Repository? = null - private val settings by lazy(LazyThreadSafetyMode.NONE) { Application.instance.sharedPrefs } - private val filesDir - get() = Application.instance.filesDir - - /** - * Returns the git repository - * - * @param localDir needed only on the creation - * @return the git repository - */ - @JvmStatic - fun getRepository(localDir: File?): Repository? { - if (repository == null && localDir != null) { - val builder = FileRepositoryBuilder() - repository = runCatching { - builder.run { - gitDir = localDir - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - fs = Java7FSFactory().detect(null) - } - readEnvironment() - }.build() - }.getOrElse { e -> - e.printStackTrace() - null - } - } - return repository - } - - @JvmStatic - val isInitialized: Boolean - get() = repository != null - - @JvmStatic - fun isGitRepo(): Boolean { - if (repository != null) { - return repository!!.objectDatabase.exists() - } - return false - } - - @JvmStatic - @Throws(Exception::class) - fun createRepository(localDir: File) { - localDir.delete() - - Git.init().setDirectory(localDir).call() - getRepository(localDir) - } - - // TODO add multiple remotes support for pull/push - @JvmStatic - fun addRemote(name: String, url: String, replace: Boolean = false) { - val storedConfig = repository!!.config - val remotes = storedConfig.getSubsections("remote") - - if (!remotes.contains(name)) { - runCatching { - val uri = URIish(url) - val refSpec = RefSpec("+refs/head/*:refs/remotes/$name/*") - - val remoteConfig = RemoteConfig(storedConfig, name) - remoteConfig.addFetchRefSpec(refSpec) - remoteConfig.addPushRefSpec(refSpec) - remoteConfig.addURI(uri) - remoteConfig.addPushURI(uri) - - remoteConfig.update(storedConfig) - - storedConfig.save() - }.onFailure { e -> - e.printStackTrace() - } - } else if (replace) { - runCatching { - val uri = URIish(url) - - val remoteConfig = RemoteConfig(storedConfig, name) - // remove the first and eventually the only uri - if (remoteConfig.urIs.size > 0) { - remoteConfig.removeURI(remoteConfig.urIs[0]) - } - if (remoteConfig.pushURIs.size > 0) { - remoteConfig.removePushURI(remoteConfig.pushURIs[0]) - } - - remoteConfig.addURI(uri) - remoteConfig.addPushURI(uri) - - remoteConfig.update(storedConfig) - - storedConfig.save() - }.onFailure { e -> - e.printStackTrace() - } - } - } - - @JvmStatic - fun closeRepository() { - if (repository != null) repository!!.close() - repository = null - } - - @JvmStatic - fun getRepositoryDirectory(): File { - return if (settings.getBoolean(PreferenceKeys.GIT_EXTERNAL, false)) { - val externalRepo = settings.getString(PreferenceKeys.GIT_EXTERNAL_REPO) - if (externalRepo != null) - File(externalRepo) - else - File(filesDir.toString(), "/store") - } else { - File(filesDir.toString(), "/store") - } - } - - @JvmStatic - fun initialize(): Repository? { - val dir = getRepositoryDirectory() - // uninitialize the repo if the dir does not exist or is absolutely empty - settings.edit { - if (!dir.exists() || !dir.isDirectory || requireNotNull(dir.listFiles()).isEmpty()) { - putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) - } else { - putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) - } - } - - // create the repository static variable in PasswordRepository - return getRepository(File(dir.absolutePath + "/.git")) - } - - /** - * Gets the .gpg files in a directory - * - * @param path the directory path - * @return the list of gpg files in that directory - */ - @JvmStatic - fun getFilesList(path: File?): ArrayList { - if (path == null || !path.exists()) return ArrayList() - - val directories = (path.listFiles(FileFilter { pathname -> pathname.isDirectory }) - ?: emptyArray()).toList() - val files = (path.listFiles(FileFilter { pathname -> pathname.extension == "gpg" }) - ?: emptyArray()).toList() - - val items = ArrayList() - items.addAll(directories) - items.addAll(files) - - return items - } - - /** - * Gets the passwords (PasswordItem) in a directory - * - * @param path the directory path - * @return a list of password items - */ - @JvmStatic - fun getPasswords(path: File, rootDir: File, sortOrder: PasswordSortOrder): ArrayList { - // We need to recover the passwords then parse the files - val passList = getFilesList(path).also { it.sortBy { f -> f.name } } - val passwordList = ArrayList() - val showHidden = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false) - - if (passList.size == 0) return passwordList - if (!showHidden) { - passList.filter { !it.isHidden }.toCollection(passList.apply { clear() }) - } - passList.forEach { file -> - passwordList.add(if (file.isFile) { - PasswordItem.newPassword(file.name, file, rootDir) - } else { - PasswordItem.newCategory(file.name, file, rootDir) - }) - } - passwordList.sortWith(sortOrder.comparator) - return passwordList - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordSortOrder.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordSortOrder.kt deleted file mode 100644 index 0613c97d..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordSortOrder.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package com.zeapo.pwdstore.utils - -import android.content.Context -import android.content.SharedPreferences -import com.zeapo.pwdstore.Application - -enum class PasswordSortOrder(val comparator: java.util.Comparator) { - - FOLDER_FIRST(Comparator { p1: PasswordItem, p2: PasswordItem -> - (p1.type + p1.name) - .compareTo(p2.type + p2.name, ignoreCase = true) - }), - - INDEPENDENT(Comparator { p1: PasswordItem, p2: PasswordItem -> - p1.name.compareTo(p2.name, ignoreCase = true) - }), - - RECENTLY_USED(Comparator { p1: PasswordItem, p2: PasswordItem -> - val recentHistory = Application.instance.getSharedPreferences("recent_password_history", Context.MODE_PRIVATE) - val timeP1 = recentHistory.getString(p1.file.absolutePath.base64()) - val timeP2 = recentHistory.getString(p2.file.absolutePath.base64()) - when { - timeP1 != null && timeP2 != null -> timeP2.compareTo(timeP1) - timeP1 != null && timeP2 == null -> return@Comparator -1 - timeP1 == null && timeP2 != null -> return@Comparator 1 - else -> p1.name.compareTo(p2.name, ignoreCase = true) - } - }), - - FILE_FIRST(Comparator { p1: PasswordItem, p2: PasswordItem -> - (p2.type + p1.name).compareTo(p1.type + p2.name, ignoreCase = true) - }); - - companion object { - - @JvmStatic - fun getSortOrder(settings: SharedPreferences): PasswordSortOrder { - return valueOf(settings.getString(PreferenceKeys.SORT_ORDER) ?: FOLDER_FIRST.name) - } - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt deleted file mode 100644 index 70818568..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package com.zeapo.pwdstore.utils - -object PreferenceKeys { - - const val APP_THEME = "app_theme" - const val APP_VERSION = "app_version" - const val AUTOFILL_ENABLE = "autofill_enable" - const val BIOMETRIC_AUTH = "biometric_auth" - const val CLEAR_CLIPBOARD_20X = "clear_clipboard_20x" - const val CLEAR_SAVED_PASS = "clear_saved_pass" - const val COPY_ON_DECRYPT = "copy_on_decrypt" - const val ENABLE_DEBUG_LOGGING = "enable_debug_logging" - const val EXPORT_PASSWORDS = "export_passwords" - const val FILTER_RECURSIVELY = "filter_recursively" - const val GENERAL_SHOW_TIME = "general_show_time" - const val GIT_CONFIG = "git_config" - const val GIT_CONFIG_AUTHOR_EMAIL = "git_config_user_email" - const val GIT_CONFIG_AUTHOR_NAME = "git_config_user_name" - const val GIT_EXTERNAL = "git_external" - const val GIT_EXTERNAL_REPO = "git_external_repo" - const val GIT_REMOTE_AUTH = "git_remote_auth" - const val GIT_REMOTE_KEY_TYPE = "git_remote_key_type" - - @Deprecated("Use GIT_REMOTE_URL instead") - const val GIT_REMOTE_LOCATION = "git_remote_location" - const val GIT_REMOTE_USE_MULTIPLEXING = "git_remote_use_multiplexing" - - @Deprecated("Use GIT_REMOTE_URL instead") - const val GIT_REMOTE_PORT = "git_remote_port" - - @Deprecated("Use GIT_REMOTE_URL instead") - const val GIT_REMOTE_PROTOCOL = "git_remote_protocol" - const val GIT_DELETE_REPO = "git_delete_repo" - - @Deprecated("Use GIT_REMOTE_URL instead") - const val GIT_REMOTE_SERVER = "git_remote_server" - const val GIT_REMOTE_URL = "git_remote_url" - - @Deprecated("Use GIT_REMOTE_URL instead") - const val GIT_REMOTE_USERNAME = "git_remote_username" - const val GIT_SERVER_INFO = "git_server_info" - const val GIT_BRANCH_NAME = "git_branch" - const val HTTPS_PASSWORD = "https_password" - const val LENGTH = "length" - const val OREO_AUTOFILL_CUSTOM_PUBLIC_SUFFIXES = "oreo_autofill_custom_public_suffixes" - const val OREO_AUTOFILL_DEFAULT_USERNAME = "oreo_autofill_default_username" - const val OREO_AUTOFILL_DIRECTORY_STRUCTURE = "oreo_autofill_directory_structure" - const val PREF_KEY_CUSTOM_DICT = "pref_key_custom_dict" - const val PREF_KEY_IS_CUSTOM_DICT = "pref_key_is_custom_dict" - const val PREF_KEY_PWGEN_TYPE = "pref_key_pwgen_type" - const val PREF_SELECT_EXTERNAL = "pref_select_external" - const val REPOSITORY_INITIALIZED = "repository_initialized" - const val REPO_CHANGED = "repo_changed" - const val SEARCH_ON_START = "search_on_start" - const val SHOW_EXTRA_CONTENT = "show_extra_content" - - @Deprecated( - message = "Use SHOW_HIDDEN_CONTENTS instead", - replaceWith = ReplaceWith("PreferenceKeys.SHOW_HIDDEN_CONTENTS") - ) - const val SHOW_HIDDEN_FOLDERS = "show_hidden_folders" - const val SHOW_HIDDEN_CONTENTS = "show_hidden_contents" - const val SORT_ORDER = "sort_order" - const val SHOW_PASSWORD = "show_password" - const val SSH_KEY = "ssh_key" - const val SSH_KEYGEN = "ssh_keygen" - const val SSH_KEY_LOCAL_PASSPHRASE = "ssh_key_local_passphrase" - const val SSH_OPENKEYSTORE_CLEAR_KEY_ID = "ssh_openkeystore_clear_keyid" - const val SSH_OPENKEYSTORE_KEYID = "ssh_openkeystore_keyid" - const val SSH_SEE_KEY = "ssh_see_key" - - @Deprecated("To be used only in Migrations.kt") - const val USE_GENERATED_KEY = "use_generated_key" - - const val PROXY_SETTINGS = "proxy_settings" - const val PROXY_HOST = "proxy_host" - const val PROXY_PORT = "proxy_port" - const val PROXY_USERNAME = "proxy_username" - const val PROXY_PASSWORD = "proxy_password" -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt b/app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt deleted file mode 100644 index 4bb3dfcb..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package com.zeapo.pwdstore.utils - -import com.zeapo.pwdstore.git.config.GitSettings -import java.io.IOException -import java.net.Authenticator -import java.net.InetSocketAddress -import java.net.PasswordAuthentication -import java.net.Proxy -import java.net.ProxySelector -import java.net.SocketAddress -import java.net.URI - -/** - * Utility class for [Proxy] handling. - */ -object ProxyUtils { - - private const val HTTP_PROXY_USER_PROPERTY = "http.proxyUser" - private const val HTTP_PROXY_PASSWORD_PROPERTY = "http.proxyPassword" - - /** - * Set the default [Proxy] and [Authenticator] for the app based on user provided settings. - */ - fun setDefaultProxy() { - ProxySelector.setDefault(object : ProxySelector() { - override fun select(uri: URI?): MutableList { - val host = GitSettings.proxyHost - val port = GitSettings.proxyPort - return if (host == null || port == -1) { - mutableListOf(Proxy.NO_PROXY) - } else { - mutableListOf(Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(host, port))) - } - } - - override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) { - if (uri == null || sa == null || ioe == null) { - throw IllegalArgumentException("Arguments can't be null.") - } - } - }) - val user = GitSettings.proxyUsername ?: "" - val password = GitSettings.proxyPassword ?: "" - if (user.isEmpty() || password.isEmpty()) { - System.clearProperty(HTTP_PROXY_USER_PROPERTY) - System.clearProperty(HTTP_PROXY_PASSWORD_PROPERTY) - } else { - System.setProperty(HTTP_PROXY_USER_PROPERTY, user) - System.setProperty(HTTP_PROXY_PASSWORD_PROPERTY, password) - } - Authenticator.setDefault(object : Authenticator() { - override fun getPasswordAuthentication(): PasswordAuthentication? { - return if (requestorType == RequestorType.PROXY) { - PasswordAuthentication(user, password.toCharArray()) - } else { - null - } - } - }) - } -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/TotpFinder.kt b/app/src/main/java/com/zeapo/pwdstore/utils/TotpFinder.kt deleted file mode 100644 index 13a47543..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/TotpFinder.kt +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package com.zeapo.pwdstore.utils - -/** - * Defines a class that can extract relevant parts of a TOTP URL for use by the app. - */ -interface TotpFinder { - - /** - * Get the TOTP secret from the given extra content. - */ - fun findSecret(content: String): String? - - /** - * Get the number of digits required in the final OTP. - */ - fun findDigits(content: String): String - - /** - * Get the TOTP timeout period. - */ - fun findPeriod(content: String): Long - - /** - * Get the algorithm for the TOTP secret. - */ - fun findAlgorithm(content: String): String -} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/UriTotpFinder.kt b/app/src/main/java/com/zeapo/pwdstore/utils/UriTotpFinder.kt deleted file mode 100644 index 23101a13..00000000 --- a/app/src/main/java/com/zeapo/pwdstore/utils/UriTotpFinder.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - * SPDX-License-Identifier: GPL-3.0-only - */ - -package com.zeapo.pwdstore.utils - -import android.net.Uri - -/** - * [Uri] backed TOTP URL parser. - */ -class UriTotpFinder : TotpFinder { - - override fun findSecret(content: String): String? { - content.split("\n".toRegex()).forEach { line -> - if (line.startsWith(TOTP_FIELDS[0])) { - return Uri.parse(line).getQueryParameter("secret") - } - if (line.startsWith(TOTP_FIELDS[1], ignoreCase = true)) { - return line.split(": *".toRegex(), 2).toTypedArray()[1] - } - } - return null - } - - override fun findDigits(content: String): String { - content.split("\n".toRegex()).forEach { line -> - if (line.startsWith(TOTP_FIELDS[0]) && - Uri.parse(line).getQueryParameter("digits") != null) { - return Uri.parse(line).getQueryParameter("digits")!! - } - } - return "6" - } - - override fun findPeriod(content: String): Long { - content.split("\n".toRegex()).forEach { line -> - if (line.startsWith(TOTP_FIELDS[0]) && - Uri.parse(line).getQueryParameter("period") != null) { - val period = Uri.parse(line).getQueryParameter("period")!!.toLongOrNull() - if (period != null && period > 0) - return period - } - } - return 30 - } - - override fun findAlgorithm(content: String): String { - content.split("\n".toRegex()).forEach { line -> - if (line.startsWith(TOTP_FIELDS[0]) && - Uri.parse(line).getQueryParameter("algorithm") != null) { - return Uri.parse(line).getQueryParameter("algorithm")!! - } - } - return "sha1" - } - - companion object { - - val TOTP_FIELDS = arrayOf( - "otpauth://totp", - "totp:" - ) - } -} -- cgit v1.2.3