summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcyberta <cyberta@riseup.net>2025-04-15 01:00:08 +0000
committercyberta <cyberta@riseup.net>2025-04-15 01:00:08 +0000
commita9f2c30d415e1349616101a11ddfa0719995ef8d (patch)
tree70838b6149deb178fc31a45cd8f38e55c110c1be
parent1b668a3163ce2d824486f045a9f0a0b1ee29256f (diff)
show error dialog in case invite code scanning fails
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Introducer.java86
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java2
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java1
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java5
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java6
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java2
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java55
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java11
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/helpers/AbstractQrScannerHelper.java3
-rw-r--r--app/src/main/res/values/strings.xml12
-rw-r--r--app/src/normal/java/se/leap/bitmaskclient/providersetup/helpers/QrScannerHelper.java35
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/base/models/IntroducerTest.java19
12 files changed, 184 insertions, 53 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Introducer.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Introducer.java
index e3175010..fe30a324 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Introducer.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Introducer.java
@@ -1,15 +1,30 @@
package se.leap.bitmaskclient.base.models;
+import static se.leap.bitmaskclient.base.models.Introducer.IntroducerException.ADDRESS_FORMAT;
+import static se.leap.bitmaskclient.base.models.Introducer.IntroducerException.AUTH_MISSING;
+import static se.leap.bitmaskclient.base.models.Introducer.IntroducerException.CERTIFICATE_LENGTH;
+import static se.leap.bitmaskclient.base.models.Introducer.IntroducerException.CERTIFICATE_MISSING;
+import static se.leap.bitmaskclient.base.models.Introducer.IntroducerException.FQDN_INVALID;
+import static se.leap.bitmaskclient.base.models.Introducer.IntroducerException.FQDN_LENGTH;
+import static se.leap.bitmaskclient.base.models.Introducer.IntroducerException.FQDN_MISSING;
+import static se.leap.bitmaskclient.base.models.Introducer.IntroducerException.UNKNOWN_TYPE;
+
+import android.content.Context;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
+import androidx.annotation.Nullable;
+
import java.io.UnsupportedEncodingException;
import java.net.IDN;
import java.net.URISyntaxException;
import java.net.URLEncoder;
+import java.util.IllegalFormatException;
import java.util.Locale;
+import se.leap.bitmaskclient.R;
+
public class Introducer implements Parcelable {
private final String type;
private final String address;
@@ -69,45 +84,52 @@ public class Introducer implements Parcelable {
public boolean validate() {
if (!"obfsvpnintro".equals(type)) {
- throw new IllegalArgumentException("Unknown type: " + type);
+ throw new IntroducerException(UNKNOWN_TYPE, "Unknown type: ", type);
}
if (!address.contains(":") || address.split(":").length != 2) {
- throw new IllegalArgumentException("Expected address in format ipaddr:port");
+ throw new IntroducerException(ADDRESS_FORMAT, "Expected address in format ipaddr:port");
}
if (certificate.length() != 70) {
- throw new IllegalArgumentException("Wrong certificate length: " + certificate.length());
+ throw new IntroducerException(CERTIFICATE_LENGTH, "Wrong certificate length: ", String.valueOf(certificate.length()));
}
if (!"localhost".equals(fullyQualifiedDomainName) && fullyQualifiedDomainName.split("\\.").length < 2) {
- throw new IllegalArgumentException("Expected a FQDN, got: " + fullyQualifiedDomainName);
+ throw new IntroducerException(FQDN_LENGTH, "Expected a FQDN, got: ", fullyQualifiedDomainName);
}
if (auth == null || auth.isEmpty()) {
- throw new IllegalArgumentException("Auth token is missing");
+ throw new IntroducerException(AUTH_MISSING, "Auth token is missing");
}
return true;
}
- public static Introducer fromUrl(String introducerUrl) throws URISyntaxException, IllegalArgumentException {
+ /**
+ * Helper method to create an Introducer model object, containing the information how to query a menshen API via a proxy
+ * @param introducerUrl String representing an 'invite code'
+ * @return Introducer model
+ * @throws NullPointerException in case the parameter introducerUrl is null
+ * @throws IntroducerException in case the parameter introducerUrl is invalid
+ */
+ public static Introducer fromUrl(String introducerUrl) throws NullPointerException, IntroducerException {
Uri uri = Uri.parse(introducerUrl);
String fqdn = uri.getQueryParameter("fqdn");
if (fqdn == null || fqdn.isEmpty()) {
- throw new IllegalArgumentException("FQDN not found in the introducer URL");
+ throw new IntroducerException(FQDN_MISSING, "FQDN not found in the introducer URL");
}
if (!isAscii(fqdn)) {
- throw new IllegalArgumentException("FQDN is not ASCII: " + fqdn);
+ throw new IntroducerException(FQDN_INVALID, "FQDN is not ASCII: " + fqdn, fqdn);
}
boolean kcp = "1".equals(uri.getQueryParameter( "kcp"));
String cert = uri.getQueryParameter( "cert");
if (cert == null || cert.isEmpty()) {
- throw new IllegalArgumentException("Cert not found in the introducer URL");
+ throw new IntroducerException(CERTIFICATE_MISSING, "Cert not found in the introducer URL");
}
String auth = uri.getQueryParameter( "auth");
if (auth == null || auth.isEmpty()) {
- throw new IllegalArgumentException("Authentication token not found in the introducer URL");
+ throw new IntroducerException(AUTH_MISSING, "Authentication token not found in the introducer URL");
}
return new Introducer(uri.getScheme(), uri.getAuthority(), cert, fqdn, kcp, auth);
}
@@ -129,4 +151,48 @@ public class Introducer implements Parcelable {
return String.format(Locale.US, "%s://%s?fqdn=%s&kcp=%d&cert=%s&auth=%s", type, address, URLEncoder.encode(fullyQualifiedDomainName, "UTF-8"), kcpEnabled ? 1 : 0, URLEncoder.encode(certificate, "UTF-8"), URLEncoder.encode(auth, "UTF-8"));
}
+ public static class IntroducerException extends IllegalArgumentException {
+ public static final int UNKNOWN_TYPE = 100;
+ public static final int ADDRESS_FORMAT = 200;
+ public static final int CERTIFICATE_LENGTH = 300;
+ public static final int CERTIFICATE_MISSING = 310;
+ public static final int FQDN_LENGTH = 400;
+ public static final int FQDN_MISSING = 410;
+ public static final int FQDN_INVALID = 420;
+ public static final int AUTH_MISSING = 500;
+
+ private final Object[] args;
+ private final int type;
+
+ public IntroducerException(int type, String defaultMsg, Object... args) {
+ super(args == null ? defaultMsg : String.format(defaultMsg, args));
+ this.type = type;
+ this.args = args;
+ }
+
+
+ @Nullable
+ public String getLocalizedMessage(Context context) {
+ if (context == null) {
+ return getMessage();
+ }
+ try {
+ return switch (type) {
+ case UNKNOWN_TYPE -> context.getString(R.string.error_invite_unknown_type, args);
+ case ADDRESS_FORMAT -> context.getString(R.string.error_invite_address_format);
+ case CERTIFICATE_LENGTH -> context.getString(R.string.error_invite_certificate_length, args);
+ case CERTIFICATE_MISSING -> context.getString(R.string.error_invite_certificate_missing);
+ case FQDN_LENGTH -> context.getString(R.string.error_invite_fqdn_length, args);
+ case FQDN_MISSING -> context.getString(R.string.error_invite_fqdn_missing);
+ case FQDN_INVALID -> context.getString(R.string.error_invite_fqdn_invalid, args);
+ case AUTH_MISSING -> context.getString(R.string.error_invite_auth_missing);
+ default -> getMessage();
+ };
+ } catch (IllegalFormatException e) {
+ e.printStackTrace();
+ return getMessage();
+ }
+
+ }
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
index b4ec23e6..1708fe83 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
@@ -983,7 +983,7 @@ public final class Provider implements Parcelable {
return introducer;
}
- public void setIntroducer(String introducerUrl) throws URISyntaxException, IllegalArgumentException {
+ public void setIntroducer(String introducerUrl) throws NullPointerException, IllegalArgumentException {
this.introducer = Introducer.fromUrl(introducerUrl);
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java
index 63ae3731..a981a6ef 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderAPI.java
@@ -75,6 +75,7 @@ public class ProviderAPI extends JobIntentService implements ProviderApiManagerB
ERRORS = "errors",
ERRORID = "errorId",
INITIAL_ACTION = "initalAction",
+ ERROR_EXTRA = "errorExtra",
BACKEND_ERROR_KEY = "error",
BACKEND_ERROR_MESSAGE = "message",
USER_MESSAGE = "userMessage",
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
index 79c6f5c4..55024b2c 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java
@@ -10,9 +10,7 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_NOK;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.RECEIVER_KEY;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_EXCEPTION;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.TOR_TIMEOUT;
-import static se.leap.bitmaskclient.providersetup.ProviderApiManagerV5.PROXY_HOST;
-import static se.leap.bitmaskclient.providersetup.ProviderApiManagerV5.SOCKS_PROXY_SCHEME;
-import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_TOR_TIMEOUT;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.SETUP_ERRORS.ERROR_TOR_TIMEOUT;
import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_PROVIDER_JSON;
import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.OFF;
@@ -25,7 +23,6 @@ import android.util.Log;
import androidx.core.content.IntentCompat;
import org.jetbrains.annotations.Blocking;
-import org.json.JSONException;
import org.json.JSONObject;
import java.util.concurrent.TimeoutException;
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java
index 965741f0..5a922419 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV3.java
@@ -59,9 +59,9 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.QUIETLY_UPDATE_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CERTIFICATE_PINNING;
-import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON;
-import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.SETUP_ERRORS.ERROR_CERTIFICATE_PINNING;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.SETUP_ERRORS.ERROR_CORRUPTED_PROVIDER_JSON;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.SETUP_ERRORS.ERROR_INVALID_CERTIFICATE;
import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_CA_CERT;
import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_EIP_SERVICE_JSON;
import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_GEOIP_JSON;
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java
index 2680f612..88f027c3 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerV5.java
@@ -19,7 +19,7 @@ import static se.leap.bitmaskclient.providersetup.ProviderAPI.PROVIDER_OK;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.QUIETLY_UPDATE_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER;
import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.ERROR_INVALID_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.SETUP_ERRORS.ERROR_INVALID_CERTIFICATE;
import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_V5_BRIDGES;
import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_V5_GATEWAYS;
import static se.leap.bitmaskclient.providersetup.ProviderSetupObservable.DOWNLOADED_V5_SERVICE_JSON;
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java
index 172d2636..ab4a64d4 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupFailedDialog.java
@@ -16,8 +16,20 @@
*/
package se.leap.bitmaskclient.providersetup;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERROR_EXTRA;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.INITIAL_ACTION;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_PROVIDER_DETAILS;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.SETUP_ERRORS.DEFAULT;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.SETUP_ERRORS.valueOf;
+
import android.app.AlertDialog;
import android.app.Dialog;
+import android.content.ClipData;
+import android.content.ClipboardManager;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
@@ -33,16 +45,6 @@ import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.base.models.Provider;
import se.leap.bitmaskclient.base.utils.PreferenceHelper;
-import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.INITIAL_ACTION;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.SET_UP_PROVIDER;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_INVALID_VPN_CERTIFICATE;
-import static se.leap.bitmaskclient.providersetup.ProviderAPI.UPDATE_PROVIDER_DETAILS;
-import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.DEFAULT;
-import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.DOWNLOAD_ERRORS.valueOf;
-
/**
* Implements a dialog to show why a download failed.
*
@@ -56,21 +58,24 @@ public class ProviderSetupFailedDialog extends DialogFragment {
private final static String KEY_DOWNLOAD_ERROR = "key_download_error";
private final static String KEY_INITAL_ACTION = "key_inital_action";
private String reasonToFail;
- private DOWNLOAD_ERRORS downloadError = DEFAULT;
+ private SETUP_ERRORS setupError = DEFAULT;
private String initialAction;
+ private String errorExtra;
+
private Provider provider;
/**
* Represent error types that need different error handling actions
*/
- public enum DOWNLOAD_ERRORS {
+ public enum SETUP_ERRORS {
DEFAULT,
ERROR_CORRUPTED_PROVIDER_JSON,
ERROR_INVALID_CERTIFICATE,
ERROR_CERTIFICATE_PINNING,
ERROR_NEW_URL_NO_VPN_PROVIDER,
- ERROR_TOR_TIMEOUT
+ ERROR_TOR_TIMEOUT,
+ ERROR_QR_CODE_SCANNING
}
/**
@@ -98,9 +103,13 @@ public class ProviderSetupFailedDialog extends DialogFragment {
}
if (errorJson.has(ERRORID)) {
- dialogFragment.downloadError = valueOf(errorJson.getString(ERRORID));
+ dialogFragment.setupError = valueOf(errorJson.getString(ERRORID));
} else if (testNewURL) {
- dialogFragment.downloadError = DOWNLOAD_ERRORS.ERROR_NEW_URL_NO_VPN_PROVIDER;
+ dialogFragment.setupError = SETUP_ERRORS.ERROR_NEW_URL_NO_VPN_PROVIDER;
+ }
+
+ if (errorJson.has(ERROR_EXTRA)) {
+ dialogFragment.errorExtra = errorJson.getString(ERROR_EXTRA);
}
if (errorJson.has(INITIAL_ACTION)) {
@@ -126,7 +135,7 @@ public class ProviderSetupFailedDialog extends DialogFragment {
builder.setMessage(reasonToFail)
.setNegativeButton(R.string.cancel, (dialog, id)
-> interfaceWithConfigurationWizard.cancelSettingUpProvider());
- switch (downloadError) {
+ switch (setupError) {
case ERROR_CORRUPTED_PROVIDER_JSON:
builder.setPositiveButton(R.string.update_provider_details, (dialog, which)
-> interfaceWithConfigurationWizard.updateProviderDetails());
@@ -140,6 +149,15 @@ public class ProviderSetupFailedDialog extends DialogFragment {
builder.setPositiveButton(R.string.retry, (dialog, id)
-> interfaceWithConfigurationWizard.addAndSelectNewProvider(provider.getMainUrl()));
break;
+ case ERROR_QR_CODE_SCANNING:
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.setPositiveButton(R.string.error_invite_copy, ((dialog, id) -> {
+ ClipboardManager clipboard = (ClipboardManager)
+ getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = ClipData.newPlainText(getContext().getString(R.string.error), errorExtra);
+ clipboard.setPrimaryClip(clip);
+ }));
+ break;
case ERROR_TOR_TIMEOUT:
builder.setPositiveButton(R.string.retry, (dialog, id) -> {
handleTorTimeoutError();
@@ -148,6 +166,7 @@ public class ProviderSetupFailedDialog extends DialogFragment {
PreferenceHelper.useSnowflake(false);
handleTorTimeoutError();
}));
+ break;
default:
builder.setPositiveButton(R.string.retry, (dialog, id)
-> interfaceWithConfigurationWizard.retrySetUpProvider(provider));
@@ -212,7 +231,7 @@ public class ProviderSetupFailedDialog extends DialogFragment {
super.onSaveInstanceState(outState);
outState.putParcelable(KEY_PROVIDER, provider);
outState.putString(KEY_REASON_TO_FAIL, reasonToFail);
- outState.putString(KEY_DOWNLOAD_ERROR, downloadError.toString());
+ outState.putString(KEY_DOWNLOAD_ERROR, setupError.toString());
outState.putString(KEY_INITAL_ACTION, initialAction);
}
@@ -227,7 +246,7 @@ public class ProviderSetupFailedDialog extends DialogFragment {
this.reasonToFail = savedInstanceState.getString(KEY_REASON_TO_FAIL);
}
if (savedInstanceState.containsKey(KEY_DOWNLOAD_ERROR)) {
- this.downloadError = valueOf(savedInstanceState.getString(KEY_DOWNLOAD_ERROR));
+ this.setupError = valueOf(savedInstanceState.getString(KEY_DOWNLOAD_ERROR));
}
if (savedInstanceState.containsKey(KEY_INITAL_ACTION)) {
this.initialAction = savedInstanceState.getString(KEY_INITAL_ACTION);
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
index 157b582c..823357ff 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ProviderSelectionFragment.java
@@ -16,6 +16,8 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
+import org.json.JSONObject;
+
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
@@ -26,9 +28,9 @@ import se.leap.bitmaskclient.base.utils.ViewHelper;
import se.leap.bitmaskclient.databinding.FProviderSelectionBinding;
import se.leap.bitmaskclient.providersetup.activities.CancelCallback;
import se.leap.bitmaskclient.providersetup.fragments.helpers.AbstractQrScannerHelper;
-import se.leap.bitmaskclient.providersetup.helpers.QrScannerHelper;
import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModel;
import se.leap.bitmaskclient.providersetup.fragments.viewmodel.ProviderSelectionViewModelFactory;
+import se.leap.bitmaskclient.providersetup.helpers.QrScannerHelper;
public class ProviderSelectionFragment extends BaseSetupFragment implements CancelCallback, AbstractQrScannerHelper.ScanResultCallback {
@@ -233,4 +235,11 @@ public class ProviderSelectionFragment extends BaseSetupFragment implements Canc
e.printStackTrace();
}
}
+
+ @Override
+ public void onScanError(JSONObject error) {
+ if (setupActivityCallback != null) {
+ setupActivityCallback.onError(error.toString());
+ }
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/helpers/AbstractQrScannerHelper.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/helpers/AbstractQrScannerHelper.java
index 132d8cc9..08cb6c82 100644
--- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/helpers/AbstractQrScannerHelper.java
+++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/helpers/AbstractQrScannerHelper.java
@@ -2,11 +2,14 @@ package se.leap.bitmaskclient.providersetup.fragments.helpers;
import androidx.fragment.app.Fragment;
+import org.json.JSONObject;
+
import se.leap.bitmaskclient.base.models.Introducer;
public abstract class AbstractQrScannerHelper {
public interface ScanResultCallback {
void onScanResult(Introducer introducer);
+ void onScanError(JSONObject error);
}
public AbstractQrScannerHelper(Fragment fragment, ScanResultCallback callback) {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bfa355ae..6a973878 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -265,4 +265,16 @@
<string name="tunnelling_description">Censors can block access to the open internet. Choose a circumvention option to bypass blocks.</string>
<string name="port_hopping">Port Hopping</string>
<string name="port_hopping_description">"Censors use traffic analysis to block access to the open internet. Port Hopping can make this harder for them. "</string>
+ <string name="error_invite_title">Oops, there\'s something wrong with this invite code:</string>
+ <string name="error_invite">Invalid invite code: %1$s</string>
+ <string name="error_invite_copy">Copy invite code</string>
+ <string name="error_invite_hint">You can copy and share the code with your provider to request support.</string>
+ <string name="error_invite_unknown_type">the invite code type %1$s is unknown.</string>
+ <string name="error_invite_address_format">the invite proxy address doesn\'t follow the format ipaddr:port.</string>
+ <string name="error_invite_certificate_length">the certificate has the wrong number of characters (%1$s).</string>
+ <string name="error_invite_certificate_missing">the invite proxy certificate is missing.</string>
+ <string name="error_invite_fqdn_length">the invite code should contain a fully qualified domain name, but got %1$s.</string>
+ <string name="error_invite_fqdn_missing">the fully qualified domain name is missing.</string>
+ <string name="error_invite_auth_missing">the authentication token is missing.</string>
+ <string name="error_invite_fqdn_invalid">the invite code contains invalid characters in %1$s</string>
</resources>
diff --git a/app/src/normal/java/se/leap/bitmaskclient/providersetup/helpers/QrScannerHelper.java b/app/src/normal/java/se/leap/bitmaskclient/providersetup/helpers/QrScannerHelper.java
index 88046360..350bbd25 100644
--- a/app/src/normal/java/se/leap/bitmaskclient/providersetup/helpers/QrScannerHelper.java
+++ b/app/src/normal/java/se/leap/bitmaskclient/providersetup/helpers/QrScannerHelper.java
@@ -1,11 +1,22 @@
package se.leap.bitmaskclient.providersetup.helpers;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORID;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS;
+import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERROR_EXTRA;
+import static se.leap.bitmaskclient.providersetup.ProviderSetupFailedDialog.SETUP_ERRORS.ERROR_QR_CODE_SCANNING;
+
+import android.content.Context;
+
import androidx.activity.result.ActivityResultLauncher;
import androidx.fragment.app.Fragment;
import com.journeyapps.barcodescanner.ScanContract;
import com.journeyapps.barcodescanner.ScanOptions;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.base.models.Introducer;
import se.leap.bitmaskclient.providersetup.fragments.helpers.AbstractQrScannerHelper;
@@ -16,14 +27,26 @@ public class QrScannerHelper extends AbstractQrScannerHelper {
public QrScannerHelper(Fragment fragment, ScanResultCallback callback) {
super(fragment, callback);
this.scannerActivityResultLauncher = fragment.registerForActivityResult(new ScanContract(), result -> {
- if(result.getContents() != null) {
+ if(result.getContents() == null) {
+ return;
+ }
+ try {
+ Introducer introducer = Introducer.fromUrl(result.getContents());
+ callback.onScanResult(introducer);
+ } catch (Introducer.IntroducerException e) {
+ e.printStackTrace();
+ JSONObject jsonObject = new JSONObject();
try {
- Introducer introducer = Introducer.fromUrl(result.getContents());
- callback.onScanResult(introducer);
- } catch (Exception e) {
- e.printStackTrace();
- //binding.editCustomProvider.setText(result.getContents());
+ Context c = fragment.getContext();
+ jsonObject.put(ERRORS, c.getString(R.string.error_invite_title) + " " + e.getLocalizedMessage(c) + "\n\n" + c.getString(R.string.error_invite_hint));
+ jsonObject.put(ERRORID, ERROR_QR_CODE_SCANNING.toString());
+ jsonObject.put(ERROR_EXTRA, c.getString(R.string.error_invite, "\n" + result.getContents().trim()));
+ callback.onScanError(jsonObject);
+ } catch (JSONException | NullPointerException ex) {
+ ex.printStackTrace();
}
+ } catch (NullPointerException npe) {
+ npe.printStackTrace();
}
});
}
diff --git a/app/src/test/java/se/leap/bitmaskclient/base/models/IntroducerTest.java b/app/src/test/java/se/leap/bitmaskclient/base/models/IntroducerTest.java
index b53f06fe..9db18114 100644
--- a/app/src/test/java/se/leap/bitmaskclient/base/models/IntroducerTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/base/models/IntroducerTest.java
@@ -12,9 +12,10 @@ import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import java.io.UnsupportedEncodingException;
-import java.net.URISyntaxException;
import java.net.URLEncoder;
+import se.leap.bitmaskclient.base.models.Introducer.IntroducerException;
+
@RunWith(RobolectricTestRunner.class)
@Config(sdk = {Build.VERSION_CODES.P})
@@ -40,7 +41,7 @@ public class IntroducerTest {
assertEquals(intro.getFullyQualifiedDomainName(), "ft1.example.org");
assertEquals("solitech_w4gOlm+sbF8spFL8E1Q6dg==", intro.getAuthToken());
assertEquals("obfsvpnintro://37.2.240.90:443?fqdn=ft1.example.org&kcp=1&cert=XXXXXXX&auth=solitech_w4gOlm%2BsbF8spFL8E1Q6dg%3D%3D", intro.toUrl());
- } catch (URISyntaxException | UnsupportedEncodingException e) {
+ } catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
@@ -48,36 +49,36 @@ public class IntroducerTest {
@Test
public void testFromUrl_homograph_attack() {
String code = "obfsvpnintro://37.2.240.90:443?fqdn=ft1.bitmasк.net&kcp=0&cert=XXXXXXX&auth=solitech_w4gOlm%2BseC5spDL8E1Q6dg";
- assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code));
+ assertThrows(IntroducerException.class, () -> Introducer.fromUrl(code));
}
@Test
public void testFromUrl_invalid_fqdn() {
String code = "obfsvpnintro://37.2.240.90:443?fqdn=file://var/wwww&kcp=0&cert=XXXXXXX&auth=solitech_w4gOlm%2BseC5spDL8E1Q6dg";
- assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code));
+ assertThrows(IntroducerException.class, () -> Introducer.fromUrl(code));
}
@Test
public void testFromUrl_missing_fqdn() {
String code = "obfsvpnintro://37.2.240.90:443?fqdn=&kcp=0&cert=XXXXXXX&auth=solitech_w4gOlm%2BseC5spDL8E1Q6dg";
- assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code));
+ assertThrows(IntroducerException.class, () -> Introducer.fromUrl(code));
String code2 = "obfsvpnintro://37.2.240.90:443?kcp=0&cert=XXXXXXX&auth=solitech_w4gOlm%2BseC5spDL8E1Q6dg";
- assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code2));
+ assertThrows(IntroducerException.class, () -> Introducer.fromUrl(code2));
}
@Test
public void testFromUrl_missing_cert() {
String code = "obfsvpnintro://37.2.240.90:443?fqdn=ft1.bitmask.net&kcp=0&cert=&auth=solitech_w4gOlm%2BseC5spDL8E1Q6dg";
- assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code));
+ assertThrows(IntroducerException.class, () -> Introducer.fromUrl(code));
}
@Test
public void testFromUrl_missing_auth() {
String code = "obfsvpnintro://37.2.240.90:443?fqdn=ft1.bitmask.net&kcp=0&cert=XXXXXXX&auth=";
- assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code));
+ assertThrows(IntroducerException.class, () -> Introducer.fromUrl(code));
String code2 = "obfsvpnintro://37.2.240.90:443?fqdn=ft1.bitmask.net&kcp=0&cert=XXXXXXX";
- assertThrows(IllegalArgumentException.class, () -> Introducer.fromUrl(code2));
+ assertThrows(IntroducerException.class, () -> Introducer.fromUrl(code2));
}
}