diff options
Diffstat (limited to 'app/src/main/java/se/leap/bitmaskclient/providersetup')
7 files changed, 144 insertions, 81 deletions
diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiConnector.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiConnector.java index 35ad9cd2..cc875c79 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiConnector.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiConnector.java @@ -18,6 +18,8 @@ package se.leap.bitmaskclient.providersetup; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; + import android.util.Pair; import java.io.IOException; @@ -26,6 +28,9 @@ import java.util.List; import java.util.Locale; import java.util.Scanner; +import javax.net.ssl.SSLHandshakeException; + +import de.blinkt.openvpn.core.NativeUtils; import de.blinkt.openvpn.core.VpnStatus; import okhttp3.MediaType; import okhttp3.OkHttpClient; @@ -39,72 +44,103 @@ import okhttp3.Response; public class ProviderApiConnector { - private static final MediaType JSON - = MediaType.parse("application/json; charset=utf-8"); + public interface ProviderApiConnectorInterface { + boolean delete(OkHttpClient okHttpClient, String deleteUrl) throws RuntimeException, IOException; + boolean canConnect(@NonNull OkHttpClient okHttpClient, String url) throws RuntimeException, IOException; + String requestStringFromServer(@NonNull String url, @NonNull String requestMethod, String jsonString, @NonNull List<Pair<String, String>> headerArgs, @NonNull OkHttpClient okHttpClient) throws RuntimeException, IOException; + + } + public static class DefaultProviderApiCpnnector implements ProviderApiConnectorInterface { + + @Override + public boolean delete(OkHttpClient okHttpClient, String deleteUrl) { + try { + Request.Builder requestBuilder = new Request.Builder() + .url(deleteUrl) + .delete(); + Request request = requestBuilder.build(); + + Response response = okHttpClient.newCall(request).execute(); + //response code 401: already logged out + if (response.isSuccessful() || response.code() == 401) { + return true; + } + } catch (IOException | RuntimeException e) { + return false; + } + + return false; + } - public static boolean delete(OkHttpClient okHttpClient, String deleteUrl) { - try { + @Override + public boolean canConnect(@NonNull OkHttpClient okHttpClient, String url) throws RuntimeException, IOException { Request.Builder requestBuilder = new Request.Builder() - .url(deleteUrl) - .delete(); + .url(url) + .method("GET", null); Request request = requestBuilder.build(); Response response = okHttpClient.newCall(request).execute(); - //response code 401: already logged out - if (response.isSuccessful() || response.code() == 401) { - return true; + if (!response.isSuccessful()) { + VpnStatus.logWarning("[API] API request failed canConnect(): " + url); } - } catch (IOException | RuntimeException e) { - return false; + return response.isSuccessful(); } - return false; - } + @Override + public String requestStringFromServer(@NonNull String url, @NonNull String requestMethod, String jsonString, @NonNull List<Pair<String, String>> headerArgs, @NonNull OkHttpClient okHttpClient) throws RuntimeException, IOException { + RequestBody jsonBody = jsonString != null ? RequestBody.create(JSON, jsonString) : null; + Request.Builder requestBuilder = new Request.Builder() + .url(url) + .method(requestMethod, jsonBody); + for (Pair<String, String> keyValPair : headerArgs) { + requestBuilder.addHeader(keyValPair.first, keyValPair.second); + } - public static boolean canConnect(@NonNull OkHttpClient okHttpClient, String url) throws RuntimeException, IOException { - Request.Builder requestBuilder = new Request.Builder() - .url(url) - .method("GET", null); - Request request = requestBuilder.build(); - - Response response = okHttpClient.newCall(request).execute(); - if (!response.isSuccessful()) { - VpnStatus.logWarning("[API] API request failed canConnect(): " + url); + //TODO: move to getHeaderArgs()? + String locale = Locale.getDefault().getLanguage() + Locale.getDefault().getCountry(); + requestBuilder.addHeader("Accept-Language", locale); + Request request = requestBuilder.build(); + + Response response = okHttpClient.newCall(request).execute(); + if (!response.isSuccessful()) { + VpnStatus.logWarning("[API] API request failed: " + url); + } + + if (response.body() != null) { + InputStream inputStream = response.body().byteStream(); + Scanner scanner = new Scanner(inputStream).useDelimiter("\\A"); + if (scanner.hasNext()) { + String result = scanner.next(); + response.body().close(); + return result; + } + } + return null; } - return response.isSuccessful(); + } + private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8"); + private static ProviderApiConnectorInterface instance = new DefaultProviderApiCpnnector(); + @VisibleForTesting + public ProviderApiConnector(ProviderApiConnectorInterface connectorInterface) { + if (!NativeUtils.isUnitTest()) { + throw new IllegalStateException("ProviderApiConnector injected with ProviderApiConnectorInterface outside of an unit test"); + } + instance = connectorInterface; } - public static String requestStringFromServer(@NonNull String url, @NonNull String request_method, String jsonString, @NonNull List<Pair<String, String>> headerArgs, @NonNull OkHttpClient okHttpClient) throws RuntimeException, IOException { - RequestBody jsonBody = jsonString != null ? RequestBody.create(JSON, jsonString) : null; - Request.Builder requestBuilder = new Request.Builder() - .url(url) - .method(request_method, jsonBody); - for (Pair<String, String> keyValPair : headerArgs) { - requestBuilder.addHeader(keyValPair.first, keyValPair.second); - } + public static boolean delete(OkHttpClient okHttpClient, String deleteUrl) throws RuntimeException, IOException { + return instance.delete(okHttpClient, deleteUrl); + } - //TODO: move to getHeaderArgs()? - String locale = Locale.getDefault().getLanguage() + Locale.getDefault().getCountry(); - requestBuilder.addHeader("Accept-Language", locale); - Request request = requestBuilder.build(); + public static boolean canConnect(@NonNull OkHttpClient okHttpClient, String url) throws RuntimeException, IOException { + return instance.canConnect(okHttpClient, url); - Response response = okHttpClient.newCall(request).execute(); - if (!response.isSuccessful()) { - VpnStatus.logWarning("[API] API request failed: " + url); - } + } - if (response.body() != null) { - InputStream inputStream = response.body().byteStream(); - Scanner scanner = new Scanner(inputStream).useDelimiter("\\A"); - if (scanner.hasNext()) { - String result = scanner.next(); - response.body().close(); - return result; - } - } - return null; + public static String requestStringFromServer(@NonNull String url, @NonNull String requestMethod, String jsonString, @NonNull List<Pair<String, String>> headerArgs, @NonNull OkHttpClient okHttpClient) throws RuntimeException, IOException { + return instance.requestStringFromServer(url, requestMethod, jsonString, headerArgs, okHttpClient); } } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java index 93648bb0..1f737b0c 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderApiManagerBase.java @@ -45,9 +45,10 @@ import static se.leap.bitmaskclient.base.models.Provider.CA_CERT; import static se.leap.bitmaskclient.base.models.Provider.GEOIP_URL; import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_API_IP; import static se.leap.bitmaskclient.base.models.Provider.PROVIDER_IP; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.RSAHelper.parseRsaKeyFromString; +import static se.leap.bitmaskclient.base.utils.ConfigHelper.getTorTimeout; +import static se.leap.bitmaskclient.base.utils.RSAHelper.parseRsaKeyFromString; import static se.leap.bitmaskclient.base.utils.ConfigHelper.getDomainFromMainURL; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.getFingerprintFromCertificate; +import static se.leap.bitmaskclient.base.utils.CertificateHelper.getFingerprintFromCertificate; import static se.leap.bitmaskclient.base.utils.ConfigHelper.getProviderFormattedString; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.deleteProviderDetailsFromPreferences; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getFromPersistedProvider; @@ -380,7 +381,7 @@ public abstract class ProviderApiManagerBase { if (TorStatusObservable.getStatus() == ON) { return; } - TorStatusObservable.waitUntil(this::isTorOnOrCancelled, 180); + TorStatusObservable.waitUntil(this::isTorOnOrCancelled, getTorTimeout()); } private boolean isTorOnOrCancelled() { @@ -1138,9 +1139,13 @@ public abstract class ProviderApiManagerBase { String deleteUrl = provider.getApiUrlWithVersion() + "/logout"; - if (ProviderApiConnector.delete(okHttpClient, deleteUrl)) { - LeapSRPSession.setToken(""); - return true; + try { + if (ProviderApiConnector.delete(okHttpClient, deleteUrl)) { + LeapSRPSession.setToken(""); + return true; + } + } catch (IOException e) { + // eat me } return false; } diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupObservable.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupObservable.java index 90a32fea..c882b0bb 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupObservable.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/ProviderSetupObservable.java @@ -16,14 +16,19 @@ package se.leap.bitmaskclient.providersetup; * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -import android.os.Handler; -import android.os.Looper; - -import java.util.Observable; +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeSupport; +import se.leap.bitmaskclient.base.utils.HandlerProvider; +import se.leap.bitmaskclient.base.utils.HandlerProvider.HandlerInterface; import se.leap.bitmaskclient.tor.TorStatusObservable; -public class ProviderSetupObservable extends Observable { +/** + * This Observable tracks the progress of a started provider bootstrapping attempt. + * Each required API call us taken into account as well as the state of tor's bootstrapping in case + * it is used for censorship circumvention. + */ +public class ProviderSetupObservable { private static final String TAG = ProviderSetupObservable.class.getSimpleName(); @@ -36,9 +41,28 @@ public class ProviderSetupObservable extends Observable { public static final int DOWNLOADED_VPN_CERTIFICATE = 100; private static ProviderSetupObservable instance; - private final Handler handler = new Handler(Looper.getMainLooper()); + private final PropertyChangeSupport changeSupport; + public static final String PROPERTY_CHANGE = "ProviderSetupObservable"; + private final HandlerInterface handler; private long lastUpdate = 0; + + + private ProviderSetupObservable() { + handler = HandlerProvider.get(); + changeSupport = new PropertyChangeSupport(this); + + } + + public void addObserver(PropertyChangeListener propertyChangeListener) { + changeSupport.addPropertyChangeListener(propertyChangeListener); + } + + public void deleteObserver(PropertyChangeListener propertyChangeListener) { + changeSupport.removePropertyChangeListener(propertyChangeListener); + } + + public static ProviderSetupObservable getInstance() { if (instance == null) { instance = new ProviderSetupObservable(); @@ -58,8 +82,8 @@ public class ProviderSetupObservable extends Observable { getInstance().progress = progress; } - getInstance().setChanged(); - getInstance().notifyObservers(); + getInstance().changeSupport.firePropertyChange(PROPERTY_CHANGE, null, getInstance()); + }, now - getInstance().lastUpdate < 500L ? 500L : 0L); getInstance().lastUpdate = System.currentTimeMillis() + 500; } @@ -72,8 +96,7 @@ public class ProviderSetupObservable extends Observable { getInstance().handler.postDelayed(() -> { getInstance().progress = (TorStatusObservable.getBootstrapProgress()) / 2; - getInstance().setChanged(); - getInstance().notifyObservers(); + getInstance().changeSupport.firePropertyChange(PROPERTY_CHANGE, null, getInstance()); }, now - getInstance().lastUpdate < 500L ? 500L : 0); getInstance().lastUpdate = System.currentTimeMillis() + 500; } @@ -84,8 +107,7 @@ public class ProviderSetupObservable extends Observable { public static void reset() { getInstance().progress = 0; - getInstance().setChanged(); - getInstance().notifyObservers(); + getInstance().changeSupport.firePropertyChange(PROPERTY_CHANGE, null, getInstance()); } public static void cancel() { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java index 00630f39..fb190dc2 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/SetupViewPagerAdapter.java @@ -1,6 +1,6 @@ package se.leap.bitmaskclient.providersetup; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask; +import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.isDefaultBitmask; import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.CIRCUMVENTION_SETUP_FRAGMENT; import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.CONFIGURE_PROVIDER_FRAGMENT; import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.NOTIFICATION_PERMISSON_EDUCATIONAL_FRAGMENT; diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java index b258a100..9235daad 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/activities/SetupActivity.java @@ -3,7 +3,7 @@ package se.leap.bitmaskclient.providersetup.activities; import static android.view.View.GONE; import static android.view.View.VISIBLE; import static androidx.appcompat.app.ActionBar.DISPLAY_SHOW_CUSTOM; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask; +import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.isDefaultBitmask; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.deleteProviderDetailsFromPreferences; import static se.leap.bitmaskclient.providersetup.fragments.SetupFragmentFactory.CONFIGURE_PROVIDER_FRAGMENT; import static se.leap.bitmaskclient.tor.TorStatusObservable.TorStatus.OFF; @@ -100,7 +100,7 @@ public class SetupActivity extends AppCompatActivity implements SetupActivityCal // indicator views for VPN permission - Intent requestVpnPermission = VpnService.prepare(this); + Intent requestVpnPermission = VpnService.prepare(this.getApplicationContext()); if (requestVpnPermission != null) { addIndicatorView(indicatorViews); addIndicatorView(indicatorViews); diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java index 34a93319..cdb8bd78 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/CircumventionSetupFragment.java @@ -1,8 +1,7 @@ package se.leap.bitmaskclient.providersetup.fragments; -import static se.leap.bitmaskclient.base.utils.ConfigHelper.isDefaultBitmask; +import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.isDefaultBitmask; -import android.content.Context; import android.graphics.Typeface; import android.os.Bundle; import android.view.LayoutInflater; @@ -17,7 +16,6 @@ import se.leap.bitmaskclient.base.utils.PreferenceHelper; import se.leap.bitmaskclient.databinding.FCircumventionSetupBinding; import se.leap.bitmaskclient.providersetup.ProviderManager; import se.leap.bitmaskclient.providersetup.activities.CancelCallback; -import se.leap.bitmaskclient.providersetup.activities.SetupActivityCallback; public class CircumventionSetupFragment extends BaseSetupFragment implements CancelCallback { diff --git a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java index ec646cac..8477c302 100644 --- a/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java +++ b/app/src/main/java/se/leap/bitmaskclient/providersetup/fragments/ConfigureProviderFragment.java @@ -9,7 +9,7 @@ import static se.leap.bitmaskclient.R.string.description_configure_provider_circ import static se.leap.bitmaskclient.base.models.Constants.BROADCAST_RESULT_CODE; 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.utils.ConfigHelper.isDefaultBitmask; +import static se.leap.bitmaskclient.base.utils.BuildConfigHelper.isDefaultBitmask; import static se.leap.bitmaskclient.base.utils.PreferenceHelper.getUseSnowflake; import static se.leap.bitmaskclient.base.utils.ViewHelper.animateContainerVisibility; import static se.leap.bitmaskclient.providersetup.ProviderAPI.CORRECTLY_DOWNLOADED_VPN_CERTIFICATE; @@ -42,9 +42,9 @@ import androidx.core.content.res.ResourcesCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.List; -import java.util.Observable; -import java.util.Observer; import se.leap.bitmaskclient.R; import se.leap.bitmaskclient.base.models.Provider; @@ -57,7 +57,7 @@ import se.leap.bitmaskclient.providersetup.TorLogAdapter; import se.leap.bitmaskclient.providersetup.activities.CancelCallback; import se.leap.bitmaskclient.tor.TorStatusObservable; -public class ConfigureProviderFragment extends BaseSetupFragment implements Observer, CancelCallback, EipSetupListener { +public class ConfigureProviderFragment extends BaseSetupFragment implements PropertyChangeListener, CancelCallback, EipSetupListener { private static final String TAG = ConfigureProviderFragment.class.getSimpleName(); @@ -158,8 +158,8 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse } @Override - public void update(Observable o, Object arg) { - if (o instanceof ProviderSetupObservable) { + public void propertyChange(PropertyChangeEvent evt) { + if (ProviderSetupObservable.PROPERTY_CHANGE.equals(evt.getPropertyName())) { Activity activity = getActivity(); if (activity == null || binding == null) { return; @@ -206,7 +206,7 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse if (ignoreProviderAPIUpdates || provider == null || (setupActivityCallback.getSelectedProvider() != null && - !setupActivityCallback.getSelectedProvider().getDomain().equals(provider.getDomain()))) { + !setupActivityCallback.getSelectedProvider().getMainUrlString().equals(provider.getMainUrlString()))) { return; } @@ -222,7 +222,9 @@ public class ConfigureProviderFragment extends BaseSetupFragment implements Obse setupActivityCallback.onProviderSelected(provider); handler.postDelayed(() -> { if (!ProviderSetupObservable.isCanceled()) { - setupActivityCallback.onConfigurationSuccess(); + if (setupActivityCallback != null) { + setupActivityCallback.onConfigurationSuccess(); + } } }, 750); break; |