mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-21 20:45:51 +03:00
Once client gets 401/403 from the server, check if remote wipe was requested.
- When the the users logs because of 401 or 403 errors, it checks if the server requested the remote wipe. If yes, locally deletes account and folders connected to the account and notify the server. If no, proceeds to ask the user to login again. - The app password is restored in the keychain. - WIP: The change also includes a test class for RemoteWipe. Signed-off-by: Camila San <hello@camila.codes>
This commit is contained in:
parent
08c7be5350
commit
19491ff85f
21 changed files with 623 additions and 58 deletions
|
@ -104,6 +104,7 @@ set(client_SRCS
|
|||
guiutility.cpp
|
||||
elidedlabel.cpp
|
||||
iconjob.cpp
|
||||
remotewipe.cpp
|
||||
creds/credentialsfactory.cpp
|
||||
creds/httpcredentialsgui.cpp
|
||||
creds/oauth.cpp
|
||||
|
|
|
@ -368,6 +368,7 @@ void AccountManager::shutdown()
|
|||
_accounts.clear();
|
||||
foreach (const auto &acc, accountsCopy) {
|
||||
emit accountRemoved(acc.data());
|
||||
emit removeAccountFolders(acc.data());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,6 @@ public:
|
|||
*/
|
||||
void deleteAccount(AccountState *account);
|
||||
|
||||
|
||||
/**
|
||||
* Creates an account and sets up some basic handlers.
|
||||
* Does *not* add the account to the account manager just yet.
|
||||
|
@ -104,6 +103,7 @@ public slots:
|
|||
Q_SIGNALS:
|
||||
void accountAdded(AccountState *account);
|
||||
void accountRemoved(AccountState *account);
|
||||
void removeAccountFolders(AccountState *account);
|
||||
|
||||
private:
|
||||
AccountManager() {}
|
||||
|
|
|
@ -147,6 +147,8 @@ AccountSettings::AccountSettings(AccountState *accountState, QWidget *parent)
|
|||
createAccountToolbox();
|
||||
connect(AccountManager::instance(), &AccountManager::accountAdded,
|
||||
this, &AccountSettings::slotAccountAdded);
|
||||
connect(this, &AccountSettings::removeAccountFolders,
|
||||
AccountManager::instance(), &AccountManager::removeAccountFolders);
|
||||
connect(ui->_folderList, &QWidget::customContextMenuRequested,
|
||||
this, &AccountSettings::slotCustomContextMenuRequested);
|
||||
connect(ui->_folderList, &QAbstractItemView::clicked,
|
||||
|
@ -334,9 +336,9 @@ void AccountSettings::slotEncryptionFlagError(const QByteArray& fileId, int http
|
|||
|
||||
void AccountSettings::slotLockForEncryptionSuccess(const QByteArray& fileId, const QByteArray &token)
|
||||
{
|
||||
accountsState()->account()->e2e()->setTokenForFolder(fileId, token);
|
||||
accountsState()->account()->e2e()->setTokenForFolder(fileId, token);
|
||||
|
||||
FolderMetadata emptyMetadata(accountsState()->account());
|
||||
FolderMetadata emptyMetadata(accountsState()->account());
|
||||
auto encryptedMetadata = emptyMetadata.encryptedMetadata();
|
||||
if (encryptedMetadata.isEmpty()) {
|
||||
//TODO: Mark the folder as unencrypted as the metadata generation failed.
|
||||
|
@ -348,36 +350,36 @@ void AccountSettings::slotLockForEncryptionSuccess(const QByteArray& fileId, con
|
|||
return;
|
||||
}
|
||||
auto storeMetadataJob = new StoreMetaDataApiJob(accountsState()->account(), fileId, emptyMetadata.encryptedMetadata());
|
||||
connect(storeMetadataJob, &StoreMetaDataApiJob::success,
|
||||
this, &AccountSettings::slotUploadMetadataSuccess);
|
||||
connect(storeMetadataJob, &StoreMetaDataApiJob::error,
|
||||
this, &AccountSettings::slotUpdateMetadataError);
|
||||
connect(storeMetadataJob, &StoreMetaDataApiJob::success,
|
||||
this, &AccountSettings::slotUploadMetadataSuccess);
|
||||
connect(storeMetadataJob, &StoreMetaDataApiJob::error,
|
||||
this, &AccountSettings::slotUpdateMetadataError);
|
||||
|
||||
storeMetadataJob->start();
|
||||
storeMetadataJob->start();
|
||||
}
|
||||
|
||||
void AccountSettings::slotUploadMetadataSuccess(const QByteArray& folderId)
|
||||
{
|
||||
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
|
||||
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
|
||||
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
|
||||
this, &AccountSettings::slotUnlockFolderSuccess);
|
||||
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
|
||||
this, &AccountSettings::slotUnlockFolderError);
|
||||
unlockJob->start();
|
||||
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
|
||||
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
|
||||
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
|
||||
this, &AccountSettings::slotUnlockFolderSuccess);
|
||||
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
|
||||
this, &AccountSettings::slotUnlockFolderError);
|
||||
unlockJob->start();
|
||||
}
|
||||
|
||||
void AccountSettings::slotUpdateMetadataError(const QByteArray& folderId, int httpReturnCode)
|
||||
{
|
||||
Q_UNUSED(httpReturnCode);
|
||||
|
||||
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
|
||||
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
|
||||
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
|
||||
this, &AccountSettings::slotUnlockFolderSuccess);
|
||||
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
|
||||
this, &AccountSettings::slotUnlockFolderError);
|
||||
unlockJob->start();
|
||||
const auto token = accountsState()->account()->e2e()->tokenForFolder(folderId);
|
||||
auto unlockJob = new UnlockEncryptFolderApiJob(accountsState()->account(), folderId, token);
|
||||
connect(unlockJob, &UnlockEncryptFolderApiJob::success,
|
||||
this, &AccountSettings::slotUnlockFolderSuccess);
|
||||
connect(unlockJob, &UnlockEncryptFolderApiJob::error,
|
||||
this, &AccountSettings::slotUnlockFolderError);
|
||||
unlockJob->start();
|
||||
}
|
||||
|
||||
void AccountSettings::slotLockForEncryptionError(const QByteArray& fileId, int httpErrorCode)
|
||||
|
|
|
@ -63,6 +63,7 @@ signals:
|
|||
void openFolderAlias(const QString &);
|
||||
void showIssuesList(AccountState *account);
|
||||
void requesetMnemonic();
|
||||
void removeAccountFolders(AccountState *account);
|
||||
|
||||
public slots:
|
||||
void slotOpenOC();
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include "accountstate.h"
|
||||
#include "accountmanager.h"
|
||||
#include "remotewipe.h"
|
||||
#include "account.h"
|
||||
#include "creds/abstractcredentials.h"
|
||||
#include "creds/httpcredentials.h"
|
||||
|
@ -24,6 +25,11 @@
|
|||
#include <QTimer>
|
||||
#include <qfontmetrics.h>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkRequest>
|
||||
#include <QBuffer>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcAccountState, "nextcloud.gui.account.state", QtInfoMsg)
|
||||
|
@ -36,11 +42,12 @@ AccountState::AccountState(AccountPtr account)
|
|||
, _waitingForNewCredentials(false)
|
||||
, _notificationsEtagResponseHeader("*")
|
||||
, _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
|
||||
, _remoteWipe(new RemoteWipe(_account))
|
||||
{
|
||||
qRegisterMetaType<AccountState *>("AccountState*");
|
||||
|
||||
connect(account.data(), &Account::invalidCredentials,
|
||||
this, &AccountState::slotInvalidCredentials);
|
||||
this, &AccountState::slotHandleRemoteWipeCheck);
|
||||
connect(account.data(), &Account::credentialsFetched,
|
||||
this, &AccountState::slotCredentialsFetched);
|
||||
connect(account.data(), &Account::credentialsAsked,
|
||||
|
@ -303,7 +310,7 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
|
|||
break;
|
||||
case ConnectionValidator::CredentialsWrong:
|
||||
case ConnectionValidator::CredentialsNotReady:
|
||||
slotInvalidCredentials();
|
||||
handleInvalidCredentials();
|
||||
break;
|
||||
case ConnectionValidator::SslError:
|
||||
setState(SignedOut);
|
||||
|
@ -322,7 +329,20 @@ void AccountState::slotConnectionValidatorResult(ConnectionValidator::Status sta
|
|||
}
|
||||
}
|
||||
|
||||
void AccountState::slotInvalidCredentials()
|
||||
void AccountState::slotHandleRemoteWipeCheck()
|
||||
{
|
||||
// make sure it changes account state and icons
|
||||
signOutByUi();
|
||||
|
||||
qCInfo(lcAccountState) << "Invalid credentials for" << _account->url().toString()
|
||||
<< "checking for remote wipe request";
|
||||
|
||||
_waitingForNewCredentials = false;
|
||||
setState(SignedOut);
|
||||
}
|
||||
|
||||
|
||||
void AccountState::handleInvalidCredentials()
|
||||
{
|
||||
if (isSignedOut() || _waitingForNewCredentials)
|
||||
return;
|
||||
|
@ -343,6 +363,7 @@ void AccountState::slotInvalidCredentials()
|
|||
account()->credentials()->askFromUser();
|
||||
}
|
||||
|
||||
|
||||
void AccountState::slotCredentialsFetched(AbstractCredentials *)
|
||||
{
|
||||
// Make a connection attempt, no matter whether the credentials are
|
||||
|
|
|
@ -29,6 +29,7 @@ namespace OCC {
|
|||
|
||||
class AccountState;
|
||||
class Account;
|
||||
class RemoteWipe;
|
||||
|
||||
typedef QExplicitlySharedDataPointer<AccountState> AccountStatePtr;
|
||||
|
||||
|
@ -150,6 +151,9 @@ public:
|
|||
*/
|
||||
void setNavigationAppsEtagResponseHeader(const QByteArray &value);
|
||||
|
||||
///Asks for user credentials
|
||||
void handleInvalidCredentials();
|
||||
|
||||
public slots:
|
||||
/// Triggers a ping to the server to update state and
|
||||
/// connection status and errors.
|
||||
|
@ -164,7 +168,11 @@ signals:
|
|||
|
||||
protected Q_SLOTS:
|
||||
void slotConnectionValidatorResult(ConnectionValidator::Status status, const QStringList &errors);
|
||||
void slotInvalidCredentials();
|
||||
|
||||
/// When client gets a 401 or 403 checks if server requested remote wipe
|
||||
/// before asking for user credentials again
|
||||
void slotHandleRemoteWipeCheck();
|
||||
|
||||
void slotCredentialsFetched(AbstractCredentials *creds);
|
||||
void slotCredentialsAsked(AbstractCredentials *creds);
|
||||
|
||||
|
@ -190,6 +198,13 @@ private:
|
|||
* Milliseconds for which to delay reconnection after 503/maintenance.
|
||||
*/
|
||||
int _maintenanceToConnectedDelay;
|
||||
|
||||
/**
|
||||
* Connects remote wipe check with the account
|
||||
* the log out triggers the check (loads app password -> create request)
|
||||
*/
|
||||
RemoteWipe *_remoteWipe;
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -358,6 +358,8 @@ void WebFlowCredentials::forgetSensitiveData() {
|
|||
|
||||
fetchUser();
|
||||
|
||||
_account->deleteAppPassword();
|
||||
|
||||
const QString kck = keychainKey(_account->url().toString(), _user, _account->id());
|
||||
if (kck.isEmpty()) {
|
||||
qCDebug(lcWebFlowCredentials()) << "InvalidateToken: User is empty, bailing out!";
|
||||
|
@ -416,6 +418,9 @@ void WebFlowCredentials::slotFinished(QNetworkReply *reply) {
|
|||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
_credentialsValid = true;
|
||||
|
||||
/// Used later for remote wipe
|
||||
_account->setAppPassword(_password);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ FolderMan::FolderMan(QObject *parent)
|
|||
this, &FolderMan::slotScheduleFolderByTime);
|
||||
_timeScheduler.start();
|
||||
|
||||
connect(AccountManager::instance(), &AccountManager::accountRemoved,
|
||||
connect(AccountManager::instance(), &AccountManager::removeAccountFolders,
|
||||
this, &FolderMan::slotRemoveFoldersForAccount);
|
||||
|
||||
connect(_lockWatcher.data(), &LockWatcher::fileUnlocked,
|
||||
|
@ -443,7 +443,7 @@ void FolderMan::slotFolderSyncPaused(Folder *f, bool paused)
|
|||
void FolderMan::slotFolderCanSyncChanged()
|
||||
{
|
||||
Folder *f = qobject_cast<Folder *>(sender());
|
||||
ASSERT(f);
|
||||
ASSERT(f);
|
||||
if (f->canSync()) {
|
||||
_socketApi->slotRegisterPath(f->alias());
|
||||
} else {
|
||||
|
@ -1084,6 +1084,73 @@ bool FolderMan::startFromScratch(const QString &localFolder)
|
|||
return true;
|
||||
}
|
||||
|
||||
void FolderMan::slotWipeFolderForAccount(AccountState *accountState)
|
||||
{
|
||||
QVarLengthArray<Folder *, 16> foldersToRemove;
|
||||
Folder::MapIterator i(_folderMap);
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
Folder *folder = i.value();
|
||||
if (folder->accountState() == accountState) {
|
||||
foldersToRemove.append(folder);
|
||||
}
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
foreach (const auto &f, foldersToRemove) {
|
||||
if (!f) {
|
||||
qCCritical(lcFolderMan) << "Can not remove null folder";
|
||||
return;
|
||||
}
|
||||
|
||||
qCInfo(lcFolderMan) << "Removing " << f->alias();
|
||||
|
||||
const bool currentlyRunning = (_currentSyncFolder == f);
|
||||
if (currentlyRunning) {
|
||||
// abort the sync now
|
||||
terminateSyncProcess();
|
||||
}
|
||||
|
||||
if (_scheduledFolders.removeAll(f) > 0) {
|
||||
emit scheduleQueueChanged();
|
||||
}
|
||||
|
||||
// wipe database
|
||||
f->wipe();
|
||||
|
||||
// wipe data
|
||||
QDir userFolder(f->path());
|
||||
if (userFolder.exists()) {
|
||||
success = userFolder.removeRecursively();
|
||||
if (!success) {
|
||||
qCWarning(lcFolderMan) << "Failed to remove existing folder " << f->path();
|
||||
} else {
|
||||
qCInfo(lcFolderMan) << "wipe: Removed file " << f->path();
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
success = true;
|
||||
qCWarning(lcFolderMan) << "folder does not exist, can not remove.";
|
||||
}
|
||||
|
||||
f->setSyncPaused(true);
|
||||
|
||||
// remove the folder configuration
|
||||
f->removeFromSettings();
|
||||
|
||||
unloadFolder(f);
|
||||
if (currentlyRunning) {
|
||||
delete f;
|
||||
}
|
||||
|
||||
_navigationPaneHelper.scheduleUpdateCloudStorageRegistry();
|
||||
}
|
||||
|
||||
emit folderListChanged(_folderMap);
|
||||
emit wipeDone(accountState, success);
|
||||
}
|
||||
|
||||
void FolderMan::setDirtyProxy()
|
||||
{
|
||||
foreach (Folder *f, _folderMap.values()) {
|
||||
|
|
|
@ -207,6 +207,11 @@ signals:
|
|||
*/
|
||||
void folderListChanged(const Folder::Map &);
|
||||
|
||||
/**
|
||||
* Emitted once slotRemoveFoldersForAccount is done wiping
|
||||
*/
|
||||
void wipeDone(AccountState *account, bool success);
|
||||
|
||||
public slots:
|
||||
|
||||
/**
|
||||
|
@ -231,6 +236,9 @@ public slots:
|
|||
// slot to schedule an ETag job (from Folder only)
|
||||
void slotScheduleETagJob(const QString &alias, RequestEtagJob *job);
|
||||
|
||||
/** Wipe folder */
|
||||
void slotWipeFolderForAccount(AccountState *accountState);
|
||||
|
||||
private slots:
|
||||
void slotFolderSyncPaused(Folder *, bool paused);
|
||||
void slotFolderCanSyncChanged();
|
||||
|
|
159
src/gui/remotewipe.cpp
Normal file
159
src/gui/remotewipe.cpp
Normal file
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright (C) by Camila Ayres <hello@camila.codes>
|
||||
*
|
||||
* 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 2 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.
|
||||
*/
|
||||
|
||||
#include "remotewipe.h"
|
||||
#include "folderman.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkRequest>
|
||||
#include <QBuffer>
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcRemoteWipe, "nextcloud.gui.remotewipe", QtInfoMsg)
|
||||
|
||||
RemoteWipe::RemoteWipe(AccountPtr account, QObject *parent)
|
||||
: _account(account),
|
||||
_appPassword(QString()),
|
||||
_accountRemoved(false),
|
||||
_networkManager(nullptr),
|
||||
_networkReplyCheck(nullptr),
|
||||
_networkReplySuccess(nullptr)
|
||||
{
|
||||
QObject::connect(AccountManager::instance(), &AccountManager::accountRemoved,
|
||||
this, [=](AccountState *) {
|
||||
_accountRemoved = true;
|
||||
});
|
||||
QObject::connect(this, &RemoteWipe::authorized, FolderMan::instance(),
|
||||
&FolderMan::slotWipeFolderForAccount);
|
||||
QObject::connect(FolderMan::instance(), &FolderMan::wipeDone, this,
|
||||
&RemoteWipe::notifyServerSuccessJob);
|
||||
QObject::connect(_account.data(), &Account::appPasswordRetrieved, this,
|
||||
&RemoteWipe::startCheckJobWithAppPassword);
|
||||
}
|
||||
|
||||
void RemoteWipe::startCheckJobWithAppPassword(QString pwd){
|
||||
if(pwd.isEmpty())
|
||||
return;
|
||||
|
||||
_appPassword = pwd;
|
||||
QUrl requestUrl = Utility::concatUrlPath(_account->url().toString(),
|
||||
QLatin1String("/index.php/core/wipe/check"));
|
||||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
"application/x-www-form-urlencoded");
|
||||
request.setUrl(requestUrl);
|
||||
request.setSslConfiguration(_account->getOrCreateSslConfig());
|
||||
auto requestBody = new QBuffer;
|
||||
QUrlQuery arguments(QString("token=%1").arg(_appPassword));
|
||||
requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
|
||||
_networkReplyCheck = _networkManager.post(request, requestBody);
|
||||
QObject::connect(_networkReplyCheck, &QNetworkReply::finished, this,
|
||||
&RemoteWipe::checkJobSlot);
|
||||
}
|
||||
|
||||
void RemoteWipe::checkJobSlot()
|
||||
{
|
||||
auto jsonData = _networkReplyCheck->readAll();
|
||||
QJsonParseError jsonParseError;
|
||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||
bool wipe = false;
|
||||
|
||||
//check for errors
|
||||
if (_networkReplyCheck->error() != QNetworkReply::NoError ||
|
||||
jsonParseError.error != QJsonParseError::NoError) {
|
||||
QString errorReason;
|
||||
QString errorFromJson = json["error"].toString();
|
||||
if (!errorFromJson.isEmpty()) {
|
||||
qCWarning(lcRemoteWipe) << QString("Error returned from the server: <em>%1<em>")
|
||||
.arg(errorFromJson.toHtmlEscaped());
|
||||
} else if (_networkReplyCheck->error() != QNetworkReply::NoError) {
|
||||
qCWarning(lcRemoteWipe) << QString("There was an error accessing the 'token' endpoint: <br><em>%1</em>")
|
||||
.arg(_networkReplyCheck->errorString().toHtmlEscaped());
|
||||
} else if (jsonParseError.error != QJsonParseError::NoError) {
|
||||
qCWarning(lcRemoteWipe) << QString("Could not parse the JSON returned from the server: <br><em>%1</em>")
|
||||
.arg(jsonParseError.errorString());
|
||||
} else {
|
||||
qCWarning(lcRemoteWipe) << QString("The reply from the server did not contain all expected fields");
|
||||
}
|
||||
|
||||
// check for wipe request
|
||||
} else if(!json.value("wipe").isUndefined()){
|
||||
wipe = json["wipe"].toBool();
|
||||
}
|
||||
|
||||
auto manager = AccountManager::instance();
|
||||
auto accountState = manager->account(_account->displayName()).data();
|
||||
|
||||
if(wipe){
|
||||
// delete account
|
||||
manager->deleteAccount(accountState);
|
||||
manager->save();
|
||||
|
||||
// delete data
|
||||
emit authorized(accountState);
|
||||
|
||||
} else {
|
||||
// ask user for his credentials again
|
||||
accountState->handleInvalidCredentials();
|
||||
}
|
||||
|
||||
_networkReplyCheck->deleteLater();
|
||||
}
|
||||
|
||||
void RemoteWipe::notifyServerSuccessJob(AccountState *accountState, bool dataWiped){
|
||||
if(_accountRemoved && dataWiped && _account == accountState->account()){
|
||||
QUrl requestUrl = Utility::concatUrlPath(_account->url().toString(),
|
||||
QLatin1String("/index.php/core/wipe/success"));
|
||||
QNetworkRequest request;
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader,
|
||||
"application/x-www-form-urlencoded");
|
||||
request.setUrl(requestUrl);
|
||||
request.setSslConfiguration(_account->getOrCreateSslConfig());
|
||||
auto requestBody = new QBuffer;
|
||||
QUrlQuery arguments(QString("token=%1").arg(_appPassword));
|
||||
requestBody->setData(arguments.query(QUrl::FullyEncoded).toLatin1());
|
||||
_networkReplySuccess = _networkManager.post(request, requestBody);
|
||||
QObject::connect(_networkReplySuccess, &QNetworkReply::finished, this,
|
||||
&RemoteWipe::notifyServerSuccessJobSlot);
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteWipe::notifyServerSuccessJobSlot()
|
||||
{
|
||||
auto jsonData = _networkReplySuccess->readAll();
|
||||
QJsonParseError jsonParseError;
|
||||
QJsonObject json = QJsonDocument::fromJson(jsonData, &jsonParseError).object();
|
||||
if (_networkReplySuccess->error() != QNetworkReply::NoError ||
|
||||
jsonParseError.error != QJsonParseError::NoError) {
|
||||
QString errorReason;
|
||||
QString errorFromJson = json["error"].toString();
|
||||
if (!errorFromJson.isEmpty()) {
|
||||
qCWarning(lcRemoteWipe) << QString("Error returned from the server: <em>%1</em>")
|
||||
.arg(errorFromJson.toHtmlEscaped());
|
||||
} else if (_networkReplySuccess->error() != QNetworkReply::NoError) {
|
||||
qCWarning(lcRemoteWipe) << QString("There was an error accessing the 'success' endpoint: <br><em>%1</em>")
|
||||
.arg(_networkReplySuccess->errorString().toHtmlEscaped());
|
||||
} else if (jsonParseError.error != QJsonParseError::NoError) {
|
||||
qCWarning(lcRemoteWipe) << QString("Could not parse the JSON returned from the server: <br><em>%1</em>")
|
||||
.arg(jsonParseError.errorString());
|
||||
} else {
|
||||
qCWarning(lcRemoteWipe) << QString("The reply from the server did not contain all expected fields.");
|
||||
}
|
||||
}
|
||||
|
||||
_networkReplySuccess->deleteLater();
|
||||
}
|
||||
}
|
61
src/gui/remotewipe.h
Normal file
61
src/gui/remotewipe.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
#ifndef REMOTEWIPE_H
|
||||
#define REMOTEWIPE_H
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
class QJsonDocument;
|
||||
class TestRemoteWipe;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class RemoteWipe : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit RemoteWipe(AccountPtr account, QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
/**
|
||||
* Notify if wipe was requested
|
||||
*/
|
||||
void authorized(AccountState*);
|
||||
|
||||
/**
|
||||
* Notify if user only needs to login again
|
||||
*/
|
||||
void askUserCredentials();
|
||||
|
||||
public slots:
|
||||
/**
|
||||
* Once receives a 401 or 403 status response it will do a fetch to
|
||||
* <server>/index.php/core/wipe/check
|
||||
*/
|
||||
void startCheckJobWithAppPassword(QString);
|
||||
|
||||
private slots:
|
||||
/**
|
||||
* If wipe is requested, delete account and data, if not continue by asking
|
||||
* the user to login again
|
||||
*/
|
||||
void checkJobSlot();
|
||||
|
||||
/**
|
||||
* Once the client has wiped all the required data a POST to
|
||||
* <server>/index.php/core/wipe/success
|
||||
*/
|
||||
void notifyServerSuccessJob(AccountState *accountState, bool);
|
||||
void notifyServerSuccessJobSlot();
|
||||
|
||||
private:
|
||||
AccountPtr _account;
|
||||
QString _appPassword;
|
||||
bool _accountRemoved;
|
||||
QNetworkAccessManager _networkManager;
|
||||
QNetworkReply *_networkReplyCheck;
|
||||
QNetworkReply *_networkReplySuccess;
|
||||
|
||||
friend class ::TestRemoteWipe;
|
||||
};
|
||||
}
|
||||
#endif // REMOTEWIPE_H
|
|
@ -99,6 +99,7 @@ void Flow2AuthCredsPage::asyncAuthResult(Flow2Auth::Result r, const QString &use
|
|||
_appPassword = appPassword;
|
||||
OwncloudWizard *ocWizard = qobject_cast<OwncloudWizard *>(wizard());
|
||||
Q_ASSERT(ocWizard);
|
||||
|
||||
emit connectToOCUrl(ocWizard->account()->url().toString());
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -36,9 +36,15 @@
|
|||
#include <QAuthenticator>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#include <keychain.h>
|
||||
#include "creds/abstractcredentials.h"
|
||||
|
||||
using namespace QKeychain;
|
||||
|
||||
namespace OCC {
|
||||
|
||||
Q_LOGGING_CATEGORY(lcAccount, "nextcloud.sync.account", QtInfoMsg)
|
||||
const char app_password[] = "_app-password";
|
||||
|
||||
Account::Account(QObject *parent)
|
||||
: QObject(parent)
|
||||
|
@ -53,16 +59,17 @@ AccountPtr Account::create()
|
|||
AccountPtr acc = AccountPtr(new Account);
|
||||
acc->setSharedThis(acc);
|
||||
|
||||
//TODO: This probably needs to have a better
|
||||
// coupling, but it should work for now.
|
||||
acc->e2e()->setAccount(acc);
|
||||
//TODO: This probably needs to have a better
|
||||
// coupling, but it should work for now.
|
||||
acc->e2e()->setAccount(acc);
|
||||
|
||||
return acc;
|
||||
}
|
||||
|
||||
ClientSideEncryption* Account::e2e()
|
||||
{
|
||||
// Qt expects everything in the connect to be a pointer, so return a pointer.
|
||||
return &_e2e;
|
||||
// Qt expects everything in the connect to be a pointer, so return a pointer.
|
||||
return &_e2e;
|
||||
}
|
||||
|
||||
Account::~Account()
|
||||
|
@ -432,6 +439,9 @@ void Account::slotCredentialsAsked()
|
|||
|
||||
void Account::handleInvalidCredentials()
|
||||
{
|
||||
// Retrieving password will trigger remote wipe check job
|
||||
retrieveAppPassword();
|
||||
|
||||
emit invalidCredentials();
|
||||
}
|
||||
|
||||
|
@ -503,4 +513,63 @@ void Account::setNonShib(bool nonShib)
|
|||
}
|
||||
}
|
||||
|
||||
void Account::setAppPassword(QString appPassword){
|
||||
const QString kck = AbstractCredentials::keychainKey(
|
||||
url().toString(),
|
||||
davUser() + app_password,
|
||||
id()
|
||||
);
|
||||
|
||||
WritePasswordJob *job = new WritePasswordJob(Theme::instance()->appName());
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(kck);
|
||||
job->setBinaryData(appPassword.toLatin1());
|
||||
connect(job, &WritePasswordJob::finished, [](Job *) {
|
||||
qCInfo(lcAccount) << "appPassword stored in keychain";
|
||||
});
|
||||
job->start();
|
||||
}
|
||||
|
||||
void Account::retrieveAppPassword(){
|
||||
const QString kck = AbstractCredentials::keychainKey(
|
||||
url().toString(),
|
||||
credentials()->user() + app_password,
|
||||
id()
|
||||
);
|
||||
|
||||
ReadPasswordJob *job = new ReadPasswordJob(Theme::instance()->appName());
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(kck);
|
||||
connect(job, &WritePasswordJob::finished, [this](Job *incoming) {
|
||||
ReadPasswordJob *readJob = static_cast<ReadPasswordJob *>(incoming);
|
||||
QString pwd("");
|
||||
// Error or no valid public key error out
|
||||
if (readJob->error() == NoError &&
|
||||
readJob->binaryData().length() > 0) {
|
||||
pwd = readJob->binaryData();
|
||||
}
|
||||
|
||||
emit appPasswordRetrieved(pwd);
|
||||
});
|
||||
job->start();
|
||||
}
|
||||
|
||||
void Account::deleteAppPassword(){
|
||||
const QString kck = AbstractCredentials::keychainKey(
|
||||
url().toString(),
|
||||
credentials()->user() + app_password,
|
||||
id()
|
||||
);
|
||||
|
||||
if (kck.isEmpty()) {
|
||||
qCDebug(lcAccount) << "appPassword is empty";
|
||||
return;
|
||||
}
|
||||
|
||||
DeletePasswordJob *job = new DeletePasswordJob(Theme::instance()->appName());
|
||||
job->setInsecureFallback(false);
|
||||
job->setKey(kck);
|
||||
job->start();
|
||||
}
|
||||
|
||||
} // namespace OCC
|
||||
|
|
|
@ -41,6 +41,12 @@ class QNetworkReply;
|
|||
class QUrl;
|
||||
class QNetworkAccessManager;
|
||||
|
||||
namespace QKeychain {
|
||||
class Job;
|
||||
class WritePasswordJob;
|
||||
class ReadPasswordJob;
|
||||
}
|
||||
|
||||
namespace OCC {
|
||||
|
||||
class AbstractCredentials;
|
||||
|
@ -50,7 +56,6 @@ class QuotaInfo;
|
|||
class AccessManager;
|
||||
class SimpleNetworkJob;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Reimplement this to handle SSL errors from libsync
|
||||
* @ingroup libsync
|
||||
|
@ -236,6 +241,11 @@ public:
|
|||
|
||||
ClientSideEncryption* e2e();
|
||||
|
||||
/// Used in RemoteWipe
|
||||
void retrieveAppPassword();
|
||||
void setAppPassword(QString appPassword);
|
||||
void deleteAppPassword();
|
||||
|
||||
public slots:
|
||||
/// Used when forgetting credentials
|
||||
void clearQNAMCache();
|
||||
|
@ -262,6 +272,9 @@ signals:
|
|||
void accountChangedAvatar();
|
||||
void accountChangedDisplayName();
|
||||
|
||||
/// Used in RemoteWipe
|
||||
void appPasswordRetrieved(QString);
|
||||
|
||||
protected Q_SLOTS:
|
||||
void slotCredentialsFetched();
|
||||
void slotCredentialsAsked();
|
||||
|
|
|
@ -60,17 +60,32 @@ nextcloud_add_benchmark(LargeSync "syncenginetestutils.h")
|
|||
SET(FolderMan_SRC ../src/gui/folderman.cpp)
|
||||
list(APPEND FolderMan_SRC ../src/gui/folder.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/socketapi.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/syncrunfilelog.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/lockwatcher.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/guiutility.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/navigationpanehelper.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/connectionvalidator.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/clientproxy.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/accountstate.cpp )
|
||||
list(APPEND FolderMan_SRC ../src/gui/remotewipe.cpp )
|
||||
list(APPEND FolderMan_SRC ${FolderWatcher_SRC})
|
||||
list(APPEND FolderMan_SRC stub.cpp )
|
||||
list(APPEND FolderMan_SRC stubfolderman.cpp )
|
||||
nextcloud_add_test(FolderMan "${FolderMan_SRC}")
|
||||
|
||||
SET(RemoteWipe_SRC ../src/gui/remotewipe.cpp)
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/clientproxy.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/guiutility.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/connectionvalidator.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/accountstate.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/socketapi.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/folder.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/syncrunfilelog.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/folderwatcher_linux.cpp )
|
||||
list(APPEND RemoteWipe_SRC ../src/gui/folderwatcher.cpp )
|
||||
list(APPEND RemoteWipe_SRC ${RemoteWipe_SRC})
|
||||
list(APPEND RemoteWipe_SRC stubremotewipe.cpp )
|
||||
nextcloud_add_test(RemoteWipe "${RemoteWipe_SRC}")
|
||||
|
||||
nextcloud_add_test(OAuth "syncenginetestutils.h;../src/gui/creds/oauth.cpp")
|
||||
|
||||
configure_file(test_journal.db "${PROJECT_BINARY_DIR}/bin/test_journal.db" COPYONLY)
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
// stub to prevent linker error
|
||||
#include "accountmanager.h"
|
||||
|
||||
OCC::AccountManager *OCC::AccountManager::instance() { return static_cast<AccountManager *>(new QObject); }
|
||||
void OCC::AccountManager::save(bool) { }
|
||||
void OCC::AccountManager::saveAccountState(AccountState *) { }
|
||||
void OCC::AccountManager::save(bool saveCredentials) { Q_UNUSED(saveCredentials); }
|
||||
void OCC::AccountManager::deleteAccount(AccountState *) { }
|
||||
void OCC::AccountManager::accountRemoved(OCC::AccountState*) { }
|
||||
OCC::AccountStatePtr OCC::AccountManager::account(const QString &){ return AccountStatePtr(); }
|
||||
void OCC::AccountManager::removeAccountFolders(OCC::AccountState*) { }
|
||||
const QMetaObject OCC::AccountManager::staticMetaObject = QObject::staticMetaObject;
|
30
test/stubremotewipe.cpp
Normal file
30
test/stubremotewipe.cpp
Normal file
|
@ -0,0 +1,30 @@
|
|||
// stub to prevent linker error
|
||||
#include "accountmanager.h"
|
||||
#include "accountstate.h"
|
||||
#include "socketapi.h"
|
||||
#include "folderman.h"
|
||||
|
||||
OCC::AccountManager *OCC::AccountManager::instance() { return static_cast<AccountManager *>(new QObject); }
|
||||
void OCC::AccountManager::save(bool) { }
|
||||
OCC::AccountState *OCC::AccountManager::addAccount(const AccountPtr& ac) { return new OCC::AccountState(ac); }
|
||||
void OCC::AccountManager::deleteAccount(AccountState *) { }
|
||||
void OCC::AccountManager::accountRemoved(OCC::AccountState*) { }
|
||||
OCC::AccountStatePtr OCC::AccountManager::account(const QString &){ return AccountStatePtr(); }
|
||||
const QMetaObject OCC::AccountManager::staticMetaObject = QObject::staticMetaObject;
|
||||
|
||||
OCC::FolderMan *OCC::FolderMan::instance() { return static_cast<FolderMan *>(new QObject); }
|
||||
void OCC::FolderMan::wipeDone(OCC::AccountState*, bool) { }
|
||||
OCC::Folder* OCC::FolderMan::addFolder(OCC::AccountState* as, OCC::FolderDefinition const &f) { return nullptr; }
|
||||
void OCC::FolderMan::slotWipeFolderForAccount(OCC::AccountState*) { }
|
||||
QString OCC::FolderMan::unescapeAlias(QString const&){ return QString(); }
|
||||
QString OCC::FolderMan::escapeAlias(QString const&){ return QString(); }
|
||||
void OCC::FolderMan::scheduleFolder(OCC::Folder*){ }
|
||||
OCC::SocketApi *OCC::FolderMan::socketApi(){ return new SocketApi; }
|
||||
OCC::Folder::Map OCC::FolderMan::map() { return OCC::Folder::Map(); }
|
||||
void OCC::FolderMan::setSyncEnabled(bool) { }
|
||||
void OCC::FolderMan::slotSyncOnceFileUnlocks(QString const&) { }
|
||||
void OCC::FolderMan::slotScheduleETagJob(QString const&, OCC::RequestEtagJob*){ }
|
||||
OCC::Folder *OCC::FolderMan::folderForPath(QString const&) { return nullptr; }
|
||||
OCC::Folder* OCC::FolderMan::folder(QString const&) { return nullptr; }
|
||||
void OCC::FolderMan::folderSyncStateChange(OCC::Folder*) { }
|
||||
const QMetaObject OCC::FolderMan::staticMetaObject = QObject::staticMetaObject;
|
|
@ -14,29 +14,10 @@
|
|||
#include "account.h"
|
||||
#include "accountstate.h"
|
||||
#include "configfile.h"
|
||||
#include "creds/httpcredentials.h"
|
||||
#include "testhelper.h"
|
||||
|
||||
using namespace OCC;
|
||||
|
||||
class HttpCredentialsTest : public HttpCredentials {
|
||||
public:
|
||||
HttpCredentialsTest(const QString& user, const QString& password)
|
||||
: HttpCredentials(user, password)
|
||||
{}
|
||||
|
||||
void askFromUser() Q_DECL_OVERRIDE {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
static FolderDefinition folderDefinition(const QString &path) {
|
||||
FolderDefinition d;
|
||||
d.localPath = path;
|
||||
d.targetPath = path;
|
||||
d.alias = path;
|
||||
return d;
|
||||
}
|
||||
|
||||
class HttpCredentials;
|
||||
|
||||
class TestFolderMan: public QObject
|
||||
{
|
||||
|
|
28
test/testhelper.h
Normal file
28
test/testhelper.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef TESTHELPER_H
|
||||
#define TESTHELPER_H
|
||||
|
||||
#include "folder.h"
|
||||
#include "creds/httpcredentials.h"
|
||||
|
||||
using namespace OCC;
|
||||
|
||||
class HttpCredentialsTest : public HttpCredentials {
|
||||
public:
|
||||
HttpCredentialsTest(const QString& user, const QString& password)
|
||||
: HttpCredentials(user, password)
|
||||
{}
|
||||
|
||||
void askFromUser() Q_DECL_OVERRIDE {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
static FolderDefinition folderDefinition(const QString &path) {
|
||||
FolderDefinition d;
|
||||
d.localPath = path;
|
||||
d.targetPath = path;
|
||||
d.alias = path;
|
||||
return d;
|
||||
}
|
||||
|
||||
#endif // TESTHELPER_H
|
83
test/testremotewipe.cpp
Normal file
83
test/testremotewipe.cpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* This software is in the public domain, furnished "as is", without technical
|
||||
* support, and with no warranty, express or implied, as to its usefulness for
|
||||
* any purpose.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <qglobal.h>
|
||||
#include <QTemporaryDir>
|
||||
#include <QtTest>
|
||||
|
||||
#include "remotewipe.h"
|
||||
|
||||
#include "common/utility.h"
|
||||
#include "folderman.h"
|
||||
#include "account.h"
|
||||
#include "accountstate.h"
|
||||
#include "configfile.h"
|
||||
|
||||
#include "testhelper.h"
|
||||
|
||||
using namespace OCC;
|
||||
|
||||
class TestRemoteWipe: public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
// TODO
|
||||
void testWipe(){
|
||||
// QTemporaryDir dir;
|
||||
// ConfigFile::setConfDir(dir.path()); // we don't want to pollute the user's config file
|
||||
// QVERIFY(dir.isValid());
|
||||
|
||||
// QDir dirToRemove(dir.path());
|
||||
// QVERIFY(dirToRemove.mkpath("nextcloud"));
|
||||
|
||||
// QString dirPath = dirToRemove.canonicalPath();
|
||||
|
||||
// AccountPtr account = Account::create();
|
||||
// QVERIFY(account);
|
||||
|
||||
// auto manager = AccountManager::instance();
|
||||
// QVERIFY(manager);
|
||||
|
||||
// AccountState *newAccountState = manager->addAccount(account);
|
||||
// manager->save();
|
||||
// QVERIFY(newAccountState);
|
||||
|
||||
// QUrl url("http://example.de");
|
||||
// HttpCredentialsTest *cred = new HttpCredentialsTest("testuser", "secret");
|
||||
// account->setCredentials(cred);
|
||||
// account->setUrl( url );
|
||||
|
||||
// FolderMan *folderman = FolderMan::instance();
|
||||
// folderman->addFolder(newAccountState, folderDefinition(dirPath + "/sub/nextcloud/"));
|
||||
|
||||
// // check if account exists
|
||||
// qDebug() << "Does account exists?!";
|
||||
// QVERIFY(!account->id().isEmpty());
|
||||
|
||||
// manager->deleteAccount(newAccountState);
|
||||
// manager->save();
|
||||
|
||||
// // check if account exists
|
||||
// qDebug() << "Does account exists yet?!";
|
||||
// QVERIFY(account);
|
||||
|
||||
// // check if folder exists
|
||||
// QVERIFY(dirToRemove.exists());
|
||||
|
||||
// // remote folders
|
||||
// qDebug() << "Removing folder for account " << newAccountState->account()->url();
|
||||
|
||||
// folderman->slotWipeFolderForAccount(newAccountState);
|
||||
|
||||
// // check if folders dont exist anymore
|
||||
// QCOMPARE(dirToRemove.exists(), false);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(TestRemoteWipe)
|
||||
#include "testremotewipe.moc"
|
Loading…
Reference in a new issue