mirror of
https://github.com/nextcloud/talk-android
synced 2025-07-21 19:55:07 +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" />
|
android:theme="@style/AppTheme" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".account.WebViewLoginActivity"
|
android:name=".account.BrowserLoginActivity"
|
||||||
android:theme="@style/AppTheme" />
|
android:theme="@style/AppTheme" />
|
||||||
|
|
||||||
<activity android:name=".contacts.ContactsActivity"
|
<activity android:name=".contacts.ContactsActivity"
|
||||||
|
@ -158,7 +158,7 @@ class AccountVerificationActivity : BaseActivity() {
|
|||||||
bundle.putString(KEY_USERNAME, username)
|
bundle.putString(KEY_USERNAME, username)
|
||||||
bundle.putString(KEY_PASSWORD, "")
|
bundle.putString(KEY_PASSWORD, "")
|
||||||
|
|
||||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
val intent = Intent(context, BrowserLoginActivity::class.java)
|
||||||
intent.putExtras(bundle)
|
intent.putExtras(bundle)
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
} else {
|
} 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()
|
val bundle = Bundle()
|
||||||
bundle.putString(BundleKeys.KEY_BASE_URL, queryUrl.replace("/status.php", ""))
|
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)
|
intent.putExtras(bundle)
|
||||||
startActivity(intent)
|
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.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
import com.nextcloud.talk.account.AccountVerificationActivity
|
import com.nextcloud.talk.account.AccountVerificationActivity
|
||||||
|
import com.nextcloud.talk.account.BrowserLoginActivity
|
||||||
import com.nextcloud.talk.account.ServerSelectionActivity
|
import com.nextcloud.talk.account.ServerSelectionActivity
|
||||||
import com.nextcloud.talk.account.SwitchAccountActivity
|
import com.nextcloud.talk.account.SwitchAccountActivity
|
||||||
import com.nextcloud.talk.account.WebViewLoginActivity
|
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
import com.nextcloud.talk.chat.ChatActivity
|
import com.nextcloud.talk.chat.ChatActivity
|
||||||
import com.nextcloud.talk.events.CertificateEvent
|
import com.nextcloud.talk.events.CertificateEvent
|
||||||
@ -235,7 +235,7 @@ open class BaseActivity : AppCompatActivity() {
|
|||||||
val temporaryClassNames: MutableList<String> = ArrayList()
|
val temporaryClassNames: MutableList<String> = ArrayList()
|
||||||
temporaryClassNames.add(ServerSelectionActivity::class.java.name)
|
temporaryClassNames.add(ServerSelectionActivity::class.java.name)
|
||||||
temporaryClassNames.add(AccountVerificationActivity::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)
|
temporaryClassNames.add(SwitchAccountActivity::class.java.name)
|
||||||
if (!temporaryClassNames.contains(javaClass.name)) {
|
if (!temporaryClassNames.contains(javaClass.name)) {
|
||||||
appPreferences.removeTemporaryClientCertAlias()
|
appPreferences.removeTemporaryClientCertAlias()
|
||||||
|
@ -10,7 +10,6 @@
|
|||||||
package com.nextcloud.talk.activities
|
package com.nextcloud.talk.activities
|
||||||
|
|
||||||
import android.app.KeyguardManager
|
import android.app.KeyguardManager
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.provider.ContactsContract
|
import android.provider.ContactsContract
|
||||||
@ -24,8 +23,8 @@ import androidx.lifecycle.ProcessLifecycleOwner
|
|||||||
import autodagger.AutoInjector
|
import autodagger.AutoInjector
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.account.BrowserLoginActivity
|
||||||
import com.nextcloud.talk.account.ServerSelectionActivity
|
import com.nextcloud.talk.account.ServerSelectionActivity
|
||||||
import com.nextcloud.talk.account.WebViewLoginActivity
|
|
||||||
import com.nextcloud.talk.api.NcApi
|
import com.nextcloud.talk.api.NcApi
|
||||||
import com.nextcloud.talk.application.NextcloudTalkApplication
|
import com.nextcloud.talk.application.NextcloudTalkApplication
|
||||||
import com.nextcloud.talk.chat.ChatActivity
|
import com.nextcloud.talk.chat.ChatActivity
|
||||||
@ -93,7 +92,7 @@ class MainActivity :
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun lockScreenIfConditionsApply() {
|
fun lockScreenIfConditionsApply() {
|
||||||
val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
val keyguardManager = getSystemService(KEYGUARD_SERVICE) as KeyguardManager
|
||||||
if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {
|
if (keyguardManager.isKeyguardSecure && appPreferences.isScreenLocked) {
|
||||||
if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
|
if (!SecurityUtils.checkIfWeAreAuthenticated(appPreferences.screenLockTimeout)) {
|
||||||
val lockIntent = Intent(context, LockedActivity::class.java)
|
val lockIntent = Intent(context, LockedActivity::class.java)
|
||||||
@ -104,7 +103,7 @@ class MainActivity :
|
|||||||
|
|
||||||
private fun launchServerSelection() {
|
private fun launchServerSelection() {
|
||||||
if (isBrandingUrlSet()) {
|
if (isBrandingUrlSet()) {
|
||||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
val intent = Intent(context, BrowserLoginActivity::class.java)
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putString(BundleKeys.KEY_BASE_URL, resources.getString(R.string.weblogin_url))
|
bundle.putString(BundleKeys.KEY_BASE_URL, resources.getString(R.string.weblogin_url))
|
||||||
intent.putExtras(bundle)
|
intent.putExtras(bundle)
|
||||||
|
@ -70,8 +70,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
import com.nextcloud.android.common.ui.theme.utils.ColorRole
|
||||||
import com.nextcloud.talk.R
|
import com.nextcloud.talk.R
|
||||||
|
import com.nextcloud.talk.account.BrowserLoginActivity
|
||||||
import com.nextcloud.talk.account.ServerSelectionActivity
|
import com.nextcloud.talk.account.ServerSelectionActivity
|
||||||
import com.nextcloud.talk.account.WebViewLoginActivity
|
|
||||||
import com.nextcloud.talk.activities.BaseActivity
|
import com.nextcloud.talk.activities.BaseActivity
|
||||||
import com.nextcloud.talk.activities.CallActivity
|
import com.nextcloud.talk.activities.CallActivity
|
||||||
import com.nextcloud.talk.activities.MainActivity
|
import com.nextcloud.talk.activities.MainActivity
|
||||||
@ -1951,7 +1951,7 @@ class ConversationsListActivity :
|
|||||||
deleteUserAndRestartApp()
|
deleteUserAndRestartApp()
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
|
.setNegativeButton(R.string.nc_settings_reauthorize) { _, _ ->
|
||||||
val intent = Intent(context, WebViewLoginActivity::class.java)
|
val intent = Intent(context, BrowserLoginActivity::class.java)
|
||||||
val bundle = Bundle()
|
val bundle = Bundle()
|
||||||
bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl!!)
|
bundle.putString(BundleKeys.KEY_BASE_URL, currentUser!!.baseUrl!!)
|
||||||
bundle.putBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT, true)
|
bundle.putBoolean(BundleKeys.KEY_REAUTHORIZE_ACCOUNT, true)
|
||||||
|
@ -3,15 +3,17 @@
|
|||||||
~ Nextcloud Talk - Android Client
|
~ Nextcloud Talk - Android Client
|
||||||
~
|
~
|
||||||
~ SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
|
~ SPDX-FileCopyrightText: 2017 Mario Danic <mario@lovelyhq.com>
|
||||||
|
~ SPDX-FileCopyrightText: 2025 Julius Linus <juliuslinus1@gmail.com>
|
||||||
~ SPDX-License-Identifier: GPL-3.0-or-later
|
~ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
-->
|
-->
|
||||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/colorPrimary"
|
||||||
android:keepScreenOn="true"
|
android:keepScreenOn="true"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<ProgressBar
|
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
android:id="@+id/progress_bar"
|
android:id="@+id/progress_bar"
|
||||||
android:layout_width="@dimen/item_height"
|
android:layout_width="@dimen/item_height"
|
||||||
android:layout_height="@dimen/item_height"
|
android:layout_height="@dimen/item_height"
|
||||||
@ -21,15 +23,36 @@
|
|||||||
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
android:layout_marginEnd="@dimen/activity_horizontal_margin"
|
||||||
android:layout_marginRight="@dimen/activity_horizontal_margin"
|
android:layout_marginRight="@dimen/activity_horizontal_margin"
|
||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
android:indeterminateTint="@color/colorPrimary"
|
android:indeterminateTint="@color/white"
|
||||||
android:indeterminateTintMode="src_in" />
|
android:indeterminateTintMode="src_in" />
|
||||||
|
|
||||||
<WebView
|
<LinearLayout
|
||||||
android:id="@+id/webview"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:visibility="invisible">
|
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>
|
</RelativeLayout>
|
||||||
|
@ -869,4 +869,6 @@ How to translate with transifex:
|
|||||||
<string name="conversation_archived">Conversation is archived</string>
|
<string name="conversation_archived">Conversation is archived</string>
|
||||||
<string name="local_time">Local time: %1$s</string>
|
<string name="local_time">Local time: %1$s</string>
|
||||||
<string name="open_notes">Open Notes</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>
|
</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=".*-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"/>
|
<trust file=".*-sources[.]jar" regex="true" reason="Android Studio downloads source jars but doesn't add checksums - fixes building in AS"/>
|
||||||
</trusted-artifacts>
|
</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-keys>
|
||||||
<trusted-key id="02A36B6DB7056EB5E6FFEF893DA731F041734930" group="org.parceler"/>
|
<trusted-key id="02A36B6DB7056EB5E6FFEF893DA731F041734930" group="org.parceler"/>
|
||||||
<trusted-key id="03C123038C20AAE9E286C857479D601F3A7B5C1A" group="com.github.ajalt.clikt"/>
|
<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"/>
|
<sha256 value="75c6d60b57b164d6847078c54a359c86e87f222ed4d6300cc6d04cd28bff6301" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="com.github.chrisbanes" name="PhotoView" version="2.3.0">
|
||||||
<artifact name="PhotoView-2.3.0.aar">
|
<artifact name="PhotoView-2.3.0.aar">
|
||||||
<sha256 value="6c8989f2945d50ab38b3e0300064f1f8d2d75bbcae1434fe535d9fb6898e9ad6" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<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"/>
|
<sha256 value="66c4446d73eb75555b73efd0082473e910f1ac43abed33b597418465a2144052" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="com.github.nextcloud-deps" name="ChatKit" version="0.4.2">
|
||||||
<artifact name="ChatKit-0.4.2.aar">
|
<artifact name="ChatKit-0.4.2.aar">
|
||||||
<sha256 value="e35716ca794a1678f5a5d5c57967fbf2e5e6d1b5abd9b08c5fbef590f95aa171" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
<sha256 value="e35716ca794a1678f5a5d5c57967fbf2e5e6d1b5abd9b08c5fbef590f95aa171" origin="Generated by Gradle" reason="Artifact is not signed"/>
|
||||||
@ -13414,6 +13436,11 @@
|
|||||||
<sha256 value="41858c84753fd96a6b7c51122fccef39558c91cc08264e08506bcf20e0e63733" origin="Generated by Gradle"/>
|
<sha256 value="41858c84753fd96a6b7c51122fccef39558c91cc08264e08506bcf20e0e63733" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="com.google.guava" name="guava-parent" version="26.0-android">
|
||||||
<artifact name="guava-parent-26.0-android.pom">
|
<artifact name="guava-parent-26.0-android.pom">
|
||||||
<sha256 value="f8698ab46ca996ce889c1afc8ca4f25eb8ac6b034dc898d4583742360016cc04" origin="Generated by Gradle"/>
|
<sha256 value="f8698ab46ca996ce889c1afc8ca4f25eb8ac6b034dc898d4583742360016cc04" origin="Generated by Gradle"/>
|
||||||
@ -13815,10 +13842,10 @@
|
|||||||
</component>
|
</component>
|
||||||
<component group="com.mebigfatguy.fb-contrib" name="fb-contrib" version="7.6.11">
|
<component group="com.mebigfatguy.fb-contrib" name="fb-contrib" version="7.6.11">
|
||||||
<artifact name="fb-contrib-7.6.11.jar">
|
<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>
|
||||||
<artifact name="fb-contrib-7.6.11.pom">
|
<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>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
<component group="com.mebigfatguy.fb-contrib" name="fb-contrib" version="7.6.4">
|
<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"/>
|
<sha256 value="c86ee198a35a3715487860f419cbf642e7e4d9e8714777947dbe6a4e3a20ab58" origin="Generated by Gradle" reason="A key couldn't be downloaded"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="commons-io" name="commons-io" version="2.13.0">
|
||||||
<artifact name="commons-io-2.13.0.jar">
|
<artifact name="commons-io-2.13.0.jar">
|
||||||
<sha256 value="671eaa39688dac2ffaa4645b3c9980ae2d0ea2471e4ae6a5da199cd15ae23666" origin="Generated by Gradle"/>
|
<sha256 value="671eaa39688dac2ffaa4645b3c9980ae2d0ea2471e4ae6a5da199cd15ae23666" origin="Generated by Gradle"/>
|
||||||
@ -15651,7 +15694,7 @@
|
|||||||
</component>
|
</component>
|
||||||
<component group="io.opencensus" name="opencensus-api" version="0.31.0">
|
<component group="io.opencensus" name="opencensus-api" version="0.31.0">
|
||||||
<artifact name="opencensus-api-0.31.0.jar">
|
<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>
|
||||||
<artifact name="opencensus-api-0.31.0.pom">
|
<artifact name="opencensus-api-0.31.0.pom">
|
||||||
<sha256 value="9b479591e7c3e0ab45381fa043a91657815bdd8c3593af010c7ac36f4c905919" origin="Generated by Gradle"/>
|
<sha256 value="9b479591e7c3e0ab45381fa043a91657815bdd8c3593af010c7ac36f4c905919" origin="Generated by Gradle"/>
|
||||||
@ -15940,6 +15983,11 @@
|
|||||||
<sha256 value="d8a30f1f1b4e411e4e759b83e4a962fb22a969b57d181c8a1fba24a3bea71e3e" origin="Generated by Gradle"/>
|
<sha256 value="d8a30f1f1b4e411e4e759b83e4a962fb22a969b57d181c8a1fba24a3bea71e3e" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="org.apache" name="apache" version="13">
|
||||||
<artifact name="apache-13.pom">
|
<artifact name="apache-13.pom">
|
||||||
<sha256 value="ff513db0361fd41237bef4784968bc15aae478d4ec0a9496f811072ccaf3841d" origin="Generated by Gradle"/>
|
<sha256 value="ff513db0361fd41237bef4784968bc15aae478d4ec0a9496f811072ccaf3841d" origin="Generated by Gradle"/>
|
||||||
@ -16255,6 +16303,19 @@
|
|||||||
<sha256 value="a67538865ae0f3744b21e91ca47d461054737c552d1ff29da714c860c992d5bb" origin="Generated by Gradle"/>
|
<sha256 value="a67538865ae0f3744b21e91ca47d461054737c552d1ff29da714c860c992d5bb" origin="Generated by Gradle"/>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</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">
|
<component group="org.apache.logging" name="logging-parent" version="10.6.0">
|
||||||
<artifact name="logging-parent-10.6.0.pom">
|
<artifact name="logging-parent-10.6.0.pom">
|
||||||
<sha256 value="f827475840a64083b585ec8dbbf7093b6fd02624293cec37d56edf9fc354109e" origin="Generated by Gradle"/>
|
<sha256 value="f827475840a64083b585ec8dbbf7093b6fd02624293cec37d56edf9fc354109e" origin="Generated by Gradle"/>
|
||||||
|
Loading…
Reference in New Issue
Block a user