c2c-sync/app/src/main/java/com/nextcloud/client/account/UserAccountManagerImpl.java

465 lines
16 KiB
Java

/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2023-2024 TSI-mc <surinder.kumar@t-systems.com>
* SPDX-FileCopyrightText: 2019 Chris Narkiewicz <hello@ezaquarii.com>
* SPDX-License-Identifier: AGPL-3.0-or-later OR GPL-2.0-only
*/
package com.nextcloud.client.account;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import com.nextcloud.client.onboarding.FirstRunActivity;
import com.nextcloud.common.NextcloudClient;
import com.nextcloud.utils.extensions.AccountExtensionsKt;
import com.nmc.android.ui.LauncherActivity;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.authentication.AuthenticatorActivity;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.ArbitraryDataProviderImpl;
import com.owncloud.android.datamodel.OCFile;
import com.owncloud.android.lib.common.OwnCloudAccount;
import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
import com.owncloud.android.lib.common.UserInfo;
import com.owncloud.android.lib.common.accounts.AccountUtils;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.status.OwnCloudVersion;
import com.owncloud.android.lib.resources.users.GetUserInfoRemoteOperation;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class UserAccountManagerImpl implements UserAccountManager {
private static final String TAG = UserAccountManagerImpl.class.getSimpleName();
private static final String PREF_SELECT_OC_ACCOUNT = "select_oc_account";
private Context context;
private final AccountManager accountManager;
public static UserAccountManagerImpl fromContext(Context context) {
AccountManager am = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
return new UserAccountManagerImpl(context, am);
}
@Inject
public UserAccountManagerImpl(
Context context,
AccountManager accountManager
) {
this.context = context;
this.accountManager = accountManager;
}
@Override
public void removeAllAccounts() {
for (Account account : getAccounts()) {
accountManager.removeAccount(account, null, null);
}
}
@Override
public boolean removeUser(User user) {
try {
AccountManagerFuture<Boolean> result = accountManager.removeAccount(user.toPlatformAccount(),
null,
null);
return result.getResult();
} catch (OperationCanceledException| AuthenticatorException| IOException ex) {
return false;
}
}
@Override
@NonNull
public Account[] getAccounts() {
return accountManager.getAccountsByType(getAccountType());
}
@Override
@NonNull
public List<User> getAllUsers() {
Account[] accounts = getAccounts();
List<User> users = new ArrayList<>(accounts.length);
for (Account account : accounts) {
User user = createUserFromAccount(account);
if (user != null) {
users.add(user);
}
}
return users;
}
@Override
public boolean exists(Account account) {
try {
if (account == null) {
Log_OC.d(TAG, "account is null");
return false;
}
Account[] nextcloudAccounts = getAccounts();
if (nextcloudAccounts.length == 0) {
Log_OC.d(TAG, "nextcloudAccounts are empty");
return false;
}
if (account.name.isEmpty()) {
Log_OC.d(TAG, "account name is empty");
return false;
}
int lastAtPos = account.name.lastIndexOf('@');
if (lastAtPos == -1) {
Log_OC.d(TAG, "lastAtPos cannot be found");
return false;
}
boolean isLastAtPosInBoundsForHostAndPort = lastAtPos + 1 < account.name.length();
if (!isLastAtPosInBoundsForHostAndPort) {
Log_OC.d(TAG, "lastAtPos not in bounds");
return false;
}
String hostAndPort = account.name.substring(lastAtPos + 1);
String username = account.name.substring(0, lastAtPos);
if (hostAndPort.isEmpty() || username.isEmpty()) {
Log_OC.d(TAG, "hostAndPort or username is empty");
return false;
}
String otherHostAndPort;
String otherUsername;
for (Account otherAccount : nextcloudAccounts) {
// Skip null accounts or accounts with null names
if (otherAccount == null || otherAccount.name.isEmpty()) {
continue;
}
lastAtPos = otherAccount.name.lastIndexOf('@');
// Skip invalid account names
if (lastAtPos == -1) {
continue;
}
boolean isLastAtPosInBoundsForOtherHostAndPort = lastAtPos + 1 < otherAccount.name.length();
if (!isLastAtPosInBoundsForOtherHostAndPort) {
continue;
}
otherHostAndPort = otherAccount.name.substring(lastAtPos + 1);
otherUsername = otherAccount.name.substring(0, lastAtPos);
if (otherHostAndPort.equals(hostAndPort) &&
otherUsername.equalsIgnoreCase(username)) {
return true;
}
}
return false;
} catch (Exception e) {
Log_OC.d(TAG, "Exception caught at UserAccountManagerImpl.exists(): " + e);
return false;
}
}
@Override
@NonNull
public Account getCurrentAccount() {
Account[] ocAccounts = getAccounts();
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(context);
SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(context);
String accountName = appPreferences.getString(PREF_SELECT_OC_ACCOUNT, null);
Account defaultAccount = Arrays.stream(ocAccounts)
.filter(account -> account.name.equals(accountName))
.findFirst()
.orElse(null);
// take first which is not pending for removal account as fallback
if (defaultAccount == null) {
defaultAccount = Arrays.stream(ocAccounts)
.filter(account -> !arbitraryDataProvider.getBooleanValue(account.name, PENDING_FOR_REMOVAL))
.findFirst()
.orElse(null);
}
if (defaultAccount == null) {
if (ocAccounts.length > 0) {
defaultAccount = ocAccounts[0];
} else {
defaultAccount = getAnonymousAccount();
}
}
return defaultAccount;
}
private Account getAnonymousAccount() {
return new Account("Anonymous", context.getString(R.string.anonymous_account_type));
}
/**
* Temporary solution to convert platform account to user instance.
* It takes null and returns null on error to ease error handling
* in legacy code.
*
* @param account Account instance
* @return User instance or null, if conversion failed
*/
@Nullable
private User createUserFromAccount(@NonNull Account account) {
Context safeContext = context != null ? context : MainApp.getAppContext();
if (safeContext == null) {
Log_OC.e(TAG, "Unable to obtain a valid context");
return null;
}
if (AccountExtensionsKt.isAnonymous(account, safeContext)) {
return null;
}
OwnCloudAccount ownCloudAccount;
try {
ownCloudAccount = new OwnCloudAccount(account, safeContext);
} catch (Exception ex) {
return null;
}
/*
* Server version
*/
String serverVersionStr = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_VERSION);
OwnCloudVersion serverVersion;
if (serverVersionStr != null) {
serverVersion = new OwnCloudVersion(serverVersionStr);
} else {
serverVersion = MainApp.MINIMUM_SUPPORTED_SERVER_VERSION;
}
/*
* Server address
*/
String serverAddressStr = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL);
if (serverAddressStr == null || serverAddressStr.isEmpty()) {
return AnonymousUser.fromContext(safeContext);
}
URI serverUri = URI.create(serverAddressStr); // TODO: validate
return new RegisteredUser(
account,
ownCloudAccount,
new Server(serverUri, serverVersion)
);
}
/**
* Get user. If user cannot be retrieved due to data error, anonymous user is returned instead.
*
*
* @return User instance
*/
@NonNull
@Override
public User getUser() {
Account account = getCurrentAccount();
User user = createUserFromAccount(account);
return user != null ? user : AnonymousUser.fromContext(context);
}
@Override
@NonNull
public Optional<User> getUser(CharSequence accountName) {
Account account = getAccountByName(accountName.toString());
User user = createUserFromAccount(account);
return Optional.ofNullable(user);
}
@Override
public User getAnonymousUser() {
return AnonymousUser.fromContext(context);
}
@Override
@Nullable
public OwnCloudAccount getCurrentOwnCloudAccount() {
try {
Account currentPlatformAccount = getCurrentAccount();
return new OwnCloudAccount(currentPlatformAccount, context);
} catch (AccountUtils.AccountNotFoundException | IllegalArgumentException ex) {
return null;
}
}
@Override
@NonNull
public Account getAccountByName(String name) {
for (Account account : getAccounts()) {
if (account.name.equals(name)) {
return account;
}
}
return getAnonymousAccount();
}
@Override
public boolean setCurrentOwnCloudAccount(String accountName) {
boolean result = false;
if (accountName != null) {
for (final Account account : getAccounts()) {
if (accountName.equals(account.name)) {
SharedPreferences.Editor appPrefs = PreferenceManager.getDefaultSharedPreferences(context).edit();
appPrefs.putString(PREF_SELECT_OC_ACCOUNT, accountName);
appPrefs.apply();
result = true;
break;
}
}
}
return result;
}
@Override
public boolean setCurrentOwnCloudAccount(int hashCode) {
boolean result = false;
if (hashCode != 0) {
for (final User user : getAllUsers()) {
if (hashCode == user.hashCode()) {
SharedPreferences.Editor appPrefs = PreferenceManager.getDefaultSharedPreferences(context).edit();
appPrefs.putString(PREF_SELECT_OC_ACCOUNT, user.getAccountName());
appPrefs.apply();
result = true;
break;
}
}
}
return result;
}
@Deprecated
@Override
@NonNull
public OwnCloudVersion getServerVersion(Account account) {
OwnCloudVersion serverVersion = MainApp.MINIMUM_SUPPORTED_SERVER_VERSION;
if (account != null) {
AccountManager accountMgr = AccountManager.get(MainApp.getAppContext());
String serverVersionStr = accountMgr.getUserData(account, com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_OC_VERSION);
if (serverVersionStr != null) {
serverVersion = new OwnCloudVersion(serverVersionStr);
}
}
return serverVersion;
}
@Override
public void resetOwnCloudAccount() {
SharedPreferences.Editor appPrefs = PreferenceManager.getDefaultSharedPreferences(context).edit();
appPrefs.putString(PREF_SELECT_OC_ACCOUNT, null);
appPrefs.apply();
}
@Override
public boolean accountOwnsFile(OCFile file, Account account) {
final String ownerId = file.getOwnerId();
return TextUtils.isEmpty(ownerId) || account.name.split("@")[0].equalsIgnoreCase(ownerId);
}
@Override
public boolean userOwnsFile(OCFile file, User user) {
return accountOwnsFile(file, user.toPlatformAccount());
}
public boolean migrateUserId() {
Account[] ocAccounts = accountManager.getAccountsByType(MainApp.getAccountType(context));
String userId;
String displayName;
GetUserInfoRemoteOperation remoteUserNameOperation = new GetUserInfoRemoteOperation();
int failed = 0;
for (Account account : ocAccounts) {
String storedUserId = accountManager.getUserData(account, com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID);
if (!TextUtils.isEmpty(storedUserId)) {
continue;
}
// add userId
try {
OwnCloudAccount ocAccount = new OwnCloudAccount(account, context);
NextcloudClient nextcloudClient = OwnCloudClientManagerFactory
.getDefaultSingleton()
.getNextcloudClientFor(ocAccount, context);
RemoteOperationResult<UserInfo> result = remoteUserNameOperation.execute(nextcloudClient);
if (result.isSuccess()) {
UserInfo userInfo = result.getResultData();
userId = userInfo.getId();
displayName = userInfo.getDisplayName();
} else {
// skip account, try it next time
Log_OC.e(TAG, "Error while getting username for account: " + account.name);
failed++;
continue;
}
} catch (Exception e) {
Log_OC.e(TAG, "Error while getting username: " + e.getMessage());
failed++;
continue;
}
accountManager.setUserData(account,
com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_DISPLAY_NAME,
displayName);
accountManager.setUserData(account,
com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID,
userId);
}
return failed == 0;
}
private String getAccountType() {
return context.getString(R.string.account_type);
}
@Override
public void startAccountCreation(final Activity activity) {
// skipping AuthenticatorActivity redirection when user is on Launcher or FirstRun Activity
if (activity instanceof LauncherActivity || activity instanceof FirstRunActivity) return;
Intent intent = new Intent(context, AuthenticatorActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}