mirror of
https://github.com/android-password-store/Android-Password-Store
synced 2025-08-29 21:38:03 +00:00
fix: ensure parent hierarchy exists when creating passwords
Also refactor to use NIO Paths APIs Fixes #2755
This commit is contained in:
parent
114507cfa6
commit
c047752ef7
@ -53,9 +53,18 @@ import com.google.zxing.integration.android.IntentIntegrator.QR_CODE
|
|||||||
import com.google.zxing.qrcode.QRCodeReader
|
import com.google.zxing.qrcode.QRCodeReader
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.nio.file.Paths
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.io.path.absolutePathString
|
||||||
|
import kotlin.io.path.createDirectories
|
||||||
|
import kotlin.io.path.deleteIfExists
|
||||||
|
import kotlin.io.path.exists
|
||||||
|
import kotlin.io.path.isSameFileAs
|
||||||
|
import kotlin.io.path.nameWithoutExtension
|
||||||
|
import kotlin.io.path.pathString
|
||||||
|
import kotlin.io.path.relativeTo
|
||||||
|
import kotlin.io.path.writeBytes
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import logcat.LogPriority.ERROR
|
import logcat.LogPriority.ERROR
|
||||||
@ -75,7 +84,6 @@ class PasswordCreationActivity : BasePGPActivity() {
|
|||||||
intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false)
|
intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false)
|
||||||
}
|
}
|
||||||
private val editing by unsafeLazy { intent.getBooleanExtra(EXTRA_EDITING, false) }
|
private val editing by unsafeLazy { intent.getBooleanExtra(EXTRA_EDITING, false) }
|
||||||
private val oldFileName by unsafeLazy { intent.getStringExtra(EXTRA_FILE_NAME) }
|
|
||||||
private var oldCategory: String? = null
|
private var oldCategory: String? = null
|
||||||
private var copy: Boolean = false
|
private var copy: Boolean = false
|
||||||
|
|
||||||
@ -308,6 +316,7 @@ class PasswordCreationActivity : BasePGPActivity() {
|
|||||||
/** Encrypts the password and the extra content */
|
/** Encrypts the password and the extra content */
|
||||||
private fun encrypt() {
|
private fun encrypt() {
|
||||||
with(binding) {
|
with(binding) {
|
||||||
|
val oldName = suggestedName
|
||||||
val editName = filename.text.toString().trim()
|
val editName = filename.text.toString().trim()
|
||||||
val editPass = password.text.toString()
|
val editPass = password.text.toString()
|
||||||
val editExtra = extraContent.text.toString()
|
val editExtra = extraContent.text.toString()
|
||||||
@ -335,21 +344,24 @@ class PasswordCreationActivity : BasePGPActivity() {
|
|||||||
val path =
|
val path =
|
||||||
when {
|
when {
|
||||||
// If we allowed the user to edit the relative path, we have to consider it here
|
// If we allowed the user to edit the relative path, we have to consider it here
|
||||||
// instead
|
// instead of fullPath.
|
||||||
// of fullPath.
|
|
||||||
directoryInputLayout.isEnabled -> {
|
directoryInputLayout.isEnabled -> {
|
||||||
val editRelativePath = directory.text.toString().trim()
|
val editRelativePath = directory.text.toString().trim()
|
||||||
if (editRelativePath.isEmpty()) {
|
if (editRelativePath.isEmpty()) {
|
||||||
snackbar(message = resources.getString(R.string.path_toast_text))
|
snackbar(message = resources.getString(R.string.path_toast_text))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val passwordDirectory = File("$repoPath/${editRelativePath.trim('/')}")
|
val passwordDirectory = Paths.get(repoPath, editRelativePath.trim('/'))
|
||||||
if (!passwordDirectory.exists() && !passwordDirectory.mkdir()) {
|
passwordDirectory.createDirectories()
|
||||||
snackbar(message = "Failed to create directory ${editRelativePath.trim('/')}")
|
if (!passwordDirectory.exists()) {
|
||||||
|
snackbar(
|
||||||
|
message =
|
||||||
|
"Failed to create directory ${passwordDirectory.relativeTo(Paths.get(repoPath)).pathString}"
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
"${passwordDirectory.path}/$editName.gpg"
|
"${passwordDirectory.pathString}/$editName.gpg"
|
||||||
}
|
}
|
||||||
else -> "$fullPath/$editName.gpg"
|
else -> "$fullPath/$editName.gpg"
|
||||||
}
|
}
|
||||||
@ -362,34 +374,34 @@ class PasswordCreationActivity : BasePGPActivity() {
|
|||||||
repository.encrypt(gpgIdentifiers, content.byteInputStream(), outputStream)
|
repository.encrypt(gpgIdentifiers, content.byteInputStream(), outputStream)
|
||||||
outputStream
|
outputStream
|
||||||
}
|
}
|
||||||
val file = File(path)
|
val passwordFile = Paths.get(path)
|
||||||
// If we're not editing, this file should not already exist!
|
// If we're not editing, this file should not already exist!
|
||||||
// Additionally, if we were editing and the incoming and outgoing
|
// Additionally, if we were editing and the incoming and outgoing
|
||||||
// filenames differ, it means we renamed. Ensure that the target
|
// filenames differ, it means we renamed. Ensure that the target
|
||||||
// doesn't already exist to prevent an accidental overwrite.
|
// doesn't already exist to prevent an accidental overwrite.
|
||||||
if (
|
if (
|
||||||
(!editing || (editing && suggestedName != file.nameWithoutExtension)) && file.exists()
|
(!editing || (editing && suggestedName != passwordFile.nameWithoutExtension)) &&
|
||||||
|
passwordFile.exists()
|
||||||
) {
|
) {
|
||||||
snackbar(message = getString(R.string.password_creation_duplicate_error))
|
snackbar(message = getString(R.string.password_creation_duplicate_error))
|
||||||
return@runCatching
|
return@runCatching
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!file.isInsideRepository()) {
|
if (!passwordFile.toFile().isInsideRepository()) {
|
||||||
snackbar(message = getString(R.string.message_error_destination_outside_repo))
|
snackbar(message = getString(R.string.message_error_destination_outside_repo))
|
||||||
return@runCatching
|
return@runCatching
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(dispatcherProvider.io()) { file.writeBytes(result.toByteArray()) }
|
withContext(dispatcherProvider.io()) { passwordFile.writeBytes(result.toByteArray()) }
|
||||||
|
|
||||||
// associate the new password name with the last name's timestamp in
|
// associate the new password name with the last name's timestamp in history
|
||||||
// history
|
|
||||||
val preference = getSharedPreferences("recent_password_history", Context.MODE_PRIVATE)
|
val preference = getSharedPreferences("recent_password_history", Context.MODE_PRIVATE)
|
||||||
val oldFilePathHash = "$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg".base64()
|
val oldFilePathHash = "$repoPath/${oldCategory?.trim('/')}/$suggestedName.gpg".base64()
|
||||||
val timestamp = preference.getString(oldFilePathHash)
|
val timestamp = preference.getString(oldFilePathHash)
|
||||||
if (timestamp != null) {
|
if (timestamp != null) {
|
||||||
preference.edit {
|
preference.edit {
|
||||||
remove(oldFilePathHash)
|
remove(oldFilePathHash)
|
||||||
putString(file.absolutePath.base64(), timestamp)
|
putString(passwordFile.absolutePathString().base64(), timestamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,22 +414,23 @@ class PasswordCreationActivity : BasePGPActivity() {
|
|||||||
val directoryStructure = AutofillPreferences.directoryStructure(applicationContext)
|
val directoryStructure = AutofillPreferences.directoryStructure(applicationContext)
|
||||||
val entry = passwordEntryFactory.create(content.encodeToByteArray())
|
val entry = passwordEntryFactory.create(content.encodeToByteArray())
|
||||||
returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password)
|
returnIntent.putExtra(RETURN_EXTRA_PASSWORD, entry.password)
|
||||||
val username = entry.username ?: directoryStructure.getUsernameFor(file)
|
val username =
|
||||||
|
entry.username ?: directoryStructure.getUsernameFor(passwordFile.toFile())
|
||||||
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
|
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
directoryInputLayout.isVisible &&
|
directoryInputLayout.isVisible &&
|
||||||
directoryInputLayout.isEnabled &&
|
directoryInputLayout.isEnabled &&
|
||||||
oldFileName != null
|
oldName != editName
|
||||||
) {
|
) {
|
||||||
val oldFile = File("$repoPath/${oldCategory?.trim('/')}/$oldFileName.gpg")
|
val oldPath = Paths.get(repoPath, oldCategory?.trim('/') ?: "", "$oldName.gpg")
|
||||||
if (oldFile.path != file.path && !oldFile.delete()) {
|
if (!oldPath.isSameFileAs(passwordFile) && !oldPath.deleteIfExists()) {
|
||||||
setResult(RESULT_CANCELED)
|
setResult(RESULT_CANCELED)
|
||||||
MaterialAlertDialogBuilder(this@PasswordCreationActivity)
|
MaterialAlertDialogBuilder(this@PasswordCreationActivity)
|
||||||
.setTitle(R.string.password_creation_file_fail_title)
|
.setTitle(R.string.password_creation_file_fail_title)
|
||||||
.setMessage(
|
.setMessage(
|
||||||
getString(R.string.password_creation_file_delete_fail_message, oldFileName)
|
getString(R.string.password_creation_file_delete_fail_message, oldName)
|
||||||
)
|
)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ -> finish() }
|
.setPositiveButton(android.R.string.ok) { _, _ -> finish() }
|
||||||
|
Loading…
x
Reference in New Issue
Block a user