Skip to content
This repository was archived by the owner on Oct 15, 2024. It is now read-only.

Commit b4f6fc5

Browse files
authored
Introduce app-wide HTTPS proxy setting (#1134)
1 parent 0d6b7f1 commit b4f6fc5

File tree

11 files changed

+322
-7
lines changed

11 files changed

+322
-7
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
android:name=".ui.onboarding.activity.OnboardingActivity"
3737
android:configChanges="orientation|screenSize" />
3838

39+
<activity android:name=".ui.proxy.ProxySelectorActivity"
40+
android:windowSoftInputMode="adjustResize" />
41+
3942
<activity
4043
android:name=".LaunchActivity"
4144
android:configChanges="orientation|screenSize"

app/src/main/java/com/zeapo/pwdstore/Application.kt

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,31 @@ import com.github.ajalt.timberkt.Timber.DebugTree
1414
import com.github.ajalt.timberkt.Timber.plant
1515
import com.zeapo.pwdstore.git.sshj.setUpBouncyCastleForSshj
1616
import com.zeapo.pwdstore.utils.PreferenceKeys
17+
import com.zeapo.pwdstore.utils.ProxyUtils
1718
import com.zeapo.pwdstore.utils.getString
1819
import com.zeapo.pwdstore.utils.sharedPrefs
1920

2021
@Suppress("Unused")
2122
class Application : android.app.Application(), SharedPreferences.OnSharedPreferenceChangeListener {
2223

24+
private val prefs by lazy { sharedPrefs }
25+
2326
override fun onCreate() {
2427
super.onCreate()
2528
instance = this
2629
if (BuildConfig.ENABLE_DEBUG_FEATURES ||
27-
sharedPrefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false)) {
30+
prefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false)) {
2831
plant(DebugTree())
2932
}
30-
sharedPrefs.registerOnSharedPreferenceChangeListener(this)
33+
prefs.registerOnSharedPreferenceChangeListener(this)
3134
setNightMode()
3235
setUpBouncyCastleForSshj()
3336
runMigrations(applicationContext)
37+
ProxyUtils.setDefaultProxy()
3438
}
3539

3640
override fun onTerminate() {
37-
sharedPrefs.unregisterOnSharedPreferenceChangeListener(this)
41+
prefs.unregisterOnSharedPreferenceChangeListener(this)
3842
super.onTerminate()
3943
}
4044

@@ -45,7 +49,7 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
4549
}
4650

4751
private fun setNightMode() {
48-
AppCompatDelegate.setDefaultNightMode(when (sharedPrefs.getString(PreferenceKeys.APP_THEME)
52+
AppCompatDelegate.setDefaultNightMode(when (prefs.getString(PreferenceKeys.APP_THEME)
4953
?: getString(R.string.app_theme_def)) {
5054
"light" -> MODE_NIGHT_NO
5155
"dark" -> MODE_NIGHT_YES

app/src/main/java/com/zeapo/pwdstore/UserPreference.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import com.zeapo.pwdstore.git.sshj.SshKey
5050
import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary
5151
import com.zeapo.pwdstore.sshkeygen.ShowSshKeyFragment
5252
import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity
53+
import com.zeapo.pwdstore.ui.proxy.ProxySelectorActivity
5354
import com.zeapo.pwdstore.utils.BiometricAuthenticator
5455
import com.zeapo.pwdstore.utils.PasswordRepository
5556
import com.zeapo.pwdstore.utils.PreferenceKeys
@@ -418,6 +419,11 @@ class UserPreference : AppCompatActivity() {
418419
}
419420
}
420421

422+
findPreference<Preference>(PreferenceKeys.PROXY_SETTINGS)?.onPreferenceClickListener = ClickListener {
423+
startActivity(Intent(requireContext(), ProxySelectorActivity::class.java))
424+
true
425+
}
426+
421427
val prefCustomXkpwdDictionary = findPreference<Preference>(PreferenceKeys.PREF_KEY_CUSTOM_DICT)
422428
prefCustomXkpwdDictionary?.onPreferenceClickListener = ClickListener {
423429
prefsActivity.storeCustomDictionaryPath()

app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import com.zeapo.pwdstore.Application
1111
import com.zeapo.pwdstore.utils.PasswordRepository
1212
import com.zeapo.pwdstore.utils.PreferenceKeys
1313
import com.zeapo.pwdstore.utils.getEncryptedGitPrefs
14+
import com.zeapo.pwdstore.utils.getEncryptedProxyPrefs
1415
import com.zeapo.pwdstore.utils.getString
1516
import com.zeapo.pwdstore.utils.sharedPrefs
1617
import java.io.File
@@ -54,6 +55,7 @@ object GitSettings {
5455

5556
private val settings by lazy(LazyThreadSafetyMode.PUBLICATION) { Application.instance.sharedPrefs }
5657
private val encryptedSettings by lazy(LazyThreadSafetyMode.PUBLICATION) { Application.instance.getEncryptedGitPrefs() }
58+
private val proxySettings by lazy(LazyThreadSafetyMode.PUBLICATION) { Application.instance.getEncryptedProxyPrefs() }
5759

5860
var authMode
5961
get() = AuthMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH))
@@ -108,6 +110,38 @@ object GitSettings {
108110
}
109111
}
110112

113+
var proxyHost
114+
get() = proxySettings.getString(PreferenceKeys.PROXY_HOST)
115+
set(value) {
116+
proxySettings.edit {
117+
putString(PreferenceKeys.PROXY_HOST, value)
118+
}
119+
}
120+
121+
var proxyPort
122+
get() = proxySettings.getInt(PreferenceKeys.PROXY_PORT, -1)
123+
set(value) {
124+
proxySettings.edit {
125+
putInt(PreferenceKeys.PROXY_PORT, value)
126+
}
127+
}
128+
129+
var proxyUsername
130+
get() = settings.getString(PreferenceKeys.PROXY_USERNAME)
131+
set(value) {
132+
proxySettings.edit {
133+
putString(PreferenceKeys.PROXY_USERNAME, value)
134+
}
135+
}
136+
137+
var proxyPassword
138+
get() = proxySettings.getString(PreferenceKeys.PROXY_PASSWORD)
139+
set(value) {
140+
proxySettings.edit {
141+
putString(PreferenceKeys.PROXY_PASSWORD, value)
142+
}
143+
}
144+
111145
sealed class UpdateConnectionSettingsResult {
112146
class MissingUsername(val newProtocol: Protocol) : UpdateConnectionSettingsResult()
113147
class AuthModeMismatch(val newProtocol: Protocol, val validModes: List<AuthMode>) : UpdateConnectionSettingsResult()
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
3+
* SPDX-License-Identifier: GPL-3.0-only
4+
*/
5+
6+
package com.zeapo.pwdstore.ui.proxy
7+
8+
import android.os.Bundle
9+
import android.os.Handler
10+
import android.os.Looper
11+
import android.util.Patterns
12+
import androidx.appcompat.app.AppCompatActivity
13+
import androidx.core.content.edit
14+
import androidx.core.os.postDelayed
15+
import androidx.core.widget.doOnTextChanged
16+
import com.zeapo.pwdstore.R
17+
import com.zeapo.pwdstore.databinding.ActivityProxySelectorBinding
18+
import com.zeapo.pwdstore.git.config.GitSettings
19+
import com.zeapo.pwdstore.utils.PreferenceKeys
20+
import com.zeapo.pwdstore.utils.ProxyUtils
21+
import com.zeapo.pwdstore.utils.getEncryptedProxyPrefs
22+
import com.zeapo.pwdstore.utils.getString
23+
import com.zeapo.pwdstore.utils.viewBinding
24+
25+
private val IP_ADDRESS_REGEX = Patterns.IP_ADDRESS.toRegex()
26+
private val WEB_ADDRESS_REGEX = Patterns.WEB_URL.toRegex()
27+
28+
class ProxySelectorActivity : AppCompatActivity() {
29+
30+
private val binding by viewBinding(ActivityProxySelectorBinding::inflate)
31+
private val proxyPrefs by lazy(LazyThreadSafetyMode.NONE) { applicationContext.getEncryptedProxyPrefs() }
32+
33+
override fun onCreate(savedInstanceState: Bundle?) {
34+
super.onCreate(savedInstanceState)
35+
setContentView(binding.root)
36+
with(binding) {
37+
proxyHost.setText(proxyPrefs.getString(PreferenceKeys.PROXY_HOST))
38+
proxyUser.setText(proxyPrefs.getString(PreferenceKeys.PROXY_USERNAME))
39+
proxyPrefs.getInt(PreferenceKeys.PROXY_PORT, -1).takeIf { it != -1 }?.let {
40+
proxyPort.setText("$it")
41+
}
42+
proxyPassword.setText(proxyPrefs.getString(PreferenceKeys.PROXY_PASSWORD))
43+
save.setOnClickListener { saveSettings() }
44+
proxyHost.doOnTextChanged { text, _, _, _ ->
45+
if (text != null) {
46+
proxyHost.error = if (text.matches(IP_ADDRESS_REGEX) || text.matches(WEB_ADDRESS_REGEX)) {
47+
null
48+
} else {
49+
getString(R.string.invalid_proxy_url)
50+
}
51+
}
52+
}
53+
}
54+
55+
}
56+
57+
private fun saveSettings() {
58+
proxyPrefs.edit {
59+
binding.proxyHost.text?.toString()?.takeIf { it.isNotEmpty() }.let {
60+
GitSettings.proxyHost = it
61+
}
62+
binding.proxyUser.text?.toString()?.takeIf { it.isNotEmpty() }.let {
63+
GitSettings.proxyUsername = it
64+
}
65+
binding.proxyPort.text?.toString()?.takeIf { it.isNotEmpty() }?.let {
66+
GitSettings.proxyPort = it.toInt()
67+
}
68+
binding.proxyPassword.text?.toString()?.takeIf { it.isNotEmpty() }.let {
69+
GitSettings.proxyPassword = it
70+
}
71+
}
72+
ProxyUtils.setDefaultProxy()
73+
Handler(Looper.getMainLooper()).postDelayed(500) { finish() }
74+
}
75+
}

app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ val Context.clipboard
7171
*/
7272
fun Context.getEncryptedGitPrefs() = getEncryptedPrefs("git_operation")
7373

74+
/**
75+
* Wrapper for [getEncryptedPrefs] to get the encrypted preference set for the HTTP
76+
* proxy.
77+
*/
78+
fun Context.getEncryptedProxyPrefs() = getEncryptedPrefs("http_proxy")
79+
7480
/**
7581
* Get an instance of [EncryptedSharedPreferences] with the given [fileName]
7682
*/

app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,10 @@ object PreferenceKeys {
8080

8181
@Deprecated("To be used only in Migrations.kt")
8282
const val USE_GENERATED_KEY = "use_generated_key"
83+
84+
const val PROXY_SETTINGS = "proxy_settings"
85+
const val PROXY_HOST = "proxy_host"
86+
const val PROXY_PORT = "proxy_port"
87+
const val PROXY_USERNAME = "proxy_username"
88+
const val PROXY_PASSWORD = "proxy_password"
8389
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
3+
* SPDX-License-Identifier: GPL-3.0-only
4+
*/
5+
6+
package com.zeapo.pwdstore.utils
7+
8+
import com.zeapo.pwdstore.git.config.GitSettings
9+
import java.io.IOException
10+
import java.net.Authenticator
11+
import java.net.InetSocketAddress
12+
import java.net.PasswordAuthentication
13+
import java.net.Proxy
14+
import java.net.ProxySelector
15+
import java.net.SocketAddress
16+
import java.net.URI
17+
18+
/**
19+
* Utility class for [Proxy] handling.
20+
*/
21+
object ProxyUtils {
22+
23+
private const val HTTP_PROXY_USER_PROPERTY = "http.proxyUser"
24+
private const val HTTP_PROXY_PASSWORD_PROPERTY = "http.proxyPassword"
25+
26+
/**
27+
* Set the default [Proxy] and [Authenticator] for the app based on user provided settings.
28+
*/
29+
fun setDefaultProxy() {
30+
ProxySelector.setDefault(object : ProxySelector() {
31+
override fun select(uri: URI?): MutableList<Proxy> {
32+
val host = GitSettings.proxyHost
33+
val port = GitSettings.proxyPort
34+
return if (host == null || port == -1) {
35+
mutableListOf()
36+
} else {
37+
mutableListOf(Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(host, port)))
38+
}
39+
}
40+
41+
override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) {
42+
if (uri == null || sa == null || ioe == null) {
43+
throw IllegalArgumentException("Arguments can't be null.")
44+
}
45+
}
46+
})
47+
val user = GitSettings.proxyUsername ?: ""
48+
val password = GitSettings.proxyPassword ?: ""
49+
if (user.isEmpty() || password.isEmpty()) {
50+
System.clearProperty(HTTP_PROXY_USER_PROPERTY)
51+
System.clearProperty(HTTP_PROXY_PASSWORD_PROPERTY)
52+
} else {
53+
System.setProperty(HTTP_PROXY_USER_PROPERTY, user)
54+
System.setProperty(HTTP_PROXY_PASSWORD_PROPERTY, password)
55+
}
56+
Authenticator.setDefault(object : Authenticator() {
57+
override fun getPasswordAuthentication(): PasswordAuthentication? {
58+
return if (requestorType == RequestorType.PROXY) {
59+
PasswordAuthentication(user, password.toCharArray())
60+
} else {
61+
null
62+
}
63+
}
64+
})
65+
}
66+
}

0 commit comments

Comments
 (0)