fix(deps): update dependency com.slack.lint:slack-lint-checks to v0.6.0 (#2697)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
renovate[bot] 2023-09-27 18:45:59 +00:00 committed by GitHub
parent 403e378dc3
commit 47d65d0740
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 141 additions and 155 deletions

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
<issue <issue
id="StopShip" id="StopShip"
@ -26,17 +26,6 @@
file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-jdk18on/1.75/5adfef8a71a0933454739264b56283cc73dd2383/bcpkix-jdk18on-1.75.jar"/> file="$GRADLE_USER_HOME/caches/modules-2/files-2.1/org.bouncycastle/bcpkix-jdk18on/1.75/5adfef8a71a0933454739264b56283cc73dd2383/bcpkix-jdk18on-1.75.jar"/>
</issue> </issue>
<issue
id="RawDispatchersUse"
message="Use SlackDispatchers."
errorLine1=" runBlocking(Dispatchers.Main) { suspendCoroutine { cont -> askForPassword(cont, isRetry) } }"
errorLine2=" ~~~~~~~~~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/git/sshj/SshjSessionFactory.kt"
line="60"
column="19"/>
</issue>
<issue <issue
id="DenyListedApi" id="DenyListedApi"
message="Use Context#getDrawableCompat() instead" message="Use Context#getDrawableCompat() instead"
@ -59,39 +48,6 @@
column="31"/> column="31"/>
</issue> </issue>
<issue
id="DenyListedApi"
message="Use the structured concurrent CoroutineScope#launch and Flow#collect APIs instead of reactive Flow#onEach and Flow#launchIn. Suspend calls like Flow#collect can be refactored into standalone suspend funs and mixed in with regular control flow in a suspend context, but calls that invoke CoroutineScope#launch and Flow#collect at the same time hide the suspend context, encouraging the developer to continue working in the reactive domain."
errorLine1=" .launchIn(lifecycleScope)"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/passwords/PasswordFragment.kt"
line="212"
column="8"/>
</issue>
<issue
id="DenyListedApi"
message="Use the structured concurrent CoroutineScope#launch and Flow#collect APIs instead of reactive Flow#onEach and Flow#launchIn. Suspend calls like Flow#collect can be refactored into standalone suspend funs and mixed in with regular control flow in a suspend context, but calls that invoke CoroutineScope#launch and Flow#collect at the same time hide the suspend context, encouraging the developer to continue working in the reactive domain."
errorLine1=" .launchIn(lifecycleScope)"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/dialogs/PasswordGeneratorDialogFragment.kt"
line="68"
column="8"/>
</issue>
<issue
id="DenyListedApi"
message="Use the structured concurrent CoroutineScope#launch and Flow#collect APIs instead of reactive Flow#onEach and Flow#launchIn. Suspend calls like Flow#collect can be refactored into standalone suspend funs and mixed in with regular control flow in a suspend context, but calls that invoke CoroutineScope#launch and Flow#collect at the same time hide the suspend context, encouraging the developer to continue working in the reactive domain."
errorLine1=" .launchIn(lifecycleScope)"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/app/passwordstore/ui/folderselect/SelectFolderFragment.kt"
line="65"
column="8"/>
</issue>
<issue <issue
id="MissingQuantity" id="MissingQuantity"
message="For locale &quot;it&quot; (Italian) the following quantity should also be defined: `many`" message="For locale &quot;it&quot; (Italian) the following quantity should also be defined: `many`"
@ -227,37 +183,4 @@
column="4"/> column="4"/>
</issue> </issue>
<issue
id="UnknownNullness"
message="Should explicitly declare type here since implicit type does not specify nullness (Lazy&lt;Array&lt;(GitCommand&lt;out (Any or Any?)> or GitCommand&lt;out (Any or Any?)>?)>>)"
errorLine1=" override val commands by unsafeLazy {"
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/git/operation/BreakOutOfDetached.kt"
line="33"
column="16"/>
</issue>
<issue
id="UnknownNullness"
message="Should explicitly declare type here since implicit type does not specify nullness (Array&lt;(GitCommand&lt;out (Any or Any?)> or GitCommand&lt;out (Any or Any?)>?)>)"
errorLine1=" override val commands ="
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/git/operation/ResetToRemoteOperation.kt"
line="14"
column="16"/>
</issue>
<issue
id="UnknownNullness"
message="Should explicitly declare type here since implicit type does not specify nullness (Array&lt;(GitCommand&lt;out (Any or Any?)> or GitCommand&lt;out (Any or Any?)>?)>)"
errorLine1=" override val commands ="
errorLine2=" ~~~~~~~~">
<location
file="src/main/java/app/passwordstore/util/git/operation/SyncOperation.kt"
line="14"
column="16"/>
</issue>
</issues> </issues>

View file

@ -114,7 +114,11 @@ object PasswordRepository {
val dir = getRepositoryDirectory() val dir = getRepositoryDirectory()
// Un-initialize the repo if the dir does not exist or is absolutely empty // Un-initialize the repo if the dir does not exist or is absolutely empty
settings.edit { settings.edit {
if (!dir.exists() || !dir.isDirectory || requireNotNull(dir.listFiles()).isEmpty()) { if (
!dir.exists() ||
!dir.isDirectory ||
requireNotNull(dir.listFiles()) { "Failed to list files in ${dir.path}" }.isEmpty()
) {
putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false) putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)
} else { } else {
putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true)

View file

@ -31,9 +31,8 @@ import app.passwordstore.util.settings.PreferenceKeys
import com.github.michaelbull.result.getOrElse import com.github.michaelbull.result.getOrElse
import com.github.michaelbull.result.runCatching import com.github.michaelbull.result.runCatching
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch
import reactivecircus.flowbinding.android.widget.afterTextChanges import reactivecircus.flowbinding.android.widget.afterTextChanges
import reactivecircus.flowbinding.android.widget.checkedChanges import reactivecircus.flowbinding.android.widget.checkedChanges
@ -55,17 +54,18 @@ class PasswordGeneratorDialogFragment : DialogFragment() {
binding.lengthNumber.setText(prefs.getInt(PreferenceKeys.LENGTH, 20).toString()) binding.lengthNumber.setText(prefs.getInt(PreferenceKeys.LENGTH, 20).toString())
binding.passwordText.typeface = Typeface.MONOSPACE binding.passwordText.typeface = Typeface.MONOSPACE
merge( lifecycleScope.launch {
binding.numerals.checkedChanges().skipInitialValue(), merge(
binding.symbols.checkedChanges().skipInitialValue(), binding.numerals.checkedChanges().skipInitialValue(),
binding.uppercase.checkedChanges().skipInitialValue(), binding.symbols.checkedChanges().skipInitialValue(),
binding.lowercase.checkedChanges().skipInitialValue(), binding.uppercase.checkedChanges().skipInitialValue(),
binding.ambiguous.checkedChanges().skipInitialValue(), binding.lowercase.checkedChanges().skipInitialValue(),
binding.pronounceable.checkedChanges().skipInitialValue(), binding.ambiguous.checkedChanges().skipInitialValue(),
binding.lengthNumber.afterTextChanges().skipInitialValue(), binding.pronounceable.checkedChanges().skipInitialValue(),
) binding.lengthNumber.afterTextChanges().skipInitialValue(),
.onEach { generate(binding.passwordText) } )
.launchIn(lifecycleScope) .collect { generate(binding.passwordText) }
}
return builder return builder
.run { .run {

View file

@ -27,8 +27,7 @@ import com.github.michaelbull.result.runCatching
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.onEach
import me.zhanghai.android.fastscroll.FastScrollerBuilder import me.zhanghai.android.fastscroll.FastScrollerBuilder
@AndroidEntryPoint @AndroidEntryPoint
@ -57,12 +56,16 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) {
FastScrollerBuilder(binding.passRecycler).build() FastScrollerBuilder(binding.passRecycler).build()
registerForContextMenu(binding.passRecycler) registerForContextMenu(binding.passRecycler)
val path = requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) val path =
requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) {
"Cannot navigate if ${PasswordStore.REQUEST_ARG_PATH} is not provided"
}
model.navigateTo(File(path), listMode = ListMode.DirectoriesOnly, pushPreviousLocation = false) model.navigateTo(File(path), listMode = ListMode.DirectoriesOnly, pushPreviousLocation = false)
model.searchResult lifecycleScope.launch {
.flowWithLifecycle(lifecycle) model.searchResult.flowWithLifecycle(lifecycle).collect { result ->
.onEach { result -> recyclerAdapter.submitList(result.passwordItems) } recyclerAdapter.submitList(result.passwordItems)
.launchIn(lifecycleScope) }
}
} }
override fun onAttach(context: Context) { override fun onAttach(context: Context) {

View file

@ -221,7 +221,10 @@ class GitServerConfigActivity : BaseGitActivity() {
/** Clones the repository, the directory exists, deletes it */ /** Clones the repository, the directory exists, deletes it */
private fun cloneRepository() { private fun cloneRepository() {
val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory()) val localDir =
requireNotNull(PasswordRepository.getRepositoryDirectory()) {
"Repository directory must be set before cloning"
}
val localDirFiles = localDir.listFiles() ?: emptyArray() val localDirFiles = localDir.listFiles() ?: emptyArray()
// Warn if non-empty folder unless it's a just-initialized store that has just a .git folder // Warn if non-empty folder unless it's a just-initialized store that has just a .git folder
if ( if (

View file

@ -50,8 +50,6 @@ import com.github.michaelbull.result.runCatching
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import me.zhanghai.android.fastscroll.FastScrollerBuilder import me.zhanghai.android.fastscroll.FastScrollerBuilder
@ -177,11 +175,13 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
recyclerAdapter.makeSelectable(recyclerView) recyclerAdapter.makeSelectable(recyclerView)
registerForContextMenu(recyclerView) registerForContextMenu(recyclerView)
val path = requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) val path =
requireNotNull(requireArguments().getString(PasswordStore.REQUEST_ARG_PATH)) {
"Cannot navigate if ${PasswordStore.REQUEST_ARG_PATH} is not provided"
}
model.navigateTo(File(path), pushPreviousLocation = false) model.navigateTo(File(path), pushPreviousLocation = false)
model.searchResult lifecycleScope.launch {
.flowWithLifecycle(lifecycle) model.searchResult.flowWithLifecycle(lifecycle).collect { result ->
.onEach { result ->
// Only run animations when the new list is filtered, i.e., the user submitted a search, // Only run animations when the new list is filtered, i.e., the user submitted a search,
// and not on folder navigation since the latter leads to too many removal animations. // and not on folder navigation since the latter leads to too many removal animations.
(recyclerView.itemAnimator as OnOffItemAnimator).isEnabled = result.isFiltered (recyclerView.itemAnimator as OnOffItemAnimator).isEnabled = result.isFiltered
@ -209,7 +209,7 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
} }
} }
} }
.launchIn(lifecycleScope) }
} }
private val actionModeCallback = private val actionModeCallback =

View file

@ -83,8 +83,16 @@ class PasswordStore : BaseGitActivity() {
private val passwordMoveAction = private val passwordMoveAction =
registerForActivityResult(StartActivityForResult()) { result -> registerForActivityResult(StartActivityForResult()) { result ->
val intentData = result.data ?: return@registerForActivityResult val intentData = result.data ?: return@registerForActivityResult
val filesToMove = requireNotNull(intentData.getStringArrayExtra("Files")) val filesToMove =
val target = File(requireNotNull(intentData.getStringExtra("SELECTED_FOLDER_PATH"))) requireNotNull(intentData.getStringArrayExtra("Files")) {
"'Files' intent extra must be set"
}
val target =
File(
requireNotNull(intentData.getStringExtra("SELECTED_FOLDER_PATH")) {
"'SELECTED_FOLDER_PATH' intent extra must be set"
}
)
val repositoryPath = PasswordRepository.getRepositoryDirectory().absolutePath val repositoryPath = PasswordRepository.getRepositoryDirectory().absolutePath
if (!target.isDirectory) { if (!target.isDirectory) {
logcat(ERROR) { "Tried moving passwords to a non-existing folder." } logcat(ERROR) { "Tried moving passwords to a non-existing folder." }
@ -103,7 +111,12 @@ class PasswordStore : BaseGitActivity() {
} }
val destinationFile = File(target.absolutePath + "/" + source.name) val destinationFile = File(target.absolutePath + "/" + source.name)
val basename = source.nameWithoutExtension val basename = source.nameWithoutExtension
val sourceLongName = getLongName(requireNotNull(source.parent), repositoryPath, basename) val sourceLongName =
getLongName(
requireNotNull(source.parent) { "$file has no parent" },
repositoryPath,
basename
)
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
if (destinationFile.exists()) { if (destinationFile.exists()) {
logcat(ERROR) { "Trying to move a file that already exists." } logcat(ERROR) { "Trying to move a file that already exists." }
@ -132,7 +145,11 @@ class PasswordStore : BaseGitActivity() {
val source = File(filesToMove[0]) val source = File(filesToMove[0])
val basename = source.nameWithoutExtension val basename = source.nameWithoutExtension
val sourceLongName = val sourceLongName =
getLongName(requireNotNull(source.parent), repositoryPath, basename) getLongName(
requireNotNull(source.parent) { "$basename has no parent" },
repositoryPath,
basename
)
val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename) val destinationLongName = getLongName(target.absolutePath, repositoryPath, basename)
withContext(dispatcherProvider.main()) { withContext(dispatcherProvider.main()) {
commitChange( commitChange(

View file

@ -125,7 +125,7 @@ private constructor(
// https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE // https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE
private fun makeSaveInfo(): SaveInfo? { private fun makeSaveInfo(): SaveInfo? {
if (!canBeSaved) return null if (!canBeSaved) return null
check(saveFlags != null) check(saveFlags != null) { "saveFlags must not be null" }
val idsToSave = scenario.fieldsToSave.toTypedArray() val idsToSave = scenario.fieldsToSave.toTypedArray()
if (idsToSave.isEmpty()) return null if (idsToSave.isEmpty()) return null
var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD

View file

@ -244,7 +244,7 @@ private constructor(
// https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE // https://developer.android.com/reference/android/service/autofill/SaveInfo#FLAG_DELAY_SAVE
private fun makeSaveInfo(): SaveInfo? { private fun makeSaveInfo(): SaveInfo? {
if (!canBeSaved) return null if (!canBeSaved) return null
check(saveFlags != null) check(saveFlags != null) { "saveFlags must not be null" }
val idsToSave = scenario.fieldsToSave.toTypedArray() val idsToSave = scenario.fieldsToSave.toTypedArray()
if (idsToSave.isEmpty()) return null if (idsToSave.isEmpty()) return null
var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD var saveDataTypes = SaveInfo.SAVE_DATA_TYPE_PASSWORD

View file

@ -40,7 +40,9 @@ class AutofillPublisherChangedException(val formOrigin: FormOrigin) :
) { ) {
init { init {
require(formOrigin is FormOrigin.App) require(formOrigin is FormOrigin.App) {
"${this::class.java.simpleName} is only applicable for apps"
}
} }
} }

View file

@ -9,6 +9,7 @@ import app.passwordstore.R
import app.passwordstore.data.repo.PasswordRepository import app.passwordstore.data.repo.PasswordRepository
import app.passwordstore.util.extensions.unsafeLazy import app.passwordstore.util.extensions.unsafeLazy
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.eclipse.jgit.api.GitCommand
import org.eclipse.jgit.api.RebaseCommand import org.eclipse.jgit.api.RebaseCommand
import org.eclipse.jgit.api.ResetCommand import org.eclipse.jgit.api.ResetCommand
import org.eclipse.jgit.lib.RepositoryState import org.eclipse.jgit.lib.RepositoryState
@ -30,7 +31,7 @@ class BreakOutOfDetached(callingActivity: AppCompatActivity) : GitOperation(call
git.checkout().setName(localBranch), git.checkout().setName(localBranch),
) )
override val commands by unsafeLazy { override val commands: Array<GitCommand<out Any>> by unsafeLazy {
if (merging) { if (merging) {
// We need to run some non-command operations first // We need to run some non-command operations first
repository.writeMergeCommitMsg(null) repository.writeMergeCommitMsg(null)

View file

@ -15,6 +15,7 @@ import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import app.passwordstore.R import app.passwordstore.R
import app.passwordstore.injection.prefs.GitPreferences import app.passwordstore.injection.prefs.GitPreferences
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.git.sshj.InteractivePasswordFinder import app.passwordstore.util.git.sshj.InteractivePasswordFinder
import app.passwordstore.util.settings.AuthMode import app.passwordstore.util.settings.AuthMode
import app.passwordstore.util.settings.PreferenceKeys import app.passwordstore.util.settings.PreferenceKeys
@ -32,7 +33,8 @@ import kotlin.coroutines.resume
class CredentialFinder( class CredentialFinder(
private val callingActivity: FragmentActivity, private val callingActivity: FragmentActivity,
private val authMode: AuthMode, private val authMode: AuthMode,
) : InteractivePasswordFinder() { dispatcherProvider: DispatcherProvider,
) : InteractivePasswordFinder(dispatcherProvider) {
private val hiltEntryPoint = private val hiltEntryPoint =
EntryPointAccessors.fromApplication( EntryPointAccessors.fromApplication(

View file

@ -121,7 +121,8 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
authMethod: SshAuthMethod, authMethod: SshAuthMethod,
credentialsProvider: CredentialsProvider? = null credentialsProvider: CredentialsProvider? = null
) { ) {
sshSessionFactory = SshjSessionFactory(authMethod, hostKeyFile, sshFacade) sshSessionFactory =
SshjSessionFactory(authMethod, hostKeyFile, sshFacade, hiltEntryPoint.dispatcherProvider())
commands.filterIsInstance<TransportCommand<*, *>>().forEach { command -> commands.filterIsInstance<TransportCommand<*, *>>().forEach { command ->
command.setTransportConfigCallback { transport: Transport -> command.setTransportConfigCallback { transport: Transport ->
(transport as? SshTransport)?.sshSessionFactory = sshSessionFactory (transport as? SshTransport)?.sshSessionFactory = sshSessionFactory
@ -217,7 +218,13 @@ abstract class GitOperation(protected val callingActivity: FragmentActivity) {
} }
AuthMode.Password -> { AuthMode.Password -> {
val httpsCredentialProvider = val httpsCredentialProvider =
HttpsCredentialsProvider(CredentialFinder(callingActivity, AuthMode.Password)) HttpsCredentialsProvider(
CredentialFinder(
callingActivity,
AuthMode.Password,
hiltEntryPoint.dispatcherProvider()
)
)
registerAuthProviders(SshAuthMethod.Password(authActivity), httpsCredentialProvider) registerAuthProviders(SshAuthMethod.Password(authActivity), httpsCredentialProvider)
} }
AuthMode.None -> {} AuthMode.None -> {}

View file

@ -6,12 +6,13 @@ package app.passwordstore.util.git.operation
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode.TRACK import org.eclipse.jgit.api.CreateBranchCommand.SetupUpstreamMode.TRACK
import org.eclipse.jgit.api.GitCommand
import org.eclipse.jgit.api.ResetCommand import org.eclipse.jgit.api.ResetCommand
class ResetToRemoteOperation(callingActivity: AppCompatActivity, remoteBranch: String) : class ResetToRemoteOperation(callingActivity: AppCompatActivity, remoteBranch: String) :
GitOperation(callingActivity) { GitOperation(callingActivity) {
override val commands = override val commands: Array<GitCommand<out Any>> =
arrayOf( arrayOf(
// Fetch everything from the origin remote // Fetch everything from the origin remote
git.fetch().setRemote("origin").setRemoveDeletedRefs(true), git.fetch().setRemote("origin").setRemoveDeletedRefs(true),

View file

@ -5,13 +5,14 @@
package app.passwordstore.util.git.operation package app.passwordstore.util.git.operation
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import org.eclipse.jgit.api.GitCommand
class SyncOperation( class SyncOperation(
callingActivity: AppCompatActivity, callingActivity: AppCompatActivity,
rebase: Boolean, rebase: Boolean,
) : GitOperation(callingActivity) { ) : GitOperation(callingActivity) {
override val commands = override val commands: Array<GitCommand<out Any>> =
arrayOf( arrayOf(
// Stage all files // Stage all files
git.add().addFilepattern("."), git.add().addFilepattern("."),

View file

@ -6,6 +6,7 @@ package app.passwordstore.util.git.sshj
import android.util.Base64 import android.util.Base64
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import app.passwordstore.util.coroutines.DispatcherProvider
import app.passwordstore.util.git.operation.CredentialFinder import app.passwordstore.util.git.operation.CredentialFinder
import app.passwordstore.util.settings.AuthMode import app.passwordstore.util.settings.AuthMode
import app.passwordstore.util.ssh.SSHFacade import app.passwordstore.util.ssh.SSHFacade
@ -20,7 +21,6 @@ import java.util.Collections
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.suspendCoroutine import kotlin.coroutines.suspendCoroutine
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import logcat.LogPriority.WARN import logcat.LogPriority.WARN
import logcat.logcat import logcat.logcat
@ -49,15 +49,18 @@ sealed class SshAuthMethod(val activity: AppCompatActivity) {
class SshKey(activity: AppCompatActivity) : SshAuthMethod(activity) class SshKey(activity: AppCompatActivity) : SshAuthMethod(activity)
} }
abstract class InteractivePasswordFinder : PasswordFinder { abstract class InteractivePasswordFinder(private val dispatcherProvider: DispatcherProvider) :
PasswordFinder {
private var isRetry = false private var isRetry = false
abstract fun askForPassword(cont: Continuation<String?>, isRetry: Boolean) abstract fun askForPassword(cont: Continuation<String?>, isRetry: Boolean)
final override fun reqPassword(resource: Resource<*>?): CharArray { override fun reqPassword(resource: Resource<*>?): CharArray {
val password = val password =
runBlocking(Dispatchers.Main) { suspendCoroutine { cont -> askForPassword(cont, isRetry) } } runBlocking(dispatcherProvider.main()) {
suspendCoroutine { cont -> askForPassword(cont, isRetry) }
}
isRetry = true isRetry = true
return password?.toCharArray() ?: throw SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER) return password?.toCharArray() ?: throw SSHException(DisconnectReason.AUTH_CANCELLED_BY_USER)
} }
@ -69,6 +72,7 @@ class SshjSessionFactory(
private val authMethod: SshAuthMethod, private val authMethod: SshAuthMethod,
private val hostKeyFile: File, private val hostKeyFile: File,
private val sshFacade: SSHFacade, private val sshFacade: SSHFacade,
private val dispatcherProvider: DispatcherProvider,
) : SshSessionFactory() { ) : SshSessionFactory() {
private var currentSession: SshjSession? = null private var currentSession: SshjSession? = null
@ -80,10 +84,12 @@ class SshjSessionFactory(
tms: Int tms: Int
): RemoteSession { ): RemoteSession {
return currentSession return currentSession
?: SshjSession(uri, uri.user, authMethod, hostKeyFile, sshFacade).connect().also { ?: SshjSession(uri, uri.user, authMethod, hostKeyFile, sshFacade, dispatcherProvider)
logcat { "New SSH connection created" } .connect()
currentSession = it .also {
} logcat { "New SSH connection created" }
currentSession = it
}
} }
fun close() { fun close() {
@ -125,6 +131,7 @@ private class SshjSession(
private val authMethod: SshAuthMethod, private val authMethod: SshAuthMethod,
private val hostKeyFile: File, private val hostKeyFile: File,
private val sshFacade: SSHFacade, private val sshFacade: SSHFacade,
private val dispatcherProvider: DispatcherProvider,
) : RemoteSession { ) : RemoteSession {
private lateinit var ssh: SSHClient private lateinit var ssh: SSHClient
@ -151,7 +158,8 @@ private class SshjSession(
ssh.addHostKeyVerifier(makeTofuHostKeyVerifier(hostKeyFile)) ssh.addHostKeyVerifier(makeTofuHostKeyVerifier(hostKeyFile))
ssh.connect(uri.host, uri.port.takeUnless { it == -1 } ?: 22) ssh.connect(uri.host, uri.port.takeUnless { it == -1 } ?: 22)
if (!ssh.isConnected) throw IOException() if (!ssh.isConnected) throw IOException()
val passwordAuth = AuthPassword(CredentialFinder(authMethod.activity, AuthMode.Password)) val passwordAuth =
AuthPassword(CredentialFinder(authMethod.activity, AuthMode.Password, dispatcherProvider))
when (authMethod) { when (authMethod) {
is SshAuthMethod.Password -> { is SshAuthMethod.Password -> {
ssh.auth(username, passwordAuth) ssh.auth(username, passwordAuth)
@ -159,7 +167,10 @@ private class SshjSession(
is SshAuthMethod.SshKey -> { is SshAuthMethod.SshKey -> {
val pubkeyAuth = val pubkeyAuth =
AuthPublickey( AuthPublickey(
sshFacade.keyProvider(ssh, CredentialFinder(authMethod.activity, AuthMode.SshKey)) sshFacade.keyProvider(
ssh,
CredentialFinder(authMethod.activity, AuthMode.SshKey, dispatcherProvider)
)
) )
ssh.auth(username, pubkeyAuth, passwordAuth) ssh.auth(username, pubkeyAuth, passwordAuth)
} }

View file

@ -57,7 +57,10 @@ class PasswordExportService : Service() {
*/ */
private fun exportPasswords(targetDirectory: DocumentFile) { private fun exportPasswords(targetDirectory: DocumentFile) {
val repositoryDirectory = requireNotNull(PasswordRepository.getRepositoryDirectory()) val repositoryDirectory =
requireNotNull(PasswordRepository.getRepositoryDirectory()) {
"Password directory must be set to export them"
}
val sourcePassDir = DocumentFile.fromFile(repositoryDirectory) val sourcePassDir = DocumentFile.fromFile(repositoryDirectory)
logcat { "Copying ${repositoryDirectory.path} to $targetDirectory" } logcat { "Copying ${repositoryDirectory.path} to $targetDirectory" }

View file

@ -68,7 +68,7 @@ constructor(
var url var url
get() = settings.getString(PreferenceKeys.GIT_REMOTE_URL) get() = settings.getString(PreferenceKeys.GIT_REMOTE_URL)
private set(value) { private set(value) {
require(value != null) require(value != null) { "Cannot set a null URL" }
if (value == url) return if (value == url) return
settings.edit { putString(PreferenceKeys.GIT_REMOTE_URL, value) } settings.edit { putString(PreferenceKeys.GIT_REMOTE_URL, value) }
if (PasswordRepository.isInitialized) PasswordRepository.addRemote("origin", value, true) if (PasswordRepository.isInitialized) PasswordRepository.addRemote("origin", value, true)

View file

@ -15,6 +15,7 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.autofill.AutofillManager import android.view.autofill.AutofillManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import app.passwordstore.databinding.ActivityOreoAutofillSmsBinding import app.passwordstore.databinding.ActivityOreoAutofillSmsBinding
import app.passwordstore.util.autofill.AutofillResponseBuilder import app.passwordstore.util.autofill.AutofillResponseBuilder
@ -113,18 +114,19 @@ class AutofillSmsActivity : AppCompatActivity() {
return return
} }
registerReceiver( ContextCompat.registerReceiver(
this,
smsCodeRetrievedReceiver, smsCodeRetrievedReceiver,
IntentFilter(SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION), IntentFilter(SmsCodeRetriever.SMS_CODE_RETRIEVED_ACTION),
SmsRetriever.SEND_PERMISSION, SmsRetriever.SEND_PERMISSION,
null null,
ContextCompat.RECEIVER_EXPORTED,
) )
lifecycleScope.launch { waitForSms() } lifecycleScope.launch { waitForSms() }
} }
// Retry starting the SMS code retriever after a permission request. // Retry starting the SMS code retriever after a permission request.
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
@Suppress("DEPRECATION")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (resultCode != Activity.RESULT_OK) return if (resultCode != Activity.RESULT_OK) return

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
</issues> </issues>

View file

@ -116,7 +116,7 @@ private class AutofillFormParser(
if (trustedBrowserInfo?.multiOriginMethod == BrowserMultiOriginMethod.WebView) { if (trustedBrowserInfo?.multiOriginMethod == BrowserMultiOriginMethod.WebView) {
FormField(node, fieldIndex, true, inheritedWebOrigin) FormField(node, fieldIndex, true, inheritedWebOrigin)
} else { } else {
check(inheritedWebOrigin == null) check(inheritedWebOrigin == null) { "'inheritedWebOrigin' should be null here" }
FormField(node, fieldIndex, false) FormField(node, fieldIndex, false)
} }
if (field.relevantField) { if (field.relevantField) {

View file

@ -105,7 +105,9 @@ public sealed class AutofillScenario<out T : Any> {
val genericPassword = mutableListOf<T>() val genericPassword = mutableListOf<T>()
fun build(): AutofillScenario<T> { fun build(): AutofillScenario<T> {
require(genericPassword.isEmpty() || (currentPassword.isEmpty() && newPassword.isEmpty())) require(genericPassword.isEmpty() || (currentPassword.isEmpty() && newPassword.isEmpty())) {
"Password requirements failed."
}
return if (currentPassword.isNotEmpty() || newPassword.isNotEmpty()) { return if (currentPassword.isNotEmpty() || newPassword.isNotEmpty()) {
ClassifiedAutofillScenario( ClassifiedAutofillScenario(
username = username, username = username,

View file

@ -357,13 +357,17 @@ private constructor(
logcat { "$name: Matched $type" } logcat { "$name: Matched $type" }
when (type) { when (type) {
FillableFieldType.Username -> { FillableFieldType.Username -> {
check(matchResult.size == 1 && scenarioBuilder.username == null) check(matchResult.size == 1 && scenarioBuilder.username == null) {
"Scenario has existing username or too many matches"
}
scenarioBuilder.username = matchResult.single() scenarioBuilder.username = matchResult.single()
// Hidden username fields should be saved but not filled. // Hidden username fields should be saved but not filled.
scenarioBuilder.fillUsername = scenarioBuilder.username!!.isVisible == true scenarioBuilder.fillUsername = scenarioBuilder.username!!.isVisible == true
} }
FillableFieldType.Otp -> { FillableFieldType.Otp -> {
check(matchResult.size == 1 && scenarioBuilder.otp == null) check(matchResult.size == 1 && scenarioBuilder.otp == null) {
"Scenario has existing OTP or too many matches"
}
scenarioBuilder.otp = matchResult.single() scenarioBuilder.otp = matchResult.single()
} }
FillableFieldType.CurrentPassword -> scenarioBuilder.currentPassword.addAll(matchResult) FillableFieldType.CurrentPassword -> scenarioBuilder.currentPassword.addAll(matchResult)

View file

@ -10,9 +10,9 @@ import kotlin.test.Test
class PublicSuffixListLoaderTest { class PublicSuffixListLoaderTest {
@Test @Test
fun testLoadingBundledPublicSuffixList() { fun testLoadingBundledPublicSuffixList() {
requireNotNull(javaClass.classLoader).getResourceAsStream("publicsuffixes").buffered().use { requireNotNull(javaClass.classLoader) { "Null classloader????" }
stream -> .getResourceAsStream("publicsuffixes")
PublicSuffixListLoader.load(stream) .buffered()
} .use { stream -> PublicSuffixListLoader.load(stream) }
} }
} }

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
</issues> </issues>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
</issues> </issues>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
</issues> </issues>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
</issues> </issues>

View file

@ -89,7 +89,7 @@ thirdparty-nonfree-googlePlayAuthApiPhone = "com.google.android.gms:play-service
thirdparty-nonfree-sentry = "io.sentry:sentry-android:6.29.0" thirdparty-nonfree-sentry = "io.sentry:sentry-android:6.29.0"
thirdparty-pgpainless = "org.pgpainless:pgpainless-core:1.6.2" thirdparty-pgpainless = "org.pgpainless:pgpainless-core:1.6.2"
thirdparty-plumber = { module = "com.squareup.leakcanary:plumber-android-startup", version.ref = "leakcanary" } thirdparty-plumber = { module = "com.squareup.leakcanary:plumber-android-startup", version.ref = "leakcanary" }
thirdparty-slack-lints = "com.slack.lint:slack-lint-checks:0.5.1" thirdparty-slack-lints = "com.slack.lint:slack-lint-checks:0.6.0"
thirdparty-slf4j-api = { module = "org.slf4j:slf4j-api", version = { strictly = "[1.7, 1.8[", prefer = "1.7.36" } } thirdparty-slf4j-api = { module = "org.slf4j:slf4j-api", version = { strictly = "[1.7, 1.8[", prefer = "1.7.36" } }
thirdparty-sshj = "com.hierynomus:sshj:0.36.0" thirdparty-sshj = "com.hierynomus:sshj:0.36.0"
thirdparty-uri = "com.eygraber:uri-kmp:0.0.14" thirdparty-uri = "com.eygraber:uri-kmp:0.0.14"

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
</issues> </issues>

View file

@ -30,7 +30,7 @@ class WordListParserTest {
companion object { companion object {
fun getDefaultWordList(): InputStream { fun getDefaultWordList(): InputStream {
return requireNotNull(this::class.java.classLoader) return requireNotNull(this::class.java.classLoader) { "Null classloader????" }
.getResourceAsStream("diceware_wordlist.txt") .getResourceAsStream("diceware_wordlist.txt")
} }
} }

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
</issues> </issues>

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
</issues> </issues>

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
<issue <issue
id="InvalidPackage" id="InvalidPackage"

View file

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<issues format="6" by="lint 8.3.0-alpha01" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha01)" variant="all" version="8.3.0-alpha01"> <issues format="6" by="lint 8.3.0-alpha05" type="baseline" client="gradle" dependencies="false" name="AGP (8.3.0-alpha05)" variant="all" version="8.3.0-alpha05">
</issues> </issues>