/* * ownCloud Android client application * * Copyright (C) 2012 Bartek Przybylski * Copyright (C) 2016 ownCloud Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * as published by the Free Software Foundation. * * 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.authentication; import android.accounts.Account; import android.accounts.AccountManager; import android.content.Context; import android.content.SharedPreferences; import android.net.Uri; import android.preference.PreferenceManager; import android.text.TextUtils; import com.owncloud.android.MainApp; import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; import com.owncloud.android.lib.common.UserInfo; import com.owncloud.android.lib.common.accounts.AccountUtils.Constants; 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.GetRemoteUserInfoOperation; import com.owncloud.android.ui.activity.ManageAccountsActivity; import androidx.annotation.NonNull; import androidx.annotation.Nullable; /** * Helper class for dealing with accounts. */ public final class AccountUtils { private static final String TAG = AccountUtils.class.getSimpleName(); private static final String PREF_SELECT_OC_ACCOUNT = "select_oc_account"; public static final int ACCOUNT_VERSION = 1; public static final int ACCOUNT_VERSION_WITH_PROPER_ID = 2; public static final String ACCOUNT_USES_STANDARD_PASSWORD = "ACCOUNT_USES_STANDARD_PASSWORD"; private AccountUtils() { // Required empty constructor } /** * Can be used to get the currently selected ownCloud {@link Account} in the * application preferences. * * @param context The current application {@link Context} * @return The ownCloud {@link Account} currently saved in preferences, or the first * {@link Account} available, if valid (still registered in the system as ownCloud * account). If none is available and valid, returns null. */ public static @Nullable Account getCurrentOwnCloudAccount(Context context) { Account[] ocAccounts = getAccounts(context); Account defaultAccount = null; ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver()); SharedPreferences appPreferences = PreferenceManager.getDefaultSharedPreferences(context); String accountName = appPreferences.getString(PREF_SELECT_OC_ACCOUNT, null); // account validation: the saved account MUST be in the list of ownCloud Accounts known by the AccountManager if (accountName != null) { for (Account account : ocAccounts) { if (account.name.equals(accountName)) { defaultAccount = account; break; } } } if (defaultAccount == null && ocAccounts.length > 0) { // take first which is not pending for removal account as fallback for (Account account: ocAccounts) { boolean pendingForRemoval = arbitraryDataProvider.getBooleanValue(account, ManageAccountsActivity.PENDING_FOR_REMOVAL); if (!pendingForRemoval) { defaultAccount = account; break; } } } return defaultAccount; } public static Account[] getAccounts(Context context) { AccountManager accountManager = AccountManager.get(context); return accountManager.getAccountsByType(MainApp.getAccountType(context)); } public static boolean exists(Account account, Context context) { Account[] ocAccounts = getAccounts(context); if (account != null && account.name != null) { int lastAtPos = account.name.lastIndexOf('@'); String hostAndPort = account.name.substring(lastAtPos + 1); String username = account.name.substring(0, lastAtPos); String otherHostAndPort; String otherUsername; for (Account otherAccount : ocAccounts) { lastAtPos = otherAccount.name.lastIndexOf('@'); otherHostAndPort = otherAccount.name.substring(lastAtPos + 1); otherUsername = otherAccount.name.substring(0, lastAtPos); if (otherHostAndPort.equals(hostAndPort) && otherUsername.equalsIgnoreCase(username)) { return true; } } } return false; } /** * returns the user's name based on the account name. * * @param accountName the account name * @return the user's name */ public static String getAccountUsername(String accountName) { if (accountName != null) { return accountName.substring(0, accountName.lastIndexOf('@')); } else { return null; } } /** * Returns owncloud account identified by accountName or null if it does not exist. * @param context the context * @param accountName name of account to be returned * @return owncloud account named accountName */ public static Account getOwnCloudAccountByName(Context context, String accountName) { Account[] ocAccounts = AccountManager.get(context).getAccountsByType(MainApp.getAccountType(context)); for (Account account : ocAccounts) { if(account.name.equals(accountName)) { return account; } } return null; } public static boolean setCurrentOwnCloudAccount(final Context context, String accountName) { boolean result = false; if (accountName != null) { for (final Account account : getAccounts(context)) { 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; } public static void resetOwnCloudAccount(Context context) { SharedPreferences.Editor appPrefs = PreferenceManager.getDefaultSharedPreferences(context).edit(); appPrefs.putString(PREF_SELECT_OC_ACCOUNT, null); appPrefs.apply(); } /** * Access the version of the OC server corresponding to an account SAVED IN THE ACCOUNTMANAGER * * @param account ownCloud account * @return Version of the OC server corresponding to account, according to the data saved * in the system AccountManager */ public static @NonNull 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, Constants.KEY_OC_VERSION); if (serverVersionStr != null) { serverVersion = new OwnCloudVersion(serverVersionStr); } } return serverVersion; } public static boolean hasSearchSupport(Account account) { return getServerVersion(account).isSearchSupported(); } /** * Update the accounts in AccountManager to meet the current version of accounts expected by the app, if needed. *

* Introduced to handle a change in the structure of stored account names needed to allow different OC servers in * the same domain, but not in the same path. * * @param context Used to access the AccountManager. */ public static void updateAccountVersion(Context context) { Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(context); AccountManager accountMgr = AccountManager.get(context); if (currentAccount != null) { String currentAccountVersion = accountMgr.getUserData(currentAccount, Constants.KEY_OC_ACCOUNT_VERSION); if (!String.valueOf(ACCOUNT_VERSION_WITH_PROPER_ID).equalsIgnoreCase(currentAccountVersion)) { Log_OC.i(TAG, "Upgrading accounts to account version #" + ACCOUNT_VERSION_WITH_PROPER_ID); Account[] ocAccounts = accountMgr.getAccountsByType(MainApp.getAccountType(context)); String serverUrl; String username; String displayName; String newAccountName; Account newAccount; GetRemoteUserInfoOperation remoteUserNameOperation = new GetRemoteUserInfoOperation(); for (Account account : ocAccounts) { // build new account name serverUrl = accountMgr.getUserData(account, Constants.KEY_OC_BASE_URL); // update user name try { OwnCloudAccount ocAccount = new OwnCloudAccount(account, context); OwnCloudClient client = OwnCloudClientManagerFactory.getDefaultSingleton() .getClientFor(ocAccount, context); RemoteOperationResult result = remoteUserNameOperation.execute(client); if (result.isSuccess()) { UserInfo userInfo = (UserInfo) result.getData().get(0); username = userInfo.id; displayName = userInfo.displayName; } else { // skip account, try it next time Log_OC.e(TAG, "Error while getting username for account: " + account.name); continue; } } catch (Exception e) { Log_OC.e(TAG, "Error while getting username: " + e.getMessage()); continue; } newAccountName = com.owncloud.android.lib.common.accounts.AccountUtils. buildAccountName(Uri.parse(serverUrl), username); // migrate to a new account, if needed if (!newAccountName.equals(account.name)) { newAccount = migrateAccount(context, currentAccount, accountMgr, serverUrl, newAccountName, account); } else { // servers which base URL is in the root of their domain need no change Log_OC.d(TAG, account.name + " needs no upgrade "); newAccount = account; } accountMgr.setUserData(newAccount, Constants.KEY_DISPLAY_NAME, displayName); accountMgr.setUserData(newAccount, Constants.KEY_USER_ID, username); // at least, upgrade account version Log_OC.d(TAG, "Setting version " + ACCOUNT_VERSION_WITH_PROPER_ID + " to " + newAccountName); accountMgr.setUserData(newAccount, Constants.KEY_OC_ACCOUNT_VERSION, Integer.toString(ACCOUNT_VERSION_WITH_PROPER_ID)); } } } } @NonNull private static Account migrateAccount(Context context, Account currentAccount, AccountManager accountMgr, String serverUrl, String newAccountName, Account account) { Log_OC.d(TAG, "Upgrading " + account.name + " to " + newAccountName); // create the new account Account newAccount = new Account(newAccountName, MainApp.getAccountType(context)); String password = accountMgr.getPassword(account); accountMgr.addAccountExplicitly(newAccount, (password != null) ? password : "", null); // copy base URL accountMgr.setUserData(newAccount, Constants.KEY_OC_BASE_URL, serverUrl); // copy server version accountMgr.setUserData( newAccount, Constants.KEY_OC_VERSION, accountMgr.getUserData(account, Constants.KEY_OC_VERSION) ); // copy cookies accountMgr.setUserData( newAccount, Constants.KEY_COOKIES, accountMgr.getUserData(account, Constants.KEY_COOKIES) ); // copy type of authentication final String isSamlStr = accountMgr.getUserData(account, Constants.KEY_SUPPORTS_SAML_WEB_SSO); if (Boolean.parseBoolean(isSamlStr)) { accountMgr.setUserData(newAccount, Constants.KEY_SUPPORTS_SAML_WEB_SSO, "TRUE"); } final String isOauthStr = accountMgr.getUserData(account, Constants.KEY_SUPPORTS_OAUTH2); if (Boolean.parseBoolean(isOauthStr)) { accountMgr.setUserData(newAccount, Constants.KEY_SUPPORTS_OAUTH2, "TRUE"); } /* TODO - study if it's possible to run this method in a background thread to copy the authToken if (isOAuth || isSaml) { accountMgr.setAuthToken(newAccount, mAuthTokenType, mAuthToken); } */ // don't forget the account saved in preferences as the current one if (currentAccount.name.equals(account.name)) { AccountUtils.setCurrentOwnCloudAccount(context, newAccountName); } // remove the old account accountMgr.removeAccount(account, null, null); // will assume it succeeds, not a big deal otherwise return newAccount; } /** * Checks if an account owns the file (file's ownerId is the same as account name) * @param file File to check * @param account account to compare * @return false if ownerId is not set or owner is a different account */ public static boolean accountOwnsFile(OCFile file, Account account) { return !TextUtils.isEmpty(file.getOwnerId()) && account.name.split("@")[0].equals(file.getOwnerId()); } }