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 /main/src/ui | |
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
Diffstat (limited to 'main/src/ui')
-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 |
4 files changed, 104 insertions, 33 deletions
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; } |