Merge pull request #15 from gjabell/feature/self-signed-ssl
Added SSL Validation Override and CA Selection
This commit is contained in:
11
app/src/main/java/com/github/gotify/SSLSettings.java
Normal file
11
app/src/main/java/com/github/gotify/SSLSettings.java
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package com.github.gotify;
|
||||||
|
|
||||||
|
public class SSLSettings {
|
||||||
|
public boolean validateSSL;
|
||||||
|
public String cert;
|
||||||
|
|
||||||
|
public SSLSettings(boolean validateSSL, String cert) {
|
||||||
|
this.validateSSL = validateSSL;
|
||||||
|
this.cert = cert;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,8 @@ public class Settings {
|
|||||||
public void clear() {
|
public void clear() {
|
||||||
url(null);
|
url(null);
|
||||||
token(null);
|
token(null);
|
||||||
|
validateSSL(true);
|
||||||
|
cert(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void user(String name, boolean admin) {
|
public void user(String name, boolean admin) {
|
||||||
@@ -57,4 +59,24 @@ public class Settings {
|
|||||||
public void serverVersion(String version) {
|
public void serverVersion(String version) {
|
||||||
sharedPreferences.edit().putString("version", version).apply();
|
sharedPreferences.edit().putString("version", version).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean validateSSL() {
|
||||||
|
return sharedPreferences.getBoolean("validateSSL", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validateSSL(boolean validateSSL) {
|
||||||
|
sharedPreferences.edit().putBoolean("validateSSL", validateSSL).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String cert() {
|
||||||
|
return sharedPreferences.getString("cert", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cert(String cert) {
|
||||||
|
sharedPreferences.edit().putString("cert", cert).apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SSLSettings sslSettings() {
|
||||||
|
return new SSLSettings(validateSSL(), cert());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,12 +7,18 @@ import android.graphics.drawable.BitmapDrawable;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.text.format.DateUtils;
|
import android.text.format.DateUtils;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import com.github.gotify.client.ApiClient;
|
import com.github.gotify.client.ApiClient;
|
||||||
import com.github.gotify.client.JSON;
|
import com.github.gotify.client.JSON;
|
||||||
import com.github.gotify.log.Log;
|
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 java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import okio.Buffer;
|
||||||
import org.threeten.bp.OffsetDateTime;
|
import org.threeten.bp.OffsetDateTime;
|
||||||
|
|
||||||
public class Utils {
|
public class Utils {
|
||||||
@@ -49,7 +55,27 @@ public class Utils {
|
|||||||
return new ApiClient().getJSON();
|
return new ApiClient().getJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String readFileFromStream(@NonNull InputStream inputStream) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
String currentLine;
|
||||||
|
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
|
||||||
|
while ((currentLine = reader.readLine()) != null) {
|
||||||
|
sb.append(currentLine).append("\n");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new IllegalArgumentException("failed to read input");
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
public interface DrawableReceiver {
|
public interface DrawableReceiver {
|
||||||
void loaded(Drawable drawable);
|
void loaded(Drawable drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static InputStream stringToInputStream(String str) {
|
||||||
|
if (str == null) return null;
|
||||||
|
return new Buffer().writeUtf8(str).inputStream();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
106
app/src/main/java/com/github/gotify/api/CertUtils.java
Normal file
106
app/src/main/java/com/github/gotify/api/CertUtils.java
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package com.github.gotify.api;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import com.github.gotify.SSLSettings;
|
||||||
|
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 {
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.github.gotify.api;
|
package com.github.gotify.api;
|
||||||
|
|
||||||
|
import com.github.gotify.SSLSettings;
|
||||||
import com.github.gotify.Settings;
|
import com.github.gotify.Settings;
|
||||||
|
import com.github.gotify.Utils;
|
||||||
import com.github.gotify.client.ApiClient;
|
import com.github.gotify.client.ApiClient;
|
||||||
import com.github.gotify.client.api.UserApi;
|
import com.github.gotify.client.api.UserApi;
|
||||||
import com.github.gotify.client.api.VersionApi;
|
import com.github.gotify.client.api.VersionApi;
|
||||||
@@ -8,14 +10,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) {
|
public static ApiClient unauthorized(String baseUrl, SSLSettings sslSettings) {
|
||||||
ApiClient client = new ApiClient();
|
ApiClient client = new ApiClient();
|
||||||
|
client.setVerifyingSsl(sslSettings.validateSSL);
|
||||||
|
client.setSslCaCert(Utils.stringToInputStream(sslSettings.cert));
|
||||||
client.setBasePath(baseUrl);
|
client.setBasePath(baseUrl);
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApiClient basicAuth(String baseUrl, String username, String password) {
|
public static ApiClient basicAuth(
|
||||||
ApiClient client = unauthorized(baseUrl);
|
String baseUrl, 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);
|
||||||
@@ -23,18 +28,18 @@ public class ClientFactory {
|
|||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ApiClient clientToken(String baseUrl, String token) {
|
public static ApiClient clientToken(String baseUrl, SSLSettings sslSettings, String token) {
|
||||||
ApiClient client = unauthorized(baseUrl);
|
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) {
|
public static VersionApi versionApi(String baseUrl, SSLSettings sslSettings) {
|
||||||
return new VersionApi(unauthorized(baseUrl));
|
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.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());
|
VersionApi versionApi = ClientFactory.versionApi(settings.url(), settings.sslSettings());
|
||||||
Api.withLogging(versionApi::getVersionAsync)
|
Api.withLogging(versionApi::getVersionAsync)
|
||||||
.handleInUIThread(this, callback, errorCallback);
|
.handleInUIThread(this, callback, errorCallback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.github.gotify.login;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import butterknife.BindView;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import com.github.gotify.R;
|
||||||
|
|
||||||
|
class AdvancedDialog {
|
||||||
|
|
||||||
|
private Context context;
|
||||||
|
private ViewHolder holder;
|
||||||
|
private CompoundButton.OnCheckedChangeListener onCheckedChangeListener;
|
||||||
|
private Runnable onClickSelectCaCertificate;
|
||||||
|
private Runnable onClickRemoveCaCertificate;
|
||||||
|
|
||||||
|
AdvancedDialog(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
AdvancedDialog onDisableSSLChanged(
|
||||||
|
CompoundButton.OnCheckedChangeListener onCheckedChangeListener) {
|
||||||
|
this.onCheckedChangeListener = onCheckedChangeListener;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AdvancedDialog onClickSelectCaCertificate(Runnable onClickSelectCaCertificate) {
|
||||||
|
this.onClickSelectCaCertificate = onClickSelectCaCertificate;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AdvancedDialog onClickRemoveCaCertificate(Runnable onClickRemoveCaCertificate) {
|
||||||
|
this.onClickRemoveCaCertificate = onClickRemoveCaCertificate;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
AdvancedDialog show(boolean disableSSL, @Nullable String selectedCertificate) {
|
||||||
|
|
||||||
|
View dialogView =
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.advanced_settings_dialog, null);
|
||||||
|
holder = new ViewHolder(dialogView);
|
||||||
|
holder.disableSSL.setChecked(disableSSL);
|
||||||
|
holder.disableSSL.setOnCheckedChangeListener(onCheckedChangeListener);
|
||||||
|
|
||||||
|
if (selectedCertificate == null) {
|
||||||
|
showSelectCACertificate();
|
||||||
|
} else {
|
||||||
|
showRemoveCACertificate(selectedCertificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
new AlertDialog.Builder(context)
|
||||||
|
.setView(dialogView)
|
||||||
|
.setTitle(R.string.advanced_settings)
|
||||||
|
.setPositiveButton(context.getString(R.string.done), (ignored, ignored2) -> {})
|
||||||
|
.show();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showSelectCACertificate() {
|
||||||
|
holder.toggleCaCert.setText(R.string.select_ca_certificate);
|
||||||
|
holder.toggleCaCert.setOnClickListener((a) -> onClickSelectCaCertificate.run());
|
||||||
|
holder.selectedCaCertificate.setText(R.string.no_certificate_selected);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showRemoveCACertificate(String certificate) {
|
||||||
|
holder.toggleCaCert.setText(R.string.remove_ca_certificate);
|
||||||
|
holder.toggleCaCert.setOnClickListener(
|
||||||
|
(a) -> {
|
||||||
|
showSelectCACertificate();
|
||||||
|
onClickRemoveCaCertificate.run();
|
||||||
|
});
|
||||||
|
holder.selectedCaCertificate.setText(certificate);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ViewHolder {
|
||||||
|
@BindView(R.id.disableSSL)
|
||||||
|
CheckBox disableSSL;
|
||||||
|
|
||||||
|
@BindView(R.id.toggle_ca_cert)
|
||||||
|
Button toggleCaCert;
|
||||||
|
|
||||||
|
@BindView(R.id.seleceted_ca_cert)
|
||||||
|
TextView selectedCaCertificate;
|
||||||
|
|
||||||
|
ViewHolder(View view) {
|
||||||
|
ButterKnife.bind(this, view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,17 @@
|
|||||||
package com.github.gotify.login;
|
package com.github.gotify.login;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.ProgressBar;
|
import android.widget.ProgressBar;
|
||||||
|
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.BindView;
|
import butterknife.BindView;
|
||||||
@@ -15,10 +19,12 @@ import butterknife.ButterKnife;
|
|||||||
import butterknife.OnClick;
|
import butterknife.OnClick;
|
||||||
import butterknife.OnTextChanged;
|
import butterknife.OnTextChanged;
|
||||||
import com.github.gotify.R;
|
import com.github.gotify.R;
|
||||||
|
import com.github.gotify.SSLSettings;
|
||||||
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;
|
||||||
@@ -30,9 +36,15 @@ 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.InputStream;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
|
||||||
public class LoginActivity extends AppCompatActivity {
|
public class LoginActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
// return value from startActivityForResult when choosing a certificate
|
||||||
|
private final int FILE_SELECT_CODE = 1;
|
||||||
|
|
||||||
@BindView(R.id.username)
|
@BindView(R.id.username)
|
||||||
EditText usernameField;
|
EditText usernameField;
|
||||||
|
|
||||||
@@ -42,6 +54,9 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
@BindView(R.id.password)
|
@BindView(R.id.password)
|
||||||
EditText passwordField;
|
EditText passwordField;
|
||||||
|
|
||||||
|
@BindView(R.id.advanced_settings)
|
||||||
|
ImageView toggleAdvanced;
|
||||||
|
|
||||||
@BindView(R.id.checkurl)
|
@BindView(R.id.checkurl)
|
||||||
Button checkUrlButton;
|
Button checkUrlButton;
|
||||||
|
|
||||||
@@ -56,6 +71,10 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private Settings settings;
|
private Settings settings;
|
||||||
|
|
||||||
|
private boolean disableSSLValidation;
|
||||||
|
private String caCertContents;
|
||||||
|
private AdvancedDialog advancedDialog;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -68,9 +87,14 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@OnTextChanged(R.id.gotify_url)
|
@OnTextChanged(R.id.gotify_url)
|
||||||
public void onUrlChange() {
|
public void onUrlChange() {
|
||||||
usernameField.setVisibility(View.INVISIBLE);
|
invalidateUrl();
|
||||||
passwordField.setVisibility(View.INVISIBLE);
|
}
|
||||||
loginButton.setVisibility(View.INVISIBLE);
|
|
||||||
|
private void invalidateUrl() {
|
||||||
|
usernameField.setVisibility(View.GONE);
|
||||||
|
passwordField.setVisibility(View.GONE);
|
||||||
|
loginButton.setVisibility(View.GONE);
|
||||||
|
checkUrlButton.setText(getString(R.string.check_url));
|
||||||
}
|
}
|
||||||
|
|
||||||
@OnClick(R.id.checkurl)
|
@OnClick(R.id.checkurl)
|
||||||
@@ -82,18 +106,102 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkUrlProgress.setVisibility(View.VISIBLE);
|
checkUrlProgress.setVisibility(View.VISIBLE);
|
||||||
checkUrlButton.setVisibility(View.INVISIBLE);
|
checkUrlButton.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)::getVersionAsync)
|
Api.withLogging(
|
||||||
|
ClientFactory.versionApi(
|
||||||
|
fixedUrl,
|
||||||
|
new SSLSettings(!disableSSLValidation, caCertContents))
|
||||||
|
::getVersionAsync)
|
||||||
.handleInUIThread(this, onValidUrl(fixedUrl), onInvalidUrl(fixedUrl));
|
.handleInUIThread(this, onValidUrl(fixedUrl), onInvalidUrl(fixedUrl));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.advanced_settings)
|
||||||
|
void toggleShowAdvanced() {
|
||||||
|
String selectedCertName =
|
||||||
|
caCertContents != null ? getNameOfCertContent(caCertContents) : null;
|
||||||
|
|
||||||
|
advancedDialog =
|
||||||
|
new AdvancedDialog(this)
|
||||||
|
.onDisableSSLChanged(
|
||||||
|
(ignored, disable) -> {
|
||||||
|
invalidateUrl();
|
||||||
|
disableSSLValidation = disable;
|
||||||
|
})
|
||||||
|
.onClickSelectCaCertificate(
|
||||||
|
() -> {
|
||||||
|
invalidateUrl();
|
||||||
|
doSelectCACertificate();
|
||||||
|
})
|
||||||
|
.onClickRemoveCaCertificate(
|
||||||
|
() -> {
|
||||||
|
invalidateUrl();
|
||||||
|
caCertContents = null;
|
||||||
|
})
|
||||||
|
.show(disableSSLValidation, selectedCertName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSelectCACertificate() {
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
|
||||||
|
// we don't really care what kind of file it is as long as we can parse it
|
||||||
|
intent.setType("*/*");
|
||||||
|
intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
|
||||||
|
try {
|
||||||
|
startActivityForResult(
|
||||||
|
Intent.createChooser(intent, getString(R.string.select_ca_file)),
|
||||||
|
FILE_SELECT_CODE);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
// case for user not having a file browser installed
|
||||||
|
Utils.showSnackBar(LoginActivity.this, getString(R.string.please_install_file_browser));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
|
||||||
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
|
try {
|
||||||
|
if (requestCode == FILE_SELECT_CODE) {
|
||||||
|
if (resultCode != RESULT_OK) {
|
||||||
|
throw new IllegalArgumentException(String.format("result was %d", resultCode));
|
||||||
|
} else if (data == null) {
|
||||||
|
throw new IllegalArgumentException("file path was null");
|
||||||
|
}
|
||||||
|
|
||||||
|
Uri uri = data.getData();
|
||||||
|
if (uri == null) {
|
||||||
|
throw new IllegalArgumentException("file path was null");
|
||||||
|
}
|
||||||
|
|
||||||
|
InputStream fileStream = getContentResolver().openInputStream(uri);
|
||||||
|
if (fileStream == null) {
|
||||||
|
throw new IllegalArgumentException("file path was invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
String content = Utils.readFileFromStream(fileStream);
|
||||||
|
String name = getNameOfCertContent(content);
|
||||||
|
|
||||||
|
// temporarily set the contents (don't store to settings until they decide to login)
|
||||||
|
caCertContents = content;
|
||||||
|
advancedDialog.showRemoveCACertificate(name);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Utils.showSnackBar(
|
||||||
|
LoginActivity.this, getString(R.string.select_ca_failed, e.getMessage()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getNameOfCertContent(String content) {
|
||||||
|
Certificate ca = CertUtils.parseCertificate(content);
|
||||||
|
return ((X509Certificate) ca).getSubjectDN().getName();
|
||||||
|
}
|
||||||
|
|
||||||
private Callback.SuccessCallback<VersionInfo> onValidUrl(String url) {
|
private Callback.SuccessCallback<VersionInfo> onValidUrl(String url) {
|
||||||
return (version) -> {
|
return (version) -> {
|
||||||
settings.url(url);
|
settings.url(url);
|
||||||
checkUrlProgress.setVisibility(View.INVISIBLE);
|
checkUrlProgress.setVisibility(View.GONE);
|
||||||
checkUrlButton.setVisibility(View.VISIBLE);
|
checkUrlButton.setVisibility(View.VISIBLE);
|
||||||
checkUrlButton.setText(getString(R.string.found_gotify_version, version.getVersion()));
|
checkUrlButton.setText(getString(R.string.found_gotify_version, version.getVersion()));
|
||||||
usernameField.setVisibility(View.VISIBLE);
|
usernameField.setVisibility(View.VISIBLE);
|
||||||
@@ -105,7 +213,7 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private Callback.ErrorCallback onInvalidUrl(String url) {
|
private Callback.ErrorCallback onInvalidUrl(String url) {
|
||||||
return (exception) -> {
|
return (exception) -> {
|
||||||
checkUrlProgress.setVisibility(View.INVISIBLE);
|
checkUrlProgress.setVisibility(View.GONE);
|
||||||
checkUrlButton.setVisibility(View.VISIBLE);
|
checkUrlButton.setVisibility(View.VISIBLE);
|
||||||
Utils.showSnackBar(LoginActivity.this, versionError(url, exception));
|
Utils.showSnackBar(LoginActivity.this, versionError(url, exception));
|
||||||
};
|
};
|
||||||
@@ -116,17 +224,22 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
String username = usernameField.getText().toString();
|
String username = usernameField.getText().toString();
|
||||||
String password = passwordField.getText().toString();
|
String password = passwordField.getText().toString();
|
||||||
|
|
||||||
loginButton.setVisibility(View.INVISIBLE);
|
loginButton.setVisibility(View.GONE);
|
||||||
loginProgress.setVisibility(View.VISIBLE);
|
loginProgress.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
ApiClient client = ClientFactory.basicAuth(settings.url(), username, password);
|
ApiClient client =
|
||||||
|
ClientFactory.basicAuth(
|
||||||
|
settings.url(),
|
||||||
|
new 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onInvalidLogin(ApiException e) {
|
private void onInvalidLogin(ApiException e) {
|
||||||
loginButton.setVisibility(View.VISIBLE);
|
loginButton.setVisibility(View.VISIBLE);
|
||||||
loginProgress.setVisibility(View.INVISIBLE);
|
loginProgress.setVisibility(View.GONE);
|
||||||
Utils.showSnackBar(this, getString(R.string.wronguserpw));
|
Utils.showSnackBar(this, getString(R.string.wronguserpw));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +266,9 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private void onCreatedClient(Client client) {
|
private void onCreatedClient(Client client) {
|
||||||
settings.token(client.getToken());
|
settings.token(client.getToken());
|
||||||
|
settings.validateSSL(!disableSSLValidation);
|
||||||
|
settings.cert(caCertContents);
|
||||||
|
|
||||||
Utils.showSnackBar(this, getString(R.string.created_client));
|
Utils.showSnackBar(this, getString(R.string.created_client));
|
||||||
startActivity(new Intent(this, InitializationActivity.class));
|
startActivity(new Intent(this, InitializationActivity.class));
|
||||||
finish();
|
finish();
|
||||||
@@ -160,12 +276,12 @@ public class LoginActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
private void onFailedToCreateClient(ApiException e) {
|
private void onFailedToCreateClient(ApiException e) {
|
||||||
Utils.showSnackBar(this, getString(R.string.create_client_failed));
|
Utils.showSnackBar(this, getString(R.string.create_client_failed));
|
||||||
loginProgress.setVisibility(View.INVISIBLE);
|
loginProgress.setVisibility(View.GONE);
|
||||||
loginButton.setVisibility(View.VISIBLE);
|
loginButton.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onCancelClientDialog(DialogInterface dialog, int which) {
|
private void onCancelClientDialog(DialogInterface dialog, int which) {
|
||||||
loginProgress.setVisibility(View.INVISIBLE);
|
loginProgress.setVisibility(View.GONE);
|
||||||
loginButton.setVisibility(View.VISIBLE);
|
loginButton.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ import java.util.List;
|
|||||||
public class ListMessageAdapter extends BaseAdapter {
|
public class ListMessageAdapter extends BaseAdapter {
|
||||||
|
|
||||||
private Context content;
|
private Context content;
|
||||||
|
private Picasso picasso;
|
||||||
private List<MessageWithImage> items;
|
private List<MessageWithImage> items;
|
||||||
private Delete delete;
|
private Delete delete;
|
||||||
|
|
||||||
ListMessageAdapter(Context context, List<MessageWithImage> items, Delete delete) {
|
ListMessageAdapter(
|
||||||
|
Context context, Picasso picasso, List<MessageWithImage> items, Delete delete) {
|
||||||
super();
|
super();
|
||||||
this.content = context;
|
this.content = context;
|
||||||
|
this.picasso = picasso;
|
||||||
this.items = items;
|
this.items = items;
|
||||||
this.delete = delete;
|
this.delete = delete;
|
||||||
}
|
}
|
||||||
@@ -62,8 +65,7 @@ public class ListMessageAdapter extends BaseAdapter {
|
|||||||
final MessageWithImage message = items.get(position);
|
final MessageWithImage message = items.get(position);
|
||||||
holder.message.setText(message.message.getMessage());
|
holder.message.setText(message.message.getMessage());
|
||||||
holder.title.setText(message.message.getTitle());
|
holder.title.setText(message.message.getTitle());
|
||||||
Picasso.get()
|
picasso.load(message.image)
|
||||||
.load(message.image)
|
|
||||||
.error(R.drawable.ic_alarm)
|
.error(R.drawable.ic_alarm)
|
||||||
.placeholder(R.drawable.ic_placeholder)
|
.placeholder(R.drawable.ic_placeholder)
|
||||||
.into(holder.image);
|
.into(holder.image);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import com.github.gotify.MissedMessageUtil;
|
|||||||
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.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;
|
||||||
@@ -46,11 +47,13 @@ import com.github.gotify.messages.provider.MessageWithImage;
|
|||||||
import com.github.gotify.service.WebSocketService;
|
import com.github.gotify.service.WebSocketService;
|
||||||
import com.google.android.material.navigation.NavigationView;
|
import com.google.android.material.navigation.NavigationView;
|
||||||
import com.squareup.okhttp.HttpUrl;
|
import com.squareup.okhttp.HttpUrl;
|
||||||
|
import com.squareup.picasso.OkHttp3Downloader;
|
||||||
import com.squareup.picasso.Picasso;
|
import com.squareup.picasso.Picasso;
|
||||||
import com.squareup.picasso.Target;
|
import com.squareup.picasso.Target;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
@@ -95,6 +98,8 @@ public class MessagesActivity extends AppCompatActivity
|
|||||||
private boolean isLoadMore = false;
|
private boolean isLoadMore = false;
|
||||||
private Integer selectAppIdOnDrawerClose = null;
|
private Integer selectAppIdOnDrawerClose = null;
|
||||||
|
|
||||||
|
private Picasso picasso;
|
||||||
|
|
||||||
// we need to keep the target references otherwise they get gc'ed before they can be called.
|
// we need to keep the target references otherwise they get gc'ed before they can be called.
|
||||||
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
|
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection")
|
||||||
private final List<Target> targetReferences = new ArrayList<>();
|
private final List<Target> targetReferences = new ArrayList<>();
|
||||||
@@ -107,7 +112,10 @@ 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.token());
|
picasso = makePicasso();
|
||||||
|
|
||||||
|
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();
|
||||||
@@ -116,7 +124,7 @@ public class MessagesActivity extends AppCompatActivity
|
|||||||
messages = new MessageFacade(new MessageApi(client), appsHolder);
|
messages = new MessageFacade(new MessageApi(client), appsHolder);
|
||||||
|
|
||||||
messagesView.setOnScrollListener(this);
|
messagesView.setOnScrollListener(this);
|
||||||
messagesView.setAdapter(new ListMessageAdapter(this, emptyList(), this::delete));
|
messagesView.setAdapter(new ListMessageAdapter(this, picasso, emptyList(), this::delete));
|
||||||
|
|
||||||
swipeRefreshLayout.setOnRefreshListener(this::onRefresh);
|
swipeRefreshLayout.setOnRefreshListener(this::onRefresh);
|
||||||
drawer.addDrawerListener(
|
drawer.addDrawerListener(
|
||||||
@@ -158,8 +166,7 @@ public class MessagesActivity extends AppCompatActivity
|
|||||||
item.setCheckable(true);
|
item.setCheckable(true);
|
||||||
Target t = Utils.toDrawable(getResources(), item::setIcon);
|
Target t = Utils.toDrawable(getResources(), item::setIcon);
|
||||||
targetReferences.add(t);
|
targetReferences.add(t);
|
||||||
Picasso.get()
|
picasso.load(app.getImage())
|
||||||
.load(app.getImage())
|
|
||||||
.error(R.drawable.ic_alarm)
|
.error(R.drawable.ic_alarm)
|
||||||
.placeholder(R.drawable.ic_placeholder)
|
.placeholder(R.drawable.ic_placeholder)
|
||||||
.resize(100, 100)
|
.resize(100, 100)
|
||||||
@@ -167,6 +174,15 @@ public class MessagesActivity extends AppCompatActivity
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Picasso makePicasso() {
|
||||||
|
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||||
|
CertUtils.applySslSettings(builder, settings.sslSettings());
|
||||||
|
|
||||||
|
OkHttp3Downloader downloader = new OkHttp3Downloader(builder.build());
|
||||||
|
|
||||||
|
return new Picasso.Builder(this).downloader(downloader).build();
|
||||||
|
}
|
||||||
|
|
||||||
private void initDrawer() {
|
private void initDrawer() {
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
navigationView.setItemIconTintList(null);
|
navigationView.setItemIconTintList(null);
|
||||||
@@ -264,6 +280,12 @@ public class MessagesActivity extends AppCompatActivity
|
|||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
picasso.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onScrollStateChanged(AbsListView view, int scrollState) {}
|
public void onScrollStateChanged(AbsListView view, int scrollState) {}
|
||||||
|
|
||||||
@@ -401,7 +423,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.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();
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package com.github.gotify.service;
|
package com.github.gotify.service;
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import com.github.gotify.SSLSettings;
|
||||||
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;
|
||||||
@@ -15,15 +17,9 @@ import okhttp3.WebSocket;
|
|||||||
import okhttp3.WebSocketListener;
|
import okhttp3.WebSocketListener;
|
||||||
|
|
||||||
public class WebSocketConnection {
|
public class WebSocketConnection {
|
||||||
|
private OkHttpClient client;
|
||||||
private static final JSON gson = Utils.json();
|
private static final JSON gson = Utils.json();
|
||||||
|
|
||||||
private final OkHttpClient client =
|
|
||||||
new OkHttpClient.Builder()
|
|
||||||
.readTimeout(0, TimeUnit.MILLISECONDS)
|
|
||||||
.pingInterval(1, TimeUnit.MINUTES)
|
|
||||||
.connectTimeout(10, TimeUnit.SECONDS)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private final Handler handler = new Handler();
|
private final Handler handler = new Handler();
|
||||||
private int errorCount = 0;
|
private int errorCount = 0;
|
||||||
|
|
||||||
@@ -38,7 +34,16 @@ public class WebSocketConnection {
|
|||||||
private Runnable onReconnected;
|
private Runnable onReconnected;
|
||||||
private boolean isClosed;
|
private boolean isClosed;
|
||||||
|
|
||||||
WebSocketConnection(String baseUrl, String token) {
|
WebSocketConnection(String baseUrl, SSLSettings settings, String token) {
|
||||||
|
OkHttpClient.Builder builder =
|
||||||
|
new OkHttpClient.Builder()
|
||||||
|
.readTimeout(0, TimeUnit.MILLISECONDS)
|
||||||
|
.pingInterval(1, TimeUnit.MINUTES)
|
||||||
|
.connectTimeout(10, TimeUnit.SECONDS);
|
||||||
|
CertUtils.applySslSettings(builder, settings);
|
||||||
|
|
||||||
|
client = builder.build();
|
||||||
|
|
||||||
this.baseUrl = baseUrl;
|
this.baseUrl = baseUrl;
|
||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,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.token()));
|
new MissedMessageUtil(
|
||||||
|
ClientFactory.clientToken(
|
||||||
|
settings.url(), settings.sslSettings(), settings.token()));
|
||||||
Log.i("Create " + getClass().getSimpleName());
|
Log.i("Create " + getClass().getSimpleName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,7 +83,7 @@ public class WebSocketService extends Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
connection =
|
connection =
|
||||||
new WebSocketConnection(settings.url(), 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)
|
||||||
|
|||||||
4
app/src/main/res/drawable/ic_settings.xml
Normal file
4
app/src/main/res/drawable/ic_settings.xml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<vector android:height="24dp" android:viewportHeight="20"
|
||||||
|
android:viewportWidth="20" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="#FF000000" android:pathData="M15.95,10.78c0.03,-0.25 0.05,-0.51 0.05,-0.78s-0.02,-0.53 -0.06,-0.78l1.69,-1.32c0.15,-0.12 0.19,-0.34 0.1,-0.51l-1.6,-2.77c-0.1,-0.18 -0.31,-0.24 -0.49,-0.18l-1.99,0.8c-0.42,-0.32 -0.86,-0.58 -1.35,-0.78L12,2.34c-0.03,-0.2 -0.2,-0.34 -0.4,-0.34H8.4c-0.2,0 -0.36,0.14 -0.39,0.34l-0.3,2.12c-0.49,0.2 -0.94,0.47 -1.35,0.78l-1.99,-0.8c-0.18,-0.07 -0.39,0 -0.49,0.18l-1.6,2.77c-0.1,0.18 -0.06,0.39 0.1,0.51l1.69,1.32c-0.04,0.25 -0.07,0.52 -0.07,0.78s0.02,0.53 0.06,0.78L2.37,12.1c-0.15,0.12 -0.19,0.34 -0.1,0.51l1.6,2.77c0.1,0.18 0.31,0.24 0.49,0.18l1.99,-0.8c0.42,0.32 0.86,0.58 1.35,0.78l0.3,2.12c0.04,0.2 0.2,0.34 0.4,0.34h3.2c0.2,0 0.37,-0.14 0.39,-0.34l0.3,-2.12c0.49,-0.2 0.94,-0.47 1.35,-0.78l1.99,0.8c0.18,0.07 0.39,0 0.49,-0.18l1.6,-2.77c0.1,-0.18 0.06,-0.39 -0.1,-0.51l-1.67,-1.32zM10,13c-1.65,0 -3,-1.35 -3,-3s1.35,-3 3,-3 3,1.35 3,3 -1.35,3 -3,3z"/>
|
||||||
|
</vector>
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:minWidth="40dp"
|
android:minWidth="40dp"
|
||||||
android:minHeight="40dp"
|
android:minHeight="40dp"
|
||||||
android:visibility="invisible"
|
android:visibility="gone"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="0.501"
|
app:layout_constraintHorizontal_bias="0.501"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@@ -38,9 +38,10 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:minWidth="40dp"
|
android:minWidth="40dp"
|
||||||
android:minHeight="40dp"
|
android:minHeight="40dp"
|
||||||
android:visibility="invisible"
|
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
|
||||||
@@ -85,7 +86,7 @@
|
|||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:hint="@string/username"
|
android:hint="@string/username"
|
||||||
android:inputType="textPersonName"
|
android:inputType="textPersonName"
|
||||||
android:visibility="invisible"
|
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/checkurl"
|
app:layout_constraintTop_toBottomOf="@+id/checkurl"
|
||||||
@@ -102,7 +103,7 @@
|
|||||||
android:ems="10"
|
android:ems="10"
|
||||||
android:hint="@string/password"
|
android:hint="@string/password"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword"
|
||||||
android:visibility="invisible"
|
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/username"
|
app:layout_constraintTop_toBottomOf="@+id/username"
|
||||||
@@ -119,7 +120,7 @@
|
|||||||
android:background="@color/colorPrimaryDark"
|
android:background="@color/colorPrimaryDark"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:text="@string/login"
|
android:text="@string/login"
|
||||||
android:visibility="invisible"
|
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/password"
|
app:layout_constraintTop_toBottomOf="@+id/password"
|
||||||
@@ -129,17 +130,26 @@
|
|||||||
android:id="@+id/checkurl"
|
android:id="@+id/checkurl"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginEnd="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
app:layout_constraintWidth_max="280dp"
|
|
||||||
android:background="@color/colorPrimaryDark"
|
android:background="@color/colorPrimaryDark"
|
||||||
android:text="@string/check_url"
|
android:text="@string/check_url"
|
||||||
android:textColor="@android:color/white"
|
android:textColor="@android:color/white"
|
||||||
android:visibility="visible"
|
android:visibility="visible"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toStartOf="@+id/advanced_settings"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="@+id/gotify_url"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/gotify_url" />
|
app:layout_constraintTop_toBottomOf="@+id/gotify_url"
|
||||||
|
app:layout_constraintWidth_max="280dp" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/advanced_settings"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/check_url"
|
||||||
|
android:visibility="visible"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/gotify_url"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/checkurl"
|
||||||
|
app:srcCompat="@drawable/ic_settings" />
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
24
app/src/main/res/layout/advanced_settings_dialog.xml
Normal file
24
app/src/main/res/layout/advanced_settings_dialog.xml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical" android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="20dp">
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/disableSSL"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/disabled_validate_ssl" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/toggle_ca_cert"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/select_ca_certificate" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/seleceted_ca_cert"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/no_certificate_selected" />
|
||||||
|
</LinearLayout>
|
||||||
@@ -31,6 +31,12 @@
|
|||||||
<string name="gotify_url">Gotify URL</string>
|
<string name="gotify_url">Gotify URL</string>
|
||||||
<string name="username">Username</string>
|
<string name="username">Username</string>
|
||||||
<string name="password">Password</string>
|
<string name="password">Password</string>
|
||||||
|
<string name="show_advanced">Show Advanced Options</string>
|
||||||
|
<string name="disabled_validate_ssl">Disable SSL Validation</string>
|
||||||
|
<string name="select_ca_certificate">Select CA Certificate</string>
|
||||||
|
<string name="select_ca_file">Select a Certificate File</string>
|
||||||
|
<string name="please_install_file_browser">Please install a file browser</string>
|
||||||
|
<string name="select_ca_failed">Failed to read CA: %s</string>
|
||||||
<string name="login">Login</string>
|
<string name="login">Login</string>
|
||||||
<string name="check_url">Check URL</string>
|
<string name="check_url">Check URL</string>
|
||||||
<string name="gotify_logo">gotify logo</string>
|
<string name="gotify_logo">gotify logo</string>
|
||||||
@@ -44,4 +50,8 @@
|
|||||||
<string name="websocket_init">Initializing</string>
|
<string name="websocket_init">Initializing</string>
|
||||||
<string name="connection">%s@%s</string>
|
<string name="connection">%s@%s</string>
|
||||||
<string name="server_version">Gotify-Server v%s</string>
|
<string name="server_version">Gotify-Server v%s</string>
|
||||||
|
<string name="advanced_settings">Advanced Settings</string>
|
||||||
|
<string name="done">Done</string>
|
||||||
|
<string name="no_certificate_selected">No certificate selected</string>
|
||||||
|
<string name="remove_ca_certificate">Remove CA Certificate</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user