Fix protocol validation (#726)

* GitOperation: code cleanup

* WIP: Fix validation

* Fixup SSH validation

* Spotless

* Remove logging of MalformedURLException

Co-authored-by: Fabian Henneke <fabian@henneke.me>
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
Harsh Shandilya 2020-04-20 13:29:10 +05:30 committed by GitHub
parent 99aa0d9bb2
commit 47c2875e93
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 36 additions and 37 deletions

View file

@ -4,6 +4,7 @@
*/ */
package com.zeapo.pwdstore.git package com.zeapo.pwdstore.git
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
@ -13,6 +14,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit import androidx.core.content.edit
import androidx.core.text.isDigitsOnly import androidx.core.text.isDigitsOnly
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.github.ajalt.timberkt.e
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.zeapo.pwdstore.git.config.ConnectionMode import com.zeapo.pwdstore.git.config.ConnectionMode
import com.zeapo.pwdstore.git.config.Protocol import com.zeapo.pwdstore.git.config.Protocol
@ -21,7 +23,7 @@ import com.zeapo.pwdstore.utils.PasswordRepository
import com.zeapo.pwdstore.utils.getEncryptedPrefs import com.zeapo.pwdstore.utils.getEncryptedPrefs
import java.io.File import java.io.File
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URL import java.net.URI
import timber.log.Timber import timber.log.Timber
/** /**
@ -31,7 +33,7 @@ import timber.log.Timber
abstract class BaseGitActivity : AppCompatActivity() { abstract class BaseGitActivity : AppCompatActivity() {
lateinit var protocol: Protocol lateinit var protocol: Protocol
lateinit var connectionMode: ConnectionMode lateinit var connectionMode: ConnectionMode
lateinit var url: String var url: String? = null
lateinit var serverHostname: String lateinit var serverHostname: String
lateinit var serverPort: String lateinit var serverPort: String
lateinit var serverUser: String lateinit var serverUser: String
@ -90,48 +92,43 @@ abstract class BaseGitActivity : AppCompatActivity() {
if (serverHostname.isEmpty() || !serverPort.isDigitsOnly()) if (serverHostname.isEmpty() || !serverPort.isDigitsOnly())
return false return false
val previousUrl = if (::url.isInitialized) url else "" val previousUrl = url ?: ""
val hostnamePart = serverHostname val hostnamePart = serverHostname
val pathPart = if (serverPath.startsWith('/')) serverPath else "/$serverPath" val pathPart = if (serverPath.startsWith('/')) serverPath else "/$serverPath"
url = when (protocol) { val newUrl = when (protocol) {
Protocol.Ssh -> { Protocol.Ssh -> {
val userPart = if (serverUser.isEmpty()) "" else "$serverUser@" val userPart = if (serverUser.isEmpty()) "" else "$serverUser@"
val portPart = val portPart =
if (serverPort == "22" || serverPort.isEmpty()) "" else ":$serverPort" if (serverPort == "22" || serverPort.isEmpty()) "" else ":$serverPort"
if (hostnamePart.startsWith("ssh://"))
hostnamePart.replace("ssh://", "")
// We have to specify the ssh scheme as this is the only way to pass a custom port. // We have to specify the ssh scheme as this is the only way to pass a custom port.
val urlWithFreeEntryScheme = "$userPart$hostnamePart$portPart$pathPart" "ssh://$userPart$hostnamePart$portPart$pathPart"
val parsedUrl = try {
URL(urlWithFreeEntryScheme)
} catch (_: MalformedURLException) {
return false
}
if (parsedUrl.protocol == null)
"ssh://$urlWithFreeEntryScheme"
else
urlWithFreeEntryScheme
} }
Protocol.Https -> { Protocol.Https -> {
val portPart = val portPart =
if (serverPort == "443" || serverPort.isEmpty()) "" else ":$serverPort" if (serverPort == "443" || serverPort.isEmpty()) "" else ":$serverPort"
val urlWithFreeEntryScheme = "$hostnamePart$portPart$pathPart" val urlWithFreeEntryScheme = "$hostnamePart$portPart$pathPart"
val parsedUrl = try { when {
URL(urlWithFreeEntryScheme) urlWithFreeEntryScheme.startsWith("https://") -> urlWithFreeEntryScheme
} catch (_: MalformedURLException) { urlWithFreeEntryScheme.startsWith("http://") -> urlWithFreeEntryScheme.replaceFirst("http", "https")
return false else -> "https://$urlWithFreeEntryScheme"
}
when (parsedUrl.protocol) {
null -> "https://$urlWithFreeEntryScheme"
"http" -> urlWithFreeEntryScheme.replaceFirst("http:", "https:")
else -> urlWithFreeEntryScheme
} }
} }
} }
try {
if (URI(newUrl).rawAuthority == null)
return false
} catch (_: MalformedURLException) {
return false
}
if (PasswordRepository.isInitialized) if (PasswordRepository.isInitialized)
PasswordRepository.addRemote("origin", url, true) PasswordRepository.addRemote("origin", newUrl, true)
// HTTPS authentication sends the password to the server, so we must wipe the password when // HTTPS authentication sends the password to the server, so we must wipe the password when
// the server is changed. // the server is changed.
if (url != previousUrl && protocol == Protocol.Https) if (newUrl != previousUrl && protocol == Protocol.Https)
encryptedSettings.edit { remove("https_password") } encryptedSettings.edit { remove("https_password") }
url = newUrl
return true return true
} }
@ -145,8 +142,11 @@ abstract class BaseGitActivity : AppCompatActivity() {
* @param operation The type of git operation to launch * @param operation The type of git operation to launch
*/ */
fun launchGitOperation(operation: Int) { fun launchGitOperation(operation: Int) {
val op: GitOperation if (url == null) {
val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory(this)) setResult(Activity.RESULT_CANCELED)
finish()
return
}
try { try {
// Before launching the operation with OpenKeychain auth, we need to issue several requests // Before launching the operation with OpenKeychain auth, we need to issue several requests
// to the OpenKeychain API. IdentityBuild will take care of launching the relevant intents, // to the OpenKeychain API. IdentityBuild will take care of launching the relevant intents,
@ -163,8 +163,9 @@ abstract class BaseGitActivity : AppCompatActivity() {
return return
} }
op = when (operation) { val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory(this))
REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> CloneOperation(localDir, this).setCommand(url) val op = when (operation) {
REQUEST_CLONE, GitOperation.GET_SSH_KEY_FROM_CLONE -> CloneOperation(localDir, this).setCommand(url!!)
REQUEST_PULL -> PullOperation(localDir, this).setCommand() REQUEST_PULL -> PullOperation(localDir, this).setCommand()
REQUEST_PUSH -> PushOperation(localDir, this).setCommand() REQUEST_PUSH -> PushOperation(localDir, this).setCommand()
REQUEST_SYNC -> SyncOperation(localDir, this).setCommands() REQUEST_SYNC -> SyncOperation(localDir, this).setCommands()

View file

@ -183,9 +183,8 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
.setView(dialogView) .setView(dialogView)
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
if (keyPair.decrypt(passphrase.text.toString())) { if (keyPair.decrypt(passphrase.text.toString())) {
val rememberPassphrase = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_passphrase).isChecked if (dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_passphrase).isChecked) {
if (rememberPassphrase) { encryptedSettings.edit { putString("ssh_key_local_passphrase", passphrase.text.toString()) }
encryptedSettings.edit().putString("ssh_key_local_passphrase", passphrase.text.toString()).apply()
} }
// Authenticate using the ssh-key and then execute the command // Authenticate using the ssh-key and then execute the command
setAuthentication(sshKey, username, passphrase.text.toString()).execute() setAuthentication(sshKey, username, passphrase.text.toString()).execute()
@ -233,9 +232,8 @@ abstract class GitOperation(fileDir: File, internal val callingActivity: Activit
.setMessage(callingActivity.resources.getString(R.string.password_dialog_text)) .setMessage(callingActivity.resources.getString(R.string.password_dialog_text))
.setView(dialogView) .setView(dialogView)
.setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ -> .setPositiveButton(callingActivity.resources.getString(R.string.dialog_ok)) { _, _ ->
val rememberPassphrase = dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_passphrase).isChecked if (dialogView.findViewById<MaterialCheckBox>(R.id.git_auth_remember_passphrase).isChecked) {
if (rememberPassphrase) { encryptedSettings.edit { putString("https_password", passwordView.text.toString()) }
encryptedSettings.edit().putString("https_password", passwordView.text.toString()).apply()
} }
// authenticate using the user/pwd and then execute the command // authenticate using the user/pwd and then execute the command
setAuthentication(username, passwordView.text.toString()).execute() setAuthentication(username, passwordView.text.toString()).execute()

View file

@ -50,7 +50,7 @@ open class GitOperationActivity : BaseGitActivity() {
* @param operation the operation to execute can be REQUEST_PULL or REQUEST_PUSH * @param operation the operation to execute can be REQUEST_PULL or REQUEST_PUSH
*/ */
private fun syncRepository(operation: Int) { private fun syncRepository(operation: Int) {
if (serverUser.isEmpty() || serverHostname.isEmpty() || url.isEmpty()) if (serverUser.isEmpty() || serverHostname.isEmpty() || url.isNullOrEmpty())
MaterialAlertDialogBuilder(this) MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.set_information_dialog_text)) .setMessage(getString(R.string.set_information_dialog_text))
.setPositiveButton(getString(R.string.dialog_positive)) { _, _ -> .setPositiveButton(getString(R.string.dialog_positive)) { _, _ ->
@ -65,7 +65,7 @@ open class GitOperationActivity : BaseGitActivity() {
.show() .show()
else { else {
// check that the remote origin is here, else add it // check that the remote origin is here, else add it
PasswordRepository.addRemote("origin", url, true) PasswordRepository.addRemote("origin", url!!, true)
launchGitOperation(operation) launchGitOperation(operation)
} }
} }