mirror of
https://github.com/nextcloud/talk-android
synced 2025-06-21 12:39:58 +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"
|
||||
|
||||
versionCode 130
|
||||
versionName "8.0.0alpha1"
|
||||
versionName "9.0.0alpha1"
|
||||
|
||||
flavorDimensions "default"
|
||||
renderscriptTargetApi 19
|
||||
@ -159,8 +159,6 @@ ext {
|
||||
lifecycle_version = '2.2.0-rc03'
|
||||
coil_version = "0.9.1"
|
||||
room_version = "2.2.3"
|
||||
geckoviewChannel = "nightly"
|
||||
geckoviewVersion = "71.0.20200108003105"
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
@ -187,7 +185,6 @@ dependencies {
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3'
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
|
||||
implementation "org.mozilla.geckoview:geckoview:${geckoviewVersion}"
|
||||
implementation "com.github.stateless4j:stateless4j:2.6.0"
|
||||
|
||||
// ViewModel and LiveData
|
||||
|
@ -23,7 +23,6 @@ import android.annotation.SuppressLint
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import com.nextcloud.talk.jobs.NotificationWorker
|
||||
import com.nextcloud.talk.jobs.PushRegistrationWorker
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import androidx.work.Data
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
@ -38,8 +37,6 @@ class MagicFirebaseMessagingService : FirebaseMessagingService(), KoinComponent
|
||||
override fun onNewToken(token: String) {
|
||||
super.onNewToken(token)
|
||||
appPreferences.pushToken = token
|
||||
val pushRegistrationWork: OneTimeWorkRequest = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java).build()
|
||||
WorkManager.getInstance().enqueue(pushRegistrationWork)
|
||||
}
|
||||
|
||||
@SuppressLint("LongLogTag")
|
||||
|
@ -37,12 +37,11 @@ import com.nextcloud.talk.BuildConfig
|
||||
import com.nextcloud.talk.components.filebrowser.webdav.DavUtils
|
||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||
import com.nextcloud.talk.jobs.CapabilitiesWorker
|
||||
import com.nextcloud.talk.jobs.PushRegistrationWorker
|
||||
import com.nextcloud.talk.jobs.SignalingSettingsWorker
|
||||
import com.nextcloud.talk.models.ExternalSignalingServer
|
||||
import com.nextcloud.talk.models.database.UserEntity
|
||||
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
||||
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
||||
import com.nextcloud.talk.models.json.push.PushConfiguration
|
||||
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings
|
||||
import com.nextcloud.talk.newarch.di.module.*
|
||||
import com.nextcloud.talk.newarch.domain.di.module.UseCasesModule
|
||||
@ -71,7 +70,6 @@ import org.koin.android.ext.android.inject
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.android.ext.koin.androidLogger
|
||||
import org.koin.core.context.startKoin
|
||||
import org.mozilla.geckoview.GeckoRuntime
|
||||
import org.webrtc.PeerConnectionFactory
|
||||
import org.webrtc.voiceengine.WebRtcAudioManager
|
||||
import org.webrtc.voiceengine.WebRtcAudioUtils
|
||||
@ -140,8 +138,6 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration
|
||||
Security.insertProviderAt(Conscrypt.newProvider(), 1)
|
||||
ClosedInterfaceImpl().providerInstallerInstallIfNeededAsync()
|
||||
|
||||
val pushRegistrationWork = OneTimeWorkRequest.Builder(PushRegistrationWorker::class.java)
|
||||
.build()
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java)
|
||||
.build()
|
||||
val periodicCapabilitiesUpdateWork = PeriodicWorkRequest.Builder(
|
||||
@ -152,8 +148,6 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration
|
||||
val signalingSettingsWork = OneTimeWorkRequest.Builder(SignalingSettingsWorker::class.java)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(this)
|
||||
.enqueue(pushRegistrationWork)
|
||||
WorkManager.getInstance(this)
|
||||
.enqueue(accountRemovalWork)
|
||||
WorkManager.getInstance(this)
|
||||
@ -202,7 +196,7 @@ class NextcloudTalkApplication : Application(), LifecycleObserver, Configuration
|
||||
userNg.displayName = user.displayName
|
||||
try {
|
||||
userNg.pushConfiguration =
|
||||
LoganSquare.parse(user.pushConfigurationState, PushConfigurationState::class.java)
|
||||
LoganSquare.parse(user.pushConfigurationState, PushConfiguration::class.java)
|
||||
} catch (e: Exception) {
|
||||
// no push
|
||||
}
|
||||
|
@ -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.json.userprofile.UserProfileOverall
|
||||
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
||||
import com.nextcloud.talk.newarch.features.account.serverentry.ServerEntryView
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.newarch.local.models.getCredentials
|
||||
import com.nextcloud.talk.newarch.local.models.other.UserStatus
|
||||
@ -276,7 +277,7 @@ class SettingsController : BaseController() {
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
router.setRoot(RouterTransaction.with(
|
||||
ServerSelectionController()
|
||||
ServerEntryView()
|
||||
)
|
||||
.pushChangeHandler(VerticalChangeHandler())
|
||||
.popChangeHandler(VerticalChangeHandler())
|
||||
@ -395,7 +396,7 @@ class SettingsController : BaseController() {
|
||||
|
||||
addAccountButton!!.addPreferenceClickListener { view15 ->
|
||||
router.pushController(
|
||||
RouterTransaction.with(ServerSelectionController()).pushChangeHandler(
|
||||
RouterTransaction.with(ServerEntryView()).pushChangeHandler(
|
||||
VerticalChangeHandler()
|
||||
)
|
||||
.popChangeHandler(VerticalChangeHandler())
|
||||
@ -567,13 +568,6 @@ class SettingsController : BaseController() {
|
||||
.host
|
||||
|
||||
reauthorizeButton!!.addPreferenceClickListener { view14 ->
|
||||
router.pushController(
|
||||
RouterTransaction.with(
|
||||
WebViewLoginController(currentUser!!.baseUrl, true)
|
||||
)
|
||||
.pushChangeHandler(VerticalChangeHandler())
|
||||
.popChangeHandler(VerticalChangeHandler())
|
||||
)
|
||||
}
|
||||
|
||||
if (currentUser!!.displayName != null) {
|
||||
|
@ -260,9 +260,6 @@ class SwitchAccountController : BaseController {
|
||||
bundle.putString(BundleKeys.KEY_USERNAME, importAccount.username)
|
||||
bundle.putString(BundleKeys.KEY_TOKEN, importAccount.token)
|
||||
bundle.putBoolean(BundleKeys.KEY_IS_ACCOUNT_IMPORT, true)
|
||||
router.pushController(RouterTransaction.with(AccountVerificationController(bundle))
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
}
|
||||
|
||||
override fun getTitle(): String? {
|
||||
|
@ -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.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
import com.nextcloud.talk.controllers.AccountVerificationController
|
||||
import com.nextcloud.talk.controllers.ServerSelectionController
|
||||
import com.nextcloud.talk.controllers.SwitchAccountController
|
||||
import com.nextcloud.talk.controllers.WebViewLoginController
|
||||
import com.nextcloud.talk.controllers.base.providers.ActionBarProvider
|
||||
import com.nextcloud.talk.utils.FABAwareScrollingViewBehavior
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
@ -137,9 +134,6 @@ abstract class BaseController : ButterKnifeController(), ComponentCallbacks {
|
||||
|
||||
private fun cleanTempCertPreference() {
|
||||
val temporaryClassNames = ArrayList<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)
|
||||
|
||||
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
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import lombok.Data
|
||||
import org.parceler.Parcel
|
||||
|
||||
@ -31,30 +32,31 @@ import org.parceler.Parcel
|
||||
@Data
|
||||
@JsonObject
|
||||
@Parcelize
|
||||
data class PushConfigurationState(
|
||||
@JsonField(name = ["pushToken"])
|
||||
@Serializable
|
||||
data class PushConfiguration(
|
||||
@SerialName("pushToken")
|
||||
var pushToken: String? = null,
|
||||
@JsonField(name = ["deviceIdentifier"])
|
||||
@SerialName("deviceIdentifier")
|
||||
var deviceIdentifier: String? = null,
|
||||
@JsonField(name = ["deviceIdentifierSignature"])
|
||||
@SerialName("deviceIdentifierSignature")
|
||||
var deviceIdentifierSignature: String? = null,
|
||||
@JsonField(name = ["userPublicKey"])
|
||||
@SerialName("userPublicKey")
|
||||
var userPublicKey: String? = null,
|
||||
@JsonField(name = ["usesRegularPass"])
|
||||
var usesRegularPass: Boolean = false
|
||||
@SerialName("state")
|
||||
var pushConfigurationStateWrapper: PushConfigurationStateWrapper? = null
|
||||
|
||||
) : Parcelable {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as PushConfigurationState
|
||||
other as PushConfiguration
|
||||
|
||||
if (pushToken != other.pushToken) return false
|
||||
if (deviceIdentifier != other.deviceIdentifier) return false
|
||||
if (deviceIdentifierSignature != other.deviceIdentifierSignature) return false
|
||||
if (userPublicKey != other.userPublicKey) return false
|
||||
if (usesRegularPass != other.usesRegularPass) return false
|
||||
if (pushConfigurationStateWrapper != other.pushConfigurationStateWrapper) return false
|
||||
|
||||
return true
|
||||
}
|
||||
@ -64,7 +66,27 @@ data class PushConfigurationState(
|
||||
result = 31 * result + (deviceIdentifier?.hashCode() ?: 0)
|
||||
result = 31 * result + (deviceIdentifierSignature?.hashCode() ?: 0)
|
||||
result = 31 * result + (userPublicKey?.hashCode() ?: 0)
|
||||
result = 31 * result + usesRegularPass.hashCode()
|
||||
result = 31 * result + pushConfigurationStateWrapper.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.JsonObject
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import lombok.Data
|
||||
|
||||
@ -33,14 +34,18 @@ import lombok.Data
|
||||
data class IceServer @JvmOverloads constructor(
|
||||
@JvmField
|
||||
@JsonField(name = ["url"])
|
||||
@SerialName("url")
|
||||
var url: String? = null,
|
||||
@JvmField
|
||||
@JsonField(name = ["urls"])
|
||||
@SerialName("urls")
|
||||
var urls: List<String>? = null,
|
||||
@JvmField
|
||||
@JsonField(name = ["username"])
|
||||
@SerialName("username")
|
||||
var username: String? = null,
|
||||
@JvmField
|
||||
@JsonField(name = ["credential"])
|
||||
@SerialName("credential")
|
||||
var credential: String? = null
|
||||
) : Parcelable
|
@ -23,6 +23,7 @@ import android.os.Parcelable
|
||||
import com.bluelinelabs.logansquare.annotation.JsonField
|
||||
import com.bluelinelabs.logansquare.annotation.JsonObject
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import lombok.Data
|
||||
|
||||
@ -33,14 +34,18 @@ import lombok.Data
|
||||
data class SignalingSettings @JvmOverloads constructor(
|
||||
@JvmField
|
||||
@JsonField(name = ["stunservers"])
|
||||
@SerialName("stunservers")
|
||||
var stunServers: List<IceServer>? = null,
|
||||
@JvmField
|
||||
@JsonField(name = ["turnservers"])
|
||||
@SerialName("turnservers")
|
||||
var turnServers: List<IceServer>? = null,
|
||||
@JvmField
|
||||
@JsonField(name = ["server"])
|
||||
@SerialName("server")
|
||||
var externalSignalingServer: String? = null,
|
||||
@JvmField
|
||||
@JsonField(name = ["ticket"])
|
||||
@SerialName("ticket")
|
||||
var externalSignalingTicket: String? = null
|
||||
) : Parcelable
|
||||
|
@ -54,6 +54,10 @@ class UsersRepositoryImpl(private val usersDao: UsersDao) : UsersRepository {
|
||||
return usersDao.updateUser(user)
|
||||
}
|
||||
|
||||
override suspend fun insertUser(user: UserNgEntity): Long {
|
||||
return usersDao.saveUser(user)
|
||||
}
|
||||
|
||||
override suspend fun setUserAsActiveWithId(id: Long) {
|
||||
usersDao.setUserAsActiveWithId(id)
|
||||
}
|
||||
|
@ -57,7 +57,6 @@ import okhttp3.logging.HttpLoggingInterceptor.Logger
|
||||
import org.koin.android.ext.koin.androidApplication
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
import org.mozilla.geckoview.GeckoRuntime
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
|
||||
import java.io.IOException
|
||||
@ -73,7 +72,6 @@ import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.X509KeyManager
|
||||
|
||||
val NetworkModule = module {
|
||||
single { createGeckoRuntime(androidContext()) }
|
||||
single { createService(get()) }
|
||||
single { createLegacyNcApi(get()) }
|
||||
single { createRetrofit(get()) }
|
||||
@ -91,10 +89,6 @@ val NetworkModule = module {
|
||||
|
||||
}
|
||||
|
||||
fun createGeckoRuntime(context: Context): GeckoRuntime {
|
||||
return GeckoRuntime.create(context)
|
||||
}
|
||||
|
||||
fun createCookieManager(): CookieManager {
|
||||
val cookieManager = CookieManager()
|
||||
cookieManager.setCookiePolicy(ACCEPT_ALL)
|
||||
|
@ -43,9 +43,38 @@ val UseCasesModule = module {
|
||||
single { createGetProfileUseCase(get(), get()) }
|
||||
single { createGetSignalingUseCase(get(), get()) }
|
||||
single { createGetCapabilitiesUseCase(get(), get()) }
|
||||
single { createRegisterPushWithProxyUseCase(get(), get()) }
|
||||
single { createRegisterPushWithServerUseCase(get(), get()) }
|
||||
single { createUnregisterPushWithProxyUseCase(get(), get()) }
|
||||
single { createUnregisterPushWithServerUseCase(get(), get()) }
|
||||
factory { createChatViewModelFactory(get(), get(), get(), get(), get(), get()) }
|
||||
}
|
||||
|
||||
fun createUnregisterPushWithServerUseCase(nextcloudTalkRepository: NextcloudTalkRepository,
|
||||
apiErrorHandler: ApiErrorHandler
|
||||
): UnregisterPushWithServerUseCase {
|
||||
return UnregisterPushWithServerUseCase(nextcloudTalkRepository, apiErrorHandler)
|
||||
}
|
||||
|
||||
fun createUnregisterPushWithProxyUseCase(nextcloudTalkRepository: NextcloudTalkRepository,
|
||||
apiErrorHandler: ApiErrorHandler
|
||||
): UnregisterPushWithProxyUseCase {
|
||||
return UnregisterPushWithProxyUseCase(nextcloudTalkRepository, apiErrorHandler)
|
||||
}
|
||||
|
||||
|
||||
fun createRegisterPushWithServerUseCase(nextcloudTalkRepository: NextcloudTalkRepository,
|
||||
apiErrorHandler: ApiErrorHandler
|
||||
): RegisterPushWithServerUseCase {
|
||||
return RegisterPushWithServerUseCase(nextcloudTalkRepository, apiErrorHandler)
|
||||
}
|
||||
|
||||
fun createRegisterPushWithProxyUseCase(nextcloudTalkRepository: NextcloudTalkRepository,
|
||||
apiErrorHandler: ApiErrorHandler
|
||||
): RegisterPushWithProxyUseCase {
|
||||
return RegisterPushWithProxyUseCase(nextcloudTalkRepository, apiErrorHandler)
|
||||
}
|
||||
|
||||
fun createGetCapabilitiesUseCase(nextcloudTalkRepository: NextcloudTalkRepository,
|
||||
apiErrorHandler: ApiErrorHandler
|
||||
): GetCapabilitiesUseCase {
|
||||
|
@ -30,6 +30,7 @@ interface UsersRepository {
|
||||
fun getUserWithId(id: Long): UserNgEntity
|
||||
suspend fun getUserWithUsernameAndServer(username: String, server: String): UserNgEntity?
|
||||
suspend fun updateUser(user: UserNgEntity): Int
|
||||
suspend fun insertUser(user: UserNgEntity): Long
|
||||
suspend fun setUserAsActiveWithId(id: Long)
|
||||
suspend fun deleteUserWithId(id: Long)
|
||||
suspend fun setAnyUserAsActive(): Boolean
|
||||
|
@ -34,7 +34,6 @@ interface NextcloudTalkRepository {
|
||||
suspend fun unregisterPushWithServerForUser(user: UserNgEntity): GenericOverall
|
||||
suspend fun registerPushWithProxyForUser(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 getProfileForUser(user: UserNgEntity): UserProfileOverall
|
||||
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 {
|
||||
val definitionParameters = params as DefinitionParameters
|
||||
return nextcloudTalkRepository.setFavoriteValueForConversation(
|
||||
definitionParameters.get(0),
|
||||
definitionParameters.get(1),
|
||||
definitionParameters.get(2)
|
||||
definitionParameters[0],
|
||||
definitionParameters[1],
|
||||
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 com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetCapabilitiesUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetProfileUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetSignalingSettingsUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.*
|
||||
import com.nextcloud.talk.newarch.features.account.loginentry.LoginEntryViewModelFactory
|
||||
import com.nextcloud.talk.newarch.features.account.serverentry.ServerEntryViewModelFactory
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
@ -18,7 +16,7 @@ val AccountModule = module {
|
||||
)
|
||||
}
|
||||
factory {
|
||||
createLoginEntryViewModelFactory(androidApplication(), get(), get(), get(), get(), get() )
|
||||
createLoginEntryViewModelFactory(androidApplication(), get(), get(), get(), get(), get(), get(), get())
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,10 +34,12 @@ fun createLoginEntryViewModelFactory(
|
||||
getProfileUseCase: GetProfileUseCase,
|
||||
getCapabilitiesUseCase: GetCapabilitiesUseCase,
|
||||
getSignalingSettingsUseCase: GetSignalingSettingsUseCase,
|
||||
registerPushWithServerUseCase: RegisterPushWithServerUseCase,
|
||||
registerPushWithProxyUseCase: RegisterPushWithProxyUseCase,
|
||||
appPreferences: AppPreferences,
|
||||
usersRepository: UsersRepository
|
||||
): LoginEntryViewModelFactory {
|
||||
return LoginEntryViewModelFactory(
|
||||
application, getProfileUseCase, getCapabilitiesUseCase, getSignalingSettingsUseCase, appPreferences, usersRepository
|
||||
application, getProfileUseCase, getCapabilitiesUseCase, getSignalingSettingsUseCase, registerPushWithServerUseCase, registerPushWithProxyUseCase, appPreferences, usersRepository
|
||||
)
|
||||
}
|
@ -22,11 +22,14 @@
|
||||
|
||||
package com.nextcloud.talk.newarch.features.account.loginentry
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.*
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.Observer
|
||||
import com.bluelinelabs.conductor.RouterTransaction
|
||||
@ -35,10 +38,9 @@ import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseView
|
||||
import com.nextcloud.talk.newarch.features.conversationslist.ConversationsListView
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
import de.cotech.hw.fido.WebViewFidoBridge
|
||||
import kotlinx.android.synthetic.main.login_entry_view.view.*
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.mozilla.geckoview.*
|
||||
import org.mozilla.geckoview.GeckoSessionSettings.USER_AGENT_MODE_MOBILE
|
||||
import java.util.*
|
||||
|
||||
class LoginEntryView(val bundle: Bundle) : BaseView() {
|
||||
@ -48,11 +50,7 @@ class LoginEntryView(val bundle: Bundle) : BaseView() {
|
||||
private lateinit var viewModel: LoginEntryViewModel
|
||||
val factory: LoginEntryViewModelFactory by inject()
|
||||
|
||||
private lateinit var geckoView: GeckoView
|
||||
private lateinit var geckoSession: GeckoSession
|
||||
private val geckoRuntime: GeckoRuntime by inject()
|
||||
|
||||
private val assembledPrefix = resources?.getString(R.string.nc_talk_login_scheme) + protocolSuffix + "login/"
|
||||
private var assembledPrefix = ""
|
||||
|
||||
private val webLoginUserAgent: String
|
||||
get() = (Build.MANUFACTURER.substring(0, 1).toUpperCase(
|
||||
@ -65,11 +63,14 @@ class LoginEntryView(val bundle: Bundle) : BaseView() {
|
||||
return R.layout.login_entry_view
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
|
||||
actionBar?.hide()
|
||||
viewModel = viewModelProvider(factory).get(LoginEntryViewModel::class.java)
|
||||
val view = super.onCreateView(inflater, container)
|
||||
|
||||
assembledPrefix = resources?.getString(R.string.nc_talk_login_scheme) + protocolSuffix + "login/"
|
||||
|
||||
viewModel.state.observe(this@LoginEntryView, Observer {
|
||||
when (it.state) {
|
||||
LoginEntryState.FAILED -> {
|
||||
@ -80,82 +81,83 @@ class LoginEntryView(val bundle: Bundle) : BaseView() {
|
||||
}
|
||||
LoginEntryState.CHECKING -> {
|
||||
view.progressBar.isVisible = true
|
||||
geckoView.isVisible = false
|
||||
view.webView.isVisible = false
|
||||
}
|
||||
else -> {
|
||||
if (router?.hasRootController() == true) {
|
||||
router.popController(this)
|
||||
} else {
|
||||
router.setRoot(RouterTransaction.with(ConversationsListView())
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
}
|
||||
// all good, proceed
|
||||
router.setRoot(RouterTransaction.with(ConversationsListView())
|
||||
.pushChangeHandler(HorizontalChangeHandler())
|
||||
.popChangeHandler(HorizontalChangeHandler()))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
geckoView = view.geckoView
|
||||
activity?.let {
|
||||
val settings = GeckoSessionSettings.Builder()
|
||||
//.usePrivateMode(true)
|
||||
//.useTrackingProtection(true)
|
||||
.userAgentMode(USER_AGENT_MODE_MOBILE)
|
||||
.userAgentOverride(webLoginUserAgent)
|
||||
.suspendMediaWhenInactive(true)
|
||||
.allowJavascript(true)
|
||||
|
||||
geckoView.autofillEnabled = true
|
||||
geckoSession = GeckoSession(settings.build())
|
||||
geckoSession.open(geckoRuntime)
|
||||
geckoSession.progressDelegate = createProgressDelegate()
|
||||
geckoSession.navigationDelegate = createNavigationDelegate()
|
||||
geckoView.setSession(geckoSession)
|
||||
bundle.getString(BundleKeys.KEY_BASE_URL)?.let { baseUrl ->
|
||||
geckoSession.loadUri("$baseUrl/index.php/login/flow", mapOf<String, String>("OCS-APIRequest" to "true"))
|
||||
}
|
||||
}
|
||||
|
||||
val baseUrl = bundle.get(BundleKeys.KEY_BASE_URL)
|
||||
val headers: MutableMap<String, String> = hashMapOf()
|
||||
headers["OCS-APIRequest"] = "true"
|
||||
|
||||
setupWebView(view)
|
||||
view.webView.loadUrl("$baseUrl/index.php/login/flow", headers)
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun createNavigationDelegate(): GeckoSession.NavigationDelegate {
|
||||
return object : GeckoSession.NavigationDelegate {
|
||||
override fun onLoadRequest(p0: GeckoSession, p1: GeckoSession.NavigationDelegate.LoadRequest): GeckoResult<AllowOrDeny>? {
|
||||
if (p1.uri.startsWith(assembledPrefix)) {
|
||||
viewModel.parseData(assembledPrefix, dataSeparator, p1.uri)
|
||||
return GeckoResult.DENY
|
||||
override fun onSaveViewState(view: View, outState: Bundle) {
|
||||
view.webView.saveState(outState)
|
||||
super.onSaveViewState(view, outState)
|
||||
}
|
||||
|
||||
override fun onRestoreViewState(view: View, savedViewState: Bundle) {
|
||||
super.onRestoreViewState(view, savedViewState)
|
||||
view.webView.restoreState(savedViewState)
|
||||
}
|
||||
|
||||
private fun setupWebView(loginEntryView: View) {
|
||||
loginEntryView.webView.apply {
|
||||
settings.allowFileAccess = false
|
||||
settings.allowFileAccessFromFileURLs = false
|
||||
settings.javaScriptEnabled = true
|
||||
settings.javaScriptCanOpenWindowsAutomatically = false
|
||||
settings.domStorageEnabled = true
|
||||
settings.userAgentString = webLoginUserAgent
|
||||
settings.saveFormData = false
|
||||
settings.savePassword = false
|
||||
settings.setRenderPriority(WebSettings.RenderPriority.HIGH)
|
||||
clearCache(true)
|
||||
clearFormData()
|
||||
clearHistory()
|
||||
clearSslPreferences()
|
||||
}
|
||||
|
||||
val webViewFidoBridge = WebViewFidoBridge.createInstanceForWebView(activity as AppCompatActivity?, loginEntryView.webView)
|
||||
CookieSyncManager.createInstance(activity)
|
||||
CookieManager.getInstance().removeAllCookies(null)
|
||||
|
||||
loginEntryView.webView.webViewClient = object : WebViewClient() {
|
||||
var initialPageLoad = true
|
||||
override fun shouldInterceptRequest(view: WebView?, request: WebResourceRequest?): WebResourceResponse? {
|
||||
webViewFidoBridge?.delegateShouldInterceptRequest(view, request)
|
||||
return super.shouldInterceptRequest(view, request)
|
||||
}
|
||||
|
||||
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
|
||||
if (request?.url.toString().startsWith(assembledPrefix)) {
|
||||
viewModel.parseData(assembledPrefix, dataSeparator, request?.url.toString())
|
||||
return true
|
||||
}
|
||||
return super.onLoadRequest(p0, p1)
|
||||
return super.shouldOverrideUrlLoading(view, request)
|
||||
}
|
||||
|
||||
override fun onPageFinished(view: WebView?, url: String?) {
|
||||
if (initialPageLoad) {
|
||||
initialPageLoad = false
|
||||
loginEntryView.progressBar?.isVisible = false
|
||||
loginEntryView.webView?.isVisible = true
|
||||
}
|
||||
super.onPageFinished(view, url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createProgressDelegate(): GeckoSession.ProgressDelegate {
|
||||
return object : GeckoSession.ProgressDelegate {
|
||||
private var initialLoad = true
|
||||
|
||||
override fun onPageStop(session: GeckoSession, success: Boolean) = Unit
|
||||
|
||||
override fun onSecurityChange(
|
||||
session: GeckoSession,
|
||||
securityInfo: GeckoSession.ProgressDelegate.SecurityInformation
|
||||
) = Unit
|
||||
|
||||
override fun onPageStart(session: GeckoSession, url: String) = Unit
|
||||
|
||||
override fun onProgressChange(session: GeckoSession, progress: Int) {
|
||||
if (initialLoad) {
|
||||
view?.pageProgressBar?.progress = progress
|
||||
view?.pageProgressBar?.isVisible = progress in 1..99
|
||||
}
|
||||
|
||||
if (progress == 100) {
|
||||
initialLoad = false
|
||||
view?.pageProgressBar?.isVisible = false
|
||||
view?.geckoView?.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -5,16 +5,19 @@ import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.nextcloud.talk.models.LoginData
|
||||
import com.nextcloud.talk.models.json.capabilities.CapabilitiesOverall
|
||||
import com.nextcloud.talk.models.json.push.PushConfiguration
|
||||
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
||||
import com.nextcloud.talk.models.json.push.PushConfigurationStateWrapper
|
||||
import com.nextcloud.talk.models.json.push.PushRegistrationOverall
|
||||
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettingsOverall
|
||||
import com.nextcloud.talk.models.json.userprofile.UserProfileOverall
|
||||
import com.nextcloud.talk.newarch.conversationsList.mvp.BaseViewModel
|
||||
import com.nextcloud.talk.newarch.data.model.ErrorModel
|
||||
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetCapabilitiesUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetProfileUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetSignalingSettingsUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.*
|
||||
import com.nextcloud.talk.newarch.domain.usecases.base.UseCaseResponse
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.utils.PushUtils
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.parameter.parametersOf
|
||||
@ -25,12 +28,15 @@ class LoginEntryViewModel constructor(
|
||||
private val getProfileUseCase: GetProfileUseCase,
|
||||
private val getCapabilitiesUseCase: GetCapabilitiesUseCase,
|
||||
private val getSignalingSettingsUseCase: GetSignalingSettingsUseCase,
|
||||
private val registerPushWithServerUseCase: RegisterPushWithServerUseCase,
|
||||
private val registerPushWithProxyUseCase: RegisterPushWithProxyUseCase,
|
||||
private val appPreferences: AppPreferences,
|
||||
private val usersRepository: UsersRepository) :
|
||||
BaseViewModel<LoginEntryView>(application) {
|
||||
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?) {
|
||||
viewModelScope.launch {
|
||||
@ -88,10 +94,13 @@ class LoginEntryViewModel constructor(
|
||||
|
||||
private suspend fun storeCredentialsOrVerify(loginData: LoginData) {
|
||||
// username and server url will be null here for sure because we do a check earlier in the process
|
||||
val user = usersRepository.getUserWithUsernameAndServer(loginData.username!!, loginData.serverUrl!!)
|
||||
if (user != null) {
|
||||
val userIfExists = usersRepository.getUserWithUsernameAndServer(loginData.username!!, loginData.serverUrl!!)
|
||||
if (userIfExists != null) {
|
||||
updatingUser = true
|
||||
user = userIfExists
|
||||
user.token = loginData.token
|
||||
usersRepository.updateUser(user)
|
||||
// complicated - we need to unregister, etc, etc, but not yet
|
||||
state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.ACCOUNT_UPDATED))
|
||||
} else {
|
||||
getProfile(loginData)
|
||||
@ -101,6 +110,7 @@ class LoginEntryViewModel constructor(
|
||||
private fun getProfile(loginData: LoginData) {
|
||||
user.username = loginData.username!!
|
||||
user.baseUrl = loginData.serverUrl!!
|
||||
user.token = loginData.token
|
||||
getProfileUseCase.invoke(viewModelScope, parametersOf(user), object : UseCaseResponse<UserProfileOverall> {
|
||||
override suspend fun onSuccess(result: UserProfileOverall) {
|
||||
result.ocs.data.userId?.let { userId ->
|
||||
@ -135,6 +145,10 @@ class LoginEntryViewModel constructor(
|
||||
getSignalingSettingsUseCase.invoke(viewModelScope, parametersOf(user), object : UseCaseResponse<SignalingSettingsOverall> {
|
||||
override suspend fun onSuccess(result: SignalingSettingsOverall) {
|
||||
user.signalingSettings = result.ocs.signalingSettings
|
||||
val pushConfiguration = PushConfiguration()
|
||||
val pushConfigurationStateWrapper = PushConfigurationStateWrapper(PushConfigurationState.PENDING, 0)
|
||||
pushConfiguration.pushConfigurationStateWrapper = pushConfigurationStateWrapper
|
||||
usersRepository.insertUser(user)
|
||||
registerForPush()
|
||||
}
|
||||
|
||||
@ -142,12 +156,13 @@ class LoginEntryViewModel constructor(
|
||||
state.postValue(LoginEntryStateWrapper(LoginEntryState.FAILED, LoginEntryStateClarification.SIGNALING_SETTINGS_FETCH_FAILED))
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun registerForPush() {
|
||||
private suspend fun registerForPush() {
|
||||
val token = appPreferences.pushToken
|
||||
if (!token.isNullOrBlank()) {
|
||||
user.pushConfiguration?.pushToken = token
|
||||
usersRepository.updateUser(user)
|
||||
registerForPushWithServer(token)
|
||||
} else {
|
||||
state.postValue(LoginEntryStateWrapper(LoginEntryState.OK, LoginEntryStateClarification.PUSH_REGISTRATION_MISSING_TOKEN))
|
||||
@ -155,10 +170,57 @@ class LoginEntryViewModel constructor(
|
||||
}
|
||||
|
||||
private fun registerForPushWithServer(token: String) {
|
||||
val options = PushUtils(usersRepository).getMapForPushRegistrationWithServer(context, token)
|
||||
registerPushWithServerUseCase.invoke(viewModelScope, parametersOf(user, options), object : UseCaseResponse<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.ViewModelProvider
|
||||
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetCapabilitiesUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetProfileUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.GetSignalingSettingsUseCase
|
||||
import com.nextcloud.talk.newarch.domain.usecases.*
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
|
||||
class LoginEntryViewModelFactory constructor(private val application: Application, private val getProfileUseCase: GetProfileUseCase, private val getCapabilitiesUseCase: GetCapabilitiesUseCase, private val getSignalingSettingsUseCase: GetSignalingSettingsUseCase, private val appPreferences: AppPreferences, private val usersRepository: UsersRepository) : ViewModelProvider.Factory {
|
||||
class LoginEntryViewModelFactory constructor(private val application: Application, private val getProfileUseCase: GetProfileUseCase, private val getCapabilitiesUseCase: GetCapabilitiesUseCase, private val getSignalingSettingsUseCase: GetSignalingSettingsUseCase, private val registerPushWithServerUseCase: RegisterPushWithServerUseCase, private val registerPushWithProxyUseCase: RegisterPushWithProxyUseCase, private val appPreferences: AppPreferences, private val usersRepository: UsersRepository) : ViewModelProvider.Factory {
|
||||
override fun <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 com.bluelinelabs.logansquare.LoganSquare
|
||||
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
||||
import com.nextcloud.talk.models.json.push.PushConfiguration
|
||||
import com.nextcloud.talk.newarch.utils.MagicJson
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class PushConfigurationConverter {
|
||||
val json = Json(MagicJson.customJsonConfiguration)
|
||||
|
||||
@TypeConverter
|
||||
fun fromPushConfigurationToString(pushConfigurationState: PushConfigurationState?): String {
|
||||
if (pushConfigurationState == null) {
|
||||
return ""
|
||||
fun fromPushConfigurationToString(pushConfiguration: PushConfiguration?): String {
|
||||
|
||||
return if (pushConfiguration == null) {
|
||||
""
|
||||
} else {
|
||||
return LoganSquare.serialize(pushConfigurationState)
|
||||
json.stringify(PushConfiguration.serializer(), pushConfiguration)
|
||||
}
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun fromStringToPushConfiguration(value: String): PushConfigurationState? {
|
||||
return LoganSquare.parse(value, PushConfigurationState::class.java)
|
||||
fun fromStringToPushConfiguration(value: String): PushConfiguration? {
|
||||
return json.parse(PushConfiguration.serializer(), value)
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,7 @@ abstract class UsersDao {
|
||||
abstract fun saveUser(user: UserNgEntity): Long
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract suspend fun saveUsers(vararg users: UserNgEntity)
|
||||
abstract suspend fun saveUsers(vararg users: UserNgEntity): List<Long>
|
||||
|
||||
// get all users not scheduled for deletion
|
||||
@Query("SELECT * FROM users where status != 2")
|
||||
|
@ -25,7 +25,7 @@ import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.nextcloud.talk.models.json.capabilities.Capabilities
|
||||
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
||||
import com.nextcloud.talk.models.json.push.PushConfiguration
|
||||
import com.nextcloud.talk.models.json.signaling.settings.SignalingSettings
|
||||
import com.nextcloud.talk.newarch.local.models.other.UserStatus
|
||||
import com.nextcloud.talk.utils.ApiUtils
|
||||
@ -43,7 +43,7 @@ data class UserNgEntity(
|
||||
@ColumnInfo(name = "display_name") var displayName: String? = null,
|
||||
@ColumnInfo(
|
||||
name = "push_configuration"
|
||||
) var pushConfiguration: PushConfigurationState? = null,
|
||||
) var pushConfiguration: PushConfiguration? = null,
|
||||
@ColumnInfo(name = "capabilities") var capabilities: Capabilities? = null,
|
||||
@ColumnInfo(name = "client_auth_cert") var clientCertificate: String? = null,
|
||||
@ColumnInfo(
|
||||
|
@ -23,12 +23,14 @@ package com.nextcloud.talk.utils
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.models.SignatureVerification
|
||||
import com.nextcloud.talk.models.json.push.PushConfigurationState
|
||||
import com.nextcloud.talk.models.json.push.PushConfiguration
|
||||
import com.nextcloud.talk.newarch.domain.repository.offline.UsersRepository
|
||||
import com.nextcloud.talk.newarch.local.models.UserNgEntity
|
||||
import com.nextcloud.talk.newarch.utils.hashWithAlgorithm
|
||||
import com.nextcloud.talk.utils.preferences.AppPreferences
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.koin.core.KoinComponent
|
||||
@ -38,6 +40,7 @@ import java.security.*
|
||||
import java.security.spec.InvalidKeySpecException
|
||||
import java.security.spec.PKCS8EncodedKeySpec
|
||||
import java.security.spec.X509EncodedKeySpec
|
||||
import java.util.HashMap
|
||||
|
||||
class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
|
||||
val appPreferences: AppPreferences by inject()
|
||||
@ -46,12 +49,52 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
|
||||
private val keysFile: File
|
||||
private val publicKeyFile: File
|
||||
private val privateKeyFile: File
|
||||
|
||||
fun getMapForPushRegistrationWithServer(user: UserNgEntity): Map<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(
|
||||
signatureBytes: ByteArray?,
|
||||
subjectBytes: ByteArray?
|
||||
): SignatureVerification {
|
||||
val signature: Signature?
|
||||
var pushConfigurationState: PushConfigurationState?
|
||||
var pushConfiguration: PushConfiguration?
|
||||
var publicKey: PublicKey?
|
||||
val signatureVerification =
|
||||
SignatureVerification()
|
||||
@ -61,10 +104,10 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
|
||||
signature = Signature.getInstance("SHA512withRSA")
|
||||
if (userEntities.isNotEmpty()) {
|
||||
for (userEntity in userEntities) {
|
||||
pushConfigurationState = userEntity.pushConfiguration
|
||||
if (pushConfigurationState?.userPublicKey != null) {
|
||||
pushConfiguration = userEntity.pushConfiguration
|
||||
if (pushConfiguration?.userPublicKey != null) {
|
||||
publicKey = readKeyFromString(
|
||||
true, pushConfigurationState.userPublicKey!!
|
||||
true, pushConfiguration.userPublicKey!!
|
||||
) as PublicKey?
|
||||
signature.initVerify(publicKey)
|
||||
signature.update(subjectBytes)
|
||||
@ -141,7 +184,6 @@ class PushUtils(val usersRepository: UsersRepository) : KoinComponent {
|
||||
// we failed to generate the key
|
||||
else {
|
||||
// We already have the key
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
|
@ -5,18 +5,8 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:background="@color/colorPrimary">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/pageProgressBar"
|
||||
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"
|
||||
<WebView
|
||||
android:id="@+id/webView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorPrimary"
|
||||
@ -28,7 +18,7 @@
|
||||
android:layout_width="@dimen/item_height"
|
||||
android:layout_height="@dimen/item_height"
|
||||
android:layout_centerInParent="true"
|
||||
android:visibility="gone"
|
||||
android:visibility="visible"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/white"
|
||||
android:indeterminateTintMode="src_in" />
|
||||
|
Loading…
Reference in New Issue
Block a user