summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2021-08-24 19:25:59 +0200
committerArne Schwabe <arne@rfc2549.org>2021-08-24 19:25:59 +0200
commit09c5cb0a0d33c58897026b7b4b1901c1ddc088f1 (patch)
tree2a881c94bd517b145178a1bd08f71d69914115d4
parentd1eb15b8dd6b4cb599ea7a084e61e24dfdbc74f4 (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.java2
-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-xmain/src/main/res/values/strings.xml7
-rw-r--r--main/src/ui/AndroidManifest.xml7
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/MainActivity.java34
-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.java14
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;
}