314 lines
12 KiB
Java
314 lines
12 KiB
Java
package com.github.gotify.login;
|
|
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.text.Editable;
|
|
import android.text.TextWatcher;
|
|
import android.view.View;
|
|
import android.widget.EditText;
|
|
import androidx.annotation.Nullable;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.appcompat.view.ContextThemeWrapper;
|
|
import com.github.gotify.R;
|
|
import com.github.gotify.SSLSettings;
|
|
import com.github.gotify.Settings;
|
|
import com.github.gotify.Utils;
|
|
import com.github.gotify.api.ApiException;
|
|
import com.github.gotify.api.Callback;
|
|
import com.github.gotify.api.CertUtils;
|
|
import com.github.gotify.api.ClientFactory;
|
|
import com.github.gotify.client.ApiClient;
|
|
import com.github.gotify.client.api.ClientApi;
|
|
import com.github.gotify.client.api.UserApi;
|
|
import com.github.gotify.client.model.Client;
|
|
import com.github.gotify.client.model.VersionInfo;
|
|
import com.github.gotify.databinding.ActivityLoginBinding;
|
|
import com.github.gotify.init.InitializationActivity;
|
|
import com.github.gotify.log.Log;
|
|
import com.github.gotify.log.LogsActivity;
|
|
import com.github.gotify.log.UncaughtExceptionHandler;
|
|
import java.io.InputStream;
|
|
import java.security.cert.Certificate;
|
|
import java.security.cert.X509Certificate;
|
|
import okhttp3.HttpUrl;
|
|
|
|
import static com.github.gotify.api.Callback.callInUI;
|
|
|
|
public class LoginActivity extends AppCompatActivity {
|
|
|
|
// return value from startActivityForResult when choosing a certificate
|
|
private final int FILE_SELECT_CODE = 1;
|
|
|
|
private ActivityLoginBinding binding;
|
|
private Settings settings;
|
|
|
|
private boolean disableSSLValidation;
|
|
private String caCertContents;
|
|
private AdvancedDialog advancedDialog;
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
UncaughtExceptionHandler.registerCurrentThread();
|
|
binding = ActivityLoginBinding.inflate(getLayoutInflater());
|
|
setContentView(binding.getRoot());
|
|
Log.i("Entering " + getClass().getSimpleName());
|
|
settings = new Settings(this);
|
|
}
|
|
|
|
@Override
|
|
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
|
|
super.onPostCreate(savedInstanceState);
|
|
|
|
binding.gotifyUrl.addTextChangedListener(
|
|
new TextWatcher() {
|
|
@Override
|
|
public void beforeTextChanged(
|
|
CharSequence charSequence, int i, int i1, int i2) {}
|
|
|
|
@Override
|
|
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
|
|
invalidateUrl();
|
|
}
|
|
|
|
@Override
|
|
public void afterTextChanged(Editable editable) {}
|
|
});
|
|
|
|
binding.checkurl.setOnClickListener(ignored -> doCheckUrl());
|
|
binding.openLogs.setOnClickListener(ignored -> openLogs());
|
|
binding.advancedSettings.setOnClickListener(ignored -> toggleShowAdvanced());
|
|
binding.login.setOnClickListener(ignored -> doLogin());
|
|
}
|
|
|
|
private void invalidateUrl() {
|
|
binding.username.setVisibility(View.GONE);
|
|
binding.password.setVisibility(View.GONE);
|
|
binding.login.setVisibility(View.GONE);
|
|
binding.checkurl.setText(getString(R.string.check_url));
|
|
}
|
|
|
|
public void doCheckUrl() {
|
|
String url = binding.gotifyUrl.getText().toString();
|
|
HttpUrl parsedUrl = HttpUrl.parse(url);
|
|
if (parsedUrl == null) {
|
|
Utils.showSnackBar(LoginActivity.this, "Invalid URL (include http:// or https://)");
|
|
return;
|
|
}
|
|
|
|
if ("http".equals(parsedUrl.scheme())) {
|
|
showHttpWarning();
|
|
}
|
|
|
|
binding.checkurlProgress.setVisibility(View.VISIBLE);
|
|
binding.checkurl.setVisibility(View.GONE);
|
|
|
|
final String trimmedUrl = url.trim();
|
|
final String fixedUrl =
|
|
trimmedUrl.endsWith("/")
|
|
? trimmedUrl.substring(0, trimmedUrl.length() - 1)
|
|
: trimmedUrl;
|
|
|
|
try {
|
|
ClientFactory.versionApi(fixedUrl, tempSSLSettings())
|
|
.getVersion()
|
|
.enqueue(callInUI(this, onValidUrl(fixedUrl), onInvalidUrl(fixedUrl)));
|
|
} catch (Exception e) {
|
|
binding.checkurlProgress.setVisibility(View.GONE);
|
|
binding.checkurl.setVisibility(View.VISIBLE);
|
|
String errorMsg =
|
|
getString(R.string.version_failed, fixedUrl + "/version", e.getMessage());
|
|
Utils.showSnackBar(LoginActivity.this, errorMsg);
|
|
}
|
|
}
|
|
|
|
public void showHttpWarning() {
|
|
new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AppTheme_Dialog))
|
|
.setTitle(R.string.warning)
|
|
.setCancelable(true)
|
|
.setMessage(R.string.http_warning)
|
|
.setPositiveButton(R.string.i_understand, (a, b) -> {})
|
|
.show();
|
|
}
|
|
|
|
public void openLogs() {
|
|
startActivity(new Intent(this, LogsActivity.class));
|
|
}
|
|
|
|
void toggleShowAdvanced() {
|
|
String selectedCertName =
|
|
caCertContents != null ? getNameOfCertContent(caCertContents) : null;
|
|
|
|
advancedDialog =
|
|
new AdvancedDialog(this, getLayoutInflater())
|
|
.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(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(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) {
|
|
return (version) -> {
|
|
settings.url(url);
|
|
binding.checkurlProgress.setVisibility(View.GONE);
|
|
binding.checkurl.setVisibility(View.VISIBLE);
|
|
binding.checkurl.setText(
|
|
getString(R.string.found_gotify_version, version.getVersion()));
|
|
binding.username.setVisibility(View.VISIBLE);
|
|
binding.username.requestFocus();
|
|
binding.password.setVisibility(View.VISIBLE);
|
|
binding.login.setVisibility(View.VISIBLE);
|
|
};
|
|
}
|
|
|
|
private Callback.ErrorCallback onInvalidUrl(String url) {
|
|
return (exception) -> {
|
|
binding.checkurlProgress.setVisibility(View.GONE);
|
|
binding.checkurl.setVisibility(View.VISIBLE);
|
|
Utils.showSnackBar(LoginActivity.this, versionError(url, exception));
|
|
};
|
|
}
|
|
|
|
public void doLogin() {
|
|
String username = binding.username.getText().toString();
|
|
String password = binding.password.getText().toString();
|
|
|
|
binding.login.setVisibility(View.GONE);
|
|
binding.loginProgress.setVisibility(View.VISIBLE);
|
|
|
|
ApiClient client =
|
|
ClientFactory.basicAuth(settings.url(), tempSSLSettings(), username, password);
|
|
client.createService(UserApi.class)
|
|
.currentUser()
|
|
.enqueue(callInUI(this, (user) -> newClientDialog(client), this::onInvalidLogin));
|
|
}
|
|
|
|
private void onInvalidLogin(ApiException e) {
|
|
binding.login.setVisibility(View.VISIBLE);
|
|
binding.loginProgress.setVisibility(View.GONE);
|
|
Utils.showSnackBar(this, getString(R.string.wronguserpw));
|
|
}
|
|
|
|
private void newClientDialog(ApiClient client) {
|
|
EditText clientName = new EditText(this);
|
|
clientName.setText(Build.MODEL);
|
|
|
|
new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AppTheme_Dialog))
|
|
.setTitle(R.string.create_client_title)
|
|
.setMessage(R.string.create_client_message)
|
|
.setView(clientName)
|
|
.setPositiveButton(R.string.create, doCreateClient(client, clientName))
|
|
.setNegativeButton(R.string.cancel, this::onCancelClientDialog)
|
|
.show();
|
|
}
|
|
|
|
public DialogInterface.OnClickListener doCreateClient(ApiClient client, EditText nameProvider) {
|
|
return (a, b) -> {
|
|
Client newClient = new Client().name(nameProvider.getText().toString());
|
|
client.createService(ClientApi.class)
|
|
.createClient(newClient)
|
|
.enqueue(callInUI(this, this::onCreatedClient, this::onFailedToCreateClient));
|
|
};
|
|
}
|
|
|
|
private void onCreatedClient(Client client) {
|
|
settings.token(client.getToken());
|
|
settings.validateSSL(!disableSSLValidation);
|
|
settings.cert(caCertContents);
|
|
|
|
Utils.showSnackBar(this, getString(R.string.created_client));
|
|
startActivity(new Intent(this, InitializationActivity.class));
|
|
finish();
|
|
}
|
|
|
|
private void onFailedToCreateClient(ApiException e) {
|
|
Utils.showSnackBar(this, getString(R.string.create_client_failed));
|
|
binding.loginProgress.setVisibility(View.GONE);
|
|
binding.login.setVisibility(View.VISIBLE);
|
|
}
|
|
|
|
private void onCancelClientDialog(DialogInterface dialog, int which) {
|
|
binding.loginProgress.setVisibility(View.GONE);
|
|
binding.login.setVisibility(View.VISIBLE);
|
|
}
|
|
|
|
private String versionError(String url, ApiException exception) {
|
|
return getString(R.string.version_failed_status_code, url + "/version", exception.code());
|
|
}
|
|
|
|
private SSLSettings tempSSLSettings() {
|
|
return new SSLSettings(!disableSSLValidation, caCertContents);
|
|
}
|
|
}
|