mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-20 11:15:02 +01:00
Further work on new login flow
Signed-off-by: Mario Danic <mario@lovelyhq.com>
This commit is contained in:
parent
b1dcada075
commit
739e63782f
@ -48,7 +48,7 @@ android {
|
|||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
versionCode 130
|
versionCode 130
|
||||||
versionName "8.0.0alpha1"
|
versionName "9.0.0alpha1"
|
||||||
|
|
||||||
flavorDimensions "default"
|
flavorDimensions "default"
|
||||||
renderscriptTargetApi 19
|
renderscriptTargetApi 19
|
||||||
@ -159,8 +159,6 @@ ext {
|
|||||||
lifecycle_version = '2.2.0-rc03'
|
lifecycle_version = '2.2.0-rc03'
|
||||||
coil_version = "0.9.1"
|
coil_version = "0.9.1"
|
||||||
room_version = "2.2.3"
|
room_version = "2.2.3"
|
||||||
geckoviewChannel = "nightly"
|
|
||||||
geckoviewVersion = "71.0.20200108003105"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations.all {
|
configurations.all {
|
||||||
@ -187,7 +185,6 @@ dependencies {
|
|||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||||
|
|
||||||
implementation "org.mozilla.geckoview:geckoview:${geckoviewVersion}"
|
|
||||||
implementation "com.github.stateless4j:stateless4j:2.6.0"
|
implementation "com.github.stateless4j:stateless4j:2.6.0"
|
||||||
|
|
||||||
// ViewModel and LiveData
|
// ViewModel and LiveData
|
||||||
|
@ -23,7 +23,6 @@ import android.annotation.SuppressLint
|
|||||||
import com.google.firebase.messaging.FirebaseMessagingService
|
import com.google.firebase.messaging.FirebaseMessagingService
|
||||||
import com.google.firebase.messaging.RemoteMessage
|
import com.google.firebase.messaging.RemoteMessage
|
||||||
import com.nextcloud.talk.jobs.NotificationWorker
|
import com.nextcloud.talk.jobs.NotificationWorker
|
||||||
import com.nextcloud.talk.jobs.PushRegistrationWorker
|
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||||
import androidx.work.Data
|
import androidx.work.Data
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
@ -38,8 +37,6 @@ class MagicFirebaseMessagingService : FirebaseMessagingService(), KoinComponent
|
|||||||
override fun onNewToken(token: String) {
|
override fun onNewToken(token: String) {
|
||||||
super.onNewToken(token)
|
super.onNewToken(token)
|
||||||
appPreferences.pushToken = token
|
appPreferences.pushToken = token
|
||||||
val pushRegistrationWork: OneTimeWorkRequest = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build()
|
|
||||||
WorkManager.getInstance().enqueue(pushRegistrationWork)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("LongLogTag")
|
@SuppressLint("LongLogTag")
|
||||||
|
@ -37,12 +37,11 @@ import com.nextcloud.talk.BuildConfig
|
|||||||
import com.nextcloud.talk.components.filebrowser.webdav.DavUtils
|
import com.nextcloud.talk.components.filebrowser.webdav.DavUtils
|
||||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||||
import com.nextcloud.talk.jobs.CapabilitiesWorker
|
import com.nextcloud.talk.jobs.CapabilitiesWorker
|
||||||
import com.nextcloud.talk.jobs.PushRegistrationWorker
|
|
||||||
import com.nextcloud.talk.jobs.SignalingSettingsWorker
|
import com.nextcloud.talk.jobs.SignalingSettingsWorker
|
||||||
import com.nextcloud.talk.models.ExternalSignalingServer
|
import com.nextcloud.talk.models.ExternalSignalingServer
|
||||||
import com.nextcloud.talk.models.database.UserEntity
|
import com.nextcloud.talk.models.database.UserEntity
|
||||||
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
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.models.json.signaling.settings.SignalingSettings
|
||||||
import com.nextcloud.talk.newarch.di.module.*
|
import com.nextcloud.talk.newarch.di.module.*
|
||||||
import com.nextcloud.talk.newarch.domain.di.module.UseCasesModule
|
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.androidContext
|
||||||
import org.koin.android.ext.koin.androidLogger
|
import org.koin.android.ext.koin.androidLogger
|
||||||
import org.koin.core.context.startKoin
|
import org.koin.core.context.startKoin
|
||||||
import org.mozilla.geckoview.GeckoRuntime
|
|
||||||
import org.webrtc.PeerConnectionFactory
|
import org.webrtc.PeerConnectionFactory
|
||||||
import org.webrtc.voiceengine.WebRtcAudioManager
|
import org.webrtc.voiceengine.WebRtcAudioManager
|
||||||
import org.webrtc.voiceengine.WebRtcAudioUtils
|
import org.webrtc.voiceengine.WebRtcAudioUtils
|
||||||
@ -140,8 +138,6 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration
|
|||||||
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
||||||
ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync()
|
ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync()
|
||||||
|
|
||||||
val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
|
|
||||||
.build()
|
|
||||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java)
|
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java)
|
||||||
.build()
|
.build()
|
||||||
val periodicCapabilitiesUpdateWork = PeriodicWorkRequest.Builder(
|
val periodicCapabilitiesUpdateWork = PeriodicWorkRequest.Builder(
|
||||||
@ -152,8 +148,6 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration
|
|||||||
val signalingSettingsWork = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java)
|
val signalingSettingsWork = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
WorkManager.getInstance(this)
|
|
||||||
.enqueue(pushRegistrationWork)
|
|
||||||
WorkManager.getInstance(this)
|
WorkManager.getInstance(this)
|
||||||
.enqueue(accountRemovalWork)
|
.enqueue(accountRemovalWork)
|
||||||
WorkManager.getInstance(this)
|
WorkManager.getInstance(this)
|
||||||
@ -202,7 +196,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration
|
|||||||
userNg.displayName = user.displayName
|
userNg.displayName = user.displayName
|
||||||
try {
|
try {
|
||||||
userNg.pushConfiguration =
|
userNg.pushConfiguration =
|
||||||
LoganSquare.parse(user.pushConfigurationState, PushConfigurationState::class.java)
|
LoganSquare.parse(user.pushConfigurationState, PushConfiguration::class.java)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// no push
|
// no push
|
||||||
}
|
}
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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`<ObservableSubscribeProxy<Status>>(AutoDispose.autoDisposable(scopeProvider))
|
|
||||||
.subscribe(object : Observer<Status> {
|
|
||||||
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`<ObservableSubscribeProxy<RoomsOverall>>(AutoDispose.autoDisposable(scopeProvider))
|
|
||||||
.subscribe(object : Observer<RoomsOverall> {
|
|
||||||
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`<ObservableSubscribeProxy<UserProfileOverall>>(AutoDispose.autoDisposable(scopeProvider))
|
|
||||||
.subscribe(object : Observer<UserProfileOverall> {
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -60,6 +60,7 @@ import com.nextcloud.talk.jobs.AccountRemovalWorker
|
|||||||
import com.nextcloud.talk.models.RingtoneSettings
|
import com.nextcloud.talk.models.RingtoneSettings
|
||||||
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
|
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
|
||||||
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
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.UserNgEntity
|
||||||
import com.nextcloud.talk.newarch.local.models.getCredentials
|
import com.nextcloud.talk.newarch.local.models.getCredentials
|
||||||
import com.nextcloud.talk.newarch.local.models.other.UserStatus
|
import com.nextcloud.talk.newarch.local.models.other.UserStatus
|
||||||
@ -276,7 +277,7 @@ class SettingsController : BaseController() {
|
|||||||
} else {
|
} else {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
router.setRoot(RouterTransaction.with(
|
router.setRoot(RouterTransaction.with(
|
||||||
ServerSelectionController()
|
ServerEntryView()
|
||||||
)
|
)
|
||||||
.pushChangeHandler(VerticalChangeHandler())
|
.pushChangeHandler(VerticalChangeHandler())
|
||||||
.popChangeHandler(VerticalChangeHandler())
|
.popChangeHandler(VerticalChangeHandler())
|
||||||
@ -395,7 +396,7 @@ class SettingsController : BaseController() {
|
|||||||
|
|
||||||
addAccountButton!!.addPreferenceClickListener { view15 ->
|
addAccountButton!!.addPreferenceClickListener { view15 ->
|
||||||
router.pushController(
|
router.pushController(
|
||||||
RouterTransaction.with(ServerSelectionController()).pushChangeHandler(
|
RouterTransaction.with(ServerEntryView()).pushChangeHandler(
|
||||||
VerticalChangeHandler()
|
VerticalChangeHandler()
|
||||||
)
|
)
|
||||||
.popChangeHandler(VerticalChangeHandler())
|
.popChangeHandler(VerticalChangeHandler())
|
||||||
@ -567,13 +568,6 @@ class SettingsController : BaseController() {
|
|||||||
.host
|
.host
|
||||||
|
|
||||||
reauthorizeButton!!.addPreferenceClickListener { view14 ->
|
reauthorizeButton!!.addPreferenceClickListener { view14 ->
|
||||||
router.pushController(
|
|
||||||
RouterTransaction.with(
|
|
||||||
WebViewLoginController(currentUser!!.baseUrl, true)
|
|
||||||
)
|
|
||||||
.pushChangeHandler(VerticalChangeHandler())
|
|
||||||
.popChangeHandler(VerticalChangeHandler())
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentUser!!.displayName != null) {
|
if (currentUser!!.displayName != null) {
|
||||||
|
@ -260,9 +260,6 @@ class SwitchAccountController : BaseController {
|
|||||||
bundle.putString(BundleKeys.KEY_USERNAME, importAccount.username)
|
bundle.putString(BundleKeys.KEY_USERNAME, importAccount.username)
|
||||||
bundle.putString(BundleKeys.KEY_TOKEN, importAccount.token)
|
bundle.putString(BundleKeys.KEY_TOKEN, importAccount.token)
|
||||||
bundle.putBoolean(BundleKeys.KEY_IS_ACCOUNT_IMPORT, true)
|
bundle.putBoolean(BundleKeys.KEY_IS_ACCOUNT_IMPORT, true)
|
||||||
router.pushController(RouterTransaction.with(AccountVerificationController(bundle))
|
|
||||||
.pushChangeHandler(HorizontalChangeHandler())
|
|
||||||
.popChangeHandler(HorizontalChangeHandler()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getTitle(): String? {
|
override fun getTitle(): String? {
|
||||||
|
@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
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<String, String> = 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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -42,10 +42,7 @@ import com.google.android.material.appbar.AppBarLayout
|
|||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.activities.MainActivity
|
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.SwitchAccountController
|
||||||
import com.nextcloud.talk.controllers.WebViewLoginController
|
|
||||||
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
|
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
|
||||||
import com.nextcloud.talk.utils.FABAwareScrollingViewBehavior
|
import com.nextcloud.talk.utils.FABAwareScrollingViewBehavior
|
||||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
@ -137,9 +134,6 @@ abstract class BaseController : ButterKnifeController(), ComponentCallbacks {
|
|||||||
|
|
||||||
private fun cleanTempCertPreference() {
|
private fun cleanTempCertPreference() {
|
||||||
val temporaryClassNames = ArrayList<String>()
|
val temporaryClassNames = ArrayList<String>()
|
||||||
temporaryClassNames.add(ServerSelectionController::class.java.name)
|
|
||||||
temporaryClassNames.add(AccountVerificationController::class.java.name)
|
|
||||||
temporaryClassNames.add(WebViewLoginController::class.java.name)
|
|
||||||
temporaryClassNames.add(SwitchAccountController::class.java.name)
|
temporaryClassNames.add(SwitchAccountController::class.java.name)
|
||||||
|
|
||||||
if (!temporaryClassNames.contains(javaClass.name)) {
|
if (!temporaryClassNames.contains(javaClass.name)) {
|
||||||
|
@ -1,167 +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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
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<String, String> =
|
|
||||||
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<PushRegistrationOverall> {
|
|
||||||
override fun onSubscribe(d: Disposable) {}
|
|
||||||
@SuppressLint("CheckResult")
|
|
||||||
override fun onNext(pushRegistrationOverall: PushRegistrationOverall) {
|
|
||||||
val proxyMap: MutableMap<String, String> =
|
|
||||||
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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,9 +21,10 @@
|
|||||||
package com.nextcloud.talk.models.json.push
|
package com.nextcloud.talk.models.json.push
|
||||||
|
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
|
||||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
import lombok.Data
|
import lombok.Data
|
||||||
import org.parceler.Parcel
|
import org.parceler.Parcel
|
||||||
|
|
||||||
@ -31,30 +32,31 @@ import org.parceler.Parcel
|
|||||||
@Data
|
@Data
|
||||||
@JsonObject
|
@JsonObject
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class PushConfigurationState(
|
@Serializable
|
||||||
@JsonField(name = ["pushToken"])
|
data class PushConfiguration(
|
||||||
|
@SerialName("pushToken")
|
||||||
var pushToken: String? = null,
|
var pushToken: String? = null,
|
||||||
@JsonField(name = ["deviceIdentifier"])
|
@SerialName("deviceIdentifier")
|
||||||
var deviceIdentifier: String? = null,
|
var deviceIdentifier: String? = null,
|
||||||
@JsonField(name = ["deviceIdentifierSignature"])
|
@SerialName("deviceIdentifierSignature")
|
||||||
var deviceIdentifierSignature: String? = null,
|
var deviceIdentifierSignature: String? = null,
|
||||||
@JsonField(name = ["userPublicKey"])
|
@SerialName("userPublicKey")
|
||||||
var userPublicKey: String? = null,
|
var userPublicKey: String? = null,
|
||||||
@JsonField(name = ["usesRegularPass"])
|
@SerialName("state")
|
||||||
var usesRegularPass: Boolean = false
|
var pushConfigurationStateWrapper: PushConfigurationStateWrapper? = null
|
||||||
|
|
||||||
) : Parcelable {
|
) : Parcelable {
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (javaClass != other?.javaClass) return false
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
other as PushConfigurationState
|
other as PushConfiguration
|
||||||
|
|
||||||
if (pushToken != other.pushToken) return false
|
if (pushToken != other.pushToken) return false
|
||||||
if (deviceIdentifier != other.deviceIdentifier) return false
|
if (deviceIdentifier != other.deviceIdentifier) return false
|
||||||
if (deviceIdentifierSignature != other.deviceIdentifierSignature) return false
|
if (deviceIdentifierSignature != other.deviceIdentifierSignature) return false
|
||||||
if (userPublicKey != other.userPublicKey) return false
|
if (userPublicKey != other.userPublicKey) return false
|
||||||
if (usesRegularPass != other.usesRegularPass) return false
|
if (pushConfigurationStateWrapper != other.pushConfigurationStateWrapper) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -64,7 +66,27 @@ data class PushConfigurationState(
|
|||||||
result = 31 * result + (deviceIdentifier?.hashCode() ?: 0)
|
result = 31 * result + (deviceIdentifier?.hashCode() ?: 0)
|
||||||
result = 31 * result + (deviceIdentifierSignature?.hashCode() ?: 0)
|
result = 31 * result + (deviceIdentifierSignature?.hashCode() ?: 0)
|
||||||
result = 31 * result + (userPublicKey?.hashCode() ?: 0)
|
result = 31 * result + (userPublicKey?.hashCode() ?: 0)
|
||||||
result = 31 * result + usesRegularPass.hashCode()
|
result = 31 * result + pushConfigurationStateWrapper.hashCode()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
@ -23,6 +23,7 @@ import android.os.Parcelable
|
|||||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import lombok.Data
|
import lombok.Data
|
||||||
|
|
||||||
@ -33,14 +34,18 @@ import lombok.Data
|
|||||||
data class IceServer @JvmOverloads constructor(
|
data class IceServer @JvmOverloads constructor(
|
||||||
@JvmField
|
@JvmField
|
||||||
@JsonField(name = ["url"])
|
@JsonField(name = ["url"])
|
||||||
|
@SerialName("url")
|
||||||
var url: String? = null,
|
var url: String? = null,
|
||||||
@JvmField
|
@JvmField
|
||||||
@JsonField(name = ["urls"])
|
@JsonField(name = ["urls"])
|
||||||
|
@SerialName("urls")
|
||||||
var urls: List<String>? = null,
|
var urls: List<String>? = null,
|
||||||
@JvmField
|
@JvmField
|
||||||
@JsonField(name = ["username"])
|
@JsonField(name = ["username"])
|
||||||
|
@SerialName("username")
|
||||||
var username: String? = null,
|
var username: String? = null,
|
||||||
@JvmField
|
@JvmField
|
||||||
@JsonField(name = ["credential"])
|
@JsonField(name = ["credential"])
|
||||||
|
@SerialName("credential")
|
||||||
var credential: String? = null
|
var credential: String? = null
|
||||||
) : Parcelable
|
) : Parcelable
|
@ -23,6 +23,7 @@ import android.os.Parcelable
|
|||||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||||
import kotlinx.android.parcel.Parcelize
|
import kotlinx.android.parcel.Parcelize
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import lombok.Data
|
import lombok.Data
|
||||||
|
|
||||||
@ -33,14 +34,18 @@ import lombok.Data
|
|||||||
data class SignalingSettings @JvmOverloads constructor(
|
data class SignalingSettings @JvmOverloads constructor(
|
||||||
@JvmField
|
@JvmField
|
||||||
@JsonField(name = ["stunservers"])
|
@JsonField(name = ["stunservers"])
|
||||||
|
@SerialName("stunservers")
|
||||||
var stunServers: List<IceServer>? = null,
|
var stunServers: List<IceServer>? = null,
|
||||||
@JvmField
|
@JvmField
|
||||||
@JsonField(name = ["turnservers"])
|
@JsonField(name = ["turnservers"])
|
||||||
|
@SerialName("turnservers")
|
||||||
var turnServers: List<IceServer>? = null,
|
var turnServers: List<IceServer>? = null,
|
||||||
@JvmField
|
@JvmField
|
||||||
@JsonField(name = ["server"])
|
@JsonField(name = ["server"])
|
||||||
|
@SerialName("server")
|
||||||
var externalSignalingServer: String? = null,
|
var externalSignalingServer: String? = null,
|
||||||
@JvmField
|
@JvmField
|
||||||
@JsonField(name = ["ticket"])
|
@JsonField(name = ["ticket"])
|
||||||
|
@SerialName("ticket")
|
||||||
var externalSignalingTicket: String? = null
|
var externalSignalingTicket: String? = null
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
@ -54,6 +54,10 @@ class UsersRepositoryImpl(private val usersDao: UsersDao) : UsersRepository {
|
|||||||
return usersDao.updateUser(user)
|
return usersDao.updateUser(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun insertUser(user: UserNgEntity): Long {
|
||||||
|
return usersDao.saveUser(user)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun setUserAsActiveWithId(id: Long) {
|
override suspend fun setUserAsActiveWithId(id: Long) {
|
||||||
usersDao.setUserAsActiveWithId(id)
|
usersDao.setUserAsActiveWithId(id)
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,6 @@ import okhttp3.logging.HttpLoggingInterceptor.Logger
|
|||||||
import org.koin.android.ext.koin.androidApplication
|
import org.koin.android.ext.koin.androidApplication
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import org.mozilla.geckoview.GeckoRuntime
|
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@ -73,7 +72,6 @@ import javax.net.ssl.SSLSocketFactory
|
|||||||
import javax.net.ssl.X509KeyManager
|
import javax.net.ssl.X509KeyManager
|
||||||
|
|
||||||
val NetworkModule = module {
|
val NetworkModule = module {
|
||||||
single { createGeckoRuntime(androidContext()) }
|
|
||||||
single { createService(get()) }
|
single { createService(get()) }
|
||||||
single { createLegacyNcApi(get()) }
|
single { createLegacyNcApi(get()) }
|
||||||
single { createRetrofit(get()) }
|
single { createRetrofit(get()) }
|
||||||
@ -91,10 +89,6 @@ val NetworkModule = module {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createGeckoRuntime(context: Context): GeckoRuntime {
|
|
||||||
return GeckoRuntime.create(context)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createCookieManager(): CookieManager {
|
fun createCookieManager(): CookieManager {
|
||||||
val cookieManager = CookieManager()
|
val cookieManager = CookieManager()
|
||||||
cookieManager.setCookiePolicy(ACCEPT_ALL)
|
cookieManager.setCookiePolicy(ACCEPT_ALL)
|
||||||
|
@ -43,9 +43,38 @@ val UseCasesModule = module {
|
|||||||
single { createGetProfileUseCase(get(), get()) }
|
single { createGetProfileUseCase(get(), get()) }
|
||||||
single { createGetSignalingUseCase(get(), get()) }
|
single { createGetSignalingUseCase(get(), get()) }
|
||||||
single { createGetCapabilitiesUseCase(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()) }
|
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,
|
fun createGetCapabilitiesUseCase(nextcloudTalkRepository: NextcloudTalkRepository,
|
||||||
apiErrorHandler: ApiErrorHandler
|
apiErrorHandler: ApiErrorHandler
|
||||||
): GetCapabilitiesUseCase {
|
): GetCapabilitiesUseCase {
|
||||||
|
@ -30,6 +30,7 @@ interface UsersRepository {
|
|||||||
fun getUserWithId(id: Long): UserNgEntity
|
fun getUserWithId(id: Long): UserNgEntity
|
||||||
suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity?
|
suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity?
|
||||||
suspend fun updateUser(user: UserNgEntity): Int
|
suspend fun updateUser(user: UserNgEntity): Int
|
||||||
|
suspend fun insertUser(user: UserNgEntity): Long
|
||||||
suspend fun setUserAsActiveWithId(id: Long)
|
suspend fun setUserAsActiveWithId(id: Long)
|
||||||
suspend fun deleteUserWithId(id: Long)
|
suspend fun deleteUserWithId(id: Long)
|
||||||
suspend fun setAnyUserAsActive(): Boolean
|
suspend fun setAnyUserAsActive(): Boolean
|
||||||
|
@ -34,7 +34,6 @@ interface NextcloudTalkRepository {
|
|||||||
suspend fun unregisterPushWithServerForUser(user: UserNgEntity): GenericOverall
|
suspend fun unregisterPushWithServerForUser(user: UserNgEntity): GenericOverall
|
||||||
suspend fun registerPushWithProxyForUser(user: UserNgEntity, options: Map<String, String>): Any
|
suspend fun registerPushWithProxyForUser(user: UserNgEntity, options: Map<String, String>): Any
|
||||||
suspend fun unregisterPushWithProxyForUser(user: UserNgEntity, options: Map<String, String>): Any
|
suspend fun unregisterPushWithProxyForUser(user: UserNgEntity, options: Map<String, String>): Any
|
||||||
|
|
||||||
suspend fun getSignalingSettingsForUser(user: UserNgEntity): SignalingSettingsOverall
|
suspend fun getSignalingSettingsForUser(user: UserNgEntity): SignalingSettingsOverall
|
||||||
suspend fun getProfileForUser(user: UserNgEntity): UserProfileOverall
|
suspend fun getProfileForUser(user: UserNgEntity): UserProfileOverall
|
||||||
suspend fun getConversationsForUser(user: UserNgEntity): List<Conversation>
|
suspend fun getConversationsForUser(user: UserNgEntity): List<Conversation>
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * Nextcloud Talk application
|
||||||
|
* *
|
||||||
|
* * @author Mario Danic
|
||||||
|
* * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.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<Any, Any?>(apiErrorHandler) {
|
||||||
|
override suspend fun run(params: Any?): Any {
|
||||||
|
val definitionParameters = params as DefinitionParameters
|
||||||
|
return nextcloudTalkRepository.registerPushWithProxyForUser(definitionParameters[0], definitionParameters[1])
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * Nextcloud Talk application
|
||||||
|
* *
|
||||||
|
* * @author Mario Danic
|
||||||
|
* * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.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<PushRegistrationOverall, Any?>(apiErrorHandler) {
|
||||||
|
override suspend fun run(params: Any?): PushRegistrationOverall {
|
||||||
|
val definitionParameters = params as DefinitionParameters
|
||||||
|
return nextcloudTalkRepository.registerPushWithServerForUser(definitionParameters[0], definitionParameters[1])
|
||||||
|
}
|
||||||
|
}
|
@ -34,9 +34,9 @@ class SetConversationFavoriteValueUseCase constructor(
|
|||||||
override suspend fun run(params: Any?): GenericOverall {
|
override suspend fun run(params: Any?): GenericOverall {
|
||||||
val definitionParameters = params as DefinitionParameters
|
val definitionParameters = params as DefinitionParameters
|
||||||
return nextcloudTalkRepository.setFavoriteValueForConversation(
|
return nextcloudTalkRepository.setFavoriteValueForConversation(
|
||||||
definitionParameters.get(0),
|
definitionParameters[0],
|
||||||
definitionParameters.get(1),
|
definitionParameters[1],
|
||||||
definitionParameters.get(2)
|
definitionParameters[2]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * Nextcloud Talk application
|
||||||
|
* *
|
||||||
|
* * @author Mario Danic
|
||||||
|
* * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.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<Any, Any?>(apiErrorHandler) {
|
||||||
|
override suspend fun run(params: Any?): Any {
|
||||||
|
val definitionParameters = params as DefinitionParameters
|
||||||
|
return nextcloudTalkRepository.unregisterPushWithProxyForUser(definitionParameters[0], definitionParameters[1])
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* * Nextcloud Talk application
|
||||||
|
* *
|
||||||
|
* * @author Mario Danic
|
||||||
|
* * Copyright (C) 2017-2020 Mario Danic <mario@lovelyhq.com>
|
||||||
|
* *
|
||||||
|
* * This program is free software: you can redistribute it and/or modify
|
||||||
|
* * it under the terms of the GNU General Public License as published by
|
||||||
|
* * the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* * at your option) any later version.
|
||||||
|
* *
|
||||||
|
* * This program is distributed in the hope that it will be useful,
|
||||||
|
* * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* * GNU General Public License for more details.
|
||||||
|
* *
|
||||||
|
* * You should have received a copy of the GNU General Public License
|
||||||
|
* * along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.nextcloud.talk.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<GenericOverall, Any?>(apiErrorHandler) {
|
||||||
|
override suspend fun run(params: Any?): GenericOverall {
|
||||||
|
val definitionParameters = params as DefinitionParameters
|
||||||
|
return nextcloudTalkRepository.unregisterPushWithServerForUser(definitionParameters[0])
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,7 @@ package com.nextcloud.talk.newarch.features.account.di.module
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.GetCapabilitiesUseCase
|
import com.nextcloud.talk.newarch.domain.usecases.*
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.GetProfileUseCase
|
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.GetSignalingSettingsUseCase
|
|
||||||
import com.nextcloud.talk.newarch.features.account.loginentry.LoginEntryViewModelFactory
|
import com.nextcloud.talk.newarch.features.account.loginentry.LoginEntryViewModelFactory
|
||||||
import com.nextcloud.talk.newarch.features.account.serverentry.ServerEntryViewModelFactory
|
import com.nextcloud.talk.newarch.features.account.serverentry.ServerEntryViewModelFactory
|
||||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
@ -18,7 +16,7 @@ val AccountModule = module {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
factory {
|
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,
|
getProfileUseCase: GetProfileUseCase,
|
||||||
getCapabilitiesUseCase: GetCapabilitiesUseCase,
|
getCapabilitiesUseCase: GetCapabilitiesUseCase,
|
||||||
getSignalingSettingsUseCase: GetSignalingSettingsUseCase,
|
getSignalingSettingsUseCase: GetSignalingSettingsUseCase,
|
||||||
|
registerPushWithServerUseCase: RegisterPushWithServerUseCase,
|
||||||
|
registerPushWithProxyUseCase: RegisterPushWithProxyUseCase,
|
||||||
appPreferences: AppPreferences,
|
appPreferences: AppPreferences,
|
||||||
usersRepository: UsersRepository
|
usersRepository: UsersRepository
|
||||||
): LoginEntryViewModelFactory {
|
): LoginEntryViewModelFactory {
|
||||||
return LoginEntryViewModelFactory(
|
return LoginEntryViewModelFactory(
|
||||||
application, getProfileUseCase, getCapabilitiesUseCase, getSignalingSettingsUseCase, appPreferences, usersRepository
|
application, getProfileUseCase, getCapabilitiesUseCase, getSignalingSettingsUseCase, registerPushWithServerUseCase, registerPushWithProxyUseCase, appPreferences, usersRepository
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -22,11 +22,14 @@
|
|||||||
|
|
||||||
package com.nextcloud.talk.newarch.features.account.loginentry
|
package com.nextcloud.talk.newarch.features.account.loginentry
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.webkit.*
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.bluelinelabs.conductor.RouterTransaction
|
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.conversationsList.mvp.BaseView
|
||||||
import com.nextcloud.talk.newarch.features.conversationslist.ConversationsListView
|
import com.nextcloud.talk.newarch.features.conversationslist.ConversationsListView
|
||||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||||
|
import de.cotech.hw.fido.WebViewFidoBridge
|
||||||
import kotlinx.android.synthetic.main.login_entry_view.view.*
|
import kotlinx.android.synthetic.main.login_entry_view.view.*
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.mozilla.geckoview.*
|
|
||||||
import org.mozilla.geckoview.GeckoSessionSettings.USER_AGENT_MODE_MOBILE
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class LoginEntryView(val bundle: Bundle) : BaseView() {
|
class LoginEntryView(val bundle: Bundle) : BaseView() {
|
||||||
@ -48,11 +50,7 @@ class LoginEntryView(val bundle: Bundle) : BaseView() {
|
|||||||
private lateinit var viewModel: LoginEntryViewModel
|
private lateinit var viewModel: LoginEntryViewModel
|
||||||
val factory: LoginEntryViewModelFactory by inject()
|
val factory: LoginEntryViewModelFactory by inject()
|
||||||
|
|
||||||
private lateinit var geckoView: GeckoView
|
private var assembledPrefix = ""
|
||||||
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 val webLoginUserAgent: String
|
private val webLoginUserAgent: String
|
||||||
get() = (Build.MANUFACTURER.substring(0, 1).toUpperCase(
|
get() = (Build.MANUFACTURER.substring(0, 1).toUpperCase(
|
||||||
@ -65,11 +63,14 @@ class LoginEntryView(val bundle: Bundle) : BaseView() {
|
|||||||
return R.layout.login_entry_view
|
return R.layout.login_entry_view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetJavaScriptEnabled")
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||||
actionBar?.hide()
|
actionBar?.hide()
|
||||||
viewModel = viewModelProvider(factory).get(LoginEntryViewModel::class.java)
|
viewModel = viewModelProvider(factory).get(LoginEntryViewModel::class.java)
|
||||||
val view = super.onCreateView(inflater, container)
|
val view = super.onCreateView(inflater, container)
|
||||||
|
|
||||||
|
assembledPrefix = resources?.getString(R.string.nc_talk_login_scheme) + protocolSuffix + "login/"
|
||||||
|
|
||||||
viewModel.state.observe(this@LoginEntryView, Observer {
|
viewModel.state.observe(this@LoginEntryView, Observer {
|
||||||
when (it.state) {
|
when (it.state) {
|
||||||
LoginEntryState.FAILED -> {
|
LoginEntryState.FAILED -> {
|
||||||
@ -80,82 +81,83 @@ class LoginEntryView(val bundle: Bundle) : BaseView() {
|
|||||||
}
|
}
|
||||||
LoginEntryState.CHECKING -> {
|
LoginEntryState.CHECKING -> {
|
||||||
view.progressBar.isVisible = true
|
view.progressBar.isVisible = true
|
||||||
geckoView.isVisible = false
|
view.webView.isVisible = false
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
if (router?.hasRootController() == true) {
|
router.setRoot(RouterTransaction.with(ConversationsListView())
|
||||||
router.popController(this)
|
.pushChangeHandler(HorizontalChangeHandler())
|
||||||
} else {
|
.popChangeHandler(HorizontalChangeHandler()))
|
||||||
router.setRoot(RouterTransaction.with(ConversationsListView())
|
|
||||||
.pushChangeHandler(HorizontalChangeHandler())
|
|
||||||
.popChangeHandler(HorizontalChangeHandler()))
|
|
||||||
}
|
|
||||||
// all good, proceed
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
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())
|
val baseUrl = bundle.get(BundleKeys.KEY_BASE_URL)
|
||||||
geckoSession.open(geckoRuntime)
|
val headers: MutableMap<String, String> = hashMapOf()
|
||||||
geckoSession.progressDelegate = createProgressDelegate()
|
headers["OCS-APIRequest"] = "true"
|
||||||
geckoSession.navigationDelegate = createNavigationDelegate()
|
|
||||||
geckoView.setSession(geckoSession)
|
setupWebView(view)
|
||||||
bundle.getString(BundleKeys.KEY_BASE_URL)?.let { baseUrl ->
|
view.webView.loadUrl("$baseUrl/index.php/login/flow", headers)
|
||||||
geckoSession.loadUri("$baseUrl/index.php/login/flow", mapOf<String, String>("OCS-APIRequest" to "true"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNavigationDelegate(): GeckoSession.NavigationDelegate {
|
override fun onSaveViewState(view: View, outState: Bundle) {
|
||||||
return object : GeckoSession.NavigationDelegate {
|
view.webView.saveState(outState)
|
||||||
override fun onLoadRequest(p0: GeckoSession, p1: GeckoSession.NavigationDelegate.LoadRequest): GeckoResult<AllowOrDeny>? {
|
super.onSaveViewState(view, outState)
|
||||||
if (p1.uri.startsWith(assembledPrefix)) {
|
}
|
||||||
viewModel.parseData(assembledPrefix, dataSeparator, p1.uri)
|
|
||||||
return GeckoResult.DENY
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -5,16 +5,19 @@ import androidx.lifecycle.MutableLiveData
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.nextcloud.talk.models.LoginData
|
import com.nextcloud.talk.models.LoginData
|
||||||
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
|
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.signaling.settings.SignalingSettingsOverall
|
||||||
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
|
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
|
||||||
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
|
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
|
||||||
import com.nextcloud.talk.newarch.data.model.ErrorModel
|
import com.nextcloud.talk.newarch.data.model.ErrorModel
|
||||||
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.GetCapabilitiesUseCase
|
import com.nextcloud.talk.newarch.domain.usecases.*
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.GetProfileUseCase
|
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.GetSignalingSettingsUseCase
|
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
|
import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
|
||||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||||
|
import com.nextcloud.talk.utils.PushUtils
|
||||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
@ -25,12 +28,15 @@ class LoginEntryViewModel constructor(
|
|||||||
private val getProfileUseCase: GetProfileUseCase,
|
private val getProfileUseCase: GetProfileUseCase,
|
||||||
private val getCapabilitiesUseCase: GetCapabilitiesUseCase,
|
private val getCapabilitiesUseCase: GetCapabilitiesUseCase,
|
||||||
private val getSignalingSettingsUseCase: GetSignalingSettingsUseCase,
|
private val getSignalingSettingsUseCase: GetSignalingSettingsUseCase,
|
||||||
|
private val registerPushWithServerUseCase: RegisterPushWithServerUseCase,
|
||||||
|
private val registerPushWithProxyUseCase: RegisterPushWithProxyUseCase,
|
||||||
private val appPreferences: AppPreferences,
|
private val appPreferences: AppPreferences,
|
||||||
private val usersRepository: UsersRepository) :
|
private val usersRepository: UsersRepository) :
|
||||||
BaseViewModel<LoginEntryView>(application) {
|
BaseViewModel<LoginEntryView>(application) {
|
||||||
val state: MutableLiveData<LoginEntryStateWrapper> = MutableLiveData(LoginEntryStateWrapper(LoginEntryState.PENDING_CHECK, null))
|
val state: MutableLiveData<LoginEntryStateWrapper> = 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?) {
|
fun parseData(prefix: String, separator: String, data: String?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
@ -88,10 +94,13 @@ class LoginEntryViewModel constructor(
|
|||||||
|
|
||||||
private suspend fun storeCredentialsOrVerify(loginData: LoginData) {
|
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
|
// 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!!)
|
val userIfExists = usersRepository.getUserWithUsernameAndServer(loginData.username!!, loginData.serverUrl!!)
|
||||||
if (user != null) {
|
if (userIfExists != null) {
|
||||||
|
updatingUser = true
|
||||||
|
user = userIfExists
|
||||||
user.token = loginData.token
|
user.token = loginData.token
|
||||||
usersRepository.updateUser(user)
|
usersRepository.updateUser(user)
|
||||||
|
// complicated - we need to unregister, etc, etc, but not yet
|
||||||
state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.ACCOUNT_UPDATED))
|
state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.ACCOUNT_UPDATED))
|
||||||
} else {
|
} else {
|
||||||
getProfile(loginData)
|
getProfile(loginData)
|
||||||
@ -101,6 +110,7 @@ class LoginEntryViewModel constructor(
|
|||||||
private fun getProfile(loginData: LoginData) {
|
private fun getProfile(loginData: LoginData) {
|
||||||
user.username = loginData.username!!
|
user.username = loginData.username!!
|
||||||
user.baseUrl = loginData.serverUrl!!
|
user.baseUrl = loginData.serverUrl!!
|
||||||
|
user.token = loginData.token
|
||||||
getProfileUseCase.invoke(viewModelScope, parametersOf(user), object : UseCaseResponse<UserProfileOverall> {
|
getProfileUseCase.invoke(viewModelScope, parametersOf(user), object : UseCaseResponse<UserProfileOverall> {
|
||||||
override suspend fun onSuccess(result: UserProfileOverall) {
|
override suspend fun onSuccess(result: UserProfileOverall) {
|
||||||
result.ocs.data.userId?.let { userId ->
|
result.ocs.data.userId?.let { userId ->
|
||||||
@ -135,6 +145,10 @@ class LoginEntryViewModel constructor(
|
|||||||
getSignalingSettingsUseCase.invoke(viewModelScope, parametersOf(user), object : UseCaseResponse<SignalingSettingsOverall> {
|
getSignalingSettingsUseCase.invoke(viewModelScope, parametersOf(user), object : UseCaseResponse<SignalingSettingsOverall> {
|
||||||
override suspend fun onSuccess(result: SignalingSettingsOverall) {
|
override suspend fun onSuccess(result: SignalingSettingsOverall) {
|
||||||
user.signalingSettings = result.ocs.signalingSettings
|
user.signalingSettings = result.ocs.signalingSettings
|
||||||
|
val pushConfiguration = PushConfiguration()
|
||||||
|
val pushConfigurationStateWrapper = PushConfigurationStateWrapper(PushConfigurationState.PENDING, 0)
|
||||||
|
pushConfiguration.pushConfigurationStateWrapper = pushConfigurationStateWrapper
|
||||||
|
usersRepository.insertUser(user)
|
||||||
registerForPush()
|
registerForPush()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,12 +156,13 @@ class LoginEntryViewModel constructor(
|
|||||||
state.postValue(LoginEntryStateWrapper(LoginEntryState.FAILED, LoginEntryStateClarification.SIGNALING_SETTINGS_FETCH_FAILED))
|
state.postValue(LoginEntryStateWrapper(LoginEntryState.FAILED, LoginEntryStateClarification.SIGNALING_SETTINGS_FETCH_FAILED))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun registerForPush() {
|
private suspend fun registerForPush() {
|
||||||
val token = appPreferences.pushToken
|
val token = appPreferences.pushToken
|
||||||
if (!token.isNullOrBlank()) {
|
if (!token.isNullOrBlank()) {
|
||||||
|
user.pushConfiguration?.pushToken = token
|
||||||
|
usersRepository.updateUser(user)
|
||||||
registerForPushWithServer(token)
|
registerForPushWithServer(token)
|
||||||
} else {
|
} else {
|
||||||
state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.PUSH_REGISTRATION_MISSING_TOKEN))
|
state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.PUSH_REGISTRATION_MISSING_TOKEN))
|
||||||
@ -155,10 +170,57 @@ class LoginEntryViewModel constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun registerForPushWithServer(token: String) {
|
private fun registerForPushWithServer(token: String) {
|
||||||
|
val options = PushUtils(usersRepository).getMapForPushRegistrationWithServer(context, token)
|
||||||
|
registerPushWithServerUseCase.invoke(viewModelScope, parametersOf(user, options), object : UseCaseResponse<PushRegistrationOverall> {
|
||||||
|
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<Any> {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,13 +4,11 @@ import android.app.Application
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.GetCapabilitiesUseCase
|
import com.nextcloud.talk.newarch.domain.usecases.*
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.GetProfileUseCase
|
|
||||||
import com.nextcloud.talk.newarch.domain.usecases.GetSignalingSettingsUseCase
|
|
||||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
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 <T : ViewModel?> create(modelClass: Class<T>): T {
|
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||||
return LoginEntryViewModel(application, getProfileUseCase, getCapabilitiesUseCase, getSignalingSettingsUseCase, appPreferences, usersRepository) as T
|
return LoginEntryViewModel(application, getProfileUseCase, getCapabilitiesUseCase, getSignalingSettingsUseCase, registerPushWithServerUseCase, registerPushWithProxyUseCase, appPreferences, usersRepository) as T
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,20 +22,25 @@ package com.nextcloud.talk.newarch.local.converters
|
|||||||
|
|
||||||
import androidx.room.TypeConverter
|
import androidx.room.TypeConverter
|
||||||
import com.bluelinelabs.logansquare.LoganSquare
|
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 {
|
class PushConfigurationConverter {
|
||||||
|
val json = Json(MagicJson.customJsonConfiguration)
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun fromPushConfigurationToString(pushConfigurationState: PushConfigurationState?): String {
|
fun fromPushConfigurationToString(pushConfiguration: PushConfiguration?): String {
|
||||||
if (pushConfigurationState == null) {
|
|
||||||
return ""
|
return if (pushConfiguration == null) {
|
||||||
|
""
|
||||||
} else {
|
} else {
|
||||||
return LoganSquare.serialize(pushConfigurationState)
|
json.stringify(PushConfiguration.serializer(), pushConfiguration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TypeConverter
|
@TypeConverter
|
||||||
fun fromStringToPushConfiguration(value: String): PushConfigurationState? {
|
fun fromStringToPushConfiguration(value: String): PushConfiguration? {
|
||||||
return LoganSquare.parse(value, PushConfigurationState::class.java)
|
return json.parse(PushConfiguration.serializer(), value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ abstract class UsersDao {
|
|||||||
abstract fun saveUser(user: UserNgEntity): Long
|
abstract fun saveUser(user: UserNgEntity): Long
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
abstract suspend fun saveUsers(vararg users: UserNgEntity)
|
abstract suspend fun saveUsers(vararg users: UserNgEntity): List<Long>
|
||||||
|
|
||||||
// get all users not scheduled for deletion
|
// get all users not scheduled for deletion
|
||||||
@Query("SELECT * FROM users where status != 2")
|
@Query("SELECT * FROM users where status != 2")
|
||||||
|
@ -25,7 +25,7 @@ import androidx.room.ColumnInfo
|
|||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
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.models.json.signaling.settings.SignalingSettings
|
||||||
import com.nextcloud.talk.newarch.local.models.other.UserStatus
|
import com.nextcloud.talk.newarch.local.models.other.UserStatus
|
||||||
import com.nextcloud.talk.utils.ApiUtils
|
import com.nextcloud.talk.utils.ApiUtils
|
||||||
@ -43,7 +43,7 @@ data class UserNgEntity(
|
|||||||
@ColumnInfo(name = "display_name") var displayName: String? = null,
|
@ColumnInfo(name = "display_name") var displayName: String? = null,
|
||||||
@ColumnInfo(
|
@ColumnInfo(
|
||||||
name = "push_configuration"
|
name = "push_configuration"
|
||||||
) var pushConfiguration: PushConfigurationState? = null,
|
) var pushConfiguration: PushConfiguration? = null,
|
||||||
@ColumnInfo(name = "capabilities") var capabilities: Capabilities? = null,
|
@ColumnInfo(name = "capabilities") var capabilities: Capabilities? = null,
|
||||||
@ColumnInfo(name = "client_auth_cert") var clientCertificate: String? = null,
|
@ColumnInfo(name = "client_auth_cert") var clientCertificate: String? = null,
|
||||||
@ColumnInfo(
|
@ColumnInfo(
|
||||||
|
@ -23,12 +23,14 @@ package com.nextcloud.talk.utils
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.api.NcApi
|
import com.nextcloud.talk.api.NcApi
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||||
import com.nextcloud.talk.models.SignatureVerification
|
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.domain.repository.offline.UsersRepository
|
||||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||||
|
import com.nextcloud.talk.newarch.utils.hashWithAlgorithm
|
||||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.koin.core.KoinComponent
|
import org.koin.core.KoinComponent
|
||||||
@ -38,6 +40,7 @@ import java.security.*
|
|||||||
import java.security.spec.InvalidKeySpecException
|
import java.security.spec.InvalidKeySpecException
|
||||||
import java.security.spec.PKCS8EncodedKeySpec
|
import java.security.spec.PKCS8EncodedKeySpec
|
||||||
import java.security.spec.X509EncodedKeySpec
|
import java.security.spec.X509EncodedKeySpec
|
||||||
|
import java.util.HashMap
|
||||||
|
|
||||||
class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
|
class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
|
||||||
val appPreferences: AppPreferences by inject()
|
val appPreferences: AppPreferences by inject()
|
||||||
@ -46,12 +49,52 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
|
|||||||
private val keysFile: File
|
private val keysFile: File
|
||||||
private val publicKeyFile: File
|
private val publicKeyFile: File
|
||||||
private val privateKeyFile: File
|
private val privateKeyFile: File
|
||||||
|
|
||||||
|
fun getMapForPushRegistrationWithServer(user: UserNgEntity): Map<String, String?>? {
|
||||||
|
val options = mutableMapOf<String, String?>()
|
||||||
|
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<String, String> {
|
||||||
|
val options = mutableMapOf<String, String>()
|
||||||
|
|
||||||
|
// 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(
|
fun verifySignature(
|
||||||
signatureBytes: ByteArray?,
|
signatureBytes: ByteArray?,
|
||||||
subjectBytes: ByteArray?
|
subjectBytes: ByteArray?
|
||||||
): SignatureVerification {
|
): SignatureVerification {
|
||||||
val signature: Signature?
|
val signature: Signature?
|
||||||
var pushConfigurationState: PushConfigurationState?
|
var pushConfiguration: PushConfiguration?
|
||||||
var publicKey: PublicKey?
|
var publicKey: PublicKey?
|
||||||
val signatureVerification =
|
val signatureVerification =
|
||||||
SignatureVerification()
|
SignatureVerification()
|
||||||
@ -61,10 +104,10 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
|
|||||||
signature = Signature.getInstance("SHA512withRSA")
|
signature = Signature.getInstance("SHA512withRSA")
|
||||||
if (userEntities.isNotEmpty()) {
|
if (userEntities.isNotEmpty()) {
|
||||||
for (userEntity in userEntities) {
|
for (userEntity in userEntities) {
|
||||||
pushConfigurationState = userEntity.pushConfiguration
|
pushConfiguration = userEntity.pushConfiguration
|
||||||
if (pushConfigurationState?.userPublicKey != null) {
|
if (pushConfiguration?.userPublicKey != null) {
|
||||||
publicKey = readKeyFromString(
|
publicKey = readKeyFromString(
|
||||||
true, pushConfigurationState.userPublicKey!!
|
true, pushConfiguration.userPublicKey!!
|
||||||
) as PublicKey?
|
) as PublicKey?
|
||||||
signature.initVerify(publicKey)
|
signature.initVerify(publicKey)
|
||||||
signature.update(subjectBytes)
|
signature.update(subjectBytes)
|
||||||
@ -141,7 +184,6 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
|
|||||||
// we failed to generate the key
|
// we failed to generate the key
|
||||||
else {
|
else {
|
||||||
// We already have the key
|
// We already have the key
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,18 +5,8 @@
|
|||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:background="@color/colorPrimary">
|
android:background="@color/colorPrimary">
|
||||||
|
|
||||||
<ProgressBar
|
<WebView
|
||||||
android:id="@+id/pageProgressBar"
|
android:id="@+id/webView"
|
||||||
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="3dp"
|
|
||||||
android:layout_centerInParent="true"
|
|
||||||
android:layout_marginHorizontal="8dp"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:visibility="visible"/>
|
|
||||||
|
|
||||||
<org.mozilla.geckoview.GeckoView
|
|
||||||
android:id="@+id/geckoView"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/colorPrimary"
|
android:background="@color/colorPrimary"
|
||||||
@ -28,7 +18,7 @@
|
|||||||
android:layout_width="@dimen/item_height"
|
android:layout_width="@dimen/item_height"
|
||||||
android:layout_height="@dimen/item_height"
|
android:layout_height="@dimen/item_height"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:visibility="gone"
|
android:visibility="visible"
|
||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
android:indeterminateTint="@color/white"
|
android:indeterminateTint="@color/white"
|
||||||
android:indeterminateTintMode="src_in" />
|
android:indeterminateTintMode="src_in" />
|
||||||
|
Loading…
Reference in New Issue
Block a user