Remote wipe

Signed-off-by: tobiasKaminsky <tobias@kaminsky.me>
This commit is contained in:
tobiasKaminsky 2019-04-04 14:38:13 +01:00
parent c09b116fc3
commit 0b26aa7755
No known key found for this signature in database
GPG key ID: 0E00D4D47D0C5AF7
8 changed files with 219 additions and 105 deletions

View file

@ -179,4 +179,4 @@
</arrangement>
</codeStyleSettings>
</code_scheme>
</component>
</component>

View file

@ -2278,15 +2278,15 @@ public class FileDataStorageManager {
}
public void deleteAllFiles() {
String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?";
String[] whereArgs = new String[]{account.name};
String where = ProviderTableMeta.FILE_ACCOUNT_OWNER + "= ? AND " +
ProviderTableMeta.FILE_PATH + "= ?";
String[] whereArgs = new String[]{account.name, OCFile.ROOT_PATH};
if (getContentResolver() != null) {
getContentResolver().delete(ProviderTableMeta.CONTENT_URI_FILE, where, whereArgs);
getContentResolver().delete(ProviderTableMeta.CONTENT_URI_DIR, where, whereArgs);
} else {
try {
getContentProviderClient().delete(ProviderTableMeta.CONTENT_URI_FILE, where, whereArgs);
getContentProviderClient().delete(ProviderTableMeta.CONTENT_URI_DIR, where, whereArgs);
} catch (RemoteException e) {
Log_OC.e(TAG, "Exception in deleteAllFiles for account " + account.name + ": " + e.getMessage(), e);
}

View file

@ -28,26 +28,38 @@ import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.content.ContentResolver;
import android.content.Context;
import android.text.TextUtils;
import com.evernote.android.job.Job;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.google.gson.Gson;
import com.nextcloud.client.account.UserAccountManager;
import com.nextcloud.client.preferences.AppPreferencesImpl;
import com.owncloud.android.MainApp;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.FileDataStorageManager;
import com.owncloud.android.datamodel.FilesystemDataProvider;
import com.owncloud.android.datamodel.PushConfigurationState;
import com.owncloud.android.datamodel.SyncedFolder;
import com.owncloud.android.datamodel.SyncedFolderProvider;
import com.owncloud.android.datamodel.UploadsStorageManager;
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.utils.Log_OC;
import com.owncloud.android.lib.resources.users.RemoteWipeSuccessRemoteOperation;
import com.owncloud.android.ui.activity.ContactsPreferenceActivity;
import com.owncloud.android.ui.events.AccountRemovedEvent;
import com.owncloud.android.utils.EncryptionUtils;
import com.owncloud.android.utils.FileStorageUtils;
import com.owncloud.android.utils.FilesSyncHelper;
import com.owncloud.android.utils.PushUtils;
import org.greenrobot.eventbus.EventBus;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
@ -65,28 +77,48 @@ import static com.owncloud.android.ui.activity.ManageAccountsActivity.PENDING_FO
public class AccountRemovalJob extends Job implements AccountManagerCallback<Boolean> {
public static final String TAG = "AccountRemovalJob";
public static final String ACCOUNT = "account";
public static final String REMOTE_WIPE = "remote_wipe";
private UploadsStorageManager uploadsStorageManager;
private UserAccountManager accountManager;
private UserAccountManager userAccountManager;
public AccountRemovalJob(UploadsStorageManager uploadStorageManager, UserAccountManager accountManager) {
this.uploadsStorageManager = uploadStorageManager;
this.accountManager = accountManager;
this.userAccountManager = accountManager;
}
@NonNull
@Override
protected Result onRunJob(Params params) {
protected Result onRunJob(@NotNull Params params) {
Context context = MainApp.getAppContext();
PersistableBundleCompat bundle = params.getExtras();
Account account = accountManager.getAccountByName(bundle.getString(ACCOUNT, ""));
AccountManager am = (AccountManager) context.getSystemService(ACCOUNT_SERVICE);
Account account = userAccountManager.getAccountByName(bundle.getString(ACCOUNT, ""));
AccountManager accountManager = (AccountManager) context.getSystemService(ACCOUNT_SERVICE);
boolean remoteWipe = bundle.getBoolean(REMOTE_WIPE, false);
if (account != null && am != null) {
if (account != null && accountManager != null) {
// disable contact backup job
ContactsPreferenceActivity.cancelContactBackupJobForAccount(context, account);
am.removeAccount(account, this, null);
OwnCloudClient client = null;
try {
OwnCloudAccount ocAccount = new OwnCloudAccount(account, MainApp.getAppContext());
client = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount,
MainApp.getAppContext());
} catch (Exception e) {
Log_OC.e(this, "Could not create client", e);
}
try {
AccountManagerFuture<Boolean> accountRemoval = accountManager.removeAccount(account, this, null);
boolean removal = accountRemoval.getResult();
if (!removal) {
Log_OC.e(this, "Account removal of " + account.name + " failed!");
}
} catch (Exception e) {
Log_OC.e(this, "Account removal of " + account.name + " failed!", e);
}
FileDataStorageManager storageManager = new FileDataStorageManager(account, context.getContentResolver());
@ -99,8 +131,34 @@ public class AccountRemovalJob extends Job implements AccountManagerCallback<Boo
// delete all database entries
storageManager.deleteAllFiles();
// remove contact backup job
ContactsPreferenceActivity.cancelContactBackupJobForAccount(context, account);
ContentResolver contentResolver = context.getContentResolver();
// disable daily backup
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(contentResolver);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
"false");
String arbitraryDataPushString;
if (!TextUtils.isEmpty(arbitraryDataPushString = arbitraryDataProvider.getValue(
account, PushUtils.KEY_PUSH)) &&
!TextUtils.isEmpty(context.getResources().getString(R.string.push_server_url))) {
Gson gson = new Gson();
PushConfigurationState pushArbitraryData = gson.fromJson(arbitraryDataPushString,
PushConfigurationState.class);
pushArbitraryData.setShouldBeDeleted(true);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PushUtils.KEY_PUSH,
gson.toJson(pushArbitraryData));
PushUtils.pushRegistrationToServer(userAccountManager, pushArbitraryData.getPushToken());
}
// remove pending account removal
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(context.getContentResolver());
arbitraryDataProvider.deleteKeyForAccount(account.name, PENDING_FOR_REMOVAL);
// remove synced folders set for account
@ -113,7 +171,7 @@ public class AccountRemovalJob extends Job implements AccountManagerCallback<Boo
for (SyncedFolder syncedFolder : syncedFolders) {
if (syncedFolder.getAccount().equals(account.name)) {
arbitraryDataProvider.deleteKeyForAccount(FilesSyncHelper.GLOBAL,
FilesSyncHelper.SYNCEDFOLDERINITIATED + syncedFolder.getId());
FilesSyncHelper.SYNCEDFOLDERINITIATED + syncedFolder.getId());
syncedFolderIds.add(syncedFolder.getId());
}
}
@ -132,6 +190,11 @@ public class AccountRemovalJob extends Job implements AccountManagerCallback<Boo
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.PRIVATE_KEY);
arbitraryDataProvider.deleteKeyForAccount(account.name, EncryptionUtils.PUBLIC_KEY);
if (remoteWipe && client != null) {
String authToken = client.getCredentials().getAuthToken();
new RemoteWipeSuccessRemoteOperation(authToken).execute(client);
}
return Result.SUCCESS;
} else {
return Result.FAILURE;

View file

@ -186,9 +186,11 @@ public class FileContentProvider extends ContentProvider {
children.close();
}
count += db.delete(ProviderTableMeta.FILE_TABLE_NAME,
ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1)
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ")" : ""), whereArgs);
if (uri.getPathSegments().size() > 1) {
count += db.delete(ProviderTableMeta.FILE_TABLE_NAME,
ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1)
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ")" : ""), whereArgs);
}
return count;
}
@ -203,9 +205,13 @@ public class FileContentProvider extends ContentProvider {
}
Log_OC.d(TAG, "Removing FILE " + remoteId);
count = db.delete(ProviderTableMeta.FILE_TABLE_NAME,
ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1)
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ")" : ""), whereArgs);
if (remoteId == null) {
return 0;
} else {
count = db.delete(ProviderTableMeta.FILE_TABLE_NAME,
ProviderTableMeta._ID + "=" + uri.getPathSegments().get(1)
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ")" : ""), whereArgs);
}
} catch (Exception e) {
Log_OC.d(TAG, "DB-Error removing file!", e);
} finally {
@ -476,7 +482,9 @@ public class FileContentProvider extends ContentProvider {
case ROOT_DIRECTORY:
break;
case DIRECTORY:
sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "=" + uri.getPathSegments().get(1));
if (uri.getPathSegments().size() > 1) {
sqlQuery.appendWhere(ProviderTableMeta.FILE_PARENT + "=" + uri.getPathSegments().get(1));
}
break;
case SINGLE_FILE:
if (uri.getPathSegments().size() > SINGLE_PATH_SEGMENT) {

View file

@ -463,7 +463,7 @@ public abstract class DrawerActivity extends ToolbarActivity
case R.id.nav_logout:
mCheckedMenuItem = -1;
menuItem.setChecked(false);
UserInfoActivity.openAccountRemovalConfirmationDialog(getAccount(), getSupportFragmentManager(), true);
UserInfoActivity.openAccountRemovalConfirmationDialog(getAccount(), getSupportFragmentManager());
break;
case R.id.nav_shared:
handleSearchEvents(new SearchEvent("",
@ -1430,6 +1430,8 @@ public abstract class DrawerActivity extends ToolbarActivity
@Subscribe(threadMode = ThreadMode.MAIN)
public void onAccountRemovedEvent(AccountRemovedEvent event) {
updateAccountList();
restart();
}
/**

View file

@ -69,6 +69,7 @@ import com.owncloud.android.operations.UpdateSharePermissionsOperation;
import com.owncloud.android.operations.UpdateShareViaLinkOperation;
import com.owncloud.android.services.OperationsService;
import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
import com.owncloud.android.ui.asynctasks.CheckRemoteWipeTask;
import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask;
import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
import com.owncloud.android.ui.dialog.LoadingDialog;
@ -81,6 +82,8 @@ import com.owncloud.android.utils.ErrorMessageAdapter;
import com.owncloud.android.utils.FilesSyncHelper;
import com.owncloud.android.utils.ThemeUtils;
import java.lang.ref.WeakReference;
import javax.inject.Inject;
import androidx.fragment.app.DialogFragment;
@ -394,43 +397,48 @@ public abstract class FileActivity extends DrawerActivity
* be used.
*/
protected void requestCredentialsUpdate(Context context, Account account) {
if (account == null) {
account = getAccount();
}
boolean remoteWipeSupported = accountManager.getServerVersion(account).isRemoteWipeSupported();
if (remoteWipeSupported) {
new CheckRemoteWipeTask(account, new WeakReference<>(this)).execute();
} else {
performCredentialsUpdate(account, context);
}
}
public void performCredentialsUpdate(Account account, Context context) {
try {
/// step 1 - invalidate credentials of current account
if (account == null) {
account = getAccount();
}
OwnCloudClient client;
OwnCloudAccount ocAccount = new OwnCloudAccount(account, context);
client = OwnCloudClientManagerFactory.getDefaultSingleton().removeClientFor(ocAccount);
OwnCloudClient client = OwnCloudClientManagerFactory.getDefaultSingleton().removeClientFor(ocAccount);
if (client != null) {
OwnCloudCredentials cred = client.getCredentials();
if (cred != null) {
AccountManager am = AccountManager.get(context);
if (cred.authTokenExpires()) {
am.invalidateAuthToken(
account.type,
cred.getAuthToken()
);
OwnCloudCredentials credentials = client.getCredentials();
if (credentials != null) {
AccountManager accountManager = AccountManager.get(context);
if (credentials.authTokenExpires()) {
accountManager.invalidateAuthToken(account.type, credentials.getAuthToken());
} else {
am.clearPassword(account);
accountManager.clearPassword(account);
}
}
}
/// step 2 - request credentials to user
Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
Intent updateAccountCredentials = new Intent(context, AuthenticatorActivity.class);
updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, account);
updateAccountCredentials.putExtra(
AuthenticatorActivity.EXTRA_ACTION,
AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN);
AuthenticatorActivity.EXTRA_ACTION,
AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN);
updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
startActivityForResult(updateAccountCredentials, REQUEST_CODE__UPDATE_CREDENTIALS);
} catch (com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException e) {
DisplayUtils.showSnackMessage(this, R.string.auth_account_does_not_exist);
}
}
/**

View file

@ -26,11 +26,8 @@
package com.owncloud.android.ui.activity;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ContentResolver;
import android.content.Intent;
import android.graphics.PorterDuff;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@ -52,12 +49,12 @@ import android.widget.TextView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
import com.google.gson.Gson;
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.nextcloud.client.di.Injectable;
import com.nextcloud.client.preferences.AppPreferences;
import com.owncloud.android.R;
import com.owncloud.android.datamodel.ArbitraryDataProvider;
import com.owncloud.android.datamodel.PushConfigurationState;
import com.owncloud.android.jobs.AccountRemovalJob;
import com.owncloud.android.lib.common.UserInfo;
import com.owncloud.android.lib.common.operations.RemoteOperation;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
@ -68,7 +65,6 @@ import com.owncloud.android.utils.DisplayUtils;
import com.owncloud.android.utils.PushUtils;
import com.owncloud.android.utils.ThemeUtils;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.parceler.Parcels;
@ -100,9 +96,6 @@ public class UserInfoActivity extends FileActivity implements Injectable {
private static final String TAG = UserInfoActivity.class.getSimpleName();
private static final String KEY_USER_DATA = "USER_DATA";
private static final String KEY_DIRECT_REMOVE = "DIRECT_REMOVE";
private static final int KEY_DELETE_CODE = 101;
@BindView(R.id.empty_list_view) protected LinearLayout emptyContentContainer;
@BindView(R.id.empty_list_view_text) protected TextView emptyContentMessage;
@ -179,7 +172,7 @@ public class UserInfoActivity extends FileActivity implements Injectable {
onBackPressed();
break;
case R.id.delete_account:
openAccountRemovalConfirmationDialog(account, getSupportFragmentManager(), false);
openAccountRemovalConfirmationDialog(account, getSupportFragmentManager());
break;
default:
retval = super.onOptionsItemSelected(item);
@ -309,10 +302,9 @@ public class UserInfoActivity extends FileActivity implements Injectable {
}
}
public static void openAccountRemovalConfirmationDialog(Account account, FragmentManager fragmentManager,
boolean removeDirectly) {
public static void openAccountRemovalConfirmationDialog(Account account, FragmentManager fragmentManager) {
UserInfoActivity.AccountRemovalConfirmationDialog dialog =
UserInfoActivity.AccountRemovalConfirmationDialog.newInstance(account, removeDirectly);
UserInfoActivity.AccountRemovalConfirmationDialog.newInstance(account);
dialog.show(fragmentManager, "dialog");
}
@ -320,11 +312,9 @@ public class UserInfoActivity extends FileActivity implements Injectable {
private Account account;
public static UserInfoActivity.AccountRemovalConfirmationDialog newInstance(Account account,
boolean removeDirectly) {
public static UserInfoActivity.AccountRemovalConfirmationDialog newInstance(Account account) {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_ACCOUNT, account);
bundle.putBoolean(KEY_DIRECT_REMOVE, removeDirectly);
UserInfoActivity.AccountRemovalConfirmationDialog dialog = new
UserInfoActivity.AccountRemovalConfirmationDialog();
@ -354,59 +344,22 @@ public class UserInfoActivity extends FileActivity implements Injectable {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final boolean removeDirectly = getArguments().getBoolean(KEY_DIRECT_REMOVE);
return new AlertDialog.Builder(getActivity(), R.style.Theme_ownCloud_Dialog)
.setTitle(R.string.delete_account)
.setMessage(getResources().getString(R.string.delete_account_warning, account.name))
.setIcon(R.drawable.ic_warning)
.setPositiveButton(R.string.common_ok,
(dialogInterface, i) -> {
// remove contact backup job
ContactsPreferenceActivity.cancelContactBackupJobForAccount(getActivity(), account);
ContentResolver contentResolver = getActivity().getContentResolver();
// disable daily backup
ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(
contentResolver);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name,
ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP,
"false");
String arbitraryDataPushString;
if (!TextUtils.isEmpty(arbitraryDataPushString = arbitraryDataProvider.getValue(
account, PushUtils.KEY_PUSH)) &&
!TextUtils.isEmpty(getResources().getString(R.string.push_server_url))) {
Gson gson = new Gson();
PushConfigurationState pushArbitraryData = gson.fromJson(arbitraryDataPushString,
PushConfigurationState.class);
pushArbitraryData.setShouldBeDeleted(true);
arbitraryDataProvider.storeOrUpdateKeyValue(account.name, PushUtils.KEY_PUSH,
gson.toJson(pushArbitraryData));
EventBus.getDefault().post(new TokenPushEvent());
}
if (getActivity() != null && !removeDirectly) {
Bundle bundle = new Bundle();
bundle.putParcelable(KEY_ACCOUNT, Parcels.wrap(account));
Intent intent = new Intent();
intent.putExtras(bundle);
getActivity().setResult(KEY_DELETE_CODE, intent);
getActivity().finish();
} else {
AccountManager am = (AccountManager) getActivity()
.getSystemService(ACCOUNT_SERVICE);
am.removeAccount(account, null, null);
Intent start = new Intent(getActivity(), FileDisplayActivity.class);
start.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(start);
}
// schedule job
PersistableBundleCompat bundle = new PersistableBundleCompat();
bundle.putString(AccountRemovalJob.ACCOUNT, account.name);
new JobRequest.Builder(AccountRemovalJob.TAG)
.startNow()
.setExtras(bundle)
.setUpdateCurrent(false)
.build()
.schedule();
})
.setNegativeButton(R.string.common_cancel, null)
.create();

View file

@ -0,0 +1,80 @@
/*
*
* * Nextcloud Android client application
* *
* * @author Tobias Kaminsky
* * Copyright (C) 2019 Tobias Kaminsky
* * Copyright (C) 2019 Nextcloud GmbH
* *
* * This program is free software: you can redistribute it and/or modify
* * it under the terms of the GNU Affero 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 Affero General Public License for more details.
* *
* * You should have received a copy of the GNU Affero General Public License
* * along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
package com.owncloud.android.ui.asynctasks;
import android.accounts.Account;
import android.os.AsyncTask;
import com.evernote.android.job.JobRequest;
import com.evernote.android.job.util.support.PersistableBundleCompat;
import com.owncloud.android.jobs.AccountRemovalJob;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.users.CheckRemoteWipeRemoteOperation;
import com.owncloud.android.ui.activity.FileActivity;
import java.lang.ref.WeakReference;
public class CheckRemoteWipeTask extends AsyncTask<Void, Void, Boolean> {
private Account account;
private WeakReference<FileActivity> fileActivityWeakReference;
public CheckRemoteWipeTask(Account account, WeakReference<FileActivity> fileActivityWeakReference) {
this.account = account;
this.fileActivityWeakReference = fileActivityWeakReference;
}
@Override
protected Boolean doInBackground(Void... voids) {
FileActivity fileActivity = fileActivityWeakReference.get();
if (fileActivity == null) {
Log_OC.e(this, "Check for remote wipe: no context available");
return false;
}
RemoteOperationResult checkWipeResult = new CheckRemoteWipeRemoteOperation().execute(account, fileActivity);
if (checkWipeResult.isSuccess()) {
// schedule job
PersistableBundleCompat bundle = new PersistableBundleCompat();
bundle.putString(AccountRemovalJob.ACCOUNT, account.name);
bundle.putBoolean(AccountRemovalJob.REMOTE_WIPE, true);
new JobRequest.Builder(AccountRemovalJob.TAG)
.startNow()
.setExtras(bundle)
.setUpdateCurrent(false)
.build()
.schedule();
} else {
Log_OC.e(this, "Check for remote wipe not needed -> update credentials");
fileActivity.performCredentialsUpdate(account, fileActivity);
}
return true;
}
}