diff options
author | cyBerta <cyberta@riseup.net> | 2021-12-18 01:40:16 +0100 |
---|---|---|
committer | cyBerta <cyberta@riseup.net> | 2021-12-21 00:13:33 +0100 |
commit | 7adf788272e8d24951d0005acaf74d598ba7ed22 (patch) | |
tree | 2a1a59eb054a62d54a3ab345637d5289d9015437 /app/src | |
parent | f6cc3e6ad236c3923b398b2ceae9bf2b472dc43a (diff) |
improve error handling message for custom branded clients in case of misconfigured backend
Diffstat (limited to 'app/src')
5 files changed, 55 insertions, 31 deletions
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6251949a..d1f03288 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,6 +52,7 @@ <string name="setup_error_configure_button">Configure</string> <string name="setup_error_close_button">Exit</string> <string name="setup_error_text">There was an error configuring %s with your chosen provider.\n\nYou may choose to reconfigure, or exit and configure a provider upon next launch.</string> + <string name="setup_error_text_custom">There was an error configuring %s.\n\nYou may choose to reconfigure, or exit.</string> <string name="server_unreachable_message">The server is unreachable, please try again.</string> <string name="error.security.pinnedcertificate">Security error, upgrade the app or choose another provider.</string> <string name="malformed_url">It doesn\'t seem to be a %s provider.</string> diff --git a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java index 5416b1f8..d1de62a0 100644 --- a/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java +++ b/app/src/production/java/se/leap/bitmaskclient/providersetup/ProviderApiManager.java @@ -35,9 +35,7 @@ import okhttp3.OkHttpClient; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; import se.leap.bitmaskclient.base.utils.ConfigHelper; -import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.eip.EIP; -import se.leap.bitmaskclient.eip.EipStatus; import se.leap.bitmaskclient.providersetup.connectivity.OkHttpClientGenerator; import se.leap.bitmaskclient.tor.TorStatusObservable; @@ -47,12 +45,14 @@ import static se.leap.bitmaskclient.R.string.downloading_vpn_certificate_failed; import static se.leap.bitmaskclient.R.string.error_io_exception_user_message; import static se.leap.bitmaskclient.R.string.malformed_url; import static se.leap.bitmaskclient.R.string.setup_error_text; +import static se.leap.bitmaskclient.R.string.setup_error_text_custom; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_cert; import static se.leap.bitmaskclient.R.string.warning_corrupted_provider_details; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE; import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask; import static se.leap.bitmaskclient.providersetup.ProviderAPI.ERRORS; 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; @@ -121,7 +121,7 @@ public class ProviderApiManager extends ProviderApiManagerBase { } if (provider.hasEIP() && !provider.allowsRegistered() && !provider.allowsAnonymous()) { - setErrorResult(currentDownload, setup_error_text, null); + setErrorResult(currentDownload, isDefaultBitmask() ? setup_error_text : setup_error_text_custom, null); } } diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java index f3fd5dbb..4b523edb 100644 --- a/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java +++ b/app/src/test/java/se/leap/bitmaskclient/eip/ProviderApiManagerTest.java @@ -54,6 +54,7 @@ import se.leap.bitmaskclient.testutils.MockSharedPreferences; import se.leap.bitmaskclient.tor.TorStatusObservable; import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_KEY; import static se.leap.bitmaskclient.base.models.Constants.EIP_ACTION_START; import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_KEY; @@ -78,7 +79,7 @@ import static se.leap.bitmaskclient.testutils.BackendMockResponses.BackendMockPr import static se.leap.bitmaskclient.testutils.MockHelper.mockBundle; import static se.leap.bitmaskclient.testutils.MockHelper.mockClientGenerator; import static se.leap.bitmaskclient.testutils.MockHelper.mockConfigHelper; -import static se.leap.bitmaskclient.testutils.MockHelper.mockFingerprintForCertificate; +import static se.leap.bitmaskclient.testutils.MockHelper.mockConfigHelper; import static se.leap.bitmaskclient.testutils.MockHelper.mockIntent; import static se.leap.bitmaskclient.testutils.MockHelper.mockPreferenceHelper; import static se.leap.bitmaskclient.testutils.MockHelper.mockProviderApiConnector; @@ -187,7 +188,7 @@ public class ProviderApiManagerTest { public void test_handleIntentSetupProvider_happyPath_preseededProviderAndCA() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { Provider provider = getConfiguredProvider(); - mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(NO_ERROR); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = mockBundle(); @@ -208,7 +209,7 @@ public class ProviderApiManagerTest { public void test_handleIntentSetupProvider_happyPath_no_preseededProviderAndCA() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { Provider provider = getConfiguredProvider(); - mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(NO_ERROR); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = mockBundle(); @@ -251,7 +252,7 @@ public class ProviderApiManagerTest { @Test public void test_handleIntentSetupProvider_preseededProviderAndCA_failedCAPinning() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { Provider provider = getConfiguredProvider(); - mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495"); + mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495"); mockProviderApiConnector(NO_ERROR); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = mockBundle(); @@ -272,7 +273,7 @@ public class ProviderApiManagerTest { @Test public void test_handleIntentSetupProvider_no_preseededProviderAndCA_failedPinning() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { Provider provider = new Provider("https://riseup.net"); - mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495"); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29495"); mockProviderApiConnector(NO_ERROR); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -417,7 +418,7 @@ public class ProviderApiManagerTest { Provider provider = getConfiguredProvider(); - mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(ERROR_CASE_MICONFIGURED_PROVIDER); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -437,11 +438,37 @@ public class ProviderApiManagerTest { } @Test + public void test_handleIntentSetupProvider_preseededCustomProviderAndCA_failedConfiguration() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { + + Provider provider = getConfiguredProvider(); + + mockProviderApiConnector(ERROR_CASE_MICONFIGURED_PROVIDER); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + when(ConfigHelper.isDefaultBitmask()).thenReturn(false); + + providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); + + Bundle expectedResult = mockBundle(); + expectedResult.putBoolean(BROADCAST_RESULT_KEY, false); + expectedResult.putString(ERRORS, "{\"errors\":\"There was an error configuring RiseupVPN.\"}"); + expectedResult.putParcelable(PROVIDER_KEY, provider); + + + Intent providerApiCommand = mockIntent(); + + providerApiCommand.putExtra(PROVIDER_KEY, provider); + providerApiCommand.setAction(ProviderAPI.SET_UP_PROVIDER); + providerApiCommand.putExtra(ProviderAPI.RECEIVER_KEY, mockResultReceiver(PROVIDER_NOK, expectedResult)); + + providerApiManager.handleIntent(providerApiCommand); + } + + @Test public void test_handleIntentSetupProvider_outdatedPreseededProviderAndCA_successfulConfiguration() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { Provider provider = getProvider(null, null, null, null, null, "riseup_net_outdated_config.json", null, null); - mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(NO_ERROR); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -467,7 +494,7 @@ public class ProviderApiManagerTest { Provider provider = new Provider("https://riseup.net"); - mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(ERROR_CASE_FETCH_EIP_SERVICE_CERTIFICATE_INVALID); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -495,7 +522,7 @@ public class ProviderApiManagerTest { Provider inputProvider = getConfiguredProvider(); inputProvider.setGeoIpJson(new JSONObject()); Provider expectedProvider = getConfiguredProvider(); - mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(NO_ERROR); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -525,7 +552,7 @@ public class ProviderApiManagerTest { } Provider provider = getConfiguredProvider(); - mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(ERROR_GEOIP_SERVICE_IS_DOWN); mockPreferences.edit().putBoolean(USE_BRIDGES, false).putBoolean(USE_SNOWFLAKE, false).commit(); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -556,7 +583,7 @@ public class ProviderApiManagerTest { mockTorStatusObservable(null); Provider provider = getConfiguredProvider(); - mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(ERROR_GEOIP_SERVICE_IS_DOWN_TOR_FALLBACK); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -588,7 +615,7 @@ public class ProviderApiManagerTest { Provider provider = getConfiguredProvider(); provider.setLastGeoIpUpdate(System.currentTimeMillis()); - mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(NO_ERROR); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -618,7 +645,7 @@ public class ProviderApiManagerTest { Provider provider = getConfiguredProvider(); provider.setGeoipUrl(null); provider.setGeoIpJson(new JSONObject()); - mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(NO_ERROR); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -643,7 +670,7 @@ public class ProviderApiManagerTest { public void test_handleIntentSetupProvider_APIv4_happyPath() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { Provider provider = getConfiguredProviderAPIv4(); - mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(NO_ERROR_API_V4); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); Bundle expectedResult = mockBundle(); @@ -668,7 +695,7 @@ public class ProviderApiManagerTest { public void test_handleIntentSetupProvider_TorFallback_SecondTryHappyPath() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException { Provider provider = getConfiguredProviderAPIv4(); - mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(ERROR_DNS_RESUOLUTION_TOR_FALLBACK); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -690,7 +717,7 @@ public class ProviderApiManagerTest { public void test_handleIntentSetupProvider_TorFallbackStartServiceException_SecondTryFailed() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException { Provider provider = getConfiguredProviderAPIv4(); - mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(ERROR_DNS_RESUOLUTION_TOR_FALLBACK); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback(new IllegalStateException("Tor service start not failed."), true)); @@ -709,7 +736,7 @@ public class ProviderApiManagerTest { public void test_handleIntentSetupProvider_TorFallbackTimeoutException_SecondTryFailed() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException { Provider provider = getConfiguredProviderAPIv4(); - mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(ERROR_DNS_RESUOLUTION_TOR_FALLBACK); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback()); @@ -728,7 +755,7 @@ public class ProviderApiManagerTest { public void test_handleIntentSetupProvider_TorBridgesPreferenceEnabled_Success() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException { Provider provider = getConfiguredProviderAPIv4(); - mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(NO_ERROR_API_V4); mockPreferences.edit().putBoolean(USE_BRIDGES, true).putBoolean(USE_SNOWFLAKE, true).commit(); @@ -749,7 +776,7 @@ public class ProviderApiManagerTest { public void test_handleIntentSetupProvider_TorBridgesDisabled_TorNotStarted() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, TimeoutException, InterruptedException { Provider provider = getConfiguredProviderAPIv4(); - mockFingerprintForCertificate(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper(" a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(NO_ERROR_API_V4); mockPreferences.edit().putBoolean(USE_BRIDGES, false).putBoolean(USE_SNOWFLAKE, false).commit(); @@ -793,7 +820,7 @@ public class ProviderApiManagerTest { public void test_handleIntentSetupProvider_noNetwork_NetworkError() throws IOException, CertificateEncodingException, NoSuchAlgorithmException, JSONException { Provider provider = getConfiguredProvider(); - mockFingerprintForCertificate("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); + mockConfigHelper("a5244308a1374709a9afce95e3ae47c1b44bc2398c0a70ccbf8b3a8a97f29494"); mockProviderApiConnector(NO_ERROR); providerApiManager = new ProviderApiManager(mockPreferences, mockResources, mockClientGenerator(), new TestProviderApiServiceCallback(null, false)); Bundle expectedResult = mockBundle(); diff --git a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java index f80f41ed..8d76fd41 100644 --- a/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java +++ b/app/src/test/java/se/leap/bitmaskclient/testutils/MockHelper.java @@ -416,6 +416,7 @@ public class MockHelper { when(ConfigHelper.getProviderFormattedString(any(Resources.class), anyInt())).thenCallRealMethod(); when(ConfigHelper.timezoneDistance(anyInt(), anyInt())).thenCallRealMethod(); when(ConfigHelper.isIPv4(anyString())).thenCallRealMethod(); + when(ConfigHelper.isDefaultBitmask()).thenReturn(true); } public static void mockPreferenceHelper(final Provider providerFromPrefs) { @@ -502,14 +503,6 @@ public class MockHelper { when(ProviderObservable.getInstance()).thenAnswer((Answer<ProviderObservable>) invocation -> observable); } - public static void mockFingerprintForCertificate(String mockedFingerprint) throws CertificateEncodingException, NoSuchAlgorithmException { - mockStatic(ConfigHelper.class); - when(ConfigHelper.getFingerprintFromCertificate(any(X509Certificate.class), anyString())).thenReturn(mockedFingerprint); - when(ConfigHelper.checkErroneousDownload(anyString())).thenCallRealMethod(); - when(ConfigHelper.parseX509CertificatesFromString(anyString())).thenCallRealMethod(); - when(ConfigHelper.getProviderFormattedString(any(Resources.class), anyInt())).thenCallRealMethod(); - } - public static void mockProviderApiConnector(final BackendMockProvider.TestBackendErrorCase errorCase) throws IOException { BackendMockProvider.provideBackendResponsesFor(errorCase); } @@ -568,6 +561,8 @@ public class MockHelper { thenReturn(String.format(errorMessages.getString("warning_expired_provider_cert"), "Bitmask")); when(mockedResources.getString(eq(R.string.setup_error_text), anyString())). thenReturn(String.format(errorMessages.getString("setup_error_text"), "Bitmask")); + when(mockedResources.getString(eq(R.string.setup_error_text_custom), anyString())). + thenReturn(String.format(errorMessages.getString("setup_error_text_custom"), "RiseupVPN")); when(mockedResources.getString(R.string.app_name)). thenReturn("Bitmask"); when(mockedResources.getString(eq(R.string.error_tor_timeout), anyString())). diff --git a/app/src/test/resources/error_messages.json b/app/src/test/resources/error_messages.json index ae04bdb0..f5e2d83c 100644 --- a/app/src/test/resources/error_messages.json +++ b/app/src/test/resources/error_messages.json @@ -14,6 +14,7 @@ "warning_corrupted_provider_cert": "Stored provider certificate is invalid. You can either update %s (recommended) or update the provider certificate using a commercial CA certificate.", "warning_expired_provider_cert": "Stored provider certificate is expired. You can either update %s (recommended) or update the provider certificate using a commercial CA certificate.", "setup_error_text": "There was an error configuring %s with your chosen provider.", + "setup_error_text_custom": "There was an error configuring %s.", "error_tor_timeout": "Starting bridges failed. Do you want to retry or continue with an unobfuscated secure connection to configure %s?", "error_network_connection": "%s has no internet connection. Please check your WiFi and cellular data settings." }
\ No newline at end of file |