Merge request changes
- Moved certificate-related utilities to separate class - Added settings method to return an entire SSLSettings object; refactored methods using separate parameters to take single SSLSettings parameter - Advanced Settings section on login page now hides / shows along with other buttons to prevent it from showing up in front of the loading spinner - Fixed star imports - Refactored applySslSettings as per code from merge request - Fixed formatting
This commit is contained in:
@@ -2,6 +2,7 @@ package com.github.gotify;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import com.github.gotify.api.CertUtils;
|
||||||
import com.github.gotify.client.model.User;
|
import com.github.gotify.client.model.User;
|
||||||
|
|
||||||
public class Settings {
|
public class Settings {
|
||||||
@@ -60,12 +61,23 @@ public class Settings {
|
|||||||
sharedPreferences.edit().putString("version", version).apply();
|
sharedPreferences.edit().putString("version", version).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default to always validating SSL.
|
private boolean validateSSL() {
|
||||||
public boolean validateSSL() { return sharedPreferences.getBoolean("validateSSL", true); }
|
return sharedPreferences.getBoolean("validateSSL", true);
|
||||||
|
}
|
||||||
|
|
||||||
public void validateSSL(boolean validateSSL) { sharedPreferences.edit().putBoolean("validateSSL", validateSSL).apply(); }
|
public void validateSSL(boolean validateSSL) {
|
||||||
|
sharedPreferences.edit().putBoolean("validateSSL", validateSSL).apply();
|
||||||
|
}
|
||||||
|
|
||||||
public String cert() { return sharedPreferences.getString("cert", null); }
|
private String cert() {
|
||||||
|
return sharedPreferences.getString("cert", null);
|
||||||
|
}
|
||||||
|
|
||||||
public void cert(String cert) { sharedPreferences.edit().putString("cert", cert).apply(); }
|
public void cert(String cert) {
|
||||||
|
sharedPreferences.edit().putString("cert", cert).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public CertUtils.SSLSettings sslSettings() {
|
||||||
|
return new CertUtils.SSLSettings(validateSSL(), cert());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.github.gotify;
|
package com.github.gotify;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
@@ -15,22 +14,12 @@ import com.github.gotify.log.Log;
|
|||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
import com.squareup.picasso.Picasso;
|
import com.squareup.picasso.Picasso;
|
||||||
import com.squareup.picasso.Target;
|
import com.squareup.picasso.Target;
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okio.Buffer;
|
|
||||||
import org.threeten.bp.OffsetDateTime;
|
|
||||||
|
|
||||||
import javax.net.ssl.*;
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.security.GeneralSecurityException;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Collection;
|
import java.io.InputStreamReader;
|
||||||
|
import okio.Buffer;
|
||||||
|
import org.threeten.bp.OffsetDateTime;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
public static void showSnackBar(Activity activity, String message) {
|
public static void showSnackBar(Activity activity, String message) {
|
||||||
@@ -66,17 +55,6 @@ public class Utils {
|
|||||||
return new ApiClient().getJSON();
|
return new ApiClient().getJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface DrawableReceiver {
|
|
||||||
void loaded(Drawable drawable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InputStream stringToInputStream(String str) {
|
|
||||||
if (str == null) return null;
|
|
||||||
return new Buffer()
|
|
||||||
.writeUtf8(str)
|
|
||||||
.inputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String readFileFromStream(@NonNull InputStream inputStream) {
|
public static String readFileFromStream(@NonNull InputStream inputStream) {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
String currentLine;
|
String currentLine;
|
||||||
@@ -92,87 +70,12 @@ public class Utils {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/////////////////////////
|
public interface DrawableReceiver {
|
||||||
///// SSL Utilities /////
|
void loaded(Drawable drawable);
|
||||||
/////////////////////////
|
|
||||||
public static Certificate parseCertificate(String cert) {
|
|
||||||
try {
|
|
||||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
|
|
||||||
|
|
||||||
return certificateFactory.generateCertificate(Utils.stringToInputStream(cert));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalArgumentException("certificate is invalid");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SSLSettings {
|
public static InputStream stringToInputStream(String str) {
|
||||||
boolean validateSSL;
|
if (str == null) return null;
|
||||||
String cert;
|
return new Buffer().writeUtf8(str).inputStream();
|
||||||
|
|
||||||
public SSLSettings(boolean validateSSL, String cert) {
|
|
||||||
this.validateSSL = validateSSL;
|
|
||||||
this.cert = cert;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TrustManager that trusts all SSL Certs
|
|
||||||
private static final TrustManager trustAll = new X509TrustManager() {
|
|
||||||
@SuppressLint("TrustAllX509TrustManager")
|
|
||||||
@Override
|
|
||||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
|
|
||||||
@SuppressLint("TrustAllX509TrustManager")
|
|
||||||
@Override
|
|
||||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
|
|
||||||
@Override
|
|
||||||
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[]{}; }
|
|
||||||
};
|
|
||||||
|
|
||||||
public static void applySslSettings(OkHttpClient.Builder builder, SSLSettings settings) {
|
|
||||||
// Modified from ApiClient.applySslSettings in the client package.
|
|
||||||
|
|
||||||
try {
|
|
||||||
TrustManager[] trustManagers = null;
|
|
||||||
HostnameVerifier hostnameVerifier = null;
|
|
||||||
if (!settings.validateSSL) {
|
|
||||||
trustManagers = new TrustManager[]{ trustAll };
|
|
||||||
hostnameVerifier = (hostname, session) -> true;
|
|
||||||
} else if (settings.cert != null) {
|
|
||||||
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
|
||||||
Collection<? extends Certificate> certificates = certificateFactory.generateCertificates(stringToInputStream(settings.cert));
|
|
||||||
if (certificates.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("expected non-empty set of trusted certificates");
|
|
||||||
}
|
|
||||||
KeyStore caKeyStore = newEmptyKeyStore();
|
|
||||||
int index = 0;
|
|
||||||
for (Certificate certificate : certificates) {
|
|
||||||
String certificateAlias = "ca" + Integer.toString(index++);
|
|
||||||
caKeyStore.setCertificateEntry(certificateAlias, certificate);
|
|
||||||
}
|
|
||||||
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
trustManagerFactory.init(caKeyStore);
|
|
||||||
trustManagers = trustManagerFactory.getTrustManagers();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trustManagers != null && trustManagers.length > 0) {
|
|
||||||
SSLContext context = SSLContext.getInstance("TLS");
|
|
||||||
context.init(new KeyManager[]{}, trustManagers, new SecureRandom());
|
|
||||||
builder.sslSocketFactory(context.getSocketFactory(), (X509TrustManager) trustManagers[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hostnameVerifier != null) builder.hostnameVerifier(hostnameVerifier);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// We shouldn't have issues since the cert is verified on login.
|
|
||||||
Log.e("Failed to apply SSL settings", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static KeyStore newEmptyKeyStore() throws GeneralSecurityException {
|
|
||||||
try {
|
|
||||||
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
|
||||||
keyStore.load(null, null);
|
|
||||||
return keyStore;
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new AssertionError(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
116
app/src/main/java/com/github/gotify/api/CertUtils.java
Normal file
116
app/src/main/java/com/github/gotify/api/CertUtils.java
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
package com.github.gotify.api;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import com.github.gotify.Utils;
|
||||||
|
import com.github.gotify.log.Log;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyStore;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Collection;
|
||||||
|
import javax.net.ssl.KeyManager;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.TrustManager;
|
||||||
|
import javax.net.ssl.TrustManagerFactory;
|
||||||
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
|
public class CertUtils {
|
||||||
|
public static class SSLSettings {
|
||||||
|
boolean validateSSL;
|
||||||
|
String cert;
|
||||||
|
|
||||||
|
public SSLSettings(boolean validateSSL, String cert) {
|
||||||
|
this.validateSSL = validateSSL;
|
||||||
|
this.cert = cert;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final X509TrustManager trustAll =
|
||||||
|
new X509TrustManager() {
|
||||||
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
|
@Override
|
||||||
|
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
|
||||||
|
|
||||||
|
@SuppressLint("TrustAllX509TrustManager")
|
||||||
|
@Override
|
||||||
|
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public X509Certificate[] getAcceptedIssuers() {
|
||||||
|
return new X509Certificate[] {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public static Certificate parseCertificate(String cert) {
|
||||||
|
try {
|
||||||
|
CertificateFactory certificateFactory = CertificateFactory.getInstance("X509");
|
||||||
|
|
||||||
|
return certificateFactory.generateCertificate(Utils.stringToInputStream(cert));
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new IllegalArgumentException("certificate is invalid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void applySslSettings(OkHttpClient.Builder builder, SSLSettings settings) {
|
||||||
|
// Modified from ApiClient.applySslSettings in the client package.
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!settings.validateSSL) {
|
||||||
|
SSLContext context = SSLContext.getInstance("TLS");
|
||||||
|
context.init(
|
||||||
|
new KeyManager[] {}, new TrustManager[] {trustAll}, new SecureRandom());
|
||||||
|
builder.sslSocketFactory(context.getSocketFactory(), trustAll);
|
||||||
|
builder.hostnameVerifier((a, b) -> true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (settings.cert != null) {
|
||||||
|
TrustManager[] trustManagers = certToTrustManager(settings.cert);
|
||||||
|
|
||||||
|
if (trustManagers != null && trustManagers.length > 0) {
|
||||||
|
SSLContext context = SSLContext.getInstance("TLS");
|
||||||
|
context.init(new KeyManager[] {}, trustManagers, new SecureRandom());
|
||||||
|
builder.sslSocketFactory(
|
||||||
|
context.getSocketFactory(), (X509TrustManager) trustManagers[0]);
|
||||||
|
builder.hostnameVerifier((a, b) -> true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// We shouldn't have issues since the cert is verified on login.
|
||||||
|
Log.e("Failed to apply SSL settings", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TrustManager[] certToTrustManager(String cert) throws GeneralSecurityException {
|
||||||
|
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
|
||||||
|
Collection<? extends Certificate> certificates =
|
||||||
|
certificateFactory.generateCertificates(Utils.stringToInputStream(cert));
|
||||||
|
if (certificates.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("expected non-empty set of trusted certificates");
|
||||||
|
}
|
||||||
|
KeyStore caKeyStore = newEmptyKeyStore();
|
||||||
|
int index = 0;
|
||||||
|
for (Certificate certificate : certificates) {
|
||||||
|
String certificateAlias = "ca" + Integer.toString(index++);
|
||||||
|
caKeyStore.setCertificateEntry(certificateAlias, certificate);
|
||||||
|
}
|
||||||
|
TrustManagerFactory trustManagerFactory =
|
||||||
|
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||||
|
trustManagerFactory.init(caKeyStore);
|
||||||
|
return trustManagerFactory.getTrustManagers();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static KeyStore newEmptyKeyStore() throws GeneralSecurityException {
|
||||||
|
try {
|
||||||
|
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||||
|
keyStore.load(null, null);
|
||||||
|
return keyStore;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,16 +9,17 @@ import com.github.gotify.client.auth.ApiKeyAuth;
|
|||||||
import com.github.gotify.client.auth.HttpBasicAuth;
|
import com.github.gotify.client.auth.HttpBasicAuth;
|
||||||
|
|
||||||
public class ClientFactory {
|
public class ClientFactory {
|
||||||
public static ApiClient unauthorized(String baseUrl, boolean validateSSL, String cert) {
|
public static ApiClient unauthorized(String baseUrl, CertUtils.SSLSettings sslSettings) {
|
||||||
ApiClient client = new ApiClient();
|
ApiClient client = new ApiClient();
|
||||||
client.setVerifyingSsl(validateSSL);
|
client.setVerifyingSsl(sslSettings.validateSSL);
|
||||||
client.setSslCaCert(Utils.stringToInputStream(cert));
|
client.setSslCaCert(Utils.stringToInputStream(sslSettings.cert));
|
||||||
client.setBasePath(baseUrl);
|
client.setBasePath(baseUrl);
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApiClient basicAuth(String baseUrl, boolean validateSSL, String cert, String username, String password) {
|
public static ApiClient basicAuth(
|
||||||
ApiClient client = unauthorized(baseUrl, validateSSL, cert);
|
String baseUrl, CertUtils.SSLSettings sslSettings, String username, String password) {
|
||||||
|
ApiClient client = unauthorized(baseUrl, sslSettings);
|
||||||
HttpBasicAuth auth = (HttpBasicAuth) client.getAuthentication("basicAuth");
|
HttpBasicAuth auth = (HttpBasicAuth) client.getAuthentication("basicAuth");
|
||||||
auth.setUsername(username);
|
auth.setUsername(username);
|
||||||
auth.setPassword(password);
|
auth.setPassword(password);
|
||||||
@@ -26,18 +27,19 @@ public class ClientFactory {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApiClient clientToken(String baseUrl, boolean validateSSL, String cert, String token) {
|
public static ApiClient clientToken(
|
||||||
ApiClient client = unauthorized(baseUrl, validateSSL, cert);
|
String baseUrl, CertUtils.SSLSettings sslSettings, String token) {
|
||||||
|
ApiClient client = unauthorized(baseUrl, sslSettings);
|
||||||
ApiKeyAuth tokenAuth = (ApiKeyAuth) client.getAuthentication("clientTokenHeader");
|
ApiKeyAuth tokenAuth = (ApiKeyAuth) client.getAuthentication("clientTokenHeader");
|
||||||
tokenAuth.setApiKey(token);
|
tokenAuth.setApiKey(token);
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static VersionApi versionApi(String baseUrl, boolean validateSSL, String cert) {
|
public static VersionApi versionApi(String baseUrl, CertUtils.SSLSettings sslSettings) {
|
||||||
return new VersionApi(unauthorized(baseUrl, validateSSL, cert));
|
return new VersionApi(unauthorized(baseUrl, sslSettings));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UserApi userApiWithToken(Settings settings) {
|
public static UserApi userApiWithToken(Settings settings) {
|
||||||
return new UserApi(clientToken(settings.url(), settings.validateSSL(), settings.cert(), settings.token()));
|
return new UserApi(clientToken(settings.url(), settings.sslSettings(), settings.token()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ public class InitializationActivity extends AppCompatActivity {
|
|||||||
private void requestVersion(
|
private void requestVersion(
|
||||||
final Callback.SuccessCallback<VersionInfo> callback,
|
final Callback.SuccessCallback<VersionInfo> callback,
|
||||||
final Callback.ErrorCallback errorCallback) {
|
final Callback.ErrorCallback errorCallback) {
|
||||||
VersionApi versionApi = ClientFactory.versionApi(settings.url(), settings.validateSSL(), settings.cert());
|
VersionApi versionApi = ClientFactory.versionApi(settings.url(), settings.sslSettings());
|
||||||
Api.withLogging(versionApi::getVersionAsync)
|
Api.withLogging(versionApi::getVersionAsync)
|
||||||
.handleInUIThread(this, callback, errorCallback);
|
.handleInUIThread(this, callback, errorCallback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,17 @@ import android.widget.*;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import butterknife.*;
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.OnCheckedChanged;
|
||||||
|
import butterknife.OnClick;
|
||||||
|
import butterknife.OnTextChanged;
|
||||||
import com.github.gotify.R;
|
import com.github.gotify.R;
|
||||||
import com.github.gotify.Settings;
|
import com.github.gotify.Settings;
|
||||||
import com.github.gotify.Utils;
|
import com.github.gotify.Utils;
|
||||||
import com.github.gotify.api.Api;
|
import com.github.gotify.api.Api;
|
||||||
import com.github.gotify.api.Callback;
|
import com.github.gotify.api.Callback;
|
||||||
|
import com.github.gotify.api.CertUtils;
|
||||||
import com.github.gotify.api.ClientFactory;
|
import com.github.gotify.api.ClientFactory;
|
||||||
import com.github.gotify.client.ApiClient;
|
import com.github.gotify.client.ApiClient;
|
||||||
import com.github.gotify.client.ApiException;
|
import com.github.gotify.client.ApiException;
|
||||||
@@ -28,7 +33,6 @@ import com.github.gotify.init.InitializationActivity;
|
|||||||
import com.github.gotify.log.Log;
|
import com.github.gotify.log.Log;
|
||||||
import com.github.gotify.log.UncaughtExceptionHandler;
|
import com.github.gotify.log.UncaughtExceptionHandler;
|
||||||
import com.squareup.okhttp.HttpUrl;
|
import com.squareup.okhttp.HttpUrl;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
@@ -47,6 +51,9 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
@BindView(R.id.password)
|
@BindView(R.id.password)
|
||||||
EditText passwordField;
|
EditText passwordField;
|
||||||
|
|
||||||
|
@BindView(R.id.sslGroup)
|
||||||
|
LinearLayout sslGroup;
|
||||||
|
|
||||||
@BindView(R.id.showAdvanced)
|
@BindView(R.id.showAdvanced)
|
||||||
Button toggleAdvanced;
|
Button toggleAdvanced;
|
||||||
|
|
||||||
@@ -108,15 +115,21 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
checkUrlProgress.setVisibility(View.VISIBLE);
|
checkUrlProgress.setVisibility(View.VISIBLE);
|
||||||
checkUrlButton.setVisibility(View.GONE);
|
checkUrlButton.setVisibility(View.GONE);
|
||||||
|
sslGroup.setVisibility(View.GONE);
|
||||||
|
|
||||||
final String fixedUrl = url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
|
final String fixedUrl = url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
|
||||||
|
|
||||||
Api.withLogging(ClientFactory.versionApi(fixedUrl, !disableSSLValidation, caCertContents)::getVersionAsync)
|
Api.withLogging(
|
||||||
|
ClientFactory.versionApi(
|
||||||
|
fixedUrl,
|
||||||
|
new CertUtils.SSLSettings(
|
||||||
|
!disableSSLValidation, caCertContents))
|
||||||
|
::getVersionAsync)
|
||||||
.handleInUIThread(this, onValidUrl(fixedUrl), onInvalidUrl(fixedUrl));
|
.handleInUIThread(this, onValidUrl(fixedUrl), onInvalidUrl(fixedUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.showAdvanced)
|
@OnClick(R.id.showAdvanced)
|
||||||
void doShowAdvanced() {
|
void toggleShowAdvanced() {
|
||||||
showAdvanced = !showAdvanced;
|
showAdvanced = !showAdvanced;
|
||||||
disableSSLValidationCheckBox.setVisibility(showAdvanced ? View.VISIBLE : View.GONE);
|
disableSSLValidationCheckBox.setVisibility(showAdvanced ? View.VISIBLE : View.GONE);
|
||||||
selectCACertificate.setVisibility(showAdvanced ? View.VISIBLE : View.GONE);
|
selectCACertificate.setVisibility(showAdvanced ? View.VISIBLE : View.GONE);
|
||||||
@@ -138,7 +151,9 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
startActivityForResult(Intent.createChooser(intent, getString(R.string.select_ca_file)), FILE_SELECT_CODE);
|
startActivityForResult(
|
||||||
|
Intent.createChooser(intent, getString(R.string.select_ca_file)),
|
||||||
|
FILE_SELECT_CODE);
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (ActivityNotFoundException e) {
|
||||||
// case for user not having a file browser installed
|
// case for user not having a file browser installed
|
||||||
Utils.showSnackBar(LoginActivity.this, getString(R.string.please_install_file_browser));
|
Utils.showSnackBar(LoginActivity.this, getString(R.string.please_install_file_browser));
|
||||||
@@ -167,14 +182,15 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String contents = Utils.readFileFromStream(fileStream);
|
String contents = Utils.readFileFromStream(fileStream);
|
||||||
Certificate ca = Utils.parseCertificate(contents);
|
Certificate ca = CertUtils.parseCertificate(contents);
|
||||||
|
|
||||||
caFileName.setText(((X509Certificate) ca).getSubjectDN().getName());
|
caFileName.setText(((X509Certificate) ca).getSubjectDN().getName());
|
||||||
// temporarily set the contents (don't store to settings until they decide to login)
|
// temporarily set the contents (don't store to settings until they decide to login)
|
||||||
caCertContents = contents;
|
caCertContents = contents;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Utils.showSnackBar(LoginActivity.this, getString(R.string.select_ca_failed, e.getMessage()));
|
Utils.showSnackBar(
|
||||||
|
LoginActivity.this, getString(R.string.select_ca_failed, e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,6 +204,7 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
usernameField.requestFocus();
|
usernameField.requestFocus();
|
||||||
passwordField.setVisibility(View.VISIBLE);
|
passwordField.setVisibility(View.VISIBLE);
|
||||||
loginButton.setVisibility(View.VISIBLE);
|
loginButton.setVisibility(View.VISIBLE);
|
||||||
|
sslGroup.setVisibility(View.VISIBLE);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,6 +212,7 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
return (exception) -> {
|
return (exception) -> {
|
||||||
checkUrlProgress.setVisibility(View.GONE);
|
checkUrlProgress.setVisibility(View.GONE);
|
||||||
checkUrlButton.setVisibility(View.VISIBLE);
|
checkUrlButton.setVisibility(View.VISIBLE);
|
||||||
|
sslGroup.setVisibility(View.VISIBLE);
|
||||||
Utils.showSnackBar(LoginActivity.this, versionError(url, exception));
|
Utils.showSnackBar(LoginActivity.this, versionError(url, exception));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -206,8 +224,14 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
loginButton.setVisibility(View.GONE);
|
loginButton.setVisibility(View.GONE);
|
||||||
loginProgress.setVisibility(View.VISIBLE);
|
loginProgress.setVisibility(View.VISIBLE);
|
||||||
|
sslGroup.setVisibility(View.GONE);
|
||||||
|
|
||||||
ApiClient client = ClientFactory.basicAuth(settings.url(), !disableSSLValidation, caCertContents, username, password);
|
ApiClient client =
|
||||||
|
ClientFactory.basicAuth(
|
||||||
|
settings.url(),
|
||||||
|
new CertUtils.SSLSettings(!disableSSLValidation, caCertContents),
|
||||||
|
username,
|
||||||
|
password);
|
||||||
Api.withLogging(new UserApi(client)::currentUserAsync)
|
Api.withLogging(new UserApi(client)::currentUserAsync)
|
||||||
.handleInUIThread(this, (user) -> newClientDialog(client), this::onInvalidLogin);
|
.handleInUIThread(this, (user) -> newClientDialog(client), this::onInvalidLogin);
|
||||||
}
|
}
|
||||||
@@ -215,6 +239,7 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
private void onInvalidLogin(ApiException e) {
|
private void onInvalidLogin(ApiException e) {
|
||||||
loginButton.setVisibility(View.VISIBLE);
|
loginButton.setVisibility(View.VISIBLE);
|
||||||
loginProgress.setVisibility(View.GONE);
|
loginProgress.setVisibility(View.GONE);
|
||||||
|
sslGroup.setVisibility(View.VISIBLE);
|
||||||
Utils.showSnackBar(this, getString(R.string.wronguserpw));
|
Utils.showSnackBar(this, getString(R.string.wronguserpw));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,7 +266,6 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private void onCreatedClient(Client client) {
|
private void onCreatedClient(Client client) {
|
||||||
settings.token(client.getToken());
|
settings.token(client.getToken());
|
||||||
// Set the final ssl validation status and / or cert
|
|
||||||
settings.validateSSL(!disableSSLValidation);
|
settings.validateSSL(!disableSSLValidation);
|
||||||
settings.cert(caCertContents);
|
settings.cert(caCertContents);
|
||||||
|
|
||||||
@@ -254,11 +278,13 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
Utils.showSnackBar(this, getString(R.string.create_client_failed));
|
Utils.showSnackBar(this, getString(R.string.create_client_failed));
|
||||||
loginProgress.setVisibility(View.GONE);
|
loginProgress.setVisibility(View.GONE);
|
||||||
loginButton.setVisibility(View.VISIBLE);
|
loginButton.setVisibility(View.VISIBLE);
|
||||||
|
sslGroup.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onCancelClientDialog(DialogInterface dialog, int which) {
|
private void onCancelClientDialog(DialogInterface dialog, int which) {
|
||||||
loginProgress.setVisibility(View.GONE);
|
loginProgress.setVisibility(View.GONE);
|
||||||
loginButton.setVisibility(View.VISIBLE);
|
loginButton.setVisibility(View.VISIBLE);
|
||||||
|
sslGroup.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String versionError(String url, ApiException exception) {
|
private String versionError(String url, ApiException exception) {
|
||||||
|
|||||||
@@ -107,7 +107,8 @@ public class MessagesActivity extends AppCompatActivity
|
|||||||
Log.i("Entering " + getClass().getSimpleName());
|
Log.i("Entering " + getClass().getSimpleName());
|
||||||
settings = new Settings(this);
|
settings = new Settings(this);
|
||||||
|
|
||||||
client = ClientFactory.clientToken(settings.url(), settings.validateSSL(), settings.cert(), settings.token());
|
client =
|
||||||
|
ClientFactory.clientToken(settings.url(), settings.sslSettings(), settings.token());
|
||||||
appsHolder = new ApplicationHolder(this, client);
|
appsHolder = new ApplicationHolder(this, client);
|
||||||
appsHolder.onUpdate(() -> onUpdateApps(appsHolder.get()));
|
appsHolder.onUpdate(() -> onUpdateApps(appsHolder.get()));
|
||||||
appsHolder.request();
|
appsHolder.request();
|
||||||
@@ -401,7 +402,9 @@ public class MessagesActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... ignore) {
|
protected Void doInBackground(Void... ignore) {
|
||||||
TokenApi api =
|
TokenApi api =
|
||||||
new TokenApi(ClientFactory.clientToken(settings.url(), settings.validateSSL(), settings.cert(), settings.token()));
|
new TokenApi(
|
||||||
|
ClientFactory.clientToken(
|
||||||
|
settings.url(), settings.sslSettings(), settings.token()));
|
||||||
stopService(new Intent(MessagesActivity.this, WebSocketService.class));
|
stopService(new Intent(MessagesActivity.this, WebSocketService.class));
|
||||||
try {
|
try {
|
||||||
List<Client> clients = api.getClients();
|
List<Client> clients = api.getClients();
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.github.gotify.service;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import com.github.gotify.Utils;
|
import com.github.gotify.Utils;
|
||||||
import com.github.gotify.api.Callback;
|
import com.github.gotify.api.Callback;
|
||||||
|
import com.github.gotify.api.CertUtils;
|
||||||
import com.github.gotify.client.JSON;
|
import com.github.gotify.client.JSON;
|
||||||
import com.github.gotify.client.model.Message;
|
import com.github.gotify.client.model.Message;
|
||||||
import com.github.gotify.log.Log;
|
import com.github.gotify.log.Log;
|
||||||
@@ -32,12 +33,17 @@ public class WebSocketConnection {
|
|||||||
private Runnable onReconnected;
|
private Runnable onReconnected;
|
||||||
private boolean isClosed;
|
private boolean isClosed;
|
||||||
|
|
||||||
WebSocketConnection(String baseUrl, boolean validateSSL, String cert, String token) {
|
WebSocketConnection(String baseUrl, CertUtils.SSLSettings settings, String token) {
|
||||||
OkHttpClient.Builder builder = new OkHttpClient.Builder()
|
// client = new ApiClient()
|
||||||
|
// .setVerifyingSsl(validateSSL)
|
||||||
|
// .setSslCaCert(Utils.stringToInputStream(cert))
|
||||||
|
// .getHttpClient();
|
||||||
|
OkHttpClient.Builder builder =
|
||||||
|
new OkHttpClient.Builder()
|
||||||
.readTimeout(0, TimeUnit.MILLISECONDS)
|
.readTimeout(0, TimeUnit.MILLISECONDS)
|
||||||
.pingInterval(1, TimeUnit.MINUTES)
|
.pingInterval(1, TimeUnit.MINUTES)
|
||||||
.connectTimeout(10, TimeUnit.SECONDS);
|
.connectTimeout(10, TimeUnit.SECONDS);
|
||||||
Utils.applySslSettings(builder, new Utils.SSLSettings(validateSSL, cert));
|
CertUtils.applySslSettings(builder, settings);
|
||||||
|
|
||||||
client = builder.build();
|
client = builder.build();
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ public class WebSocketService extends Service {
|
|||||||
super.onCreate();
|
super.onCreate();
|
||||||
settings = new Settings(this);
|
settings = new Settings(this);
|
||||||
missingMessageUtil =
|
missingMessageUtil =
|
||||||
new MissedMessageUtil(ClientFactory.clientToken(settings.url(), settings.validateSSL(), settings.cert(), settings.token()));
|
new MissedMessageUtil(
|
||||||
|
ClientFactory.clientToken(
|
||||||
|
settings.url(), settings.sslSettings(), settings.token()));
|
||||||
Log.i("Create " + getClass().getSimpleName());
|
Log.i("Create " + getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,7 +81,7 @@ public class WebSocketService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connection =
|
connection =
|
||||||
new WebSocketConnection(settings.url(), settings.validateSSL(), settings.cert(), settings.token())
|
new WebSocketConnection(settings.url(), settings.sslSettings(), settings.token())
|
||||||
.onOpen(this::onOpen)
|
.onOpen(this::onOpen)
|
||||||
.onClose(() -> foreground(getString(R.string.websocket_closed)))
|
.onClose(() -> foreground(getString(R.string.websocket_closed)))
|
||||||
.onBadRequest(this::onBadRequest)
|
.onBadRequest(this::onBadRequest)
|
||||||
|
|||||||
@@ -39,8 +39,9 @@
|
|||||||
android:minWidth="40dp"
|
android:minWidth="40dp"
|
||||||
android:minHeight="40dp"
|
android:minHeight="40dp"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="@+id/checkurl"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="@+id/login"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.501"
|
||||||
app:layout_constraintTop_toTopOf="@+id/login" />
|
app:layout_constraintTop_toTopOf="@+id/login" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
@@ -109,83 +110,77 @@
|
|||||||
app:layout_constraintWidth_max="280dp"
|
app:layout_constraintWidth_max="280dp"
|
||||||
tools:text="Password" />
|
tools:text="Password" />
|
||||||
|
|
||||||
<Button
|
<LinearLayout
|
||||||
android:id="@+id/showAdvanced"
|
android:id="@+id/sslGroup"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintWidth_max="280dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/login">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/showAdvanced"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
android:background="@color/colorPrimaryDark"
|
android:background="@color/colorPrimaryDark"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:text="@string/show_advanced"
|
android:text="@string/show_advanced"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/login"
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
app:layout_constraintWidth_max="280dp" />
|
|
||||||
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/disableValidateSSL"
|
android:id="@+id/disableValidateSSL"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:text="@string/disabled_validate_ssl"
|
android:text="@string/disabled_validate_ssl"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/showAdvanced"
|
app:layout_constraintTop_toBottomOf="@+id/showAdvanced" />
|
||||||
app:layout_constraintWidth_max="280dp" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/or"
|
android:id="@+id/or"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:text="@string/or"
|
android:text="@string/or"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/disableValidateSSL"
|
app:layout_constraintTop_toBottomOf="@+id/disableValidateSSL" />
|
||||||
app:layout_constraintWidth_max="280dp" />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/selectCACertificate"
|
android:id="@+id/selectCACertificate"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:text="@string/select_ca_certificate"
|
android:text="@string/select_ca_certificate"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/or"
|
app:layout_constraintTop_toBottomOf="@+id/or" />
|
||||||
app:layout_constraintWidth_max="280dp" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/caFile"
|
android:id="@+id/caFile"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:text="@string/no_ca_selected"
|
android:text="@string/no_ca_selected"
|
||||||
android:textAlignment="center"
|
android:textAlignment="center"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/selectCACertificate"
|
app:layout_constraintTop_toBottomOf="@+id/selectCACertificate" />
|
||||||
app:layout_constraintWidth_max="280dp" />
|
</LinearLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/login"
|
android:id="@+id/login"
|
||||||
|
|||||||
Reference in New Issue
Block a user