diff options
| author | cyberta <cyberta@riseup.net> | 2025-04-15 01:00:08 +0000 |
|---|---|---|
| committer | cyberta <cyberta@riseup.net> | 2025-04-15 01:00:08 +0000 |
| commit | a9f2c30d415e1349616101a11ddfa0719995ef8d (patch) | |
| tree | 70838b6149deb178fc31a45cd8f38e55c110c1be /app | |
| parent | 1b668a3163ce2d824486f045a9f0a0b1ee29256f (diff) | |
show error dialog in case invite code scanning fails
Diffstat (limited to 'app')
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)); } } |
