diff --git a/app/build.gradle b/app/build.gradle index 76e7b18d2..dacc042d4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -48,7 +48,7 @@ android { testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" versionCode 130 - versionName "8.0.0alpha1" + versionName "9.0.0alpha1" flavorDimensions "default" renderscriptTargetApi 19 @@ -159,8 +159,6 @@ ext { lifecycle_version = '2.2.0-rc03' coil_version = "0.9.1" room_version = "2.2.3" - geckoviewChannel = "nightly" - geckoviewVersion = "71.0.20200108003105" } configurations.all { @@ -187,7 +185,6 @@ dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3' implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" - implementation "org.mozilla.geckoview:geckoview:${geckoviewVersion}" implementation "com.github.stateless4j:stateless4j:2.6.0" // ViewModel and LiveData diff --git a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt index 9983c13f3..2b7498d5d 100644 --- a/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt +++ b/app/src/gplay/java/com/nextcloud/talk/services/firebase/MagicFirebaseMessagingService.kt @@ -23,7 +23,6 @@ import android.annotation.SuppressLint import com.google.firebase.messaging.FirebaseMessagingService import com.google.firebase.messaging.RemoteMessage import com.nextcloud.talk.jobs.NotificationWorker -import com.nextcloud.talk.jobs.PushRegistrationWorker import com.nextcloud.talk.utils.bundle.BundleKeys import androidx.work.Data import androidx.work.OneTimeWorkRequest @@ -38,8 +37,6 @@ class MagicFirebaseMessagingService : FirebaseMessagingService(), KoinComponent override fun onNewToken(token: String) { super.onNewToken(token) appPreferences.pushToken = token - val pushRegistrationWork: OneTimeWorkRequest = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build() - WorkManager.getInstance().enqueue(pushRegistrationWork) } @SuppressLint("LongLogTag") diff --git a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt index a0438b055..5628eefe2 100644 --- a/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt +++ b/app/src/main/java/com/nextcloud/talk/application/NextcloudTalkApplication.kt @@ -37,12 +37,11 @@ import com.nextcloud.talk.BuildConfig import com.nextcloud.talk.components.filebrowser.webdav.DavUtils import com.nextcloud.talk.jobs.AccountRemovalWorker import com.nextcloud.talk.jobs.CapabilitiesWorker -import com.nextcloud.talk.jobs.PushRegistrationWorker import com.nextcloud.talk.jobs.SignalingSettingsWorker import com.nextcloud.talk.models.ExternalSignalingServer import com.nextcloud.talk.models.database.UserEntity import com.nextcloud.talk.models.json.capabilities.Capabilities -import com.nextcloud.talk.models.json.push.PushConfigurationState +import com.nextcloud.talk.models.json.push.PushConfiguration import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings import com.nextcloud.talk.newarch.di.module.* import com.nextcloud.talk.newarch.domain.di.module.UseCasesModule @@ -71,7 +70,6 @@ import org.koin.android.ext.android.inject import org.koin.android.ext.koin.androidContext import org.koin.android.ext.koin.androidLogger import org.koin.core.context.startKoin -import org.mozilla.geckoview.GeckoRuntime import org.webrtc.PeerConnectionFactory import org.webrtc.voiceengine.WebRtcAudioManager import org.webrtc.voiceengine.WebRtcAudioUtils @@ -140,8 +138,6 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration Security.insertProviderAt(Conscrypt.newProvider(), 1) ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync() - val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java) - .build() val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java) .build() val periodicCapabilitiesUpdateWork = PeriodicWorkRequest.Builder( @@ -152,8 +148,6 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration val signalingSettingsWork = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java) .build() - WorkManager.getInstance(this) - .enqueue(pushRegistrationWork) WorkManager.getInstance(this) .enqueue(accountRemovalWork) WorkManager.getInstance(this) @@ -202,7 +196,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration userNg.displayName = user.displayName try { userNg.pushConfiguration = - LoganSquare.parse(user.pushConfigurationState, PushConfigurationState::class.java) + LoganSquare.parse(user.pushConfigurationState, PushConfiguration::class.java) } catch (e: Exception) { // no push } diff --git a/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.kt b/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.kt deleted file mode 100644 index f3d0830b1..000000000 --- a/app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.kt +++ /dev/null @@ -1,440 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 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 . - */ - -package com.nextcloud.talk.controllers - -import android.annotation.SuppressLint -import android.content.pm.ActivityInfo -import android.os.Bundle -import android.os.Handler -import android.text.TextUtils -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.TextView -import androidx.work.Data -import androidx.work.OneTimeWorkRequest -import androidx.work.WorkManager -import butterknife.BindView -import com.bluelinelabs.conductor.RouterTransaction -import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler -import com.nextcloud.talk.R -import com.nextcloud.talk.api.NcApi -import com.nextcloud.talk.controllers.base.BaseController -import com.nextcloud.talk.events.EventStatus -import com.nextcloud.talk.jobs.CapabilitiesWorker -import com.nextcloud.talk.jobs.PushRegistrationWorker -import com.nextcloud.talk.jobs.SignalingSettingsWorker -import com.nextcloud.talk.models.json.conversations.RoomsOverall -import com.nextcloud.talk.models.json.generic.Status -import com.nextcloud.talk.models.json.userprofile.UserProfileOverall -import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository -import com.nextcloud.talk.newarch.features.conversationslist.ConversationsListView -import com.nextcloud.talk.newarch.local.dao.UsersDao -import com.nextcloud.talk.newarch.local.models.UserNgEntity -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.ClosedInterfaceImpl -import com.nextcloud.talk.utils.bundle.BundleKeys -import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder -import com.uber.autodispose.AutoDispose -import com.uber.autodispose.ObservableSubscribeProxy -import io.reactivex.Observer -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.disposables.Disposable -import io.reactivex.schedulers.Schedulers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.greenrobot.eventbus.Subscribe -import org.greenrobot.eventbus.ThreadMode -import org.koin.core.KoinComponent -import org.koin.core.inject -import java.net.CookieManager - -class AccountVerificationController(args: Bundle?) : BaseController(), KoinComponent { - - val ncApi: NcApi by inject() - val cookieManager: CookieManager by inject() - val usersRepository: UsersRepository by inject() - val usersDao: UsersDao by inject() - - @JvmField - @BindView(R.id.progress_text) - internal var progressText: TextView? = null - - private var internalAccountId: Long = -1 - - private var baseUrl: String? = null - private var username: String? = null - private var token: String? = null - private var isAccountImport: Boolean = false - private var originalProtocol: String? = null - - init { - if (args != null) { - baseUrl = args.getString(BundleKeys.KEY_BASE_URL) - username = args.getString(BundleKeys.KEY_USERNAME) - token = args.getString(BundleKeys.KEY_TOKEN) - if (args.containsKey(BundleKeys.KEY_IS_ACCOUNT_IMPORT)) { - isAccountImport = true - } - if (args.containsKey(BundleKeys.KEY_ORIGINAL_PROTOCOL)) { - originalProtocol = args.getString(BundleKeys.KEY_ORIGINAL_PROTOCOL) - } - } - } - - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - return inflater.inflate(R.layout.controller_account_verification, container, false) - } - - override fun onDetach(view: View) { - eventBus.unregister(this) - super.onDetach(view) - } - - override fun onAttach(view: View) { - super.onAttach(view) - eventBus.register(this) - } - - @SuppressLint("SourceLockedOrientationActivity") - override fun onViewBound(view: View) { - super.onViewBound(view) - if (activity != null) { - activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } - - if (actionBar != null) { - actionBar!!.hide() - } - - if (isAccountImport && !baseUrl!!.startsWith("http://") && !baseUrl!!.startsWith("https://") || !TextUtils - .isEmpty(originalProtocol) && !baseUrl!!.startsWith(originalProtocol!!)) { - determineBaseUrlProtocol(true) - } else { - checkEverything() - } - } - - private fun checkEverything() { - val credentials = ApiUtils.getCredentials(username, token) - cookieManager.cookieStore.removeAll() - - findServerTalkApp(credentials) - } - - private fun determineBaseUrlProtocol(checkForcedHttps: Boolean) { - cookieManager.cookieStore.removeAll() - - val queryUrl: String - - baseUrl = baseUrl!!.replace("http://", "").replace("https://", "") - - if (checkForcedHttps) { - queryUrl = "https://" + baseUrl + ApiUtils.getUrlPostfixForStatus() - } else { - queryUrl = "http://" + baseUrl + ApiUtils.getUrlPostfixForStatus() - } - - ncApi.getServerStatus(queryUrl) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .`as`>(AutoDispose.autoDisposable(scopeProvider)) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) {} - - override fun onNext(status: Status) { - if (checkForcedHttps) { - baseUrl = "https://" + baseUrl!! - } else { - baseUrl = "http://" + baseUrl!! - } - - if (isAccountImport) { - router.replaceTopController( - RouterTransaction.with(WebViewLoginController(baseUrl, - false, username, "")) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler())) - } else { - checkEverything() - } - } - - override fun onError(e: Throwable) { - if (checkForcedHttps) { - determineBaseUrlProtocol(false) - } else { - GlobalScope.launch { - abortVerification() - - } - } - } - - override fun onComplete() { - - } - }) - } - - private fun findServerTalkApp(credentials: String?) { - ncApi.getRooms(credentials, ApiUtils.getUrlForGetRooms(baseUrl)) - .subscribeOn(Schedulers.io()) - .`as`>(AutoDispose.autoDisposable(scopeProvider)) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) {} - - override fun onNext(roomsOverall: RoomsOverall) { - fetchProfile(credentials) - } - - override fun onError(e: Throwable) { - if (activity != null && resources != null) { - activity!!.runOnUiThread { - progressText!!.text = String.format(resources!!.getString( - R.string.nc_nextcloud_talk_app_not_installed), - resources!!.getString(R.string.nc_app_name)) - } - } - - ApplicationWideMessageHolder.getInstance().messageType = ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK - - GlobalScope.launch { - abortVerification() - } - } - - override fun onComplete() { - - } - }) - } - - private suspend fun storeProfile(displayName: String?, userId: String) { - var user = usersRepository.getUserWithUsernameAndServer(username!!, baseUrl!!) - if (user == null) { - user = UserNgEntity(null, userId, username!!, baseUrl!!, token, displayName) - internalAccountId = usersDao.saveUser(user) - } else { - user.displayName = displayName - usersRepository.updateUser(user) - internalAccountId = user.id!! - } - - if (ClosedInterfaceImpl().isGooglePlayServicesAvailable) { - registerForPush() - } else { - activity!!.runOnUiThread { - progressText!!.text = progressText!!.text.toString() + "\n" + - resources!!.getString(R.string.nc_push_disabled) - } - fetchAndStoreCapabilities() - } - } - - private fun fetchProfile(credentials: String?) { - ncApi.getUserProfile(credentials, - ApiUtils.getUrlForUserProfile(baseUrl)) - .subscribeOn(Schedulers.io()) - .`as`>(AutoDispose.autoDisposable(scopeProvider)) - .subscribe(object : Observer { - override fun onSubscribe(d: Disposable) {} - - override fun onNext(userProfileOverall: UserProfileOverall) { - var displayName: String? = userProfileOverall.ocs.data.displayName - - if (!TextUtils.isEmpty(displayName)) { - GlobalScope.launch { - storeProfile(displayName, userProfileOverall.ocs.data.userId!!) - } - } else { - if (activity != null) { - activity!!.runOnUiThread { - progressText!!.text = progressText!!.text.toString() + "\n" + - resources!!.getString(R.string.nc_display_name_not_fetched) - } - } - GlobalScope.launch { - abortVerification() - } - } - } - - override fun onError(e: Throwable) { - if (activity != null) { - activity!!.runOnUiThread { - progressText!!.text = progressText!!.text.toString() + "\n" + - resources!!.getString(R.string.nc_display_name_not_fetched) - } - } - GlobalScope.launch { - abortVerification() - } - } - - override fun onComplete() { - - } - }) - } - - private fun registerForPush() { - val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build() - WorkManager.getInstance().enqueue(pushRegistrationWork) - } - - @Subscribe(threadMode = ThreadMode.BACKGROUND) - fun onMessageEvent(eventStatus: EventStatus) { - if (EventStatus.EventType.PUSH_REGISTRATION == eventStatus.eventType) { - if (internalAccountId == eventStatus.userId - && !eventStatus.allGood - && activity != null) { - activity!!.runOnUiThread { - progressText!!.text = progressText!!.text.toString() + "\n" + - resources!!.getString(R.string.nc_push_disabled) - } - } - fetchAndStoreCapabilities() - } else if (EventStatus.EventType.CAPABILITIES_FETCH == eventStatus.eventType) { - if (internalAccountId == eventStatus.userId && !eventStatus.allGood) { - if (activity != null) { - activity!!.runOnUiThread { - progressText!!.text = progressText!!.text.toString() + "\n" + - resources!!.getString(R.string.nc_capabilities_failed) - } - } - GlobalScope.launch { - abortVerification() - } - } else if (internalAccountId == eventStatus.userId && eventStatus.allGood) { - fetchAndStoreExternalSignalingSettings() - } - } else if (EventStatus.EventType.SIGNALING_SETTINGS == eventStatus.eventType) { - if (internalAccountId == eventStatus.userId && !eventStatus.allGood) { - if (activity != null) { - activity!!.runOnUiThread { - progressText!!.text = progressText!!.text.toString() + "\n" + - resources!!.getString(R.string.nc_external_server_failed) - } - } - } - - GlobalScope.launch { - proceedWithLogin() - } - } - } - - private fun fetchAndStoreCapabilities() { - val userData = Data.Builder() - .putLong(BundleKeys.KEY_INTERNAL_USER_ID, internalAccountId) - .build() - - val pushNotificationWork = OneTimeWorkRequest.Builder(CapabilitiesWorker::class.java) - .setInputData(userData) - .build() - WorkManager.getInstance().enqueue(pushNotificationWork) - } - - private fun fetchAndStoreExternalSignalingSettings() { - val userData = Data.Builder() - .putLong(BundleKeys.KEY_INTERNAL_USER_ID, internalAccountId) - .build() - - val signalingSettings = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java) - .setInputData(userData) - .build() - WorkManager.getInstance().enqueue(signalingSettings) - } - - private suspend fun proceedWithLogin() { - cookieManager.cookieStore.removeAll() - usersRepository.setUserAsActiveWithId(internalAccountId) - - if (activity != null) { - if (usersRepository.getUsers().count() == 1) { - activity!!.runOnUiThread { - router.setRoot(RouterTransaction.with(ConversationsListView()) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler())) - } - } else { - if (isAccountImport) { - ApplicationWideMessageHolder.getInstance().messageType = ApplicationWideMessageHolder.MessageType.ACCOUNT_WAS_IMPORTED - } - activity!!.runOnUiThread { - router.popToRoot() - } - } - } - } - - override fun onDestroyView(view: View) { - super.onDestroyView(view) - if (activity != null) { - activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR - } - } - - private suspend fun abortVerification() { - - if (!isAccountImport) { - if (internalAccountId != -1L) { - usersRepository.deleteUserWithId(internalAccountId) - activity!!.runOnUiThread { Handler().postDelayed({ router.popToRoot() }, 7500) } - - } else { - if (activity != null) { - activity!!.runOnUiThread { Handler().postDelayed({ router.popToRoot() }, 7500) } - } - } - } else { - ApplicationWideMessageHolder.getInstance().messageType = - ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT - if (activity != null) { - activity!!.runOnUiThread { - Handler().postDelayed({ - if (router.hasRootController()) { - if (activity != null) { - router.popToRoot() - } - } else { - if (usersRepository.getUsers().count() > 0) { - router.setRoot(RouterTransaction.with(ConversationsListView()) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler())) - } else { - router.setRoot(RouterTransaction.with(ServerSelectionController()) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler())) - } - } - }, 7500) - } - } - } - } - - companion object { - - const val TAG = "AccountVerificationController" - } -} diff --git a/app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.kt b/app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.kt deleted file mode 100644 index d271566bf..000000000 --- a/app/src/main/java/com/nextcloud/talk/controllers/ServerSelectionController.kt +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 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 . - */ -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.text.Editable -import android.text.TextUtils -import android.text.TextWatcher -import android.view.KeyEvent -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.inputmethod.EditorInfo -import android.widget.ProgressBar -import android.widget.TextView -import butterknife.BindView -import butterknife.OnClick -import com.bluelinelabs.conductor.RouterTransaction -import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler -import com.nextcloud.talk.R -import com.nextcloud.talk.api.NcApi -import com.nextcloud.talk.controllers.base.BaseController -import com.nextcloud.talk.models.json.generic.Status -import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository -import com.nextcloud.talk.utils.AccountUtils.findAccounts -import com.nextcloud.talk.utils.AccountUtils.getAppNameBasedOnPackage -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_IS_ACCOUNT_IMPORT -import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder -import com.uber.autodispose.AutoDispose -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.koin.android.ext.android.inject -import studio.carbonylgroup.textfieldboxes.ExtendedEditText -import studio.carbonylgroup.textfieldboxes.TextFieldBoxes -import java.security.cert.CertificateException - -class ServerSelectionController : BaseController() { - @JvmField - @BindView(R.id.extended_edit_text) - var serverEntry: ExtendedEditText? = null - @JvmField - @BindView(R.id.text_field_boxes) - var textFieldBoxes: TextFieldBoxes? = null - @JvmField - @BindView(R.id.progress_bar) - var progressBar: ProgressBar? = null - @JvmField - @BindView(R.id.helper_text_view) - var providersTextView: TextView? = null - @JvmField - @BindView(R.id.cert_text_view) - var certTextView: TextView? = null - - val usersRepository: UsersRepository by inject() - val ncApi: NcApi by inject() - - override fun inflateView(inflater: LayoutInflater, container: ViewGroup): View { - return inflater.inflate(R.layout.controller_server_selection, container, false) - } - - @SuppressLint("LongLogTag") - @OnClick(R.id.cert_text_view) - fun onCertClick() { - if (activity != null) { - KeyChain.choosePrivateKeyAlias(activity!!, { alias: String? -> - if (alias != null) { - appPreferences.temporaryClientCertAlias = alias - } else { - appPreferences.removeTemporaryClientCertAlias() - } - setCertTextView() - }, arrayOf("RSA", "EC"), null, null, -1, null) - } - } - - override fun onViewBound(view: View) { - super.onViewBound(view) - if (activity != null) { - activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } - if (actionBar != null) { - actionBar!!.hide() - } - textFieldBoxes!!.endIconImageButton - .setBackgroundDrawable(resources!!.getDrawable(R.drawable.ic_arrow_forward_white_24px)) - textFieldBoxes!!.endIconImageButton.alpha = 0.5f - textFieldBoxes!!.endIconImageButton.isEnabled = false - textFieldBoxes!!.endIconImageButton.visibility = View.VISIBLE - textFieldBoxes!!.endIconImageButton.setOnClickListener { view1: View? -> checkServerAndProceed() } - if (TextUtils.isEmpty(resources!!.getString(R.string.nc_providers_url)) - && TextUtils.isEmpty(resources!!.getString(R.string.nc_import_account_type))) { - providersTextView!!.visibility = View.INVISIBLE - } else { - GlobalScope.launch { - val users = usersRepository.getUsers() - val usersSize = users.count() - - withContext(Dispatchers.Main) { - if ((TextUtils.isEmpty(resources!!.getString(R.string.nc_import_account_type)) || - findAccounts(users).isEmpty()) && - usersSize == 0) { - providersTextView!!.setText(R.string.nc_get_from_provider) - providersTextView!!.setOnClickListener { view12: View? -> - val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(resources!! - .getString(R.string.nc_providers_url))) - startActivity(browserIntent) - } - } else if (findAccounts(users).isNotEmpty()) { - if (!TextUtils.isEmpty(getAppNameBasedOnPackage(resources!! - .getString(R.string.nc_import_accounts_from)))) { - if (findAccounts(users).size > 1) { - providersTextView!!.text = String.format(resources!!.getString(R.string.nc_server_import_accounts), - getAppNameBasedOnPackage(resources!! - .getString(R.string.nc_import_accounts_from))) - } else { - providersTextView!!.text = String.format(resources!!.getString(R.string.nc_server_import_account), - getAppNameBasedOnPackage(resources!! - .getString(R.string.nc_import_accounts_from))) - } - } else { - if (findAccounts(users).size > 1) { - providersTextView!!.text = resources!!.getString(R.string.nc_server_import_accounts_plain) - } else { - providersTextView!!.text = resources!!.getString(R.string.nc_server_import_account_plain) - } - } - providersTextView!!.setOnClickListener { view13: View? -> - val bundle = Bundle() - bundle.putBoolean(KEY_IS_ACCOUNT_IMPORT, true) - router.pushController(RouterTransaction.with( - SwitchAccountController(bundle)) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler())) - } - } else { - providersTextView!!.visibility = View.INVISIBLE - } - } - serverEntry!!.requestFocus() - serverEntry!!.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} - override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} - override fun afterTextChanged(editable: Editable) { - if (!textFieldBoxes!!.isOnError && !TextUtils.isEmpty(serverEntry!!.text)) { - toggleProceedButton(true) - } else { - toggleProceedButton(false) - } - } - }) - serverEntry!!.setOnEditorActionListener { textView: TextView?, i: Int, keyEvent: KeyEvent? -> - if (i == EditorInfo.IME_ACTION_DONE) { - checkServerAndProceed() - } - false - } - } - } - } - - private fun toggleProceedButton(show: Boolean) { - textFieldBoxes!!.endIconImageButton.isEnabled = show - if (show) { - textFieldBoxes!!.endIconImageButton.alpha = 1f - } else { - textFieldBoxes!!.endIconImageButton.alpha = 0.5f - } - } - - private fun checkServerAndProceed() { - var url = serverEntry!!.text.toString().trim { it <= ' ' } - serverEntry!!.isEnabled = false - progressBar!!.visibility = View.VISIBLE - if (providersTextView!!.visibility != View.INVISIBLE) { - providersTextView!!.visibility = View.INVISIBLE - certTextView!!.visibility = View.INVISIBLE - } - if (url.endsWith("/")) { - url = url.substring(0, url.length - 1) - } - val queryUrl = url + ApiUtils.getUrlPostfixForStatus() - if (url.startsWith("http://") || url.startsWith("https://")) { - checkServer(queryUrl, false) - } else { - checkServer("https://$queryUrl", true) - } - } - - private fun checkServer(queryUrl: String, checkForcedHttps: Boolean) { - ncApi.getServerStatus(queryUrl) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .`as`(AutoDispose.autoDisposable(scopeProvider)) - .subscribe({ status: Status -> - val productName = resources!!.getString(R.string.nc_server_product_name) - val versionString: String = status.version.substring(0, status.version.indexOf(".")) - val version = versionString.toInt() - if (status.installed && !status.maintenance && - !status.needsUpgrade && version >= 13) { - router.pushController(RouterTransaction.with( - WebViewLoginController(queryUrl.replace("/status.php", ""), - false)) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler())) - } else if (!status.installed) { - textFieldBoxes!!.setError(String.format( - resources!!.getString(R.string.nc_server_not_installed), productName), - true) - toggleProceedButton(false) - } else if (status.needsUpgrade) { - textFieldBoxes!!.setError(String.format(resources!!.getString(R.string.nc_server_db_upgrade_needed), - productName), true) - toggleProceedButton(false) - } else if (status.maintenance) { - textFieldBoxes!!.setError(String.format(resources!!.getString(R.string.nc_server_maintenance), - productName), - true) - toggleProceedButton(false) - } else if (!status.version.startsWith("13.")) { - textFieldBoxes!!.setError(String.format(resources!!.getString(R.string.nc_server_version), - resources!!.getString(R.string.nc_app_name) - , productName), true) - toggleProceedButton(false) - } - }, { throwable: Throwable -> - if (checkForcedHttps) { - checkServer(queryUrl.replace("https://", "http://"), false) - } else { - if (throwable.localizedMessage != null) { - textFieldBoxes!!.setError(throwable.localizedMessage, true) - } else if (throwable.cause is CertificateException) { - textFieldBoxes!!.setError(resources!!.getString(R.string.nc_certificate_error), - false) - } - if (serverEntry != null) { - serverEntry!!.isEnabled = true - } - progressBar!!.visibility = View.INVISIBLE - if (providersTextView!!.visibility != View.INVISIBLE) { - providersTextView!!.visibility = View.VISIBLE - certTextView!!.visibility = View.VISIBLE - } - toggleProceedButton(false) - } - }) { - progressBar!!.visibility = View.INVISIBLE - if (providersTextView!!.visibility != View.INVISIBLE) { - providersTextView!!.visibility = View.VISIBLE - certTextView!!.visibility = View.VISIBLE - } - } - } - - override fun onAttach(view: View) { - super.onAttach(view) - if (ApplicationWideMessageHolder.getInstance().messageType != null) { - when (ApplicationWideMessageHolder.getInstance().messageType - ) { - ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION -> { - textFieldBoxes!!.setError( - resources!!.getString(R.string.nc_account_scheduled_for_deletion), - false) - ApplicationWideMessageHolder.getInstance().messageType = null - } - ApplicationWideMessageHolder.MessageType.SERVER_WITHOUT_TALK -> { - textFieldBoxes!!.setError(resources!!.getString(R.string.nc_settings_no_talk_installed), - false) - } - ApplicationWideMessageHolder.MessageType.FAILED_TO_IMPORT_ACCOUNT -> { - textFieldBoxes!!.setError( - resources!!.getString(R.string.nc_server_failed_to_import_account), - false) - } - } - ApplicationWideMessageHolder.getInstance().messageType = null - } - setCertTextView() - } - - private fun setCertTextView() { - if (activity != null) { - activity!!.runOnUiThread { - if (!TextUtils.isEmpty(appPreferences.temporaryClientCertAlias)) { - certTextView!!.setText(R.string.nc_change_cert_auth) - } else { - certTextView!!.setText(R.string.nc_configure_cert_auth) - } - textFieldBoxes!!.setError("", true) - toggleProceedButton(true) - } - } - } - - override fun onDestroyView(view: View) { - super.onDestroyView(view) - if (activity != null) { - activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR - } - } - - companion object { - const val TAG = "ServerSelectionController" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt index e06d3b15e..c66a9e272 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/SettingsController.kt @@ -60,6 +60,7 @@ import com.nextcloud.talk.jobs.AccountRemovalWorker import com.nextcloud.talk.models.RingtoneSettings import com.nextcloud.talk.models.json.userprofile.UserProfileOverall import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository +import com.nextcloud.talk.newarch.features.account.serverentry.ServerEntryView import com.nextcloud.talk.newarch.local.models.UserNgEntity import com.nextcloud.talk.newarch.local.models.getCredentials import com.nextcloud.talk.newarch.local.models.other.UserStatus @@ -276,7 +277,7 @@ class SettingsController : BaseController() { } else { withContext(Dispatchers.Main) { router.setRoot(RouterTransaction.with( - ServerSelectionController() + ServerEntryView() ) .pushChangeHandler(VerticalChangeHandler()) .popChangeHandler(VerticalChangeHandler()) @@ -395,7 +396,7 @@ class SettingsController : BaseController() { addAccountButton!!.addPreferenceClickListener { view15 -> router.pushController( - RouterTransaction.with(ServerSelectionController()).pushChangeHandler( + RouterTransaction.with(ServerEntryView()).pushChangeHandler( VerticalChangeHandler() ) .popChangeHandler(VerticalChangeHandler()) @@ -567,13 +568,6 @@ class SettingsController : BaseController() { .host reauthorizeButton!!.addPreferenceClickListener { view14 -> - router.pushController( - RouterTransaction.with( - WebViewLoginController(currentUser!!.baseUrl, true) - ) - .pushChangeHandler(VerticalChangeHandler()) - .popChangeHandler(VerticalChangeHandler()) - ) } if (currentUser!!.displayName != null) { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.kt b/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.kt index 34f294a08..0af08c064 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/SwitchAccountController.kt @@ -260,9 +260,6 @@ class SwitchAccountController : BaseController { bundle.putString(BundleKeys.KEY_USERNAME, importAccount.username) bundle.putString(BundleKeys.KEY_TOKEN, importAccount.token) bundle.putBoolean(BundleKeys.KEY_IS_ACCOUNT_IMPORT, true) - router.pushController(RouterTransaction.with(AccountVerificationController(bundle)) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler())) } override fun getTitle(): String? { diff --git a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.kt b/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.kt deleted file mode 100644 index b03472860..000000000 --- a/app/src/main/java/com/nextcloud/talk/controllers/WebViewLoginController.kt +++ /dev/null @@ -1,455 +0,0 @@ -/* - * - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 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 . - */ -package com.nextcloud.talk.controllers - -import android.annotation.SuppressLint -import android.content.pm.ActivityInfo -import android.net.http.SslError -import android.os.Build -import android.os.Bundle -import android.security.KeyChain -import android.security.KeyChainException -import android.text.TextUtils -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.webkit.* -import android.webkit.WebSettings.RenderPriority.HIGH -import androidx.appcompat.app.AppCompatActivity -import androidx.work.OneTimeWorkRequest.Builder -import androidx.work.WorkManager -import com.bluelinelabs.conductor.RouterTransaction -import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler -import com.nextcloud.talk.R.layout -import com.nextcloud.talk.R.string -import com.nextcloud.talk.controllers.base.BaseController -import com.nextcloud.talk.events.CertificateEvent -import com.nextcloud.talk.jobs.PushRegistrationWorker -import com.nextcloud.talk.models.LoginData -import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository -import com.nextcloud.talk.newarch.local.models.other.UserStatus -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_BASE_URL -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_ORIGINAL_PROTOCOL -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_TOKEN -import com.nextcloud.talk.utils.bundle.BundleKeys.KEY_USERNAME -import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder -import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder.MessageType -import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder.MessageType.ACCOUNT_SCHEDULED_FOR_DELETION -import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder.MessageType.ACCOUNT_UPDATED_NOT_ADDED -import com.nextcloud.talk.utils.ssl.MagicTrustManager -import de.cotech.hw.fido.WebViewFidoBridge -import kotlinx.android.synthetic.main.controller_web_view_login.view.* -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import org.koin.android.ext.android.inject -import java.net.CookieManager -import java.net.URLDecoder -import java.security.PrivateKey -import java.security.cert.CertificateException -import java.security.cert.X509Certificate -import java.util.* - -class WebViewLoginController : BaseController { - private val PROTOCOL_SUFFIX = "://" - private val LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":" - - val magicTrustManager: MagicTrustManager by inject() - val cookieManager: CookieManager by inject() - val usersRepository: UsersRepository by inject() - - private var assembledPrefix: String? = null - private var baseUrl: String? = null - private var isPasswordUpdate = false - private var username: String? = null - private var password: String? = null - private var loginStep = 0 - private var automatedLoginAttempted = false - private var webViewFidoBridge: WebViewFidoBridge? = null - - constructor(bundle: Bundle) - constructor( - baseUrl: String?, - isPasswordUpdate: Boolean - ) { - this.baseUrl = baseUrl - this.isPasswordUpdate = isPasswordUpdate - } - - constructor( - baseUrl: String?, - isPasswordUpdate: Boolean, - username: String?, - password: String? - ) { - this.baseUrl = baseUrl - this.isPasswordUpdate = isPasswordUpdate - this.username = username - this.password = password - } - - private val webLoginUserAgent: String - private get() = (Build.MANUFACTURER.substring(0, 1).toUpperCase( - Locale.getDefault() - ) + - Build.MANUFACTURER.substring(1).toLowerCase( - Locale.getDefault() - ) + " " + Build.MODEL + " (" - + resources!!.getString(string.nc_app_name) + ")") - - override fun inflateView( - inflater: LayoutInflater, - container: ViewGroup - ): View { - return inflater.inflate(layout.controller_web_view_login, container, false) - } - - @SuppressLint("SetJavaScriptEnabled") - override fun onViewBound(view: View) { - super.onViewBound(view) - if (activity != null) { - activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT - } - if (actionBar != null) { - actionBar!!.hide() - } - assembledPrefix = - resources!!.getString(string.nc_talk_login_scheme) + PROTOCOL_SUFFIX + "login/" - - view.webview.apply { - settings.allowFileAccess = false - settings.allowFileAccessFromFileURLs = false - settings.javaScriptEnabled = true - settings.javaScriptCanOpenWindowsAutomatically = false - settings.domStorageEnabled = true - settings.userAgentString = webLoginUserAgent - settings.saveFormData = false - settings.savePassword = false - settings.setRenderPriority(HIGH) - clearCache(true) - clearFormData() - clearHistory() - clearSslPreferences() - } - - WebView.clearClientCertPreferences(null) - webViewFidoBridge = - WebViewFidoBridge.createInstanceForWebView(activity as AppCompatActivity?, view.webview) - CookieSyncManager.createInstance(activity) - android.webkit.CookieManager.getInstance() - .removeAllCookies(null) - val headers: MutableMap = hashMapOf() - headers["OCS-APIRequest"] = "true" - - view.webview.webViewClient = object : WebViewClient() { - private var basePageLoaded = false - override fun shouldInterceptRequest( - view: WebView, - request: WebResourceRequest - ): WebResourceResponse? { - webViewFidoBridge?.delegateShouldInterceptRequest(view, request) - return super.shouldInterceptRequest(view, request) - } - - override fun shouldOverrideUrlLoading( - view: WebView, - url: String - ): Boolean { - if (url.startsWith(assembledPrefix!!)) { - parseAndLoginFromWebView(url) - return true - } - return false - } - - override fun onPageFinished( - view: WebView, - url: String - ) { - loginStep++ - if (!basePageLoaded) { - if (view.progress_bar != null) { - view.progress_bar!!.visibility = View.GONE - } - if (view.webview != null) { - view.webview.visibility = View.VISIBLE - } - basePageLoaded = true - } - if (!TextUtils.isEmpty(username)) { - if (loginStep == 1) { - view.webview.loadUrl( - "javascript: {document.getElementsByClassName('login')[0].click(); };" - ) - } else if (!automatedLoginAttempted) { - automatedLoginAttempted = true - if (TextUtils.isEmpty(password)) { - view.webview.loadUrl( - "javascript:var justStore = document.getElementById('user').value = '" - + username - + "';" - ) - } else { - view.webview.loadUrl( - "javascript: {" + - "document.getElementById('user').value = '" + username + "';" + - "document.getElementById('password').value = '" + password + "';" + - "document.getElementById('submit').click(); };" - ) - } - } - } - super.onPageFinished(view, url) - } - - override fun onReceivedClientCertRequest( - view: WebView, - request: ClientCertRequest - ) { - val userEntity = usersRepository.getActiveUser() - var alias: String? = null - if (!isPasswordUpdate) { - alias = appPreferences.temporaryClientCertAlias - } - if (TextUtils.isEmpty(alias)) { - alias = userEntity!!.clientCertificate - } - if (!TextUtils.isEmpty(alias)) { - val finalAlias = alias - Thread(Runnable { - try { - val privateKey = - KeyChain.getPrivateKey(activity!!, finalAlias!!) - val certificates = - KeyChain.getCertificateChain(activity!!, finalAlias) - if (privateKey != null && certificates != null) { - request.proceed(privateKey, certificates) - } else { - request.cancel() - } - } catch (e: KeyChainException) { - request.cancel() - } catch (e: InterruptedException) { - request.cancel() - } - }) - .start() - } else { - KeyChain.choosePrivateKeyAlias( - activity!!, { chosenAlias: String? -> - if (chosenAlias != null) { - appPreferences.temporaryClientCertAlias = chosenAlias - Thread(Runnable { - var privateKey: PrivateKey? = null - try { - privateKey = KeyChain.getPrivateKey(activity!!, chosenAlias) - val certificates = - KeyChain.getCertificateChain(activity!!, chosenAlias) - if (privateKey != null && certificates != null) { - request.proceed(privateKey, certificates) - } else { - request.cancel() - } - } catch (e: KeyChainException) { - request.cancel() - } catch (e: InterruptedException) { - request.cancel() - } - }) - .start() - } else { - request.cancel() - } - }, arrayOf("RSA", "EC"), null, request.host, request.port, null - ) - } - } - - override fun onReceivedSslError( - view: WebView, - handler: SslErrorHandler, - error: SslError - ) { - try { - val sslCertificate = error.certificate - val f = - sslCertificate.javaClass.getDeclaredField("mX509Certificate") - f.isAccessible = true - val cert = - f[sslCertificate] as X509Certificate - if (cert == null) { - handler.cancel() - } else { - try { - magicTrustManager.checkServerTrusted( - arrayOf(cert), "generic" - ) - handler.proceed() - } catch (exception: CertificateException) { - eventBus.post(CertificateEvent(cert, magicTrustManager, handler)) - } - } - } catch (exception: Exception) { - handler.cancel() - } - } - } - view.webview.loadUrl("$baseUrl/index.php/login/flow", headers) - } - - private fun parseAndLoginFromWebView(dataString: String) { - val loginData = parseLoginData(assembledPrefix, dataString) - if (loginData != null) { - GlobalScope.launch { - val targetUser = - usersRepository.getUserWithUsernameAndServer(loginData.username!!, baseUrl!!) - var messageType: MessageType? = null - - if (!isPasswordUpdate && targetUser != null) { - messageType = ACCOUNT_UPDATED_NOT_ADDED - } - - if (targetUser != null && UserStatus.PENDING_DELETE == targetUser.status) { - ApplicationWideMessageHolder.getInstance().messageType = ACCOUNT_SCHEDULED_FOR_DELETION - if (!isPasswordUpdate) { - withContext(Dispatchers.Main) { - router.popToRoot() - } - } else { - withContext(Dispatchers.Main) { - router.popCurrentController() - } - } - } - - val finalMessageType = messageType - cookieManager.cookieStore.removeAll() - - if (!isPasswordUpdate && finalMessageType == null) { - val bundle = Bundle() - bundle.putString(KEY_USERNAME, loginData.username) - bundle.putString(KEY_TOKEN, loginData.token) - bundle.putString(KEY_BASE_URL, loginData.serverUrl) - var protocol = "" - if (baseUrl!!.startsWith("http://")) { - protocol = "http://" - } else if (baseUrl!!.startsWith("https://")) { - protocol = "https://" - } - if (!TextUtils.isEmpty(protocol)) { - bundle.putString(KEY_ORIGINAL_PROTOCOL, protocol) - } - - withContext(Dispatchers.Main) { - router.pushController( - RouterTransaction.with(AccountVerificationController(bundle)).pushChangeHandler( - HorizontalChangeHandler() - ) - .popChangeHandler(HorizontalChangeHandler()) - ) - } - } else { - if (isPasswordUpdate && targetUser != null) { - targetUser.token = loginData.token - val updatedRows = usersRepository.updateUser(targetUser) - if (updatedRows > 0) { - if (finalMessageType != null) { - ApplicationWideMessageHolder.getInstance().messageType = finalMessageType - } - - val pushRegistrationWork = Builder(PushRegistrationWorker::class.java).build() - WorkManager.getInstance() - .enqueue(pushRegistrationWork) - withContext(Dispatchers.Main) { - router.popCurrentController() - } - } else { - // do nothing - } - } else { - if (finalMessageType != null) { - ApplicationWideMessageHolder.getInstance() - .messageType = finalMessageType - } - withContext(Dispatchers.Main) { - router.popToRoot() - } - } - } - } - } - } - - private fun parseLoginData( - prefix: String?, - dataString: String - ): LoginData? { - if (dataString.length < prefix!!.length) { - return null - } - val loginData = LoginData() - // format is xxx://login/server:xxx&user:xxx&password:xxx - val data = dataString.substring(prefix.length) - val values = data.split("&") - .toTypedArray() - if (values.size != 3) { - return null - } - for (value in values) { - if (value.startsWith("user$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR")) { - loginData.username = URLDecoder.decode( - value.substring("user$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR".length) - ) - } else if (value.startsWith("password$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR")) { - loginData.token = URLDecoder.decode( - value.substring("password$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR".length) - ) - } else if (value.startsWith("server$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR")) { - loginData.serverUrl = URLDecoder.decode( - value.substring("server$LOGIN_URL_DATA_KEY_VALUE_SEPARATOR".length) - ) - } else { - return null - } - } - return if (!TextUtils.isEmpty(loginData.serverUrl) - && !TextUtils.isEmpty(loginData.username) - && - !TextUtils.isEmpty(loginData.token) - ) { - loginData - } else { - null - } - } - - override fun onDestroyView(view: View) { - super.onDestroyView(view) - if (activity != null) { - activity!!.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR - } - } - - companion object { - const val TAG = "WebViewLoginController" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt b/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt index 3bd75f302..eeb241ec4 100644 --- a/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt +++ b/app/src/main/java/com/nextcloud/talk/controllers/base/BaseController.kt @@ -42,10 +42,7 @@ import com.google.android.material.appbar.AppBarLayout import com.google.android.material.floatingactionbutton.FloatingActionButton import com.nextcloud.talk.R import com.nextcloud.talk.activities.MainActivity -import com.nextcloud.talk.controllers.AccountVerificationController -import com.nextcloud.talk.controllers.ServerSelectionController import com.nextcloud.talk.controllers.SwitchAccountController -import com.nextcloud.talk.controllers.WebViewLoginController import com.nextcloud.talk.controllers.base.providers.ActionBarProvider import com.nextcloud.talk.utils.FABAwareScrollingViewBehavior import com.nextcloud.talk.utils.preferences.AppPreferences @@ -137,9 +134,6 @@ abstract class BaseController : ButterKnifeController(), ComponentCallbacks { private fun cleanTempCertPreference() { val temporaryClassNames = ArrayList() - temporaryClassNames.add(ServerSelectionController::class.java.name) - temporaryClassNames.add(AccountVerificationController::class.java.name) - temporaryClassNames.add(WebViewLoginController::class.java.name) temporaryClassNames.add(SwitchAccountController::class.java.name) if (!temporaryClassNames.contains(javaClass.name)) { diff --git a/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.kt b/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.kt deleted file mode 100644 index 864a388fe..000000000 --- a/app/src/main/java/com/nextcloud/talk/jobs/PushRegistrationWorker.kt +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Nextcloud Talk application - * - * @author Mario Danic - * Copyright (C) 2017 Mario Danic - * - * 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 . - */ - -package com.nextcloud.talk.jobs - -import android.annotation.SuppressLint -import android.app.Application -import android.content.Context -import android.util.Base64 -import androidx.work.CoroutineWorker -import androidx.work.ListenableWorker.Result -import androidx.work.WorkerParameters -import com.nextcloud.talk.R -import com.nextcloud.talk.api.NcApi -import com.nextcloud.talk.events.EventStatus -import com.nextcloud.talk.models.json.push.PushConfigurationState -import com.nextcloud.talk.models.json.push.PushRegistrationOverall -import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository -import com.nextcloud.talk.newarch.local.models.getCredentials -import com.nextcloud.talk.newarch.utils.hashWithAlgorithm -import com.nextcloud.talk.utils.ApiUtils -import com.nextcloud.talk.utils.PushUtils -import com.nextcloud.talk.utils.preferences.AppPreferences -import io.reactivex.Observer -import io.reactivex.disposables.Disposable -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import org.greenrobot.eventbus.EventBus -import org.koin.core.KoinComponent -import org.koin.core.inject -import java.security.PublicKey -import java.util.* - -class PushRegistrationWorker( - context: Context, - workerParams: WorkerParameters -) : CoroutineWorker(context, workerParams), KoinComponent { - - val usersRepository: UsersRepository by inject() - val eventBus: EventBus by inject() - val appPreferences: AppPreferences by inject() - val application: Application by inject() - val ncApi: NcApi by inject() - - override suspend fun doWork(): Result { - val pushUtils = PushUtils(usersRepository) - pushUtils.generateRsa2048KeyPair() - pushRegistrationToServer() - return Result.success() - } - - private fun pushRegistrationToServer() { - val token: String? = appPreferences.pushToken - if (!token.isNullOrEmpty()) { - var credentials: String - val pushUtils = PushUtils(usersRepository) - val pushTokenHash = token.hashWithAlgorithm("SHA-512") - val devicePublicKey = pushUtils.readKeyFromFile(true) as PublicKey? - if (devicePublicKey != null) { - val publicKeyBytes: ByteArray? = - Base64.encode(devicePublicKey.encoded, Base64.NO_WRAP) - var publicKey = String(publicKeyBytes!!) - publicKey = publicKey.replace("(.{64})".toRegex(), "$1\n") - publicKey = "-----BEGIN PUBLIC KEY-----\n$publicKey\n-----END PUBLIC KEY-----\n" - val users = usersRepository.getUsers() - if (users.count() > 0) { - var accountPushData: PushConfigurationState? - for (userEntityObject in users) { - accountPushData = userEntityObject.pushConfiguration - if (accountPushData == null || accountPushData.pushToken != token) { - val queryMap: MutableMap = - HashMap() - queryMap["format"] = "json" - queryMap["pushTokenHash"] = pushTokenHash - queryMap["devicePublicKey"] = publicKey - queryMap["proxyServer"] = application.getString(R.string.nc_push_server_url) - credentials = userEntityObject.getCredentials() - ncApi.registerDeviceForNotificationsWithNextcloud( - credentials, - ApiUtils.getUrlNextcloudPush(userEntityObject.baseUrl), - queryMap - ) - .blockingSubscribe(object : Observer { - override fun onSubscribe(d: Disposable) {} - @SuppressLint("CheckResult") - override fun onNext(pushRegistrationOverall: PushRegistrationOverall) { - val proxyMap: MutableMap = - HashMap() - proxyMap["pushToken"] = token - proxyMap["deviceIdentifier"] = - pushRegistrationOverall.ocs.data.deviceIdentifier - proxyMap["deviceIdentifierSignature"] = pushRegistrationOverall.ocs - .data.signature - proxyMap["userPublicKey"] = pushRegistrationOverall.ocs - .data.publicKey - ncApi.registerDeviceForNotificationsWithProxy( - ApiUtils.getUrlPushProxy(), proxyMap - ).subscribe({ - val pushConfigurationState = PushConfigurationState() - pushConfigurationState.pushToken = token - pushConfigurationState.deviceIdentifier = proxyMap["deviceIdentifier"] - pushConfigurationState.deviceIdentifierSignature = proxyMap["deviceIdentifierSignature"] - pushConfigurationState.userPublicKey = proxyMap["userPublicKey"] - pushConfigurationState.usesRegularPass = false - GlobalScope.launch { - val user = usersRepository.getUserWithId(userEntityObject.id!!) - user.pushConfiguration = pushConfigurationState - usersRepository.updateUser(user) - } - - eventBus.post( - EventStatus( - userEntityObject.id!!, - EventStatus.EventType.PUSH_REGISTRATION, - true - ) - ) - }, { - eventBus.post( - EventStatus( - userEntityObject.id!!, - EventStatus.EventType.PUSH_REGISTRATION, - false)) - - }) - } - - override fun onError(e: Throwable) { - eventBus.post( - EventStatus( - userEntityObject.id!!, - EventStatus.EventType.PUSH_REGISTRATION, - false - ) - ) - } - - override fun onComplete() {} - }) - } - } - } - } - } - } - - companion object { - const val TAG = "PushRegistrationWorker" - } -} \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/models/json/push/PushConfigurationState.kt b/app/src/main/java/com/nextcloud/talk/models/json/push/PushConfiguration.kt similarity index 64% rename from app/src/main/java/com/nextcloud/talk/models/json/push/PushConfigurationState.kt rename to app/src/main/java/com/nextcloud/talk/models/json/push/PushConfiguration.kt index 98f42bdf1..62e1e5043 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/push/PushConfigurationState.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/push/PushConfiguration.kt @@ -21,9 +21,10 @@ package com.nextcloud.talk.models.json.push import android.os.Parcelable -import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable import lombok.Data import org.parceler.Parcel @@ -31,30 +32,31 @@ import org.parceler.Parcel @Data @JsonObject @Parcelize -data class PushConfigurationState( - @JsonField(name = ["pushToken"]) +@Serializable +data class PushConfiguration( + @SerialName("pushToken") var pushToken: String? = null, - @JsonField(name = ["deviceIdentifier"]) + @SerialName("deviceIdentifier") var deviceIdentifier: String? = null, - @JsonField(name = ["deviceIdentifierSignature"]) + @SerialName("deviceIdentifierSignature") var deviceIdentifierSignature: String? = null, - @JsonField(name = ["userPublicKey"]) + @SerialName("userPublicKey") var userPublicKey: String? = null, - @JsonField(name = ["usesRegularPass"]) - var usesRegularPass: Boolean = false + @SerialName("state") + var pushConfigurationStateWrapper: PushConfigurationStateWrapper? = null ) : Parcelable { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false - other as PushConfigurationState + other as PushConfiguration if (pushToken != other.pushToken) return false if (deviceIdentifier != other.deviceIdentifier) return false if (deviceIdentifierSignature != other.deviceIdentifierSignature) return false if (userPublicKey != other.userPublicKey) return false - if (usesRegularPass != other.usesRegularPass) return false + if (pushConfigurationStateWrapper != other.pushConfigurationStateWrapper) return false return true } @@ -64,7 +66,27 @@ data class PushConfigurationState( result = 31 * result + (deviceIdentifier?.hashCode() ?: 0) result = 31 * result + (deviceIdentifierSignature?.hashCode() ?: 0) result = 31 * result + (userPublicKey?.hashCode() ?: 0) - result = 31 * result + usesRegularPass.hashCode() + result = 31 * result + pushConfigurationStateWrapper.hashCode() return result } -} \ No newline at end of file +} + +enum class PushConfigurationState { + PENDING, + SERVER_REGISTRATION_DONE, + PROXY_REGISTRATION_DONE, + FAILED_WITH_SERVER_REGISTRATION, + FAILED_WITH_PROXY_REGISTRATION, + PENDING_UNREGISTRATION, + SERVER_UNREGISTRATION_DONE, + PROXY_UNREGISTRATION_DONE +} + +@Serializable +@Parcelize +data class PushConfigurationStateWrapper( + @SerialName("pushConfigurationState") + var pushConfigurationState: PushConfigurationState, + @SerialName("reason") + var reason: Int? +): Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/IceServer.kt b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/IceServer.kt index 4ba215fff..f455ed7fb 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/IceServer.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/IceServer.kt @@ -23,6 +23,7 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import lombok.Data @@ -33,14 +34,18 @@ import lombok.Data data class IceServer @JvmOverloads constructor( @JvmField @JsonField(name = ["url"]) + @SerialName("url") var url: String? = null, @JvmField @JsonField(name = ["urls"]) + @SerialName("urls") var urls: List? = null, @JvmField @JsonField(name = ["username"]) + @SerialName("username") var username: String? = null, @JvmField @JsonField(name = ["credential"]) + @SerialName("credential") var credential: String? = null ) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt index 142024da1..acc33259e 100644 --- a/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt +++ b/app/src/main/java/com/nextcloud/talk/models/json/signaling/settings/SignalingSettings.kt @@ -23,6 +23,7 @@ import android.os.Parcelable import com.bluelinelabs.logansquare.annotation.JsonField import com.bluelinelabs.logansquare.annotation.JsonObject import kotlinx.android.parcel.Parcelize +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import lombok.Data @@ -33,14 +34,18 @@ import lombok.Data data class SignalingSettings @JvmOverloads constructor( @JvmField @JsonField(name = ["stunservers"]) + @SerialName("stunservers") var stunServers: List? = null, @JvmField @JsonField(name = ["turnservers"]) + @SerialName("turnservers") var turnServers: List? = null, @JvmField @JsonField(name = ["server"]) + @SerialName("server") var externalSignalingServer: String? = null, @JvmField @JsonField(name = ["ticket"]) + @SerialName("ticket") var externalSignalingTicket: String? = null ) : Parcelable diff --git a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt index 74f22e188..ad6df77dc 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/data/repository/offline/UsersRepositoryImpl.kt @@ -54,6 +54,10 @@ class UsersRepositoryImpl(private val usersDao: UsersDao) : UsersRepository { return usersDao.updateUser(user) } + override suspend fun insertUser(user: UserNgEntity): Long { + return usersDao.saveUser(user) + } + override suspend fun setUserAsActiveWithId(id: Long) { usersDao.setUserAsActiveWithId(id) } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt index 88fd0d4e4..f7568c321 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/di/module/NetworkModule.kt @@ -57,7 +57,6 @@ import okhttp3.logging.HttpLoggingInterceptor.Logger import org.koin.android.ext.koin.androidApplication import org.koin.android.ext.koin.androidContext import org.koin.dsl.module -import org.mozilla.geckoview.GeckoRuntime import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import java.io.IOException @@ -73,7 +72,6 @@ import javax.net.ssl.SSLSocketFactory import javax.net.ssl.X509KeyManager val NetworkModule = module { - single { createGeckoRuntime(androidContext()) } single { createService(get()) } single { createLegacyNcApi(get()) } single { createRetrofit(get()) } @@ -91,10 +89,6 @@ val NetworkModule = module { } -fun createGeckoRuntime(context: Context): GeckoRuntime { - return GeckoRuntime.create(context) -} - fun createCookieManager(): CookieManager { val cookieManager = CookieManager() cookieManager.setCookiePolicy(ACCEPT_ALL) diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt index c60d7a98b..615da8361 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/di/module/UseCasesModule.kt @@ -43,9 +43,38 @@ val UseCasesModule = module { single { createGetProfileUseCase(get(), get()) } single { createGetSignalingUseCase(get(), get()) } single { createGetCapabilitiesUseCase(get(), get()) } + single { createRegisterPushWithProxyUseCase(get(), get()) } + single { createRegisterPushWithServerUseCase(get(), get()) } + single { createUnregisterPushWithProxyUseCase(get(), get()) } + single { createUnregisterPushWithServerUseCase(get(), get()) } factory { createChatViewModelFactory(get(), get(), get(), get(), get(), get()) } } +fun createUnregisterPushWithServerUseCase(nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler +): UnregisterPushWithServerUseCase { + return UnregisterPushWithServerUseCase(nextcloudTalkRepository, apiErrorHandler) +} + +fun createUnregisterPushWithProxyUseCase(nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler +): UnregisterPushWithProxyUseCase { + return UnregisterPushWithProxyUseCase(nextcloudTalkRepository, apiErrorHandler) +} + + +fun createRegisterPushWithServerUseCase(nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler +): RegisterPushWithServerUseCase { + return RegisterPushWithServerUseCase(nextcloudTalkRepository, apiErrorHandler) +} + +fun createRegisterPushWithProxyUseCase(nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler +): RegisterPushWithProxyUseCase { + return RegisterPushWithProxyUseCase(nextcloudTalkRepository, apiErrorHandler) +} + fun createGetCapabilitiesUseCase(nextcloudTalkRepository: NextcloudTalkRepository, apiErrorHandler: ApiErrorHandler ): GetCapabilitiesUseCase { diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt index 4b4fad643..a0b431126 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/offline/UsersRepository.kt @@ -30,6 +30,7 @@ interface UsersRepository { fun getUserWithId(id: Long): UserNgEntity suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity? suspend fun updateUser(user: UserNgEntity): Int + suspend fun insertUser(user: UserNgEntity): Long suspend fun setUserAsActiveWithId(id: Long) suspend fun deleteUserWithId(id: Long) suspend fun setAnyUserAsActive(): Boolean diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt index d053f6ae3..ac0051820 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/repository/online/NextcloudTalkRepository.kt @@ -34,7 +34,6 @@ interface NextcloudTalkRepository { suspend fun unregisterPushWithServerForUser(user: UserNgEntity): GenericOverall suspend fun registerPushWithProxyForUser(user: UserNgEntity, options: Map): Any suspend fun unregisterPushWithProxyForUser(user: UserNgEntity, options: Map): Any - suspend fun getSignalingSettingsForUser(user: UserNgEntity): SignalingSettingsOverall suspend fun getProfileForUser(user: UserNgEntity): UserProfileOverall suspend fun getConversationsForUser(user: UserNgEntity): List diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/RegisterPushWithProxyUseCase.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/RegisterPushWithProxyUseCase.kt new file mode 100644 index 000000000..2ebe2cf26 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/RegisterPushWithProxyUseCase.kt @@ -0,0 +1,39 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * 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 . + * + */ + +package com.nextcloud.talk.newarch.domain.usecases + +import com.nextcloud.talk.models.json.push.PushRegistrationOverall +import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler +import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository +import com.nextcloud.talk.newarch.domain.usecases.base.UseCase +import org.koin.core.parameter.DefinitionParameters + +class RegisterPushWithProxyUseCase constructor( + private val nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler? +) : UseCase(apiErrorHandler) { + override suspend fun run(params: Any?): Any { + val definitionParameters = params as DefinitionParameters + return nextcloudTalkRepository.registerPushWithProxyForUser(definitionParameters[0], definitionParameters[1]) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/RegisterPushWithServerUseCase.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/RegisterPushWithServerUseCase.kt new file mode 100644 index 000000000..c056354ce --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/RegisterPushWithServerUseCase.kt @@ -0,0 +1,39 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * 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 . + * + */ + +package com.nextcloud.talk.newarch.domain.usecases + +import com.nextcloud.talk.models.json.push.PushRegistrationOverall +import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler +import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository +import com.nextcloud.talk.newarch.domain.usecases.base.UseCase +import org.koin.core.parameter.DefinitionParameters + +class RegisterPushWithServerUseCase constructor( + private val nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler? +) : UseCase(apiErrorHandler) { + override suspend fun run(params: Any?): PushRegistrationOverall { + val definitionParameters = params as DefinitionParameters + return nextcloudTalkRepository.registerPushWithServerForUser(definitionParameters[0], definitionParameters[1]) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/SetConversationFavoriteValueUseCase.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/SetConversationFavoriteValueUseCase.kt index a706f3b63..0dd1fe2d4 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/SetConversationFavoriteValueUseCase.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/SetConversationFavoriteValueUseCase.kt @@ -34,9 +34,9 @@ class SetConversationFavoriteValueUseCase constructor( override suspend fun run(params: Any?): GenericOverall { val definitionParameters = params as DefinitionParameters return nextcloudTalkRepository.setFavoriteValueForConversation( - definitionParameters.get(0), - definitionParameters.get(1), - definitionParameters.get(2) + definitionParameters[0], + definitionParameters[1], + definitionParameters[2] ) } } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/UnregisterPushWithProxyUseCase.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/UnregisterPushWithProxyUseCase.kt new file mode 100644 index 000000000..908796f13 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/UnregisterPushWithProxyUseCase.kt @@ -0,0 +1,38 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * 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 . + * + */ + +package com.nextcloud.talk.newarch.domain.usecases + +import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler +import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository +import com.nextcloud.talk.newarch.domain.usecases.base.UseCase +import org.koin.core.parameter.DefinitionParameters + +class UnregisterPushWithProxyUseCase constructor( + private val nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler? +) : UseCase(apiErrorHandler) { + override suspend fun run(params: Any?): Any { + val definitionParameters = params as DefinitionParameters + return nextcloudTalkRepository.unregisterPushWithProxyForUser(definitionParameters[0], definitionParameters[1]) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/UnregisterPushWithServerUseCase.kt b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/UnregisterPushWithServerUseCase.kt new file mode 100644 index 000000000..db9717cb9 --- /dev/null +++ b/app/src/main/java/com/nextcloud/talk/newarch/domain/usecases/UnregisterPushWithServerUseCase.kt @@ -0,0 +1,39 @@ +/* + * + * * Nextcloud Talk application + * * + * * @author Mario Danic + * * Copyright (C) 2017-2020 Mario Danic + * * + * * 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 . + * + */ + +package com.nextcloud.talk.newarch.domain.usecases + +import com.nextcloud.talk.models.json.generic.GenericOverall +import com.nextcloud.talk.newarch.data.source.remote.ApiErrorHandler +import com.nextcloud.talk.newarch.domain.repository.online.NextcloudTalkRepository +import com.nextcloud.talk.newarch.domain.usecases.base.UseCase +import org.koin.core.parameter.DefinitionParameters + +class UnregisterPushWithServerUseCase constructor( + private val nextcloudTalkRepository: NextcloudTalkRepository, + apiErrorHandler: ApiErrorHandler? +) : UseCase(apiErrorHandler) { + override suspend fun run(params: Any?): GenericOverall { + val definitionParameters = params as DefinitionParameters + return nextcloudTalkRepository.unregisterPushWithServerForUser(definitionParameters[0]) + } +} diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/account/di/module/AccountModule.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/account/di/module/AccountModule.kt index fa668746e..f4dade958 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/account/di/module/AccountModule.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/account/di/module/AccountModule.kt @@ -2,9 +2,7 @@ package com.nextcloud.talk.newarch.features.account.di.module import android.app.Application import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository -import com.nextcloud.talk.newarch.domain.usecases.GetCapabilitiesUseCase -import com.nextcloud.talk.newarch.domain.usecases.GetProfileUseCase -import com.nextcloud.talk.newarch.domain.usecases.GetSignalingSettingsUseCase +import com.nextcloud.talk.newarch.domain.usecases.* import com.nextcloud.talk.newarch.features.account.loginentry.LoginEntryViewModelFactory import com.nextcloud.talk.newarch.features.account.serverentry.ServerEntryViewModelFactory import com.nextcloud.talk.utils.preferences.AppPreferences @@ -18,7 +16,7 @@ val AccountModule = module { ) } factory { - createLoginEntryViewModelFactory(androidApplication(), get(), get(), get(), get(), get() ) + createLoginEntryViewModelFactory(androidApplication(), get(), get(), get(), get(), get(), get(), get()) } } @@ -36,10 +34,12 @@ fun createLoginEntryViewModelFactory( getProfileUseCase: GetProfileUseCase, getCapabilitiesUseCase: GetCapabilitiesUseCase, getSignalingSettingsUseCase: GetSignalingSettingsUseCase, + registerPushWithServerUseCase: RegisterPushWithServerUseCase, + registerPushWithProxyUseCase: RegisterPushWithProxyUseCase, appPreferences: AppPreferences, usersRepository: UsersRepository ): LoginEntryViewModelFactory { return LoginEntryViewModelFactory( - application, getProfileUseCase, getCapabilitiesUseCase, getSignalingSettingsUseCase, appPreferences, usersRepository + application, getProfileUseCase, getCapabilitiesUseCase, getSignalingSettingsUseCase, registerPushWithServerUseCase, registerPushWithProxyUseCase, appPreferences, usersRepository ) } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryView.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryView.kt index 6c14043fa..acb88aa03 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryView.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryView.kt @@ -22,11 +22,14 @@ package com.nextcloud.talk.newarch.features.account.loginentry +import android.annotation.SuppressLint import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.webkit.* +import androidx.appcompat.app.AppCompatActivity import androidx.core.view.isVisible import androidx.lifecycle.Observer import com.bluelinelabs.conductor.RouterTransaction @@ -35,10 +38,9 @@ import com.nextcloud.talk.R import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView import com.nextcloud.talk.newarch.features.conversationslist.ConversationsListView import com.nextcloud.talk.utils.bundle.BundleKeys +import de.cotech.hw.fido.WebViewFidoBridge import kotlinx.android.synthetic.main.login_entry_view.view.* import org.koin.android.ext.android.inject -import org.mozilla.geckoview.* -import org.mozilla.geckoview.GeckoSessionSettings.USER_AGENT_MODE_MOBILE import java.util.* class LoginEntryView(val bundle: Bundle) : BaseView() { @@ -48,11 +50,7 @@ class LoginEntryView(val bundle: Bundle) : BaseView() { private lateinit var viewModel: LoginEntryViewModel val factory: LoginEntryViewModelFactory by inject() - private lateinit var geckoView: GeckoView - private lateinit var geckoSession: GeckoSession - private val geckoRuntime: GeckoRuntime by inject() - - private val assembledPrefix = resources?.getString(R.string.nc_talk_login_scheme) + protocolSuffix + "login/" + private var assembledPrefix = "" private val webLoginUserAgent: String get() = (Build.MANUFACTURER.substring(0, 1).toUpperCase( @@ -65,11 +63,14 @@ class LoginEntryView(val bundle: Bundle) : BaseView() { return R.layout.login_entry_view } + @SuppressLint("SetJavaScriptEnabled") override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View { actionBar?.hide() viewModel = viewModelProvider(factory).get(LoginEntryViewModel::class.java) val view = super.onCreateView(inflater, container) + assembledPrefix = resources?.getString(R.string.nc_talk_login_scheme) + protocolSuffix + "login/" + viewModel.state.observe(this@LoginEntryView, Observer { when (it.state) { LoginEntryState.FAILED -> { @@ -80,82 +81,83 @@ class LoginEntryView(val bundle: Bundle) : BaseView() { } LoginEntryState.CHECKING -> { view.progressBar.isVisible = true - geckoView.isVisible = false + view.webView.isVisible = false } else -> { - if (router?.hasRootController() == true) { - router.popController(this) - } else { - router.setRoot(RouterTransaction.with(ConversationsListView()) - .pushChangeHandler(HorizontalChangeHandler()) - .popChangeHandler(HorizontalChangeHandler())) - } - // all good, proceed + router.setRoot(RouterTransaction.with(ConversationsListView()) + .pushChangeHandler(HorizontalChangeHandler()) + .popChangeHandler(HorizontalChangeHandler())) } } }) - geckoView = view.geckoView - activity?.let { - val settings = GeckoSessionSettings.Builder() - //.usePrivateMode(true) - //.useTrackingProtection(true) - .userAgentMode(USER_AGENT_MODE_MOBILE) - .userAgentOverride(webLoginUserAgent) - .suspendMediaWhenInactive(true) - .allowJavascript(true) - geckoView.autofillEnabled = true - geckoSession = GeckoSession(settings.build()) - geckoSession.open(geckoRuntime) - geckoSession.progressDelegate = createProgressDelegate() - geckoSession.navigationDelegate = createNavigationDelegate() - geckoView.setSession(geckoSession) - bundle.getString(BundleKeys.KEY_BASE_URL)?.let { baseUrl -> - geckoSession.loadUri("$baseUrl/index.php/login/flow", mapOf("OCS-APIRequest" to "true")) - } - } + + val baseUrl = bundle.get(BundleKeys.KEY_BASE_URL) + val headers: MutableMap = hashMapOf() + headers["OCS-APIRequest"] = "true" + + setupWebView(view) + view.webView.loadUrl("$baseUrl/index.php/login/flow", headers) return view } - private fun createNavigationDelegate(): GeckoSession.NavigationDelegate { - return object : GeckoSession.NavigationDelegate { - override fun onLoadRequest(p0: GeckoSession, p1: GeckoSession.NavigationDelegate.LoadRequest): GeckoResult? { - if (p1.uri.startsWith(assembledPrefix)) { - viewModel.parseData(assembledPrefix, dataSeparator, p1.uri) - return GeckoResult.DENY + override fun onSaveViewState(view: View, outState: Bundle) { + view.webView.saveState(outState) + super.onSaveViewState(view, outState) + } + + override fun onRestoreViewState(view: View, savedViewState: Bundle) { + super.onRestoreViewState(view, savedViewState) + view.webView.restoreState(savedViewState) + } + + private fun setupWebView(loginEntryView: View) { + loginEntryView.webView.apply { + settings.allowFileAccess = false + settings.allowFileAccessFromFileURLs = false + settings.javaScriptEnabled = true + settings.javaScriptCanOpenWindowsAutomatically = false + settings.domStorageEnabled = true + settings.userAgentString = webLoginUserAgent + settings.saveFormData = false + settings.savePassword = false + settings.setRenderPriority(WebSettings.RenderPriority.HIGH) + clearCache(true) + clearFormData() + clearHistory() + clearSslPreferences() + } + + val webViewFidoBridge = WebViewFidoBridge.createInstanceForWebView(activity as AppCompatActivity?, loginEntryView.webView) + CookieSyncManager.createInstance(activity) + CookieManager.getInstance().removeAllCookies(null) + + loginEntryView.webView.webViewClient = object : WebViewClient() { + var initialPageLoad = true + override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? { + webViewFidoBridge?.delegateShouldInterceptRequest(view, request) + return super.shouldInterceptRequest(view, request) + } + + override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { + if (request?.url.toString().startsWith(assembledPrefix)) { + viewModel.parseData(assembledPrefix, dataSeparator, request?.url.toString()) + return true } - return super.onLoadRequest(p0, p1) + return super.shouldOverrideUrlLoading(view, request) + } + + override fun onPageFinished(view: WebView?, url: String?) { + if (initialPageLoad) { + initialPageLoad = false + loginEntryView.progressBar?.isVisible = false + loginEntryView.webView?.isVisible = true + } + super.onPageFinished(view, url) } } } - private fun createProgressDelegate(): GeckoSession.ProgressDelegate { - return object : GeckoSession.ProgressDelegate { - private var initialLoad = true - - override fun onPageStop(session: GeckoSession, success: Boolean) = Unit - - override fun onSecurityChange( - session: GeckoSession, - securityInfo: GeckoSession.ProgressDelegate.SecurityInformation - ) = Unit - - override fun onPageStart(session: GeckoSession, url: String) = Unit - - override fun onProgressChange(session: GeckoSession, progress: Int) { - if (initialLoad) { - view?.pageProgressBar?.progress = progress - view?.pageProgressBar?.isVisible = progress in 1..99 - } - - if (progress == 100) { - initialLoad = false - view?.pageProgressBar?.isVisible = false - view?.geckoView?.isVisible = true - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModel.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModel.kt index 456ddb73c..357a9803b 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModel.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModel.kt @@ -5,16 +5,19 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.nextcloud.talk.models.LoginData import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall +import com.nextcloud.talk.models.json.push.PushConfiguration +import com.nextcloud.talk.models.json.push.PushConfigurationState +import com.nextcloud.talk.models.json.push.PushConfigurationStateWrapper +import com.nextcloud.talk.models.json.push.PushRegistrationOverall import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall import com.nextcloud.talk.models.json.userprofile.UserProfileOverall import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel import com.nextcloud.talk.newarch.data.model.ErrorModel import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository -import com.nextcloud.talk.newarch.domain.usecases.GetCapabilitiesUseCase -import com.nextcloud.talk.newarch.domain.usecases.GetProfileUseCase -import com.nextcloud.talk.newarch.domain.usecases.GetSignalingSettingsUseCase +import com.nextcloud.talk.newarch.domain.usecases.* import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse import com.nextcloud.talk.newarch.local.models.UserNgEntity +import com.nextcloud.talk.utils.PushUtils import com.nextcloud.talk.utils.preferences.AppPreferences import kotlinx.coroutines.launch import org.koin.core.parameter.parametersOf @@ -25,12 +28,15 @@ class LoginEntryViewModel constructor( private val getProfileUseCase: GetProfileUseCase, private val getCapabilitiesUseCase: GetCapabilitiesUseCase, private val getSignalingSettingsUseCase: GetSignalingSettingsUseCase, + private val registerPushWithServerUseCase: RegisterPushWithServerUseCase, + private val registerPushWithProxyUseCase: RegisterPushWithProxyUseCase, private val appPreferences: AppPreferences, private val usersRepository: UsersRepository) : BaseViewModel(application) { val state: MutableLiveData = MutableLiveData(LoginEntryStateWrapper(LoginEntryState.PENDING_CHECK, null)) - private val user = UserNgEntity(-1, "-1", "", "") + private var user = UserNgEntity(-1, "-1", "", "") + private var updatingUser = false fun parseData(prefix: String, separator: String, data: String?) { viewModelScope.launch { @@ -88,10 +94,13 @@ class LoginEntryViewModel constructor( private suspend fun storeCredentialsOrVerify(loginData: LoginData) { // username and server url will be null here for sure because we do a check earlier in the process - val user = usersRepository.getUserWithUsernameAndServer(loginData.username!!, loginData.serverUrl!!) - if (user != null) { + val userIfExists = usersRepository.getUserWithUsernameAndServer(loginData.username!!, loginData.serverUrl!!) + if (userIfExists != null) { + updatingUser = true + user = userIfExists user.token = loginData.token usersRepository.updateUser(user) + // complicated - we need to unregister, etc, etc, but not yet state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.ACCOUNT_UPDATED)) } else { getProfile(loginData) @@ -101,6 +110,7 @@ class LoginEntryViewModel constructor( private fun getProfile(loginData: LoginData) { user.username = loginData.username!! user.baseUrl = loginData.serverUrl!! + user.token = loginData.token getProfileUseCase.invoke(viewModelScope, parametersOf(user), object : UseCaseResponse { override suspend fun onSuccess(result: UserProfileOverall) { result.ocs.data.userId?.let { userId -> @@ -135,6 +145,10 @@ class LoginEntryViewModel constructor( getSignalingSettingsUseCase.invoke(viewModelScope, parametersOf(user), object : UseCaseResponse { override suspend fun onSuccess(result: SignalingSettingsOverall) { user.signalingSettings = result.ocs.signalingSettings + val pushConfiguration = PushConfiguration() + val pushConfigurationStateWrapper = PushConfigurationStateWrapper(PushConfigurationState.PENDING, 0) + pushConfiguration.pushConfigurationStateWrapper = pushConfigurationStateWrapper + usersRepository.insertUser(user) registerForPush() } @@ -142,12 +156,13 @@ class LoginEntryViewModel constructor( state.postValue(LoginEntryStateWrapper(LoginEntryState.FAILED, LoginEntryStateClarification.SIGNALING_SETTINGS_FETCH_FAILED)) } }) - } - private fun registerForPush() { + private suspend fun registerForPush() { val token = appPreferences.pushToken if (!token.isNullOrBlank()) { + user.pushConfiguration?.pushToken = token + usersRepository.updateUser(user) registerForPushWithServer(token) } else { state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.PUSH_REGISTRATION_MISSING_TOKEN)) @@ -155,10 +170,57 @@ class LoginEntryViewModel constructor( } private fun registerForPushWithServer(token: String) { + val options = PushUtils(usersRepository).getMapForPushRegistrationWithServer(context, token) + registerPushWithServerUseCase.invoke(viewModelScope, parametersOf(user, options), object : UseCaseResponse { + override suspend fun onSuccess(result: PushRegistrationOverall) { + user.pushConfiguration?.deviceIdentifier = result.ocs.data.deviceIdentifier + user.pushConfiguration?.deviceIdentifierSignature = result.ocs.data.signature + user.pushConfiguration?.userPublicKey = result.ocs.data.publicKey + user.pushConfiguration?.pushConfigurationStateWrapper = PushConfigurationStateWrapper(PushConfigurationState.SERVER_REGISTRATION_DONE, null) + usersRepository.updateUser(user) + registerForPushWithProxy() + } + override suspend fun onError(errorModel: ErrorModel?) { + user.pushConfiguration?.pushConfigurationStateWrapper?.pushConfigurationState = PushConfigurationState.FAILED_WITH_SERVER_REGISTRATION + user.pushConfiguration?.pushConfigurationStateWrapper?.reason = errorModel?.code + usersRepository.updateUser(user) + state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.PUSH_REGISTRATION_WITH_SERVER_FAILED)) + } + }) } - private fun registerForPushWithProxy() { + private suspend fun registerForPushWithProxy() { + val options = PushUtils(usersRepository).getMapForPushRegistrationWithServer(user) + if (options != null) { + registerPushWithProxyUseCase.invoke(viewModelScope, parametersOf(user, options), object : UseCaseResponse { + override suspend fun onSuccess(result: Any) { + user.pushConfiguration?.pushConfigurationStateWrapper = PushConfigurationStateWrapper(PushConfigurationState.PROXY_REGISTRATION_DONE, null) + usersRepository.updateUser(user) + state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, if (!updatingUser) LoginEntryStateClarification.ACCOUNT_CREATED else LoginEntryStateClarification.ACCOUNT_UPDATED)) + } + + override suspend fun onError(errorModel: ErrorModel?) { + user.pushConfiguration?.pushConfigurationStateWrapper?.pushConfigurationState = PushConfigurationState.FAILED_WITH_PROXY_REGISTRATION + user.pushConfiguration?.pushConfigurationStateWrapper?.reason = errorModel?.code + usersRepository.updateUser(user) + state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.PUSH_REGISTRATION_WITH_PUSH_PROXY_FAILED)) + } + }) + } else { + user.pushConfiguration?.pushConfigurationStateWrapper?.pushConfigurationState = PushConfigurationState.FAILED_WITH_PROXY_REGISTRATION + usersRepository.updateUser(user) + state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.PUSH_REGISTRATION_WITH_PUSH_PROXY_FAILED)) + } + } + + private suspend fun setAdjustedUserAsActive() { + if (user.id == -1L) { + val adjustedUser = usersRepository.getUserWithUsernameAndServer(user.username, user.baseUrl) + adjustedUser?.id?.let { + usersRepository.setUserAsActiveWithId(it) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModelFactory.kt b/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModelFactory.kt index 7182cbb2e..74ac7c425 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModelFactory.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/features/account/loginentry/LoginEntryViewModelFactory.kt @@ -4,13 +4,11 @@ import android.app.Application import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository -import com.nextcloud.talk.newarch.domain.usecases.GetCapabilitiesUseCase -import com.nextcloud.talk.newarch.domain.usecases.GetProfileUseCase -import com.nextcloud.talk.newarch.domain.usecases.GetSignalingSettingsUseCase +import com.nextcloud.talk.newarch.domain.usecases.* import com.nextcloud.talk.utils.preferences.AppPreferences -class LoginEntryViewModelFactory constructor(private val application: Application, private val getProfileUseCase: GetProfileUseCase, private val getCapabilitiesUseCase: GetCapabilitiesUseCase, private val getSignalingSettingsUseCase: GetSignalingSettingsUseCase, private val appPreferences: AppPreferences, private val usersRepository: UsersRepository) : ViewModelProvider.Factory { +class LoginEntryViewModelFactory constructor(private val application: Application, private val getProfileUseCase: GetProfileUseCase, private val getCapabilitiesUseCase: GetCapabilitiesUseCase, private val getSignalingSettingsUseCase: GetSignalingSettingsUseCase, private val registerPushWithServerUseCase: RegisterPushWithServerUseCase, private val registerPushWithProxyUseCase: RegisterPushWithProxyUseCase, private val appPreferences: AppPreferences, private val usersRepository: UsersRepository) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { - return LoginEntryViewModel(application, getProfileUseCase, getCapabilitiesUseCase, getSignalingSettingsUseCase, appPreferences, usersRepository) as T + return LoginEntryViewModel(application, getProfileUseCase, getCapabilitiesUseCase, getSignalingSettingsUseCase, registerPushWithServerUseCase, registerPushWithProxyUseCase, appPreferences, usersRepository) as T } } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/PushConfigurationConverter.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/PushConfigurationConverter.kt index 4916a999e..e8d91b009 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/converters/PushConfigurationConverter.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/converters/PushConfigurationConverter.kt @@ -22,20 +22,25 @@ package com.nextcloud.talk.newarch.local.converters import androidx.room.TypeConverter import com.bluelinelabs.logansquare.LoganSquare -import com.nextcloud.talk.models.json.push.PushConfigurationState +import com.nextcloud.talk.models.json.push.PushConfiguration +import com.nextcloud.talk.newarch.utils.MagicJson +import kotlinx.serialization.json.Json class PushConfigurationConverter { + val json = Json(MagicJson.customJsonConfiguration) + @TypeConverter - fun fromPushConfigurationToString(pushConfigurationState: PushConfigurationState?): String { - if (pushConfigurationState == null) { - return "" + fun fromPushConfigurationToString(pushConfiguration: PushConfiguration?): String { + + return if (pushConfiguration == null) { + "" } else { - return LoganSquare.serialize(pushConfigurationState) + json.stringify(PushConfiguration.serializer(), pushConfiguration) } } @TypeConverter - fun fromStringToPushConfiguration(value: String): PushConfigurationState? { - return LoganSquare.parse(value, PushConfigurationState::class.java) + fun fromStringToPushConfiguration(value: String): PushConfiguration? { + return json.parse(PushConfiguration.serializer(), value) } } diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt index ccabd7f40..3bc4072cf 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/dao/UsersDao.kt @@ -44,7 +44,7 @@ abstract class UsersDao { abstract fun saveUser(user: UserNgEntity): Long @Insert(onConflict = OnConflictStrategy.REPLACE) - abstract suspend fun saveUsers(vararg users: UserNgEntity) + abstract suspend fun saveUsers(vararg users: UserNgEntity): List // get all users not scheduled for deletion @Query("SELECT * FROM users where status != 2") diff --git a/app/src/main/java/com/nextcloud/talk/newarch/local/models/UserNgEntity.kt b/app/src/main/java/com/nextcloud/talk/newarch/local/models/UserNgEntity.kt index 7b116d478..2dee280b0 100644 --- a/app/src/main/java/com/nextcloud/talk/newarch/local/models/UserNgEntity.kt +++ b/app/src/main/java/com/nextcloud/talk/newarch/local/models/UserNgEntity.kt @@ -25,7 +25,7 @@ import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey import com.nextcloud.talk.models.json.capabilities.Capabilities -import com.nextcloud.talk.models.json.push.PushConfigurationState +import com.nextcloud.talk.models.json.push.PushConfiguration import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings import com.nextcloud.talk.newarch.local.models.other.UserStatus import com.nextcloud.talk.utils.ApiUtils @@ -43,7 +43,7 @@ data class UserNgEntity( @ColumnInfo(name = "display_name") var displayName: String? = null, @ColumnInfo( name = "push_configuration" - ) var pushConfiguration: PushConfigurationState? = null, + ) var pushConfiguration: PushConfiguration? = null, @ColumnInfo(name = "capabilities") var capabilities: Capabilities? = null, @ColumnInfo(name = "client_auth_cert") var clientCertificate: String? = null, @ColumnInfo( diff --git a/app/src/main/java/com/nextcloud/talk/utils/PushUtils.kt b/app/src/main/java/com/nextcloud/talk/utils/PushUtils.kt index 0b41a32e7..23899db98 100644 --- a/app/src/main/java/com/nextcloud/talk/utils/PushUtils.kt +++ b/app/src/main/java/com/nextcloud/talk/utils/PushUtils.kt @@ -23,12 +23,14 @@ package com.nextcloud.talk.utils import android.content.Context import android.util.Base64 import android.util.Log +import com.nextcloud.talk.R import com.nextcloud.talk.api.NcApi import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication import com.nextcloud.talk.models.SignatureVerification -import com.nextcloud.talk.models.json.push.PushConfigurationState +import com.nextcloud.talk.models.json.push.PushConfiguration import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository import com.nextcloud.talk.newarch.local.models.UserNgEntity +import com.nextcloud.talk.newarch.utils.hashWithAlgorithm import com.nextcloud.talk.utils.preferences.AppPreferences import org.greenrobot.eventbus.EventBus import org.koin.core.KoinComponent @@ -38,6 +40,7 @@ import java.security.* import java.security.spec.InvalidKeySpecException import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec +import java.util.HashMap class PushUtils(val usersRepository: UsersRepository) : KoinComponent { val appPreferences: AppPreferences by inject() @@ -46,12 +49,52 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent { private val keysFile: File private val publicKeyFile: File private val privateKeyFile: File + + fun getMapForPushRegistrationWithServer(user: UserNgEntity): Map? { + val options = mutableMapOf() + val pushConfiguration = user.pushConfiguration + options["pushToken"] = pushConfiguration?.pushToken + options["deviceIdentifier"] = pushConfiguration?.deviceIdentifier + options["deviceIdentifierSignature"] = pushConfiguration?.deviceIdentifierSignature + options["userPublicKey"] = pushConfiguration?.userPublicKey + + if (options.containsValue(null)) { + return null + } + + return options + } + + fun getMapForPushRegistrationWithServer(context: Context, token: String) : Map { + val options = mutableMapOf() + + // Let's generate a keypair if we don't have it + generateRsa2048KeyPair() + + val pushTokenHash = token.hashWithAlgorithm("SHA-512") + var publicKey = "" + val devicePublicKey = readKeyFromFile(true) as PublicKey? + devicePublicKey?.let { + val publicKeyBytes: ByteArray = Base64.encode(it.encoded, Base64.NO_WRAP) + publicKey = String(publicKeyBytes) + publicKey = publicKey.replace("(.{64})".toRegex(), "$1\n") + publicKey = "-----BEGIN PUBLIC KEY-----\n$publicKey\n-----END PUBLIC KEY-----\n" + } + + options["format"] = "json" + options["pushTokenHash"] = pushTokenHash + options["devicePublicKey"] = publicKey + options["proxyServer"] = context.resources.getString(R.string.nc_push_server_url) + + return options + } + fun verifySignature( signatureBytes: ByteArray?, subjectBytes: ByteArray? ): SignatureVerification { val signature: Signature? - var pushConfigurationState: PushConfigurationState? + var pushConfiguration: PushConfiguration? var publicKey: PublicKey? val signatureVerification = SignatureVerification() @@ -61,10 +104,10 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent { signature = Signature.getInstance("SHA512withRSA") if (userEntities.isNotEmpty()) { for (userEntity in userEntities) { - pushConfigurationState = userEntity.pushConfiguration - if (pushConfigurationState?.userPublicKey != null) { + pushConfiguration = userEntity.pushConfiguration + if (pushConfiguration?.userPublicKey != null) { publicKey = readKeyFromString( - true, pushConfigurationState.userPublicKey!! + true, pushConfiguration.userPublicKey!! ) as PublicKey? signature.initVerify(publicKey) signature.update(subjectBytes) @@ -141,7 +184,6 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent { // we failed to generate the key else { // We already have the key - return -1 } diff --git a/app/src/main/res/layout/login_entry_view.xml b/app/src/main/res/layout/login_entry_view.xml index 9612389b3..0669a8a44 100644 --- a/app/src/main/res/layout/login_entry_view.xml +++ b/app/src/main/res/layout/login_entry_view.xml @@ -5,18 +5,8 @@ xmlns:tools="http://schemas.android.com/tools" android:background="@color/colorPrimary"> - - -