mirror of
https://github.com/nextcloud/talk-android.git
synced 2024-11-22 13:05:31 +03:00
Initial support for client certificate auth
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
473becd623
commit
ea80b3c9d9
22 changed files with 546 additions and 100 deletions
|
@ -260,7 +260,7 @@ public class CallActivity extends AppCompatActivity {
|
|||
if (!userEntity.getCurrent()) {
|
||||
userUtils.createOrUpdateUser(null,
|
||||
null, null, null,
|
||||
null, true, null, userEntity.getId(), null)
|
||||
null, true, null, userEntity.getId(), null, null)
|
||||
.subscribe(new Observer<UserEntity>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
|
|
|
@ -45,6 +45,7 @@ import com.nextcloud.talk.utils.ApiUtils;
|
|||
import com.nextcloud.talk.utils.ApplicationWideMessageHolder;
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils;
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||
|
||||
import java.net.CookieManager;
|
||||
|
||||
|
@ -72,6 +73,9 @@ public class AccountVerificationController extends BaseController {
|
|||
@Inject
|
||||
CookieManager cookieManager;
|
||||
|
||||
@Inject
|
||||
AppPreferences appPreferences;
|
||||
|
||||
@BindView(R.id.progress_text)
|
||||
TextView progressText;
|
||||
|
||||
|
@ -198,7 +202,8 @@ public class AccountVerificationController extends BaseController {
|
|||
if (!TextUtils.isEmpty(displayName)) {
|
||||
dbQueryDisposable = userUtils.createOrUpdateUser(username, token,
|
||||
baseUrl, displayName, null, true,
|
||||
userProfileOverall.getOcs().getData().getUserId(), null, null)
|
||||
userProfileOverall.getOcs().getData().getUserId(), null, null,
|
||||
appPreferences.getTemporaryClientCertAlias())
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(userEntity -> {
|
||||
|
|
|
@ -51,7 +51,7 @@ public class MagicBottomNavigationController extends BottomNavigationController
|
|||
BottomNavigationMenuItem.getEnum(itemId).getControllerClass().getConstructors();
|
||||
Controller controller = null;
|
||||
try {
|
||||
/* Determine default or Bundle constructor */
|
||||
/* Determine default or Bundle constructor */
|
||||
for (Constructor constructor : constructors) {
|
||||
if (constructor.getParameterTypes().length == 0) {
|
||||
controller = (Controller) constructor.newInstance();
|
||||
|
|
|
@ -20,10 +20,12 @@
|
|||
|
||||
package com.nextcloud.talk.controllers;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.security.KeyChain;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
|
@ -46,6 +48,7 @@ import com.nextcloud.talk.utils.ApiUtils;
|
|||
import com.nextcloud.talk.utils.ApplicationWideMessageHolder;
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils;
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
|
@ -53,6 +56,7 @@ import javax.inject.Inject;
|
|||
|
||||
import autodagger.AutoInjector;
|
||||
import butterknife.BindView;
|
||||
import butterknife.OnClick;
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||
import io.reactivex.disposables.Disposable;
|
||||
import io.reactivex.schedulers.Schedulers;
|
||||
|
@ -72,6 +76,8 @@ public class ServerSelectionController extends BaseController {
|
|||
ProgressBar progressBar;
|
||||
@BindView(R.id.helper_text_view)
|
||||
TextView providersTextView;
|
||||
@BindView(R.id.cert_text_view)
|
||||
TextView certTextView;
|
||||
|
||||
@Inject
|
||||
NcApi ncApi;
|
||||
|
@ -79,6 +85,9 @@ public class ServerSelectionController extends BaseController {
|
|||
@Inject
|
||||
UserUtils userUtils;
|
||||
|
||||
@Inject
|
||||
AppPreferences appPreferences;
|
||||
|
||||
private Disposable statusQueryDisposable;
|
||||
|
||||
@Override
|
||||
|
@ -86,6 +95,22 @@ public class ServerSelectionController extends BaseController {
|
|||
return inflater.inflate(R.layout.controller_server_selection, container, false);
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
@OnClick(R.id.cert_text_view)
|
||||
public void onCertClick() {
|
||||
if (getActivity() != null) {
|
||||
KeyChain.choosePrivateKeyAlias(getActivity(), alias -> {
|
||||
if (alias != null) {
|
||||
appPreferences.setTemporaryClientCertAlias(alias);
|
||||
} else {
|
||||
appPreferences.removeTemporaryClientCertAlias();
|
||||
}
|
||||
|
||||
setCertTextView();
|
||||
}, new String[]{"RSA", "EC"}, null, null, -1, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
|
@ -108,7 +133,7 @@ public class ServerSelectionController extends BaseController {
|
|||
|
||||
if (TextUtils.isEmpty(getResources().getString(R.string.nc_providers_url)) && (TextUtils.isEmpty(getResources
|
||||
().getString(R.string.nc_import_account_type)))) {
|
||||
providersTextView.setVisibility(View.GONE);
|
||||
providersTextView.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
if ((TextUtils.isEmpty(getResources
|
||||
().getString(R.string.nc_import_account_type)) ||
|
||||
|
@ -151,7 +176,7 @@ public class ServerSelectionController extends BaseController {
|
|||
.popChangeHandler(new HorizontalChangeHandler()));
|
||||
});
|
||||
} else {
|
||||
providersTextView.setVisibility(View.GONE);
|
||||
providersTextView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -204,8 +229,9 @@ public class ServerSelectionController extends BaseController {
|
|||
|
||||
serverEntry.setEnabled(false);
|
||||
progressBar.setVisibility(View.VISIBLE);
|
||||
if (providersTextView.getVisibility() != View.GONE) {
|
||||
if (providersTextView.getVisibility() != View.INVISIBLE) {
|
||||
providersTextView.setVisibility(View.INVISIBLE);
|
||||
certTextView.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
if (url.endsWith("/")) {
|
||||
|
@ -279,8 +305,9 @@ public class ServerSelectionController extends BaseController {
|
|||
}
|
||||
|
||||
progressBar.setVisibility(View.INVISIBLE);
|
||||
if (providersTextView.getVisibility() != View.GONE) {
|
||||
if (providersTextView.getVisibility() != View.INVISIBLE) {
|
||||
providersTextView.setVisibility(View.VISIBLE);
|
||||
certTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
toggleProceedButton(false);
|
||||
|
||||
|
@ -288,8 +315,9 @@ public class ServerSelectionController extends BaseController {
|
|||
}
|
||||
}, () -> {
|
||||
progressBar.setVisibility(View.INVISIBLE);
|
||||
if (providersTextView.getVisibility() != View.GONE) {
|
||||
if (providersTextView.getVisibility() != View.INVISIBLE) {
|
||||
providersTextView.setVisibility(View.VISIBLE);
|
||||
certTextView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
dispose();
|
||||
});
|
||||
|
@ -315,6 +343,23 @@ public class ServerSelectionController extends BaseController {
|
|||
}
|
||||
ApplicationWideMessageHolder.getInstance().setMessageType(null);
|
||||
}
|
||||
|
||||
setCertTextView();
|
||||
}
|
||||
|
||||
private void setCertTextView() {
|
||||
if (getActivity() != null) {
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (!TextUtils.isEmpty(appPreferences.getTemporaryClientCertAlias())) {
|
||||
certTextView.setText(R.string.nc_change_cert_auth);
|
||||
} else {
|
||||
certTextView.setText(R.string.nc_configure_cert_auth);
|
||||
}
|
||||
|
||||
textFieldBoxes.setError("", true);
|
||||
toggleProceedButton(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,9 +24,11 @@ import android.animation.Animator;
|
|||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.security.KeyChain;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -64,9 +66,12 @@ import net.orange_box.storebox.listeners.OnPreferenceValueChangedListener;
|
|||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import java.net.CookieManager;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
|
@ -126,6 +131,9 @@ public class SettingsController extends BaseController {
|
|||
@BindView(R.id.message_view)
|
||||
MaterialPreferenceCategory messageView;
|
||||
|
||||
@BindView(R.id.settings_client_cert)
|
||||
MaterialStandardPreference certificateSetup;
|
||||
|
||||
@BindView(R.id.message_text)
|
||||
TextView messageText;
|
||||
|
||||
|
@ -221,6 +229,45 @@ public class SettingsController extends BaseController {
|
|||
SwitchAccountController()).pushChangeHandler(new VerticalChangeHandler())
|
||||
.popChangeHandler(new VerticalChangeHandler()));
|
||||
});
|
||||
|
||||
if (userEntity.getClientCertificate() != null) {
|
||||
certificateSetup.setTitle(R.string.nc_client_cert_change);
|
||||
}
|
||||
|
||||
|
||||
String host = null;
|
||||
int port = -1;
|
||||
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(userEntity.getBaseUrl());
|
||||
host = uri.getHost();
|
||||
port = uri.getPort();
|
||||
} catch (URISyntaxException e) {
|
||||
Log.e(TAG, "Failed to create uri");
|
||||
}
|
||||
|
||||
String finalHost = host;
|
||||
int finalPort = port;
|
||||
certificateSetup.addPreferenceClickListener(v -> KeyChain.choosePrivateKeyAlias(Objects.requireNonNull(getActivity()), alias -> {
|
||||
String finalAlias = alias;
|
||||
getActivity().runOnUiThread(() -> {
|
||||
if (finalAlias != null) {
|
||||
certificateSetup.setTitle(R.string.nc_client_cert_change);
|
||||
} else {
|
||||
certificateSetup.setTitle(R.string.nc_client_cert_setup);
|
||||
}
|
||||
});
|
||||
|
||||
if (alias == null) {
|
||||
alias = "";
|
||||
}
|
||||
|
||||
|
||||
userUtils.createOrUpdateUser(null, null, null, null, null, null, null, userEntity.getId(),
|
||||
null, alias);
|
||||
}, new String[]{"RSA", "EC"}, null, finalHost, finalPort, userEntity.getClientCertificate
|
||||
()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -290,7 +337,7 @@ public class SettingsController extends BaseController {
|
|||
dbQueryDisposable = userUtils.createOrUpdateUser(null,
|
||||
null,
|
||||
null, displayName, null, true,
|
||||
userProfileOverall.getOcs().getData().getUserId(), userEntity.getId(), null)
|
||||
userProfileOverall.getOcs().getData().getUserId(), userEntity.getId(), null, null)
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(userEntityResult -> {
|
||||
|
|
|
@ -100,7 +100,7 @@ public class SwitchAccountController extends BaseController {
|
|||
UserEntity userEntity = ((AdvancedUserItem) userItems.get(position)).getEntity();
|
||||
userUtils.createOrUpdateUser(null,
|
||||
null, null, null,
|
||||
null, true, null, userEntity.getId(), null)
|
||||
null, true, null, userEntity.getId(), null, null)
|
||||
.subscribe(new Observer<UserEntity>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
package com.nextcloud.talk.controllers;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.net.http.SslCertificate;
|
||||
import android.net.http.SslError;
|
||||
|
@ -29,7 +30,6 @@ import android.security.KeyChain;
|
|||
import android.security.KeyChainException;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
@ -52,13 +52,13 @@ import com.nextcloud.talk.models.database.UserEntity;
|
|||
import com.nextcloud.talk.utils.ApplicationWideMessageHolder;
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys;
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils;
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||
import com.nextcloud.talk.utils.ssl.MagicTrustManager;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.CookieManager;
|
||||
import java.net.URLDecoder;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.CertificateException;
|
||||
|
@ -86,13 +86,16 @@ public class WebViewLoginController extends BaseController {
|
|||
@Inject
|
||||
UserUtils userUtils;
|
||||
@Inject
|
||||
AppPreferences appPreferences;
|
||||
@Inject
|
||||
ReactiveEntityStore<Persistable> dataStore;
|
||||
@Inject
|
||||
MagicTrustManager magicTrustManager;
|
||||
@Inject
|
||||
EventBus eventBus;
|
||||
@Inject
|
||||
java.net.CookieManager cookieManager;
|
||||
CookieManager cookieManager;
|
||||
|
||||
|
||||
@BindView(R.id.webview)
|
||||
WebView webView;
|
||||
|
@ -140,6 +143,7 @@ public class WebViewLoginController extends BaseController {
|
|||
return inflater.inflate(R.layout.controller_web_view_login, container, false);
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
|
@ -167,6 +171,7 @@ public class WebViewLoginController extends BaseController {
|
|||
webView.clearCache(true);
|
||||
webView.clearFormData();
|
||||
webView.clearHistory();
|
||||
WebView.clearClientCertPreferences(null);
|
||||
|
||||
CookieSyncManager.createInstance(getActivity());
|
||||
android.webkit.CookieManager.getInstance().removeAllCookies(null);
|
||||
|
@ -204,7 +209,7 @@ public class WebViewLoginController extends BaseController {
|
|||
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
|
||||
if (loginStep == 1) {
|
||||
webView.loadUrl("javascript: {document.getElementsByClassName('login')[0].click(); };");
|
||||
} else if (!automatedLoginAttempted){
|
||||
} else if (!automatedLoginAttempted) {
|
||||
automatedLoginAttempted = true;
|
||||
webView.loadUrl("javascript: {" +
|
||||
"document.getElementById('user').value = '" + username + "';" +
|
||||
|
@ -218,32 +223,55 @@ public class WebViewLoginController extends BaseController {
|
|||
|
||||
@Override
|
||||
public void onReceivedClientCertRequest(WebView view, ClientCertRequest request) {
|
||||
String host = null;
|
||||
UserEntity userEntity;
|
||||
|
||||
try {
|
||||
URL url = new URL(webView.getUrl());
|
||||
host = url.getHost();
|
||||
} catch (MalformedURLException e) {
|
||||
Log.d(TAG, "Failed to create url");
|
||||
String alias = null;
|
||||
if (!isPasswordUpdate) {
|
||||
alias = appPreferences.getTemporaryClientCertAlias();
|
||||
}
|
||||
|
||||
KeyChain.choosePrivateKeyAlias(getActivity(), alias -> {
|
||||
try {
|
||||
if (alias != null) {
|
||||
PrivateKey privateKey = KeyChain.getPrivateKey(getActivity(), alias);
|
||||
X509Certificate[] certificates = KeyChain.getCertificateChain(getActivity(), alias);
|
||||
request.proceed(privateKey, certificates);
|
||||
if (TextUtils.isEmpty(alias) && (userEntity = userUtils.getCurrentUser()) != null) {
|
||||
alias = userEntity.getClientCertificate();
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(alias)) {
|
||||
String finalAlias = alias;
|
||||
new Thread(() -> {
|
||||
try {
|
||||
PrivateKey privateKey = KeyChain.getPrivateKey(getActivity(), finalAlias);
|
||||
X509Certificate[] certificates = KeyChain.getCertificateChain(getActivity(), finalAlias);
|
||||
if (privateKey != null && certificates != null) {
|
||||
request.proceed(privateKey, certificates);
|
||||
} else {
|
||||
request.cancel();
|
||||
}
|
||||
} catch (KeyChainException | InterruptedException e) {
|
||||
request.cancel();
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
KeyChain.choosePrivateKeyAlias(getActivity(), chosenAlias -> {
|
||||
if (chosenAlias != null) {
|
||||
appPreferences.setTemporaryClientCertAlias(chosenAlias);
|
||||
new Thread(() -> {
|
||||
PrivateKey privateKey = null;
|
||||
try {
|
||||
privateKey = KeyChain.getPrivateKey(getActivity(), chosenAlias);
|
||||
X509Certificate[] certificates = KeyChain.getCertificateChain(getActivity(), chosenAlias);
|
||||
if (privateKey != null && certificates != null) {
|
||||
request.proceed(privateKey, certificates);
|
||||
} else {
|
||||
request.cancel();
|
||||
}
|
||||
} catch (KeyChainException | InterruptedException e) {
|
||||
request.cancel();
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
request.cancel();
|
||||
}
|
||||
} catch (KeyChainException e) {
|
||||
Log.e(TAG, "Failed to get keys via keychain exception");
|
||||
request.cancel();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Failed to get keys due to interruption");
|
||||
request.cancel();
|
||||
}
|
||||
}, new String[]{"RSA"}, null, host, -1, null);
|
||||
}, new String[]{"RSA", "EC"}, null, request.getHost(), request.getPort(), null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -339,7 +367,7 @@ public class WebViewLoginController extends BaseController {
|
|||
if (currentUser != null) {
|
||||
userQueryDisposable = userUtils.createOrUpdateUser(null, null,
|
||||
null, null, null, true,
|
||||
null, currentUser.getId(), null).
|
||||
null, currentUser.getId(), null, appPreferences.getTemporaryClientCertAlias()).
|
||||
subscribe(userEntity -> {
|
||||
if (finalMessageType != null) {
|
||||
ApplicationWideMessageHolder.getInstance().setMessageType(finalMessageType);
|
||||
|
|
|
@ -25,10 +25,28 @@ import android.util.Log;
|
|||
import android.view.View;
|
||||
|
||||
import com.bluelinelabs.conductor.Controller;
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||
import com.nextcloud.talk.controllers.AccountVerificationController;
|
||||
import com.nextcloud.talk.controllers.ServerSelectionController;
|
||||
import com.nextcloud.talk.controllers.WebViewLoginController;
|
||||
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider;
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import autodagger.AutoInjector;
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication.class)
|
||||
public abstract class BaseController extends RefWatchingController {
|
||||
|
||||
@Inject
|
||||
AppPreferences appPreferences;
|
||||
|
||||
private List<String> temporaryClassNames = new ArrayList<>();
|
||||
|
||||
private static final String TAG = "BaseController";
|
||||
|
||||
protected BaseController() {
|
||||
|
@ -38,6 +56,12 @@ public abstract class BaseController extends RefWatchingController {
|
|||
super(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onViewBound(@NonNull View view) {
|
||||
NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
|
||||
super.onViewBound(view);
|
||||
}
|
||||
|
||||
// Note: This is just a quick demo of how an ActionBar *can* be accessed, not necessarily how it *should*
|
||||
// be accessed. In a production app, this would use Dagger instead.
|
||||
protected ActionBar getActionBar() {
|
||||
|
@ -54,6 +78,14 @@ public abstract class BaseController extends RefWatchingController {
|
|||
protected void onAttach(@NonNull View view) {
|
||||
setTitle();
|
||||
getActionBar().setDisplayHomeAsUpEnabled(false);
|
||||
|
||||
temporaryClassNames.add(ServerSelectionController.class.getName());
|
||||
temporaryClassNames.add(AccountVerificationController.class.getName());
|
||||
temporaryClassNames.add(WebViewLoginController.class.getName());
|
||||
|
||||
if (!temporaryClassNames.contains(getClass().getName())) {
|
||||
appPreferences.removeTemporaryClientCertAlias();
|
||||
}
|
||||
super.onAttach(view);
|
||||
}
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ public abstract class BottomNavigationController extends BaseController {
|
|||
protected void onViewBound(@NonNull View view) {
|
||||
super.onViewBound(view);
|
||||
|
||||
/* Setup the BottomNavigationView with the constructor supplied Menu resource */
|
||||
/* Setup the BottomNavigationView with the constructor supplied Menu resource */
|
||||
bottomNavigationView.inflateMenu(getMenuResource());
|
||||
|
||||
bottomNavigationView.setOnNavigationItemSelectedListener(
|
||||
|
@ -138,20 +138,20 @@ public abstract class BottomNavigationController extends BaseController {
|
|||
protected void onAttach(@NonNull View view) {
|
||||
super.onAttach(view);
|
||||
|
||||
/* Fresh start, setup everything */
|
||||
/* Fresh start, setup everything */
|
||||
if (routerSavedStateBundles == null) {
|
||||
Menu menu = bottomNavigationView.getMenu();
|
||||
int menuSize = menu.size();
|
||||
routerSavedStateBundles = new SparseArray<>(menuSize);
|
||||
for (int i = 0; i < menuSize; i++) {
|
||||
MenuItem menuItem = menu.getItem(i);
|
||||
/* Ensure the first checked item is shown */
|
||||
/* Ensure the first checked item is shown */
|
||||
if (menuItem.isChecked()) {
|
||||
/*
|
||||
* Seems like the BottomNavigationView always initializes index 0 as isChecked / Selected,
|
||||
* regardless of what was set in the menu xml originally.
|
||||
* So basically all we're doing here is always setting up menuItem index 0.
|
||||
*/
|
||||
/*
|
||||
* Seems like the BottomNavigationView always initializes index 0 as isChecked / Selected,
|
||||
* regardless of what was set in the menu xml originally.
|
||||
* So basically all we're doing here is always setting up menuItem index 0.
|
||||
*/
|
||||
int itemId = menuItem.getItemId();
|
||||
configureRouter(getChildRouter(itemId), itemId);
|
||||
bottomNavigationView.setSelectedItemId(itemId);
|
||||
|
@ -160,11 +160,11 @@ public abstract class BottomNavigationController extends BaseController {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Since we are restoring our state,
|
||||
* and onRestoreInstanceState is called before onViewBound,
|
||||
* all we need to do is rebind.
|
||||
*/
|
||||
/*
|
||||
* Since we are restoring our state,
|
||||
* and onRestoreInstanceState is called before onViewBound,
|
||||
* all we need to do is rebind.
|
||||
*/
|
||||
Router childRouter = getChildRouter(currentlySelectedItemId);
|
||||
childRouter.rebindIfNeeded();
|
||||
lastActiveChildRouter = childRouter;
|
||||
|
@ -235,7 +235,7 @@ public abstract class BottomNavigationController extends BaseController {
|
|||
if (currentlySelectedItemId != itemId) {
|
||||
destroyChildRouter(lastActiveChildRouter, currentlySelectedItemId);
|
||||
|
||||
/* Ensure correct Checked state based on new selection */
|
||||
/* Ensure correct Checked state based on new selection */
|
||||
Menu menu = bottomNavigationView.getMenu();
|
||||
for (int i = 0; i < menu.size(); i++) {
|
||||
MenuItem menuItem = menu.getItem(i);
|
||||
|
@ -249,21 +249,21 @@ public abstract class BottomNavigationController extends BaseController {
|
|||
currentlySelectedItemId = itemId;
|
||||
Router childRouter = getChildRouter(currentlySelectedItemId);
|
||||
if (configureRouter(childRouter, currentlySelectedItemId)) {
|
||||
/* Determine if a Controller of same class already exists in the backstack */
|
||||
/* Determine if a Controller of same class already exists in the backstack */
|
||||
Controller backstackController;
|
||||
int size = childRouter.getBackstackSize();
|
||||
for (int i = 0; i < size; i++) {
|
||||
backstackController = childRouter.getBackstack().get(i).controller();
|
||||
if (BottomNavigationUtils.equals(backstackController.getClass(), controller.getClass())) {
|
||||
/* Match found at root - so just set new root */
|
||||
/* Match found at root - so just set new root */
|
||||
if (i == size - 1) {
|
||||
childRouter.setRoot(RouterTransaction.with(controller));
|
||||
} else {
|
||||
/* Match found at i - pop until we're at the matching Controller */
|
||||
/* Match found at i - pop until we're at the matching Controller */
|
||||
for (int j = size; j < i; j--) {
|
||||
childRouter.popCurrentController();
|
||||
}
|
||||
/* Replace the existing matching Controller with the new */
|
||||
/* Replace the existing matching Controller with the new */
|
||||
childRouter.replaceTopController(RouterTransaction.with(controller));
|
||||
}
|
||||
}
|
||||
|
@ -301,21 +301,21 @@ public abstract class BottomNavigationController extends BaseController {
|
|||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
if (lastActiveChildRouter == null && cachedSavedInstanceState != null) {
|
||||
/*
|
||||
* Here we assume that we're in a state
|
||||
* where the BottomNavigationController itself is in the backstack,
|
||||
* it has been restored, and is now being saved again.
|
||||
* In this case, the BottomNavigationController won't ever have had onViewBound() called,
|
||||
* and thus won't have any views to setup the Child Routers with.
|
||||
* In this case, we assume that we've previously had onSaveInstanceState() called
|
||||
* on us successfully, and thus have a cachedSavedInstanceState to use.
|
||||
*
|
||||
* To replicate issue this solves:
|
||||
* Navigate from BottomNavigationController to another controller not hosted in
|
||||
* the childRouter, background the app
|
||||
* (with developer setting "don't keep activities in memory" enabled on the device),
|
||||
* open the app again, and background it once more, and open it again to see it crash.
|
||||
*/
|
||||
/*
|
||||
* Here we assume that we're in a state
|
||||
* where the BottomNavigationController itself is in the backstack,
|
||||
* it has been restored, and is now being saved again.
|
||||
* In this case, the BottomNavigationController won't ever have had onViewBound() called,
|
||||
* and thus won't have any views to setup the Child Routers with.
|
||||
* In this case, we assume that we've previously had onSaveInstanceState() called
|
||||
* on us successfully, and thus have a cachedSavedInstanceState to use.
|
||||
*
|
||||
* To replicate issue this solves:
|
||||
* Navigate from BottomNavigationController to another controller not hosted in
|
||||
* the childRouter, background the app
|
||||
* (with developer setting "don't keep activities in memory" enabled on the device),
|
||||
* open the app again, and background it once more, and open it again to see it crash.
|
||||
*/
|
||||
outState.putSparseParcelableArray(
|
||||
KEY_STATE_ROUTER_BUNDLES,
|
||||
cachedSavedInstanceState.getSparseParcelableArray(KEY_STATE_ROUTER_BUNDLES));
|
||||
|
@ -323,20 +323,20 @@ public abstract class BottomNavigationController extends BaseController {
|
|||
KEY_STATE_CURRENTLY_SELECTED_ID,
|
||||
cachedSavedInstanceState.getInt(KEY_STATE_CURRENTLY_SELECTED_ID));
|
||||
} else if (currentlySelectedItemId != 0) {
|
||||
/*
|
||||
* Only save state if we have a valid item selected.
|
||||
*
|
||||
* Otherwise we may be in a state where we are in a backstack, but have never been shown.
|
||||
* I.e. if we are put in a synthesized backstack, we've never been shown any UI,
|
||||
* and therefore have nothing to save.
|
||||
*/
|
||||
/*
|
||||
* Only save state if we have a valid item selected.
|
||||
*
|
||||
* Otherwise we may be in a state where we are in a backstack, but have never been shown.
|
||||
* I.e. if we are put in a synthesized backstack, we've never been shown any UI,
|
||||
* and therefore have nothing to save.
|
||||
*/
|
||||
save(lastActiveChildRouter, currentlySelectedItemId);
|
||||
outState.putSparseParcelableArray(KEY_STATE_ROUTER_BUNDLES, routerSavedStateBundles);
|
||||
/*
|
||||
* For some reason the BottomNavigationView does not seem to correctly restore its
|
||||
* selectedId, even though the view appears with the correct state.
|
||||
* So we keep track of it manually
|
||||
*/
|
||||
/*
|
||||
* For some reason the BottomNavigationView does not seem to correctly restore its
|
||||
* selectedId, even though the view appears with the correct state.
|
||||
* So we keep track of it manually
|
||||
*/
|
||||
outState.putInt(KEY_STATE_CURRENTLY_SELECTED_ID, currentlySelectedItemId);
|
||||
lastActiveChildRouter = null;
|
||||
}
|
||||
|
@ -344,10 +344,10 @@ public abstract class BottomNavigationController extends BaseController {
|
|||
|
||||
@Override
|
||||
public boolean handleBack() {
|
||||
/*
|
||||
* The childRouter should handleBack,
|
||||
* as this BottomNavigationController doesn't have a back step sensible to the user.
|
||||
*/
|
||||
/*
|
||||
* The childRouter should handleBack,
|
||||
* as this BottomNavigationController doesn't have a back step sensible to the user.
|
||||
*/
|
||||
return lastActiveChildRouter != null && lastActiveChildRouter.handleBack();
|
||||
}
|
||||
|
||||
|
|
|
@ -352,6 +352,7 @@ public class OperationsMenuController extends BaseController {
|
|||
|
||||
|
||||
}
|
||||
|
||||
private void showResultImage(boolean everythingOK, boolean isGuestSupportError) {
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
||||
|
|
|
@ -49,7 +49,7 @@ public class DatabaseModule {
|
|||
return new SqlCipherDatabaseSource(context, Models.DEFAULT,
|
||||
context.getResources().getString(R.string.nc_app_name).toLowerCase()
|
||||
.replace(" ", "_").trim() + ".sqlite",
|
||||
context.getString(R.string.nc_talk_database_encryption_key), 3);
|
||||
context.getString(R.string.nc_talk_database_encryption_key), 4);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
@ -30,7 +30,9 @@ import com.nextcloud.talk.BuildConfig;
|
|||
import com.nextcloud.talk.api.NcApi;
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||
import com.nextcloud.talk.utils.ApiUtils;
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils;
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||
import com.nextcloud.talk.utils.ssl.MagicKeyManager;
|
||||
import com.nextcloud.talk.utils.ssl.MagicTrustManager;
|
||||
import com.nextcloud.talk.utils.ssl.SSLSocketFactoryCompat;
|
||||
|
||||
|
@ -38,9 +40,16 @@ import java.io.IOException;
|
|||
import java.net.CookieManager;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
@ -110,8 +119,35 @@ public class RestModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
SSLSocketFactoryCompat provideSslSocketFactoryCompat(MagicTrustManager magicTrustManager) {
|
||||
return new SSLSocketFactoryCompat(magicTrustManager);
|
||||
MagicKeyManager provideKeyManager(AppPreferences appPreferences, UserUtils userUtils) {
|
||||
KeyStore keyStore = null;
|
||||
try {
|
||||
keyStore = KeyStore.getInstance("AndroidKeyStore");
|
||||
keyStore.load(null);
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keyStore, null);
|
||||
X509KeyManager origKm = (X509KeyManager) kmf.getKeyManagers()[0];
|
||||
return new MagicKeyManager(origKm, userUtils, appPreferences);
|
||||
} catch (KeyStoreException e) {
|
||||
Log.e(TAG, "KeyStoreException " + e.getLocalizedMessage());
|
||||
} catch (CertificateException e) {
|
||||
Log.e(TAG, "CertificateException " + e.getLocalizedMessage());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Log.e(TAG, "NoSuchAlgorithmException " + e.getLocalizedMessage());
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "IOException " + e.getLocalizedMessage());
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
Log.e(TAG, "UnrecoverableKeyException " + e.getLocalizedMessage());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SSLSocketFactoryCompat provideSslSocketFactoryCompat(MagicKeyManager keyManager, MagicTrustManager
|
||||
magicTrustManager) {
|
||||
return new SSLSocketFactoryCompat(keyManager, magicTrustManager);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
@ -145,10 +181,10 @@ public class RestModule {
|
|||
if (BuildConfig.DEBUG) {
|
||||
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
||||
|
||||
httpClient.addInterceptor(loggingInterceptor);
|
||||
}
|
||||
|
||||
// Trust own CA and all self-signed certs
|
||||
httpClient.sslSocketFactory(sslSocketFactoryCompat, magicTrustManager);
|
||||
httpClient.retryOnConnectionFailure(true);
|
||||
httpClient.hostnameVerifier(magicTrustManager.getHostnameVerifier(OkHostnameVerifier.INSTANCE));
|
||||
|
|
|
@ -118,7 +118,7 @@ public class CapabilitiesJob extends Job {
|
|||
userUtils.createOrUpdateUser(null, null,
|
||||
null, null,
|
||||
null, null, null, internalUserEntity.getId(),
|
||||
LoganSquare.serialize(capabilitiesOverall.getOcs().getData().getCapabilities()))
|
||||
LoganSquare.serialize(capabilitiesOverall.getOcs().getData().getCapabilities()), null)
|
||||
.subscribeOn(Schedulers.newThread())
|
||||
.subscribe(new Observer<UserEntity>() {
|
||||
@Override
|
||||
|
|
|
@ -55,6 +55,8 @@ public interface User extends Parcelable, Persistable, Serializable {
|
|||
|
||||
String getCapabilities();
|
||||
|
||||
String getClientCertificate();
|
||||
|
||||
boolean getCurrent();
|
||||
|
||||
boolean getScheduledForDeletion();
|
||||
|
|
|
@ -318,7 +318,7 @@ public class PushUtils {
|
|||
null, null,
|
||||
userEntity.getDisplayName(),
|
||||
LoganSquare.serialize(pushConfigurationState), null,
|
||||
null, userEntity.getId(), null)
|
||||
null, userEntity.getId(), null, null)
|
||||
.subscribe(new Observer<UserEntity>() {
|
||||
@Override
|
||||
public void onSubscribe(Disposable d) {
|
||||
|
|
|
@ -161,7 +161,8 @@ public class UserUtils {
|
|||
@Nullable Boolean currentUser,
|
||||
@Nullable String userId,
|
||||
@Nullable Long internalId,
|
||||
@Nullable String capabilities) {
|
||||
@Nullable String capabilities,
|
||||
@Nullable String certificateAlias) {
|
||||
Result findUserQueryResult;
|
||||
if (internalId == null) {
|
||||
findUserQueryResult = dataStore.select(User.class).where(UserEntity.USERNAME.eq(username).
|
||||
|
@ -194,6 +195,10 @@ public class UserUtils {
|
|||
user.setCapabilities(capabilities);
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(certificateAlias)) {
|
||||
user.setClientCertificate(certificateAlias);
|
||||
}
|
||||
|
||||
user.setCurrent(true);
|
||||
|
||||
} else {
|
||||
|
@ -218,6 +223,11 @@ public class UserUtils {
|
|||
user.setCapabilities(capabilities);
|
||||
}
|
||||
|
||||
if (certificateAlias != null && !certificateAlias.equals(user.getClientCertificate())) {
|
||||
user.setClientCertificate(certificateAlias);
|
||||
}
|
||||
|
||||
|
||||
if (currentUser != null) {
|
||||
user.setCurrent(currentUser);
|
||||
}
|
||||
|
|
|
@ -115,8 +115,19 @@ public interface AppPreferences {
|
|||
void setPushToken(String pushToken);
|
||||
|
||||
@KeyByString("push_token")
|
||||
@RemoveMethod
|
||||
void removePushToken();
|
||||
|
||||
@KeyByString("tempClientCertAlias")
|
||||
String getTemporaryClientCertAlias();
|
||||
|
||||
@KeyByString("tempClientCertAlias")
|
||||
void setTemporaryClientCertAlias(String alias);
|
||||
|
||||
@KeyByString("tempClientCertAlias")
|
||||
@RemoveMethod
|
||||
void removeTemporaryClientCertAlias();
|
||||
|
||||
@KeyByString("pushToTalk_intro_shown")
|
||||
boolean getPushToTalkIntroShown();
|
||||
|
||||
|
@ -124,9 +135,9 @@ public interface AppPreferences {
|
|||
void setPushToTalkIntroShown(boolean shown);
|
||||
|
||||
@KeyByString("pushToTalk_intro_shown")
|
||||
@RemoveMethod
|
||||
void removePushToTalkIntroShown();
|
||||
|
||||
|
||||
@ClearMethod
|
||||
void clear();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
* Nextcloud Talk application
|
||||
*
|
||||
* @author Mario Danic
|
||||
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.nextcloud.talk.utils.ssl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.security.KeyChain;
|
||||
import android.security.KeyChainException;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication;
|
||||
import com.nextcloud.talk.models.database.UserEntity;
|
||||
import com.nextcloud.talk.utils.database.user.UserUtils;
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.security.Principal;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.net.ssl.X509KeyManager;
|
||||
|
||||
public class MagicKeyManager implements X509KeyManager {
|
||||
private static final String TAG = "MagicKeyManager";
|
||||
private final X509KeyManager keyManager;
|
||||
|
||||
private UserUtils userUtils;
|
||||
private AppPreferences appPreferences;
|
||||
private Context context;
|
||||
|
||||
public MagicKeyManager(X509KeyManager keyManager, UserUtils userUtils, AppPreferences appPreferences) {
|
||||
this.keyManager = keyManager;
|
||||
this.userUtils = userUtils;
|
||||
this.appPreferences = appPreferences;
|
||||
|
||||
context = NextcloudTalkApplication.getSharedApplication().getApplicationContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseClientAlias(String[] strings, Principal[] principals, Socket socket) {
|
||||
String alias;
|
||||
if (!TextUtils.isEmpty(alias = userUtils.getCurrentUser().getClientCertificate()) ||
|
||||
!TextUtils.isEmpty(alias = appPreferences.getTemporaryClientCertAlias())
|
||||
&& new ArrayList<>(Arrays.asList(getClientAliases())).contains(alias)) {
|
||||
return alias;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String chooseServerAlias(String s, Principal[] principals, Socket socket) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private X509Certificate[] getCertificatesForAlias(@Nullable String alias) {
|
||||
if (alias != null) {
|
||||
GetCertificatesForAliasRunnable getCertificatesForAliasRunnable = new GetCertificatesForAliasRunnable(alias);
|
||||
Thread getCertificatesThread = new Thread(getCertificatesForAliasRunnable);
|
||||
getCertificatesThread.start();
|
||||
try {
|
||||
getCertificatesThread.join();
|
||||
return getCertificatesForAliasRunnable.getCertificates();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Failed to join the thread while getting certificates: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private PrivateKey getPrivateKeyForAlias(@Nullable String alias) {
|
||||
if (alias != null) {
|
||||
GetPrivateKeyForAliasRunnable getPrivateKeyForAliasRunnable = new GetPrivateKeyForAliasRunnable(alias);
|
||||
Thread getPrivateKeyThread = new Thread(getPrivateKeyForAliasRunnable);
|
||||
getPrivateKeyThread.start();
|
||||
try {
|
||||
getPrivateKeyThread.join();
|
||||
return getPrivateKeyForAliasRunnable.getPrivateKey();
|
||||
} catch (InterruptedException e) {
|
||||
Log.e(TAG, "Failed to join the thread while getting private key: " + e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getCertificateChain(String s) {
|
||||
if (new ArrayList<>(Arrays.asList(getClientAliases())).contains(s)) {
|
||||
return getCertificatesForAlias(s);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private String[] getClientAliases() {
|
||||
Set<String> aliases = new HashSet<>();
|
||||
String alias;
|
||||
if (!TextUtils.isEmpty(alias = appPreferences.getTemporaryClientCertAlias())) {
|
||||
aliases.add(alias);
|
||||
}
|
||||
|
||||
List<UserEntity> userEntities = userUtils.getUsers();
|
||||
for (int i = 0; i < userEntities.size(); i++) {
|
||||
if (!TextUtils.isEmpty(alias = userEntities.get(i).getClientCertificate())) {
|
||||
aliases.add(alias);
|
||||
}
|
||||
}
|
||||
|
||||
return aliases.toArray(new String[aliases.size()]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getClientAliases(String s, Principal[] principals) {
|
||||
return getClientAliases();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getServerAliases(String s, Principal[] principals) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrivateKey getPrivateKey(String s) {
|
||||
if (new ArrayList<>(Arrays.asList(getClientAliases())).contains(s)) {
|
||||
return getPrivateKeyForAlias(s);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private class GetCertificatesForAliasRunnable implements Runnable {
|
||||
private volatile X509Certificate[] certificates;
|
||||
private String alias;
|
||||
|
||||
public GetCertificatesForAliasRunnable(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
certificates = KeyChain.getCertificateChain(context, alias);
|
||||
} catch (KeyChainException | InterruptedException e) {
|
||||
Log.e(TAG, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public X509Certificate[] getCertificates() {
|
||||
return certificates;
|
||||
}
|
||||
}
|
||||
|
||||
private class GetPrivateKeyForAliasRunnable implements Runnable {
|
||||
private volatile PrivateKey privateKey;
|
||||
private String alias;
|
||||
|
||||
public GetPrivateKeyForAliasRunnable(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
privateKey = KeyChain.getPrivateKey(context, alias);
|
||||
} catch (KeyChainException | InterruptedException e) {
|
||||
Log.e(TAG, e.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public PrivateKey getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -14,12 +14,10 @@ import java.net.InetAddress
|
|||
import java.net.Socket
|
||||
import java.security.GeneralSecurityException
|
||||
import java.util.*
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLSocket
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import javax.net.ssl.*
|
||||
|
||||
class SSLSocketFactoryCompat(trustManager: X509TrustManager) : SSLSocketFactory() {
|
||||
class SSLSocketFactoryCompat(keyManager: KeyManager?,
|
||||
trustManager: X509TrustManager) : SSLSocketFactory() {
|
||||
|
||||
private var delegate: SSLSocketFactory
|
||||
|
||||
|
@ -101,7 +99,10 @@ class SSLSocketFactoryCompat(trustManager: X509TrustManager) : SSLSocketFactory(
|
|||
init {
|
||||
try {
|
||||
val sslContext = SSLContext.getInstance("TLS")
|
||||
sslContext.init(null, arrayOf(trustManager), null)
|
||||
sslContext.init(
|
||||
if (keyManager != null) arrayOf(keyManager) else null,
|
||||
arrayOf(trustManager),
|
||||
null)
|
||||
delegate = sslContext.socketFactory
|
||||
} catch (e: GeneralSecurityException) {
|
||||
throw IllegalStateException() // system has no TLS
|
||||
|
|
|
@ -91,4 +91,19 @@
|
|||
android:textAllCaps="true"
|
||||
android:textColor="@color/nc_light_blue_color"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cert_text_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/helper_text_view"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginStart="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginTop="16dp"
|
||||
android:lines="2"
|
||||
android:text="@string/nc_configure_cert_auth"
|
||||
android:textAlignment="center"
|
||||
android:textAllCaps="true"
|
||||
android:textColor="@color/nc_light_blue_color"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -86,10 +86,17 @@
|
|||
apc:mp_title="@string/nc_settings_reauthorize"/>
|
||||
|
||||
<com.yarolegovich.mp.MaterialStandardPreference
|
||||
android:id="@+id/settings_remove_account"
|
||||
android:id="@+id/settings_client_cert"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/settings_reauthorize"
|
||||
apc:mp_title="@string/nc_client_cert_setup"/>
|
||||
|
||||
<com.yarolegovich.mp.MaterialStandardPreference
|
||||
android:id="@+id/settings_remove_account"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/settings_client_cert"
|
||||
apc:mp_title="@string/nc_settings_remove_account"/>
|
||||
|
||||
<com.yarolegovich.mp.MaterialStandardPreference
|
||||
|
|
|
@ -50,6 +50,8 @@
|
|||
<string name="nc_settings_use_credentials_key">proxy_credentials</string>
|
||||
<string name="nc_settings_switch_account">Switch between accounts</string>
|
||||
<string name="nc_settings_reauthorize">Reauthorize</string>
|
||||
<string name="nc_client_cert_setup">Set up client certificate</string>
|
||||
<string name="nc_client_cert_change">Change client certificate</string>
|
||||
<string name="nc_settings_remove_account">Remove account</string>
|
||||
<string name="nc_settings_add_account">Add a new account</string>
|
||||
<string name="nc_settings_wrong_account">Only current account can be reauthorized</string>
|
||||
|
@ -135,6 +137,8 @@
|
|||
<string name="nc_push_to_talk">Push-to-talk</string>
|
||||
<string name="nc_push_to_talk_desc">With microphone disabled, click&hold to use Push-to-talk</string>
|
||||
<string name="nc_store_short_desc">Have private video calls and chat using your own server.</string>
|
||||
<string name="nc_configure_cert_auth">Select authentication certificate</string>
|
||||
<string name="nc_change_cert_auth">Change authentication certificate</string>
|
||||
<string name="nc_store_full_desc">Use Nextcloud Talk to have one-on-one or group audio or video calls, create or join web conferences and send chat messages. All communication is fully encrypted and mediated by your own server, providing the highest degree of privacy possible.
|
||||
|
||||
Nextcloud Talk is easy to use and will always be completely free!
|
||||
|
|
Loading…
Reference in a new issue