diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionActivity.java
deleted file mode 100644
index 8c1de631f7f88cee2a67d118e7c2a2c540493c43..0000000000000000000000000000000000000000
--- a/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionActivity.java
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
- * Copyright MURENA SAS 2023
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.owncloud.android.ui.activity;
-
-import static com.nextcloud.android.sso.Constants.DELIMITER;
-import static com.nextcloud.android.sso.Constants.EXCEPTION_ACCOUNT_ACCESS_DECLINED;
-import static com.nextcloud.android.sso.Constants.EXCEPTION_ACCOUNT_NOT_FOUND;
-import static com.nextcloud.android.sso.Constants.NEXTCLOUD_FILES_ACCOUNT;
-import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO;
-import static com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO_EXCEPTION;
-import static com.nextcloud.android.sso.Constants.SSO_SERVER_URL;
-import static com.nextcloud.android.sso.Constants.SSO_SHARED_PREFERENCE;
-import static com.nextcloud.android.sso.Constants.SSO_TOKEN;
-import static com.nextcloud.android.sso.Constants.SSO_USER_ID;
-
-import android.accounts.Account;
-import android.accounts.AccountManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
-
-import com.nextcloud.android.utils.EncryptionUtils;
-import com.owncloud.android.lib.common.OwnCloudAccount;
-import com.owncloud.android.lib.common.accounts.AccountUtils;
-
-import java.util.Arrays;
-import java.util.UUID;
-import java.util.logging.Level;
-
-import at.bitfire.davdroid.R;
-import at.bitfire.davdroid.log.Logger;
-import at.bitfire.davdroid.util.SsoUtils;
-
-public class SsoGrantPermissionActivity extends AppCompatActivity {
-
- private static final String[] ACCEPTED_PACKAGE_LIST = {"foundation.e.notes"};
-
- private Account account;
- private String packageName;
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_sso_grant_permission);
-
- ComponentName callingActivity = getCallingActivity();
-
- if (callingActivity != null) {
- packageName = callingActivity.getPackageName();
- account = getIntent().getParcelableExtra(NEXTCLOUD_FILES_ACCOUNT);
- validateAndAutoGrandPermission();
- } else {
- Logger.INSTANCE.getLog().log(Level.SEVERE, "SsoGrantPermissionActivity: Calling Package is null");
- setResultAndExit(EXCEPTION_ACCOUNT_ACCESS_DECLINED);
- }
- }
-
- private void validateAndAutoGrandPermission() {
- if (!isValidRequest()) {
- Logger.INSTANCE.getLog().log(Level.SEVERE, "SsoGrantPermissionActivity: Invalid request");
- setResultAndExit(EXCEPTION_ACCOUNT_ACCESS_DECLINED);
- return;
- }
-
- grantPermission();
- }
-
- private boolean isValidRequest() {
- if (packageName == null || account == null) {
- return false;
- }
-
- boolean validPackage = Arrays.asList(ACCEPTED_PACKAGE_LIST)
- .contains(packageName);
-
- if (!validPackage) {
- return false;
- }
-
- return Arrays.asList(getAcceptedAccountTypeList())
- .contains(account.type);
- }
-
- private String[] getAcceptedAccountTypeList() {
- return new String[]{
- getString(R.string.eelo_account_type)
- };
- }
-
- private void grantPermission() {
- String serverUrl = getServerUrl();
-
- if (serverUrl == null) {
- return;
- }
-
- // create token
- String token = UUID.randomUUID().toString().replaceAll("-", "");
- String userId = getUserId();
-
- saveToken(token, account.name);
- setResultData(token, userId, serverUrl);
- finish();
- }
-
- @NonNull
- private String getUserId() {
- final AccountManager accountManager = AccountManager.get(this);
- final String baseUrl = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL);
- return SsoUtils.INSTANCE.sanitizeUserId(account.name, baseUrl);
- }
-
- private void setResultData(String token, String userId, String serverUrl) {
- final Bundle result = new Bundle();
- result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
- result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
- result.putString(AccountManager.KEY_AUTHTOKEN, NEXTCLOUD_SSO);
- result.putString(SSO_USER_ID, userId);
- result.putString(SSO_TOKEN, token);
- result.putString(SSO_SERVER_URL, serverUrl);
-
- Intent data = new Intent();
- data.putExtra(NEXTCLOUD_SSO, result);
- setResult(RESULT_OK, data);
- }
-
- @Nullable
- private String getServerUrl() {
- try {
- OwnCloudAccount ocAccount = new OwnCloudAccount(account, this);
- return ocAccount.getBaseUri().toString();
- } catch (AccountUtils.AccountNotFoundException e) {
- Logger.INSTANCE.getLog().log(Level.SEVERE, "SsoGrantPermissionActivity: Account not found");
- setResultAndExit(EXCEPTION_ACCOUNT_NOT_FOUND);
- }
-
- return null;
- }
-
- private void saveToken(String token, String accountName) {
- String hashedTokenWithSalt = EncryptionUtils.generateSHA512(token);
- SharedPreferences sharedPreferences = getSharedPreferences(SSO_SHARED_PREFERENCE, Context.MODE_PRIVATE);
- SharedPreferences.Editor editor = sharedPreferences.edit();
- editor.putString(packageName + DELIMITER + accountName, hashedTokenWithSalt);
- editor.apply();
- }
-
- private void setResultAndExit(String exception) {
- Intent data = new Intent();
- data.putExtra(NEXTCLOUD_SSO_EXCEPTION, exception);
- setResult(RESULT_CANCELED, data);
- finish();
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..ba791f31d1740bc302136cada0f9d25cb778b4db
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionActivity.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright MURENA SAS 2024
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.ui.activity
+
+import android.accounts.Account
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.flowWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import at.bitfire.davdroid.R
+import com.nextcloud.android.sso.Constants
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+@AndroidEntryPoint
+class SsoGrantPermissionActivity : AppCompatActivity() {
+
+ private val viewModel: SsoGrantPermissionViewModel by viewModels()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_sso_grant_permission)
+
+ lifecycleScope.launch {
+ viewModel.permissionEvent
+ .flowWithLifecycle(
+ lifecycle = lifecycle,
+ minActiveState = Lifecycle.State.CREATED
+ ).collectLatest {
+ when (it) {
+ is SsoGrantPermissionEvent.PermissionGranted -> setSuccessResult(it.bundle)
+ is SsoGrantPermissionEvent.PermissionDenied -> setCanceledResult(it.errorMessage)
+ }
+ }
+ }
+
+ validateAccount()
+ }
+
+ private fun validateAccount() {
+ val account: Account? =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(Constants.NEXTCLOUD_FILES_ACCOUNT, Account::class.java)
+ } else {
+ intent.getParcelableExtra(Constants.NEXTCLOUD_FILES_ACCOUNT)
+ }
+
+ viewModel.initValidation(
+ callingActivity = callingActivity,
+ account = account
+ )
+ }
+
+ private fun setCanceledResult(exception: String) {
+ val data = Intent()
+ data.putExtra(Constants.NEXTCLOUD_SSO_EXCEPTION, exception)
+ setResult(RESULT_CANCELED, data)
+ finish()
+ }
+
+ private fun setSuccessResult(result: Bundle) {
+ val data = Intent()
+ data.putExtra(Constants.NEXTCLOUD_SSO, result)
+ setResult(RESULT_OK, data)
+ finish()
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionEvent.kt b/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionEvent.kt
new file mode 100644
index 0000000000000000000000000000000000000000..2e69754c8b34bdf4a298347dd46fcfb49ad76e5d
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionEvent.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright MURENA SAS 2024
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.ui.activity
+
+import android.os.Bundle
+
+sealed class SsoGrantPermissionEvent {
+
+ data class PermissionGranted(val bundle: Bundle) : SsoGrantPermissionEvent()
+
+ data class PermissionDenied(val errorMessage: String) : SsoGrantPermissionEvent()
+}
diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionViewModel.kt b/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionViewModel.kt
new file mode 100644
index 0000000000000000000000000000000000000000..51ecf4cf014c7b89525b874704d920405e7dccdd
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionViewModel.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright MURENA SAS 2024
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.owncloud.android.ui.activity
+
+import android.accounts.Account
+import android.accounts.AccountManager
+import android.content.ComponentName
+import android.content.Context
+import android.os.Bundle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import at.bitfire.davdroid.R
+import at.bitfire.davdroid.db.AppDatabase
+import at.bitfire.davdroid.log.Logger.log
+import at.bitfire.davdroid.util.UserIdFetcher
+import com.nextcloud.android.sso.Constants
+import com.nextcloud.android.utils.EncryptionUtils
+import com.owncloud.android.lib.common.OwnCloudAccount
+import com.owncloud.android.lib.common.accounts.AccountUtils
+import dagger.hilt.android.lifecycle.HiltViewModel
+import dagger.hilt.android.qualifiers.ApplicationContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.launch
+import java.util.UUID
+import java.util.logging.Level
+import javax.inject.Inject
+
+@HiltViewModel
+class SsoGrantPermissionViewModel @Inject constructor(
+ @ApplicationContext private val context: Context,
+ private val database: AppDatabase,
+) : ViewModel() {
+
+ private val acceptedAccountTypes = listOf(context.getString(R.string.eelo_account_type))
+ private val acceptedPackages = listOf("foundation.e.notes")
+
+ private val _permissionEvent = MutableSharedFlow()
+ val permissionEvent = _permissionEvent.asSharedFlow()
+
+ fun initValidation(callingActivity: ComponentName?, account: Account?) {
+ viewModelScope.launch(Dispatchers.IO) {
+ val packageName = getCallingPackageName(callingActivity) ?: return@launch
+ validate(packageName, account)
+ }
+ }
+
+ private suspend fun emitPermissionDeniedEvent(message: String) {
+ _permissionEvent.emit(
+ SsoGrantPermissionEvent.PermissionDenied(
+ errorMessage = message
+ )
+ )
+ }
+
+ private suspend fun getCallingPackageName(callingActivity: ComponentName?): String? {
+ if (callingActivity != null) {
+ return callingActivity.packageName
+ }
+
+ log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Calling Package is null")
+ emitPermissionDeniedEvent(Constants.EXCEPTION_ACCOUNT_ACCESS_DECLINED)
+ return null
+ }
+
+ private suspend fun validate(packageName: String?, account: Account?) {
+ if (!isValidRequest(packageName, account)) {
+ log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Invalid request")
+ emitPermissionDeniedEvent(Constants.EXCEPTION_ACCOUNT_ACCESS_DECLINED)
+ }
+
+ val serverUrl = getServerUrl(account!!) ?: return
+
+ val token = UUID.randomUUID().toString().replace("-".toRegex(), "")
+ val userId = getUserId(account)
+
+ saveToken(
+ token = token,
+ accountName = userId,
+ packageName = packageName!!
+ )
+
+ passSuccessfulData(
+ account = account,
+ token = token,
+ userId = userId,
+ serverUrl = serverUrl
+ )
+
+ }
+
+ private fun isValidRequest(packageName: String?, account: Account?): Boolean {
+ if (packageName == null || account == null) {
+ return false
+ }
+
+ return acceptedPackages.contains(packageName) && acceptedAccountTypes.contains(account.type)
+ }
+
+ private suspend fun getServerUrl(account: Account): String? {
+ try {
+ val ocAccount = OwnCloudAccount(account, context)
+ return ocAccount.baseUri.toString()
+ } catch (e: AccountUtils.AccountNotFoundException) {
+ log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Account not found")
+ emitPermissionDeniedEvent(Constants.EXCEPTION_ACCOUNT_NOT_FOUND)
+ }
+
+ return null
+ }
+
+ private fun getUserId(account: Account): String {
+ val accountManager = AccountManager.get(context)
+ val userId = accountManager.getUserData(account, AccountUtils.Constants.KEY_USER_ID)
+
+ if (!userId.isNullOrBlank()) {
+ return userId
+ }
+
+ val principalUrl =
+ database.serviceDao().getByAccountName(account.name)?.principal?.toString()
+ ?: return account.name
+
+ return UserIdFetcher.fetch(principalUrl) ?: account.name
+ }
+
+ private fun saveToken(token: String, accountName: String, packageName: String) {
+ val hashedTokenWithSalt = EncryptionUtils.generateSHA512(token)
+ val sharedPreferences =
+ context.getSharedPreferences(Constants.SSO_SHARED_PREFERENCE, Context.MODE_PRIVATE)
+ val editor = sharedPreferences.edit()
+ editor.putString(packageName + Constants.DELIMITER + accountName, hashedTokenWithSalt)
+ editor.apply()
+ }
+
+ private suspend fun passSuccessfulData(
+ account: Account,
+ token: String,
+ userId: String,
+ serverUrl: String
+ ) {
+ val result = Bundle().apply {
+ putString(AccountManager.KEY_ACCOUNT_NAME, account.name)
+ putString(AccountManager.KEY_ACCOUNT_TYPE, account.type)
+ putString(AccountManager.KEY_AUTHTOKEN, Constants.NEXTCLOUD_SSO)
+ putString(Constants.SSO_USER_ID, userId)
+ putString(Constants.SSO_TOKEN, token)
+ putString(Constants.SSO_SERVER_URL, serverUrl)
+ }
+
+ _permissionEvent.emit(
+ SsoGrantPermissionEvent.PermissionGranted(
+ bundle = result
+ )
+ )
+ }
+}
diff --git a/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt
index 39dee0e6b47b695cba3bdd2b8056c3e756234d88..bab2fd4ad333cfa8d0eddfa78e068bb47ae23bd7 100644
--- a/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt
+++ b/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt
@@ -19,8 +19,8 @@ import at.bitfire.davdroid.resource.LocalAddressBook
import at.bitfire.davdroid.syncadapter.AccountUtils
import at.bitfire.davdroid.syncadapter.PeriodicSyncWorker
import at.bitfire.davdroid.syncadapter.SyncUtils
+import at.bitfire.davdroid.util.UserIdFetcher
import at.bitfire.davdroid.util.AuthStatePrefUtils
-import at.bitfire.davdroid.util.SsoUtils
import at.bitfire.davdroid.util.setAndVerifyUserData
import at.bitfire.ical4android.TaskProvider
import at.bitfire.vcard4android.GroupMethod
@@ -158,21 +158,38 @@ class AccountSettings(
bundle.putString(COOKIE_KEY, cookies)
}
- var baseUrl : String? = null
if (!url.isNullOrEmpty()) {
- baseUrl = AccountUtils.getOwnCloudBaseUrl(url)
+ val baseUrl = AccountUtils.getOwnCloudBaseUrl(url)
bundle.putString(NCAccountUtils.Constants.KEY_OC_BASE_URL, baseUrl)
}
- addUserIdToBundle(bundle, userName, baseUrl)
- addEmailToBundle(bundle, email, userName)
+ addUserIdToBundle(
+ bundle = bundle,
+ url = url,
+ userName = userName
+ )
+
+ addEmailToBundle(
+ bundle = bundle,
+ email = email,
+ userName = userName
+ )
return bundle
}
- private fun addUserIdToBundle(bundle: Bundle, userName: String?, baseUrl: String?) {
- userName?.let {
- val userId = SsoUtils.sanitizeUserId(it, baseUrl)
+ private fun addUserIdToBundle(bundle: Bundle, url: String?, userName: String?) {
+ var userId: String? = null
+
+ if (!url.isNullOrBlank()) {
+ userId = UserIdFetcher.fetch(url)
+ }
+
+ if (userId.isNullOrBlank()) {
+ userId = userName
+ }
+
+ userId?.let {
bundle.putString(NCAccountUtils.Constants.KEY_USER_ID, userId)
}
}
diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt
index 327163806d60e1f24ec16b7344d58a55002ed906..a7cf8c1d96ab02fc96755cbd5b1b9ddad27f8316 100644
--- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt
+++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt
@@ -280,14 +280,17 @@ class AccountDetailsFragment : Fragment() {
var baseURL : String? = null
if (config.calDAV != null) {
- baseURL = config.calDAV.principal.toString()
+ baseURL = config.calDAV.principal?.toString()
+ }
+
+ if (baseURL == null) {
+ baseURL = config.cardDAV?.principal?.toString()
}
when (activity.intent.getStringExtra(LoginActivity.ACCOUNT_TYPE)) {
context.getString(R.string.eelo_account_type) -> {
accountType = context.getString(R.string.eelo_account_type)
addressBookAccountType = context.getString(R.string.account_type_eelo_address_book)
- baseURL = credentials?.serverUri.toString()
}
context.getString(R.string.google_account_type) -> {
accountType = context.getString(R.string.google_account_type)
@@ -474,7 +477,5 @@ class AccountDetailsFragment : Fragment() {
return serviceId
}
-
}
-
-}
\ No newline at end of file
+}
diff --git a/app/src/main/kotlin/at/bitfire/davdroid/util/SsoUtils.kt b/app/src/main/kotlin/at/bitfire/davdroid/util/SsoUtils.kt
deleted file mode 100644
index 7836933cd3ac308d7cd2bacb19ff7e71b1fe1a07..0000000000000000000000000000000000000000
--- a/app/src/main/kotlin/at/bitfire/davdroid/util/SsoUtils.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright MURENA SAS 2024
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package at.bitfire.davdroid.util
-
-import java.net.URI
-
-object SsoUtils {
-
- /**
- * This method removes the baseUrl's host part from the accountName to retrieve userId
- * for ex: accountName: abc@murena.io; baseUrl: https://murena.io; so userID: abc
- */
- fun sanitizeUserId(accountName: String, baseUrl: String?): String {
- val userId = accountName.trim()
-
- val host = getHost(baseUrl) ?: return userId
- val userNameEndPart = "@$host"
-
- if (!userId.endsWith(userNameEndPart, ignoreCase = true)) {
- return userId
- }
-
- return userId.substring(0, userId.lastIndexOf(userNameEndPart, ignoreCase = true))
- }
-
- private fun getHost(baseUrl: String?): String? {
- if (baseUrl == null) {
- return null
- }
-
- val uri = URI.create(baseUrl.trim())
- return uri.host
- }
-}
diff --git a/app/src/main/kotlin/at/bitfire/davdroid/util/UserIdFetcher.kt b/app/src/main/kotlin/at/bitfire/davdroid/util/UserIdFetcher.kt
new file mode 100644
index 0000000000000000000000000000000000000000..16c7bf8a991a2442cc7e3e2c13946d0ab399c429
--- /dev/null
+++ b/app/src/main/kotlin/at/bitfire/davdroid/util/UserIdFetcher.kt
@@ -0,0 +1,45 @@
+/*
+ * Copyright MURENA SAS 2024
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package at.bitfire.davdroid.util
+
+object UserIdFetcher {
+
+ /**
+ * retrieve the userId from caldav/carddav nextcloud principal url.
+ * example: if the url is: https://abc.com/remote.php/dav/principals/users/xyz/, then this function will return xyz.
+ *
+ * this function will return null, if
+ * - `/users/` part is missing
+ */
+ fun fetch(principalUrl: String): String? {
+ val usersPart = "/users/"
+
+ var userId: String? = null
+ if (principalUrl.contains(usersPart, ignoreCase = true)) {
+ userId = principalUrl.split(usersPart, ignoreCase = true)[1]
+ if (userId.endsWith("/")) {
+ userId = userId.dropLast(1)
+ }
+
+ if (userId.isBlank()) {
+ userId = null
+ }
+ }
+
+ return userId
+ }
+}
diff --git a/app/src/test/kotlin/at/bitfire/davdroid/util/SsoUtilsTest.kt b/app/src/test/kotlin/at/bitfire/davdroid/util/UserIdFetcherTest.kt
similarity index 51%
rename from app/src/test/kotlin/at/bitfire/davdroid/util/SsoUtilsTest.kt
rename to app/src/test/kotlin/at/bitfire/davdroid/util/UserIdFetcherTest.kt
index e14b20e9d833216ec27f4f388ed84ff6258ea650..467e86774c8e7052ed46dc51b7b29d6c154c3818 100644
--- a/app/src/test/kotlin/at/bitfire/davdroid/util/SsoUtilsTest.kt
+++ b/app/src/test/kotlin/at/bitfire/davdroid/util/UserIdFetcherTest.kt
@@ -17,42 +17,38 @@
package at.bitfire.davdroid.util
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
import org.junit.Test
-class SsoUtilsTest {
+class UserIdFetcherTest {
@Test
- fun `test sanitizeUserId with empty input`() {
- val userId = ""
- val expected = ""
- val actual = SsoUtils.sanitizeUserId(userId, null)
- assertEquals(expected, actual)
+ fun `test fetch with input without 'users' part`() {
+ val url = "https://murena.io"
+ val actual = UserIdFetcher.fetch(url)
+ assertNull(actual)
}
@Test
- fun `test sanitizeUserId with input without url part`() {
- val userId = "username"
- val url = "https://murena.io"
- val expected = "username"
- val actual = SsoUtils.sanitizeUserId(userId, url)
- assertEquals(expected, actual)
+ fun `test fetch with input ends with 'users'`() {
+ val baseUrl = "https://murena.io/remote.php/dav/principals/users/"
+ val actual = UserIdFetcher.fetch(baseUrl)
+ assertNull(actual)
}
@Test
- fun `test sanitizeUserId with case sensitivity`() {
- val userId = "User@E.EMAIL@MURENA.io"
- val baseUrl = "https://murena.io/"
- val expected = "User@E.EMAIL"
- val actual = SsoUtils.sanitizeUserId(userId, baseUrl)
+ fun `test fetch with input with 'users' part`() {
+ val baseUrl = "https://murena.io/remote.php/dav/principals/users/user@e.email"
+ val expected = "user@e.email"
+ val actual = UserIdFetcher.fetch(baseUrl)
assertEquals(expected, actual)
}
@Test
- fun `test sanitizeUserId with leading or trailing spaces`() {
- val userId = " user@domain.com "
- val url = " http://domain.com "
- val expected = "user"
- val actual = SsoUtils.sanitizeUserId(userId, url)
+ fun `test fetch with input with 'users' part & ends with slash`() {
+ val baseUrl = "https://murena.io/remote.php/dav/principals/users/user@e.email/"
+ val expected = "user@e.email"
+ val actual = UserIdFetcher.fetch(baseUrl)
assertEquals(expected, actual)
}
}