mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-21 11:45:03 +01:00
Added new login option
renamed WebViewLoginActivity.kt to BrowserLoginActivity.kt Signed-off-by: rapterjet2004 <juliuslinus1@gmail.com>
This commit is contained in:
parent
e2a6728942
commit
dad5f1714a
@ -123,7 +123,7 @@
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity
|
||||
android:name=".account.WebViewLoginActivity"
|
||||
android:name=".account.BrowserLoginActivity"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<activity android:name=".contacts.ContactsActivity"
|
||||
|
@ -158,7 +158,7 @@ class AccountVerificationActivity : BaseActivity() {
|
||||
bundle.putString(KEY_USERNAME, username)
|
||||
bundle.putString(KEY_PASSWORD, "")
|
||||
|
||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
||||
val intent = Intent(context, BrowserLoginActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
} else {
|
||||
|
@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
|
||||
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.account
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.os.Bundle
|
||||
import android.text.TextUtils
|
||||
import android.util.Log
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.core.net.toUri
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import autodagger.AutoInjector
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.google.gson.JsonParser
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.databinding.ActivityWebViewLoginBinding
|
||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||
import com.nextcloud.talk.models.LoginData
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
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.ssl.SSLSocketFactoryCompat
|
||||
import com.nextcloud.talk.utils.ssl.TrustManager
|
||||
import io.reactivex.disposables.Disposable
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.ConnectionSpec
|
||||
import okhttp3.CookieJar
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.Executors
|
||||
import java.util.concurrent.ScheduledExecutorService
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.SSLHandshakeException
|
||||
import javax.net.ssl.SSLSession
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class BrowserLoginActivity : BaseActivity() {
|
||||
|
||||
private lateinit var binding: ActivityWebViewLoginBinding
|
||||
|
||||
@Inject
|
||||
lateinit var userManager: UserManager
|
||||
|
||||
@Inject
|
||||
lateinit var trustManager: TrustManager
|
||||
|
||||
@Inject
|
||||
lateinit var socketFactory: SSLSocketFactoryCompat
|
||||
|
||||
private var userQueryDisposable: Disposable? = null
|
||||
private var baseUrl: String? = null
|
||||
private var reauthorizeAccount = false
|
||||
private var username: String? = null
|
||||
private var password: String? = null
|
||||
private val loginFlowExecutorService: ScheduledExecutorService? = Executors.newSingleThreadScheduledExecutor()
|
||||
private var isLoginProcessCompleted = false
|
||||
private var token: String = ""
|
||||
|
||||
private lateinit var okHttpClient: OkHttpClient
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
binding = ActivityWebViewLoginBinding.inflate(layoutInflater)
|
||||
okHttpClient = OkHttpClient.Builder()
|
||||
.cookieJar(CookieJar.NO_COOKIES)
|
||||
.connectionSpecs(listOf(ConnectionSpec.COMPATIBLE_TLS))
|
||||
.sslSocketFactory(socketFactory, trustManager)
|
||||
.hostnameVerifier { _: String?, _: SSLSession? -> true }
|
||||
.build()
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
setContentView(binding.root)
|
||||
actionBar?.hide()
|
||||
initSystemBars()
|
||||
initViews()
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
handleIntent()
|
||||
anonymouslyPostLoginRequest()
|
||||
lifecycle.addObserver(lifecycleEventObserver)
|
||||
}
|
||||
|
||||
private fun handleIntent() {
|
||||
val extras = intent.extras!!
|
||||
baseUrl = extras.getString(KEY_BASE_URL)
|
||||
username = extras.getString(KEY_USERNAME)
|
||||
|
||||
if (extras.containsKey(BundleKeys.KEY_REAUTHORIZE_ACCOUNT)) {
|
||||
reauthorizeAccount = extras.getBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT)
|
||||
}
|
||||
|
||||
if (extras.containsKey(BundleKeys.KEY_PASSWORD)) {
|
||||
password = extras.getString(BundleKeys.KEY_PASSWORD)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initViews() {
|
||||
viewThemeUtils.material.colorMaterialButtonFilledOnPrimary(binding.cancelLoginBtn)
|
||||
viewThemeUtils.material.colorProgressBar(binding.progressBar)
|
||||
|
||||
binding.cancelLoginBtn.setOnClickListener {
|
||||
lifecycle.removeObserver(lifecycleEventObserver)
|
||||
onBackPressedDispatcher.onBackPressed()
|
||||
}
|
||||
}
|
||||
|
||||
private fun anonymouslyPostLoginRequest() {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val url = "$baseUrl/index.php/login/v2"
|
||||
try {
|
||||
val response = getResponseOfAnonymouslyPostLoginRequest(url)
|
||||
val jsonObject: com.google.gson.JsonObject = JsonParser.parseString(response).asJsonObject
|
||||
val loginUrl: String = getLoginUrl(jsonObject)
|
||||
withContext(Dispatchers.Main) {
|
||||
launchDefaultWebBrowser(loginUrl)
|
||||
}
|
||||
token = jsonObject.getAsJsonObject("poll").get("token").asString
|
||||
} catch (e: SSLHandshakeException) {
|
||||
Log.e(TAG, "Error caught at anonymouslyPostLoginRequest: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getResponseOfAnonymouslyPostLoginRequest(url: String): String? {
|
||||
val request = Request.Builder()
|
||||
.url(url)
|
||||
.post(FormBody.Builder().build())
|
||||
.addHeader("Clear-Site-Data", "cookies")
|
||||
.build()
|
||||
|
||||
okHttpClient.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("Unexpected code $response")
|
||||
}
|
||||
return response.body?.string()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLoginUrl(response: com.google.gson.JsonObject): String {
|
||||
var result: String? = response.get("login").asString
|
||||
if (result == null) {
|
||||
result = ""
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun launchDefaultWebBrowser(url: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private val lifecycleEventObserver = LifecycleEventObserver { lifecycleOwner, event ->
|
||||
if (event === Lifecycle.Event.ON_START && token != "") {
|
||||
Log.d(TAG, "Start poolLogin")
|
||||
poolLogin()
|
||||
}
|
||||
}
|
||||
|
||||
private fun poolLogin() {
|
||||
loginFlowExecutorService?.scheduleWithFixedDelay({
|
||||
if (!isLoginProcessCompleted) {
|
||||
performLoginFlowV2()
|
||||
}
|
||||
}, 0, INTERVAL, TimeUnit.SECONDS)
|
||||
}
|
||||
|
||||
private fun performLoginFlowV2() {
|
||||
val postRequestUrl = "$baseUrl/login/v2/poll"
|
||||
|
||||
val requestBody: RequestBody = FormBody.Builder()
|
||||
.add("token", token)
|
||||
.build()
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(postRequestUrl)
|
||||
.post(requestBody)
|
||||
.build()
|
||||
|
||||
try {
|
||||
okHttpClient.newCall(request).execute()
|
||||
.use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("Unexpected code $response")
|
||||
}
|
||||
val status: Int = response.code
|
||||
val response = response.body?.string()
|
||||
|
||||
Log.d(TAG, "performLoginFlowV2 status: $status")
|
||||
Log.d(TAG, "performLoginFlowV2 response: $response")
|
||||
|
||||
if (response?.isNotEmpty() == true) {
|
||||
runOnUiThread { completeLoginFlow(response, status) }
|
||||
}
|
||||
}
|
||||
} catch (e: IllegalStateException) {
|
||||
Log.e(TAG, "Error caught at performLoginFlowV2: $e")
|
||||
}
|
||||
}
|
||||
|
||||
private fun completeLoginFlow(response: String, status: Int) {
|
||||
try {
|
||||
val jsonObject = JSONObject(response)
|
||||
|
||||
val server: String = jsonObject.getString("server")
|
||||
val loginName: String = jsonObject.getString("loginName")
|
||||
val appPassword: String = jsonObject.getString("appPassword")
|
||||
|
||||
val loginData = LoginData()
|
||||
loginData.serverUrl = server
|
||||
loginData.username = loginName
|
||||
loginData.token = appPassword
|
||||
|
||||
isLoginProcessCompleted =
|
||||
(status == HTTP_OK && !server.isEmpty() && !loginName.isEmpty() && !appPassword.isEmpty())
|
||||
|
||||
parseAndLogin(loginData)
|
||||
} catch (e: JSONException) {
|
||||
Log.e(TAG, "Error caught at completeLoginFlow: $e")
|
||||
}
|
||||
|
||||
loginFlowExecutorService?.shutdown()
|
||||
lifecycle.removeObserver(lifecycleEventObserver)
|
||||
}
|
||||
|
||||
private fun dispose() {
|
||||
if (userQueryDisposable != null && !userQueryDisposable!!.isDisposed) {
|
||||
userQueryDisposable!!.dispose()
|
||||
}
|
||||
userQueryDisposable = null
|
||||
}
|
||||
|
||||
private fun parseAndLogin(loginData: LoginData) {
|
||||
dispose()
|
||||
|
||||
if (userManager.checkIfUserIsScheduledForDeletion(loginData.username!!, baseUrl!!).blockingGet()) {
|
||||
Log.e(TAG, "Tried to add already existing user who is scheduled for deletion.")
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
// however the user is not yet deleted, just start AccountRemovalWorker again to make sure to delete it.
|
||||
startAccountRemovalWorkerAndRestartApp()
|
||||
} else if (userManager.checkIfUserExists(loginData.username!!, baseUrl!!).blockingGet()) {
|
||||
if (reauthorizeAccount) {
|
||||
updateUserAndRestartApp(loginData)
|
||||
} else {
|
||||
Log.w(TAG, "It was tried to add an account that account already exists. Skipped user creation.")
|
||||
restartApp()
|
||||
}
|
||||
} else {
|
||||
startAccountVerification(loginData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAccountVerification(loginData: LoginData) {
|
||||
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)
|
||||
}
|
||||
val intent = Intent(context, AccountVerificationActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun restartApp() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun updateUserAndRestartApp(loginData: LoginData) {
|
||||
val currentUser = currentUserProvider.currentUser.blockingGet()
|
||||
if (currentUser != null) {
|
||||
currentUser.clientCertificate = appPreferences.temporaryClientCertAlias
|
||||
currentUser.token = loginData.token
|
||||
val rowsUpdated = userManager.updateOrCreateUser(currentUser).blockingGet()
|
||||
Log.d(TAG, "User rows updated: $rowsUpdated")
|
||||
restartApp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAccountRemovalWorkerAndRestartApp() {
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
|
||||
.observeForever { workInfo: WorkInfo? ->
|
||||
|
||||
when (workInfo?.state) {
|
||||
WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
|
||||
restartApp()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
dispose()
|
||||
}
|
||||
|
||||
init {
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
}
|
||||
|
||||
override val appBarLayoutType: AppBarLayoutType
|
||||
get() = AppBarLayoutType.EMPTY
|
||||
|
||||
companion object {
|
||||
private val TAG = BrowserLoginActivity::class.java.simpleName
|
||||
private const val INTERVAL = 30L
|
||||
private const val HTTP_OK = 200
|
||||
}
|
||||
}
|
@ -331,7 +331,7 @@ class ServerSelectionActivity : BaseActivity() {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_BASE_URL, queryUrl.replace("/status.php", ""))
|
||||
|
||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
||||
val intent = Intent(context, BrowserLoginActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
@ -1,463 +0,0 @@
|
||||
/*
|
||||
* Nextcloud Talk - Android Client
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Marcel Hibbe <dev@mhibbe.de>
|
||||
* SPDX-FileCopyrightText: 2022 Andy Scherzinger <info@andy-scherzinger.de>
|
||||
* SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
package com.nextcloud.talk.account
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.graphics.Bitmap
|
||||
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.util.Log
|
||||
import android.view.View
|
||||
import android.webkit.ClientCertRequest
|
||||
import android.webkit.CookieSyncManager
|
||||
import android.webkit.SslErrorHandler
|
||||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebResourceResponse
|
||||
import android.webkit.WebSettings
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkManager
|
||||
import autodagger.AutoInjector
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication.Companion.sharedApplication
|
||||
import com.nextcloud.talk.databinding.ActivityWebViewLoginBinding
|
||||
import com.nextcloud.talk.events.CertificateEvent
|
||||
import com.nextcloud.talk.jobs.AccountRemovalWorker
|
||||
import com.nextcloud.talk.models.LoginData
|
||||
import com.nextcloud.talk.users.UserManager
|
||||
import com.nextcloud.talk.utils.bundle.BundleKeys
|
||||
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.ssl.TrustManager
|
||||
import de.cotech.hw.fido.WebViewFidoBridge
|
||||
import de.cotech.hw.fido2.WebViewWebauthnBridge
|
||||
import de.cotech.hw.fido2.ui.WebauthnDialogOptions
|
||||
import io.reactivex.disposables.Disposable
|
||||
import java.lang.reflect.Field
|
||||
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.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@AutoInjector(NextcloudTalkApplication::class)
|
||||
class WebViewLoginActivity : BaseActivity() {
|
||||
|
||||
private lateinit var binding: ActivityWebViewLoginBinding
|
||||
|
||||
@Inject
|
||||
lateinit var userManager: UserManager
|
||||
|
||||
@Inject
|
||||
lateinit var trustManager: TrustManager
|
||||
|
||||
@Inject
|
||||
lateinit var cookieManager: CookieManager
|
||||
|
||||
private var assembledPrefix: String? = null
|
||||
private var userQueryDisposable: Disposable? = null
|
||||
private var baseUrl: String? = null
|
||||
private var reauthorizeAccount = false
|
||||
private var username: String? = null
|
||||
private var password: String? = null
|
||||
private var loginStep = 0
|
||||
private var automatedLoginAttempted = false
|
||||
private var webViewFidoBridge: WebViewFidoBridge? = null
|
||||
private var webViewWebauthnBridge: WebViewWebauthnBridge? = null
|
||||
|
||||
private val onBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
private val webLoginUserAgent: String
|
||||
get() = (
|
||||
Build.MANUFACTURER.substring(0, 1).uppercase(Locale.getDefault()) +
|
||||
Build.MANUFACTURER.substring(1).uppercase(Locale.getDefault()) +
|
||||
" " +
|
||||
Build.MODEL +
|
||||
" (" +
|
||||
resources!!.getString(R.string.nc_app_product_name) +
|
||||
")"
|
||||
)
|
||||
|
||||
@SuppressLint("SourceLockedOrientationActivity")
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
binding = ActivityWebViewLoginBinding.inflate(layoutInflater)
|
||||
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
setContentView(binding.root)
|
||||
actionBar?.hide()
|
||||
initSystemBars()
|
||||
|
||||
onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
|
||||
handleIntent()
|
||||
setupWebView()
|
||||
}
|
||||
|
||||
private fun handleIntent() {
|
||||
val extras = intent.extras!!
|
||||
baseUrl = extras.getString(KEY_BASE_URL)
|
||||
username = extras.getString(KEY_USERNAME)
|
||||
|
||||
if (extras.containsKey(BundleKeys.KEY_REAUTHORIZE_ACCOUNT)) {
|
||||
reauthorizeAccount = extras.getBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT)
|
||||
}
|
||||
|
||||
if (extras.containsKey(BundleKeys.KEY_PASSWORD)) {
|
||||
password = extras.getString(BundleKeys.KEY_PASSWORD)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
private fun setupWebView() {
|
||||
assembledPrefix = resources!!.getString(R.string.nc_talk_login_scheme) + PROTOCOL_SUFFIX + "login/"
|
||||
binding.webview.settings.allowFileAccess = false
|
||||
binding.webview.settings.allowFileAccessFromFileURLs = false
|
||||
binding.webview.settings.javaScriptEnabled = true
|
||||
binding.webview.settings.javaScriptCanOpenWindowsAutomatically = false
|
||||
binding.webview.settings.domStorageEnabled = true
|
||||
binding.webview.settings.userAgentString = webLoginUserAgent
|
||||
binding.webview.settings.saveFormData = false
|
||||
binding.webview.settings.savePassword = false
|
||||
binding.webview.settings.setRenderPriority(WebSettings.RenderPriority.HIGH)
|
||||
binding.webview.clearCache(true)
|
||||
binding.webview.clearFormData()
|
||||
binding.webview.clearHistory()
|
||||
WebView.clearClientCertPreferences(null)
|
||||
webViewFidoBridge = WebViewFidoBridge.createInstanceForWebView(this, binding.webview)
|
||||
|
||||
val webauthnOptionsBuilder = WebauthnDialogOptions.builder().setShowSdkLogo(true).setAllowSkipPin(true)
|
||||
webViewWebauthnBridge = WebViewWebauthnBridge.createInstanceForWebView(
|
||||
this,
|
||||
binding.webview,
|
||||
webauthnOptionsBuilder
|
||||
)
|
||||
|
||||
CookieSyncManager.createInstance(this)
|
||||
android.webkit.CookieManager.getInstance().removeAllCookies(null)
|
||||
val headers: MutableMap<String, String> = HashMap()
|
||||
headers["OCS-APIRequest"] = "true"
|
||||
binding.webview.webViewClient = object : WebViewClient() {
|
||||
private var basePageLoaded = false
|
||||
override fun shouldInterceptRequest(view: WebView, request: WebResourceRequest): WebResourceResponse? {
|
||||
webViewFidoBridge?.delegateShouldInterceptRequest(view, request)
|
||||
webViewWebauthnBridge?.delegateShouldInterceptRequest(view, request)
|
||||
return super.shouldInterceptRequest(view, request)
|
||||
}
|
||||
|
||||
override fun onPageStarted(view: WebView, url: String, favicon: Bitmap?) {
|
||||
super.onPageStarted(view, url, favicon)
|
||||
webViewFidoBridge?.delegateOnPageStarted(view, url, favicon)
|
||||
webViewWebauthnBridge?.delegateOnPageStarted(view, url, favicon)
|
||||
}
|
||||
|
||||
@Deprecated("Use shouldOverrideUrlLoading(WebView view, WebResourceRequest request)")
|
||||
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
|
||||
if (url.startsWith(assembledPrefix!!)) {
|
||||
parseAndLoginFromWebView(url)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
override fun onPageFinished(view: WebView, url: String) {
|
||||
loginStep++
|
||||
if (!basePageLoaded) {
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.webview.visibility = View.VISIBLE
|
||||
|
||||
basePageLoaded = true
|
||||
}
|
||||
if (!TextUtils.isEmpty(username)) {
|
||||
if (loginStep == 1) {
|
||||
binding.webview.loadUrl(
|
||||
"javascript: {document.getElementsByClassName('login')[0].click(); };"
|
||||
)
|
||||
} else if (!automatedLoginAttempted) {
|
||||
automatedLoginAttempted = true
|
||||
if (TextUtils.isEmpty(password)) {
|
||||
binding.webview.loadUrl(
|
||||
"javascript:var justStore = document.getElementById('user').value = '$username';"
|
||||
)
|
||||
} else {
|
||||
binding.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) {
|
||||
var alias: String? = null
|
||||
if (!reauthorizeAccount) {
|
||||
alias = appPreferences.temporaryClientCertAlias
|
||||
}
|
||||
val user = currentUserProvider.currentUser.blockingGet()
|
||||
if (TextUtils.isEmpty(alias) && user != null) {
|
||||
alias = user.clientCertificate
|
||||
}
|
||||
if (!TextUtils.isEmpty(alias)) {
|
||||
val finalAlias = alias
|
||||
Thread {
|
||||
try {
|
||||
val privateKey = KeyChain.getPrivateKey(applicationContext, finalAlias!!)
|
||||
val certificates = KeyChain.getCertificateChain(
|
||||
applicationContext,
|
||||
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(
|
||||
this@WebViewLoginActivity,
|
||||
{ chosenAlias: String? ->
|
||||
if (chosenAlias != null) {
|
||||
appPreferences!!.temporaryClientCertAlias = chosenAlias
|
||||
Thread {
|
||||
var privateKey: PrivateKey? = null
|
||||
try {
|
||||
privateKey = KeyChain.getPrivateKey(applicationContext, chosenAlias)
|
||||
val certificates = KeyChain.getCertificateChain(
|
||||
applicationContext,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("DiscouragedPrivateApi")
|
||||
@Suppress("Detekt.TooGenericExceptionCaught")
|
||||
override fun onReceivedSslError(view: WebView, handler: SslErrorHandler, error: SslError) {
|
||||
try {
|
||||
val sslCertificate = error.certificate
|
||||
val f: Field = sslCertificate.javaClass.getDeclaredField("mX509Certificate")
|
||||
f.isAccessible = true
|
||||
val cert = f[sslCertificate] as X509Certificate
|
||||
if (cert == null) {
|
||||
handler.cancel()
|
||||
} else {
|
||||
try {
|
||||
trustManager.checkServerTrusted(arrayOf(cert), "generic")
|
||||
handler.proceed()
|
||||
} catch (exception: CertificateException) {
|
||||
eventBus.post(CertificateEvent(cert, trustManager, handler))
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
handler.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Deprecated in super implementation")
|
||||
override fun onReceivedError(view: WebView, errorCode: Int, description: String, failingUrl: String) {
|
||||
super.onReceivedError(view, errorCode, description, failingUrl)
|
||||
}
|
||||
}
|
||||
binding.webview.loadUrl("$baseUrl/index.php/login/flow", headers)
|
||||
}
|
||||
|
||||
private fun dispose() {
|
||||
if (userQueryDisposable != null && !userQueryDisposable!!.isDisposed) {
|
||||
userQueryDisposable!!.dispose()
|
||||
}
|
||||
userQueryDisposable = null
|
||||
}
|
||||
|
||||
private fun parseAndLoginFromWebView(dataString: String) {
|
||||
val loginData = parseLoginData(assembledPrefix, dataString)
|
||||
if (loginData != null) {
|
||||
dispose()
|
||||
cookieManager.cookieStore.removeAll()
|
||||
|
||||
if (userManager.checkIfUserIsScheduledForDeletion(loginData.username!!, baseUrl!!).blockingGet()) {
|
||||
Log.e(TAG, "Tried to add already existing user who is scheduled for deletion.")
|
||||
Snackbar.make(binding.root, R.string.nc_common_error_sorry, Snackbar.LENGTH_LONG).show()
|
||||
// however the user is not yet deleted, just start AccountRemovalWorker again to make sure to delete it.
|
||||
startAccountRemovalWorkerAndRestartApp()
|
||||
} else if (userManager.checkIfUserExists(loginData.username!!, baseUrl!!).blockingGet()) {
|
||||
if (reauthorizeAccount) {
|
||||
updateUserAndRestartApp(loginData)
|
||||
} else {
|
||||
Log.w(TAG, "It was tried to add an account that account already exists. Skipped user creation.")
|
||||
restartApp()
|
||||
}
|
||||
} else {
|
||||
startAccountVerification(loginData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAccountVerification(loginData: LoginData) {
|
||||
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)
|
||||
}
|
||||
val intent = Intent(context, AccountVerificationActivity::class.java)
|
||||
intent.putExtras(bundle)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun restartApp() {
|
||||
val intent = Intent(context, MainActivity::class.java)
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
private fun updateUserAndRestartApp(loginData: LoginData) {
|
||||
val currentUser = currentUserProvider.currentUser.blockingGet()
|
||||
if (currentUser != null) {
|
||||
currentUser.clientCertificate = appPreferences.temporaryClientCertAlias
|
||||
currentUser.token = loginData.token
|
||||
val rowsUpdated = userManager.updateOrCreateUser(currentUser).blockingGet()
|
||||
Log.d(TAG, "User rows updated: $rowsUpdated")
|
||||
restartApp()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAccountRemovalWorkerAndRestartApp() {
|
||||
val accountRemovalWork = OneTimeWorkRequest.Builder(AccountRemovalWorker::class.java).build()
|
||||
WorkManager.getInstance(applicationContext).enqueue(accountRemovalWork)
|
||||
|
||||
WorkManager.getInstance(context).getWorkInfoByIdLiveData(accountRemovalWork.id)
|
||||
.observeForever { workInfo: WorkInfo? ->
|
||||
|
||||
when (workInfo?.state) {
|
||||
WorkInfo.State.SUCCEEDED, WorkInfo.State.FAILED, WorkInfo.State.CANCELLED -> {
|
||||
restartApp()
|
||||
}
|
||||
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: String = dataString.substring(prefix.length)
|
||||
val values: Array<String> = data.split("&").toTypedArray()
|
||||
if (values.size != PARAMETER_COUNT) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
public override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
dispose()
|
||||
}
|
||||
|
||||
init {
|
||||
sharedApplication!!.componentApplication.inject(this)
|
||||
}
|
||||
|
||||
override val appBarLayoutType: AppBarLayoutType
|
||||
get() = AppBarLayoutType.EMPTY
|
||||
|
||||
companion object {
|
||||
private val TAG = WebViewLoginActivity::class.java.simpleName
|
||||
private const val PROTOCOL_SUFFIX = "://"
|
||||
private const val LOGIN_URL_DATA_KEY_VALUE_SEPARATOR = ":"
|
||||
private const val PARAMETER_COUNT = 3
|
||||
}
|
||||
}
|
@ -29,9 +29,9 @@ import autodagger.AutoInjector
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.account.AccountVerificationActivity
|
||||
import com.nextcloud.talk.account.BrowserLoginActivity
|
||||
import com.nextcloud.talk.account.ServerSelectionActivity
|
||||
import com.nextcloud.talk.account.SwitchAccountActivity
|
||||
import com.nextcloud.talk.account.WebViewLoginActivity
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
import com.nextcloud.talk.events.CertificateEvent
|
||||
@ -235,7 +235,7 @@ open class BaseActivity : AppCompatActivity() {
|
||||
val temporaryClassNames: MutableList<String> = ArrayList()
|
||||
temporaryClassNames.add(ServerSelectionActivity::class.java.name)
|
||||
temporaryClassNames.add(AccountVerificationActivity::class.java.name)
|
||||
temporaryClassNames.add(WebViewLoginActivity::class.java.name)
|
||||
temporaryClassNames.add(BrowserLoginActivity::class.java.name)
|
||||
temporaryClassNames.add(SwitchAccountActivity::class.java.name)
|
||||
if (!temporaryClassNames.contains(javaClass.name)) {
|
||||
appPreferences.removeTemporaryClientCertAlias()
|
||||
|
@ -10,7 +10,6 @@
|
||||
package com.nextcloud.talk.activities
|
||||
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.provider.ContactsContract
|
||||
@ -24,8 +23,8 @@ import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import autodagger.AutoInjector
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.account.BrowserLoginActivity
|
||||
import com.nextcloud.talk.account.ServerSelectionActivity
|
||||
import com.nextcloud.talk.account.WebViewLoginActivity
|
||||
import com.nextcloud.talk.api.NcApi
|
||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||
import com.nextcloud.talk.chat.ChatActivity
|
||||
@ -93,7 +92,7 @@ class MainActivity :
|
||||
}
|
||||
|
||||
fun lockScreenIfConditionsApply() {
|
||||
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||
val keyguardManager = getSystemService(KEYGUARD_SERVICE) as KeyguardManager
|
||||
if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {
|
||||
if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
|
||||
val lockIntent = Intent(context, LockedActivity::class.java)
|
||||
@ -104,7 +103,7 @@ class MainActivity :
|
||||
|
||||
private fun launchServerSelection() {
|
||||
if (isBrandingUrlSet()) {
|
||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
||||
val intent = Intent(context, BrowserLoginActivity::class.java)
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_BASE_URL, resources.getString(R.string.weblogin_url))
|
||||
intent.putExtras(bundle)
|
||||
|
@ -70,8 +70,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
||||
import com.nextcloud.talk.R
|
||||
import com.nextcloud.talk.account.BrowserLoginActivity
|
||||
import com.nextcloud.talk.account.ServerSelectionActivity
|
||||
import com.nextcloud.talk.account.WebViewLoginActivity
|
||||
import com.nextcloud.talk.activities.BaseActivity
|
||||
import com.nextcloud.talk.activities.CallActivity
|
||||
import com.nextcloud.talk.activities.MainActivity
|
||||
@ -1951,7 +1951,7 @@ class ConversationsListActivity :
|
||||
deleteUserAndRestartApp()
|
||||
}
|
||||
.setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
|
||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
||||
val intent = Intent(context, BrowserLoginActivity::class.java)
|
||||
val bundle = Bundle()
|
||||
bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl!!)
|
||||
bundle.putBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT, true)
|
||||
|
@ -3,15 +3,17 @@
|
||||
~ Nextcloud Talk - Android Client
|
||||
~
|
||||
~ SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
|
||||
~ SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
|
||||
~ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorPrimary"
|
||||
android:keepScreenOn="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/progress_bar"
|
||||
android:layout_width="@dimen/item_height"
|
||||
android:layout_height="@dimen/item_height"
|
||||
@ -21,15 +23,36 @@
|
||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/activity_horizontal_margin"
|
||||
android:indeterminate="true"
|
||||
android:indeterminateTint="@color/colorPrimary"
|
||||
android:indeterminateTint="@color/white"
|
||||
android:indeterminateTintMode="src_in" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="invisible">
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/standard_double_margin"
|
||||
android:orientation="vertical"
|
||||
android:layout_alignParentBottom="true">
|
||||
|
||||
</WebView>
|
||||
<com.google.android.material.textview.MaterialTextView
|
||||
android:id="@+id/cancel_desc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/standard_double_margin"
|
||||
android:textAlignment="center"
|
||||
android:layout_gravity="center"
|
||||
android:textSize="@dimen/headline_text_size"
|
||||
android:textColor="@color/white"
|
||||
android:text="@string/please_continue_the_login_process_in_the_browser"
|
||||
/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/cancel_login_btn"
|
||||
android:layout_width="300dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="@dimen/standard_double_margin"
|
||||
android:text="@string/cancel_login"
|
||||
/>
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
@ -869,4 +869,6 @@ How to translate with transifex:
|
||||
<string name="conversation_archived">Conversation is archived</string>
|
||||
<string name="local_time">Local time: %1$s</string>
|
||||
<string name="open_notes">Open Notes</string>
|
||||
<string name="cancel_login">Cancel Login</string>
|
||||
<string name="please_continue_the_login_process_in_the_browser">Please continue the login process in the browser</string>
|
||||
</resources>
|
||||
|
@ -13,6 +13,12 @@
|
||||
<trust file=".*-javadoc[.]jar" regex="true" reason="Android Studio downloads javadoc jars but doesn't add checksums - fixes building in AS"/>
|
||||
<trust file=".*-sources[.]jar" regex="true" reason="Android Studio downloads source jars but doesn't add checksums - fixes building in AS"/>
|
||||
</trusted-artifacts>
|
||||
<ignored-keys>
|
||||
<ignored-key id="1DE461528F1F1B2A" reason="Key couldn't be downloaded from any key server"/>
|
||||
<ignored-key id="A6EA2E2BF22E0543" reason="Key couldn't be downloaded from any key server"/>
|
||||
<ignored-key id="AC7A514BC9F9BB70" reason="Key couldn't be downloaded from any key server"/>
|
||||
<ignored-key id="BDD2A76422470515" reason="Key couldn't be downloaded from any key server"/>
|
||||
</ignored-keys>
|
||||
<trusted-keys>
|
||||
<trusted-key id="02A36B6DB7056EB5E6FFEF893DA731F041734930" group="org.parceler"/>
|
||||
<trusted-key id="03C123038C20AAE9E286C857479D601F3A7B5C1A" group="com.github.ajalt.clikt"/>
|
||||
@ -11357,6 +11363,14 @@
|
||||
<sha256 value="75c6d60b57b164d6847078c54a359c86e87f222ed4d6300cc6d04cd28bff6301" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.bitfireAT" name="dav4jvm" version="2.2.1">
|
||||
<artifact name="dav4jvm-2.2.1.jar">
|
||||
<sha256 value="e6b8a9ff7ada0841198b18de2d6fd935f1f8754d3c84b8e8e3dc1840648d595d" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="dav4jvm-2.2.1.module">
|
||||
<sha256 value="977a16e0c70ee01139463fd4cbd9efd7d7a767c0c8b3e72941b69cbdd2f085d3" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.chrisbanes" name="PhotoView" version="2.3.0">
|
||||
<artifact name="PhotoView-2.3.0.aar">
|
||||
<sha256 value="6c8989f2945d50ab38b3e0300064f1f8d2d75bbcae1434fe535d9fb6898e9ad6" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
@ -11458,6 +11472,14 @@
|
||||
<sha256 value="66c4446d73eb75555b73efd0082473e910f1ac43abed33b597418465a2144052" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.nextcloud" name="android-library" version="2.20.0">
|
||||
<artifact name="android-library-2.20.0.aar">
|
||||
<sha256 value="fcc47f27babee30647ce3bae34e019edf8d71a0dcbb65d852a316678b89ac18a" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="android-library-2.20.0.module">
|
||||
<sha256 value="f695991e047ccd00c7ba6d69703fb4594b238ba770a0cce83223f2205ab09011" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.github.nextcloud-deps" name="ChatKit" version="0.4.2">
|
||||
<artifact name="ChatKit-0.4.2.aar">
|
||||
<sha256 value="e35716ca794a1678f5a5d5c57967fbf2e5e6d1b5abd9b08c5fbef590f95aa171" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
@ -13414,6 +13436,11 @@
|
||||
<sha256 value="41858c84753fd96a6b7c51122fccef39558c91cc08264e08506bcf20e0e63733" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.google.guava" name="guava" version="33.4.0-jre">
|
||||
<artifact name="guava-33.4.0-android.jar">
|
||||
<sha256 value="e335cd1678426f15e04ae4f623c58b4c176572d7258c6ea64e3437cdc01e5707" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.google.guava" name="guava-parent" version="26.0-android">
|
||||
<artifact name="guava-parent-26.0-android.pom">
|
||||
<sha256 value="f8698ab46ca996ce889c1afc8ca4f25eb8ac6b034dc898d4583742360016cc04" origin="Generated by Gradle"/>
|
||||
@ -13815,10 +13842,10 @@
|
||||
</component>
|
||||
<component group="com.mebigfatguy.fb-contrib" name="fb-contrib" version="7.6.11">
|
||||
<artifact name="fb-contrib-7.6.11.jar">
|
||||
<sha256 value="a2dc9df198e8508e5a719f7584a347ada9696dd58ce81df3c07d6cd91aee65e6" origin="Generated by Gradle"/>
|
||||
<sha256 value="a2dc9df198e8508e5a719f7584a347ada9696dd58ce81df3c07d6cd91aee65e6" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="fb-contrib-7.6.11.pom">
|
||||
<sha256 value="b84563c2a36d1fa02b5da4eebf4052cf7f3dbdb310e8a091214de136a5d28e33" origin="Generated by Gradle"/>
|
||||
<sha256 value="b84563c2a36d1fa02b5da4eebf4052cf7f3dbdb310e8a091214de136a5d28e33" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="com.mebigfatguy.fb-contrib" name="fb-contrib" version="7.6.4">
|
||||
@ -14478,6 +14505,22 @@
|
||||
<sha256 value="c86ee198a35a3715487860f419cbf642e7e4d9e8714777947dbe6a4e3a20ab58" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="commons-codec" name="commons-codec" version="1.2">
|
||||
<artifact name="commons-codec-1.2.jar">
|
||||
<sha256 value="9898a3b3857676128987b975d0b0f035becf3da5cf677266a34d6636f2b80542" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
<artifact name="commons-codec-1.2.pom">
|
||||
<sha256 value="28d6c089355487fd2e973e091a152727ac27ad2b2c1ec9cbcf916a10fc863148" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="commons-httpclient" name="commons-httpclient" version="3.1">
|
||||
<artifact name="commons-httpclient-3.1.jar">
|
||||
<pgp value="0785B3EFF60B1B1BEA94E0BB7C25280EAE63EBE5"/>
|
||||
</artifact>
|
||||
<artifact name="commons-httpclient-3.1.pom">
|
||||
<sha256 value="8a9b07d458d3e730221dbecdfafa7353ec1babd3b94c843227c47104a90c6d6c" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="commons-io" name="commons-io" version="2.13.0">
|
||||
<artifact name="commons-io-2.13.0.jar">
|
||||
<sha256 value="671eaa39688dac2ffaa4645b3c9980ae2d0ea2471e4ae6a5da199cd15ae23666" origin="Generated by Gradle"/>
|
||||
@ -15651,7 +15694,7 @@
|
||||
</component>
|
||||
<component group="io.opencensus" name="opencensus-api" version="0.31.0">
|
||||
<artifact name="opencensus-api-0.31.0.jar">
|
||||
<sha256 value="702ba55d78f39d55195dcf041fdfaab7a7490a9ac45013542487ed9e4d3a4d23" origin="Generated by Gradle"/>
|
||||
<sha256 value="702ba55d78f39d55195dcf041fdfaab7a7490a9ac45013542487ed9e4d3a4d23" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="opencensus-api-0.31.0.pom">
|
||||
<sha256 value="9b479591e7c3e0ab45381fa043a91657815bdd8c3593af010c7ac36f4c905919" origin="Generated by Gradle"/>
|
||||
@ -15940,6 +15983,11 @@
|
||||
<sha256 value="d8a30f1f1b4e411e4e759b83e4a962fb22a969b57d181c8a1fba24a3bea71e3e" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.apache" name="apache" version="10">
|
||||
<artifact name="apache-10.pom">
|
||||
<pgp value="190D5A957FF22273E601F7A7C92C5FEC70161C62"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.apache" name="apache" version="13">
|
||||
<artifact name="apache-13.pom">
|
||||
<sha256 value="ff513db0361fd41237bef4784968bc15aae478d4ec0a9496f811072ccaf3841d" origin="Generated by Gradle"/>
|
||||
@ -16255,6 +16303,19 @@
|
||||
<sha256 value="a67538865ae0f3744b21e91ca47d461054737c552d1ff29da714c860c992d5bb" origin="Generated by Gradle"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.apache.jackrabbit" name="jackrabbit-parent" version="2.13.5">
|
||||
<artifact name="jackrabbit-parent-2.13.5.pom">
|
||||
<sha256 value="19c3041c3851aa7f55f5001c427a45cfc0509ec6febd4b2c18e0a72bbfff2aa5" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.apache.jackrabbit" name="jackrabbit-webdav" version="2.13.5">
|
||||
<artifact name="jackrabbit-webdav-2.13.5.jar">
|
||||
<sha256 value="5b4734a237e6b409016fbcee7d21f8114746a8fd18f4f24587cf8974bcbd09c0" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
<artifact name="jackrabbit-webdav-2.13.5.pom">
|
||||
<sha256 value="3685bd8ec1f22fc9623883583472665404f1d028aeb4bb705f84f43a03b272e8" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||
</artifact>
|
||||
</component>
|
||||
<component group="org.apache.logging" name="logging-parent" version="10.6.0">
|
||||
<artifact name="logging-parent-10.6.0.pom">
|
||||
<sha256 value="f827475840a64083b585ec8dbbf7093b6fd02624293cec37d56edf9fc354109e" origin="Generated by Gradle"/>
|
||||
|
Loading…
Reference in New Issue
Block a user