diff options
author | Arne Schwabe <arne@rfc2549.org> | 2021-08-24 19:25:59 +0200 |
---|---|---|
committer | Arne Schwabe <arne@rfc2549.org> | 2021-08-24 19:25:59 +0200 |
commit | 09c5cb0a0d33c58897026b7b4b1901c1ddc088f1 (patch) | |
tree | 2a881c94bd517b145178a1bd08f71d69914115d4 | |
parent | d1eb15b8dd6b4cb599ea7a084e61e24dfdbc74f4 (diff) |
Implement support of openvpn://import-profile/ support
For details about the protocol see
https://github.com/OpenVPN/openvpn3/blob/master/doc/webauth.md
-rw-r--r-- | main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java | 2 | ||||
-rw-r--r-- | main/src/main/res/layout/import_remote_config.xml (renamed from main/src/main/res/layout/import_as_config.xml) | 25 | ||||
-rwxr-xr-x | main/src/main/res/values/strings.xml | 7 | ||||
-rw-r--r-- | main/src/ui/AndroidManifest.xml | 7 | ||||
-rw-r--r-- | main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java | 34 | ||||
-rw-r--r-- | main/src/ui/java/de/blinkt/openvpn/fragments/ImportRemoteConfig.kt (renamed from main/src/ui/java/de/blinkt/openvpn/fragments/ImportASConfig.kt) | 82 | ||||
-rw-r--r-- | main/src/ui/java/de/blinkt/openvpn/fragments/VPNProfileList.java | 14 |
7 files changed, 135 insertions, 36 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java b/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java index 7fb01032..52ec55d7 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java +++ b/main/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java @@ -51,7 +51,7 @@ public class VPNLaunchHelper { } } - throw new RuntimeException("Cannot find any execulte for this device's ABIs " + Arrays.toString(abis)); + throw new RuntimeException("Cannot find any executable for this device's ABIs " + Arrays.toString(abis)); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) diff --git a/main/src/main/res/layout/import_as_config.xml b/main/src/main/res/layout/import_remote_config.xml index d9651db8..f04503ce 100644 --- a/main/src/main/res/layout/import_as_config.xml +++ b/main/src/main/res/layout/import_remote_config.xml @@ -11,6 +11,29 @@ android:layout_height="match_parent" android:orientation="vertical"> + <RadioGroup + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:id="@+id/import_source_group" + > + <RadioButton + android:checked="true" + android:id="@+id/import_choice_as" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:paddingEnd="20dp" + android:text="@string/import_from_access_server" + android:paddingRight="20dp" /> + + <RadioButton + android:id="@+id/import_choice_url" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/import_from_URL" /> + + </RadioGroup> + <EditText android:id="@+id/as_servername" android:layout_width="match_parent" @@ -22,6 +45,8 @@ android:hint="@string/as_servername" android:inputType="textUri" /> + + <EditText android:id="@+id/username" android:layout_width="match_parent" diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml index 4511d80c..7cab17e4 100755 --- a/main/src/main/res/values/strings.xml +++ b/main/src/main/res/values/strings.xml @@ -33,7 +33,7 @@ <string name="ipv4_address">IPv4 Address</string> <string name="ipv6_address">IPv6 Address</string> <string name="custom_option_warning">Enter custom OpenVPN options. Use with caution. Also note that many of the tun related OpenVPN settings cannot be supported by design of the VPNSettings. If you think an important option is missing contact the author</string> - <string name="auth_username">Username</string> + <string name="auth_username">Username (leave empty for no auth)</string> <string name="auth_pwquery">Password</string> <string name="static_keys_info">For the static configuration the TLS Auth Keys will be used as static keys</string> <string name="configure_the_vpn">Configure the VPN</string> @@ -494,8 +494,9 @@ <string name="title_block_address_families">Block IPv6 (or IPv4) if not used by the VPN</string> <string name="install_keychain">Install new certificate</string> <string name="as_servername">AS servername</string> + <string name="server_url">Server URL</string> <string name="request_autologin">Request autologin profile</string> - <string name="import_from_as">Import Profile from Access Server</string> + <string name="import_from_as">Import Profile from Remote Server</string> <string name="no_default_vpn_set">Default VPN not set. Please set the Default VPN before enabling this option.</string> <string name="internal_web_view">Internal WebView</string> <string name="faq_title_ncp">Failed to negotiate cipher with server</string> @@ -506,5 +507,7 @@ <string name="use_alwayson_vpn">Please you the Always-On Feature of Android to enable VPN at boot time.</string> <string name="open_vpn_settings">Open VPN Settings</string> <string name="trigger_pending_auth_dialog">Press here open a window to enter additional required authentication</string> + <string name="import_from_access_server">OpenVPN Access Server</string> + <string name="import_from_URL">URL</string> </resources> diff --git a/main/src/ui/AndroidManifest.xml b/main/src/ui/AndroidManifest.xml index a9dfa380..0caccd5d 100644 --- a/main/src/ui/AndroidManifest.xml +++ b/main/src/ui/AndroidManifest.xml @@ -31,6 +31,13 @@ <intent-filter> <action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" /> </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + <data android:scheme="openvpn" /> + <data android:host="import-profile" /> + </intent-filter> </activity> <activity android:name=".activities.InternalWebView" /> <activity diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java b/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java index 58698ea3..fe98cf77 100644 --- a/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java +++ b/main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java @@ -5,11 +5,11 @@ package de.blinkt.openvpn.activities; -import android.annotation.TargetApi; import android.content.Intent; -import android.os.Build; +import android.net.Uri; import android.view.Menu; import android.view.MenuItem; +import android.widget.Toast; import androidx.appcompat.app.ActionBar; import androidx.viewpager.widget.ViewPager; @@ -21,6 +21,7 @@ import de.blinkt.openvpn.fragments.AboutFragment; import de.blinkt.openvpn.fragments.FaqFragment; import de.blinkt.openvpn.fragments.GeneralSettings; import de.blinkt.openvpn.fragments.GraphFragment; +import de.blinkt.openvpn.fragments.ImportRemoteConfig; import de.blinkt.openvpn.fragments.LogFragment; import de.blinkt.openvpn.fragments.SendDumpFragment; import de.blinkt.openvpn.fragments.VPNProfileList; @@ -76,8 +77,14 @@ public class MainActivity extends BaseActivity { @Override protected void onResume() { super.onResume(); - if (getIntent() != null) { - String page = getIntent().getStringExtra("PAGE"); + Intent intent = getIntent(); + if (intent != null) { + if (intent.getAction().equals(Intent.ACTION_VIEW)) + { + Uri uri = intent.getData(); + checkUriForProfileImport(uri); + } + String page = intent.getStringExtra("PAGE"); if ("graph".equals(page)) { mPager.setCurrentItem(1); } @@ -85,6 +92,25 @@ public class MainActivity extends BaseActivity { } } + private void checkUriForProfileImport(Uri uri) { + if ("openvpn".equals(uri.getScheme()) && "import-profile".equals(uri.getHost())) + { + String realUrl = uri.getEncodedPath() + "?" + uri.getEncodedQuery(); + if (!realUrl.startsWith("/https://")) + { + Toast.makeText(this, "Cannot use openvpn://import-profile/ URL that does not use https://", Toast.LENGTH_LONG).show(); + return; + } + realUrl = realUrl.substring(1); + startOpenVPNUrlImport(realUrl); + } + } + + private void startOpenVPNUrlImport(String url) { + ImportRemoteConfig asImportFrag = ImportRemoteConfig.newInstance(url); + asImportFrag.show(getSupportFragmentManager(), "dialog"); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main_menu, menu); diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/ImportASConfig.kt b/main/src/ui/java/de/blinkt/openvpn/fragments/ImportRemoteConfig.kt index 71d1ee7c..6cd322ca 100644 --- a/main/src/ui/java/de/blinkt/openvpn/fragments/ImportASConfig.kt +++ b/main/src/ui/java/de/blinkt/openvpn/fragments/ImportRemoteConfig.kt @@ -19,9 +19,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.CheckBox -import android.widget.EditText -import android.widget.Toast +import android.widget.* import androidx.fragment.app.DialogFragment import androidx.lifecycle.lifecycleScope import de.blinkt.openvpn.R @@ -86,13 +84,16 @@ fun getCompositeSSLSocketFactory(certPin: CertificatePinner, hostname: String): } -class ImportASConfig : DialogFragment() { - private lateinit var asUseAutlogin: CheckBox +class ImportRemoteConfig : DialogFragment() { + private lateinit var asUseAutologin: CheckBox private lateinit var asServername: EditText private lateinit var asUsername: EditText private lateinit var asPassword: EditText private lateinit var dialogView: View + private lateinit var importChoiceGroup: RadioGroup + private lateinit var importChoiceAS: RadioButton + internal fun getHostNameVerifier(prefs: SharedPreferences): HostnameVerifier { @@ -131,8 +132,10 @@ class ImportASConfig : DialogFragment() { val pinnedHosts: Set<String> = prefs.getStringSet("pinnedHosts", emptySet())!! val okHttpClient = OkHttpClient.Builder() - .addInterceptor(BasicAuthInterceptor(user, password)) - .connectTimeout(15, TimeUnit.SECONDS) + if (user.isNotBlank() && password.isNotBlank()) { + okHttpClient.addInterceptor(BasicAuthInterceptor(user, password)) + } + okHttpClient.connectTimeout(15, TimeUnit.SECONDS) /* Rely on system certificates if we do not have the host pinned */ if (pinnedHosts.contains(hostname)) { @@ -164,6 +167,7 @@ class ImportASConfig : DialogFragment() { val prefs = c.getSharedPreferences("pinnedCerts", Context.MODE_PRIVATE) val pedit = prefs.edit() val pinnedHosts: MutableSet<String> = prefs.getStringSet("pinnedHosts", mutableSetOf<String>())!! + .toMutableSet() pinnedHosts.add(host) @@ -177,7 +181,7 @@ class ImportASConfig : DialogFragment() { internal fun removedPinnedCert(c: Context, host: String) { val prefs = c.getSharedPreferences("pinnedCerts", Context.MODE_PRIVATE) val pedit = prefs.edit() - val pinnedHosts: MutableSet<String> = prefs.getStringSet("pinnedHosts", mutableSetOf<String>())!! + val pinnedHosts: MutableSet<String> = prefs.getStringSet("pinnedHosts", mutableSetOf<String>())!!.toMutableSet() pinnedHosts.remove(host) @@ -223,7 +227,7 @@ class ImportASConfig : DialogFragment() { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { val inflater = requireActivity().layoutInflater - dialogView = inflater.inflate(R.layout.import_as_config, null); + dialogView = inflater.inflate(R.layout.import_remote_config, null); val builder = AlertDialog.Builder(requireContext()) @@ -233,11 +237,27 @@ class ImportASConfig : DialogFragment() { asServername = dialogView.findViewById(R.id.as_servername) asUsername = dialogView.findViewById(R.id.username) asPassword = dialogView.findViewById(R.id.password) - asUseAutlogin = dialogView.findViewById(R.id.request_autologin) + asUseAutologin = dialogView.findViewById(R.id.request_autologin) + + importChoiceGroup = dialogView.findViewById(R.id.import_source_group) + importChoiceAS = dialogView.findViewById(R.id.import_choice_as) + + importChoiceGroup.setOnCheckedChangeListener { group, checkedId -> + if (checkedId == R.id.import_choice_as) + asServername.setHint(R.string.as_servername) + else + asServername.setHint(R.string.server_url) + } builder.setPositiveButton(R.string.import_config, null) builder.setNegativeButton(android.R.string.cancel) { _, _ -> } + if (arguments?.getString("url") != null) + { + asServername.setText(arguments?.getString("url")) + importChoiceGroup.check(R.id.import_choice_url) + } + val dialog = builder.create() return dialog @@ -276,8 +296,11 @@ class ImportASConfig : DialogFragment() { Toast.makeText(context, "Downloading profile", Toast.LENGTH_LONG).show() } - - val asProfileUri = getAsUrl(asServername.text.toString(), asUseAutlogin.isChecked) + val asProfileUri:HttpUrl + if (importChoiceAS.isChecked) + asProfileUri = getAsUrl(asServername.text.toString(), asUseAutologin.isChecked) + else + asProfileUri = HttpUrl.parse(asServername.text.toString()) var e: Exception? = null try { @@ -400,22 +423,43 @@ class ImportASConfig : DialogFragment() { override fun onResume() { super.onResume() - asServername.setText(Preferences.getDefaultSharedPreferences(activity).getString("as-hostname", "")) - asUsername.setText(Preferences.getDefaultSharedPreferences(activity).getString("as-username", "")) + if (arguments == null) { + asServername.setText( + Preferences.getDefaultSharedPreferences(activity).getString("as-hostname", "") + ) + asUsername.setText( + Preferences.getDefaultSharedPreferences(activity).getString("as-username", "") + ) + if (Preferences.getDefaultSharedPreferences(activity).getBoolean("as-selected", true)) { + importChoiceGroup.check(R.id.import_choice_as) + } else { + importChoiceGroup.check(R.id.import_choice_url) + } + + } } override fun onPause() { super.onPause() val prefs = Preferences.getDefaultSharedPreferences(activity) - prefs.edit().putString("as-hostname", asServername.text.toString()).apply() - prefs.edit().putString("as-username", asUsername.text.toString()).apply() + val editor = prefs.edit() + editor.putString("as-hostname", asServername.text.toString()) + editor.putString("as-username", asUsername.text.toString()) + editor.putBoolean("as-selected", importChoiceAS.isChecked) + editor.apply() } companion object { @JvmStatic - fun newInstance(): ImportASConfig { - return ImportASConfig(); + fun newInstance(url:String? = null): ImportRemoteConfig { + val frag = ImportRemoteConfig() + if (url != null) + { + val extras = Bundle() + extras.putString("url", url) + frag.arguments = extras + } + return frag } } - } diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/VPNProfileList.java b/main/src/ui/java/de/blinkt/openvpn/fragments/VPNProfileList.java index 3804926f..ce6fa7f1 100644 --- a/main/src/ui/java/de/blinkt/openvpn/fragments/VPNProfileList.java +++ b/main/src/ui/java/de/blinkt/openvpn/fragments/VPNProfileList.java @@ -21,7 +21,6 @@ import android.os.Bundle; import android.os.PersistableBundle; import androidx.annotation.RequiresApi; -import androidx.fragment.app.DialogFragment; import androidx.fragment.app.ListFragment; import android.text.Html; @@ -95,7 +94,7 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn private boolean showUserRequestDialogIfNeeded(ConnectionStatus level, Intent intent) { if (level == LEVEL_WAITING_FOR_USER_INPUT) { - if (intent.getStringExtra(EXTRA_CHALLENGE_TXT) != null) { + if (intent != null && intent.getStringExtra(EXTRA_CHALLENGE_TXT) != null) { PasswordDialogFragment pwInputFrag = PasswordDialogFragment.Companion.newInstance(intent, false); pwInputFrag.show(getParentFragmentManager(), "dialog"); @@ -126,6 +125,7 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setHasOptionsMenu(true); + setListAdapter(); } @RequiresApi(api = Build.VERSION_CODES.N_MR1) @@ -277,12 +277,6 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn } - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - setListAdapter(); - } - private void setListAdapter() { if (mArrayadapter == null) { mArrayadapter = new VPNArrayAdapter(getActivity(), R.layout.vpn_list_item, R.id.vpn_item_title); @@ -354,8 +348,8 @@ public class VPNProfileList extends ListFragment implements OnClickListener, Vpn } private boolean startASProfileImport() { - ImportASConfig asImportFrag = ImportASConfig.newInstance(); - asImportFrag.show(requireFragmentManager(), "dialog"); + ImportRemoteConfig asImportFrag = ImportRemoteConfig.newInstance(null); + asImportFrag.show(getParentFragmentManager(), "dialog"); return true; } |