mirror of
https://github.com/nextcloud/desktop.git
synced 2024-12-19 04:11:50 +03:00
f81f96915f
QWeakPointer::data is deprecated. In this case we should use QPointer
263 lines
7.8 KiB
C++
263 lines
7.8 KiB
C++
/*
|
|
* Copyright (C) 2015 by Christian Kamm <kamm@incasoftware.de>
|
|
*
|
|
* 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 "proxyauthhandler.h"
|
|
|
|
#include "proxyauthdialog.h"
|
|
#include "theme.h"
|
|
#include "configfile.h"
|
|
#include "account.h"
|
|
|
|
#include <QApplication>
|
|
|
|
#include <qt5keychain/keychain.h>
|
|
|
|
using namespace OCC;
|
|
using namespace QKeychain;
|
|
|
|
Q_LOGGING_CATEGORY(lcProxy, "nextcloud.gui.credentials.proxy", QtInfoMsg)
|
|
|
|
ProxyAuthHandler *ProxyAuthHandler::instance()
|
|
{
|
|
static ProxyAuthHandler inst;
|
|
return &inst;
|
|
}
|
|
|
|
ProxyAuthHandler::ProxyAuthHandler()
|
|
{
|
|
_dialog = new ProxyAuthDialog();
|
|
|
|
_configFile.reset(new ConfigFile);
|
|
_settings.reset(new QSettings(_configFile->configFile(), QSettings::IniFormat));
|
|
_settings->beginGroup(QLatin1String("Proxy"));
|
|
_settings->beginGroup(QLatin1String("Credentials"));
|
|
}
|
|
|
|
ProxyAuthHandler::~ProxyAuthHandler()
|
|
{
|
|
delete _dialog;
|
|
}
|
|
|
|
void ProxyAuthHandler::handleProxyAuthenticationRequired(
|
|
const QNetworkProxy &proxy,
|
|
QAuthenticator *authenticator)
|
|
{
|
|
if (!_dialog) {
|
|
return;
|
|
}
|
|
|
|
QString key = proxy.hostName() + QLatin1Char(':') + QString::number(proxy.port());
|
|
|
|
// If the proxy server has changed, forget what we know.
|
|
if (key != _proxy) {
|
|
_proxy = key;
|
|
_username.clear();
|
|
_password.clear();
|
|
_blocked = false;
|
|
_gaveCredentialsTo.clear();
|
|
|
|
// If the user explicitly configured the proxy in the
|
|
// network settings, don't ask about it.
|
|
if (_configFile->proxyType() == QNetworkProxy::HttpProxy
|
|
|| _configFile->proxyType() == QNetworkProxy::Socks5Proxy) {
|
|
_blocked = true;
|
|
}
|
|
}
|
|
|
|
if (_blocked) {
|
|
return;
|
|
}
|
|
|
|
// Find the responsible QNAM if possible.
|
|
QPointer<QNetworkAccessManager> sending_qnam = nullptr;
|
|
if (auto account = qobject_cast<Account *>(sender())) {
|
|
// Since we go into an event loop, it's possible for the account's qnam
|
|
// to be destroyed before we get back. We can use this to check for its
|
|
// liveness.
|
|
sending_qnam = account->sharedNetworkAccessManager().data();
|
|
}
|
|
if (!sending_qnam) {
|
|
qCWarning(lcProxy) << "Could not get the sending QNAM for" << sender();
|
|
}
|
|
|
|
|
|
qCInfo(lcProxy) << "Proxy auth required for" << key << proxy.type();
|
|
|
|
// If we already had a username but auth still failed,
|
|
// invalidate the old credentials! Unfortunately, authenticator->user()
|
|
// isn't reliable, so we also invalidate credentials if we previously
|
|
// gave presumably valid credentials to the same QNAM.
|
|
bool invalidated = false;
|
|
if (!_waitingForDialog && !_waitingForKeychain && (!authenticator->user().isEmpty()
|
|
|| (sending_qnam && _gaveCredentialsTo.contains(sending_qnam)))) {
|
|
qCInfo(lcProxy) << "invalidating old creds" << key;
|
|
_username.clear();
|
|
_password.clear();
|
|
invalidated = true;
|
|
_gaveCredentialsTo.clear();
|
|
}
|
|
|
|
if (_username.isEmpty() || _waitingForKeychain) {
|
|
if (invalidated || !getCredsFromKeychain()) {
|
|
if (getCredsFromDialog()) {
|
|
storeCredsInKeychain();
|
|
} else {
|
|
// dialog was cancelled, never ask for that proxy again
|
|
_blocked = true;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
qCInfo(lcProxy) << "got creds for" << _proxy;
|
|
authenticator->setUser(_username);
|
|
authenticator->setPassword(_password);
|
|
if (sending_qnam) {
|
|
_gaveCredentialsTo.insert(sending_qnam);
|
|
connect(sending_qnam, &QObject::destroyed,
|
|
this, &ProxyAuthHandler::slotSenderDestroyed);
|
|
}
|
|
}
|
|
|
|
void ProxyAuthHandler::slotSenderDestroyed(QObject *obj)
|
|
{
|
|
_gaveCredentialsTo.remove(obj);
|
|
}
|
|
|
|
bool ProxyAuthHandler::getCredsFromDialog()
|
|
{
|
|
// Open the credentials dialog
|
|
if (!_waitingForDialog) {
|
|
_dialog->reset();
|
|
_dialog->setProxyAddress(_proxy);
|
|
_dialog->open();
|
|
}
|
|
|
|
// This function can be reentered while the dialog is open.
|
|
// If that's the case, continue processing the dialog until
|
|
// it's done.
|
|
if(_dialog) {
|
|
execAwait(_dialog.data(),
|
|
&QDialog::finished,
|
|
_waitingForDialog,
|
|
QEventLoop::ExcludeSocketNotifiers);
|
|
}
|
|
|
|
if (_dialog && _dialog->result() == QDialog::Accepted) {
|
|
qCInfo(lcProxy) << "got creds for" << _proxy << "from dialog";
|
|
_username = _dialog->username();
|
|
_password = _dialog->password();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template<class T, typename PointerToMemberFunction>
|
|
void ProxyAuthHandler::execAwait(const T *sender,
|
|
PointerToMemberFunction signal,
|
|
int &counter,
|
|
const QEventLoop::ProcessEventsFlags flags)
|
|
{
|
|
if (!sender) {
|
|
return;
|
|
}
|
|
|
|
QEventLoop waitLoop;
|
|
connect(sender, signal, &waitLoop, &QEventLoop::quit);
|
|
|
|
++counter;
|
|
waitLoop.exec(flags);
|
|
--counter;
|
|
}
|
|
|
|
bool ProxyAuthHandler::getCredsFromKeychain()
|
|
{
|
|
if (_waitingForDialog) {
|
|
return false;
|
|
}
|
|
|
|
qCDebug(lcProxy) << "trying to load" << _proxy;
|
|
|
|
if (!_waitingForKeychain) {
|
|
_username = _settings->value(keychainUsernameKey()).toString();
|
|
if (_username.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
_readPasswordJob.reset(new ReadPasswordJob(Theme::instance()->appName()));
|
|
_readPasswordJob->setSettings(_settings.data());
|
|
_readPasswordJob->setInsecureFallback(false);
|
|
_readPasswordJob->setKey(keychainPasswordKey());
|
|
_readPasswordJob->setAutoDelete(false);
|
|
_readPasswordJob->start();
|
|
}
|
|
|
|
// While we wait for the password job to be done, this code may be reentered.
|
|
// This really needs the counter and the flag here, because otherwise we get
|
|
// bad behavior when we reenter this code after the flag has been switched
|
|
// but before the while loop has finished.
|
|
execAwait(_readPasswordJob.data(),
|
|
&QKeychain::Job::finished,
|
|
_waitingForKeychain);
|
|
|
|
if (_readPasswordJob->error() == NoError) {
|
|
qCInfo(lcProxy) << "got creds for" << _proxy << "from keychain";
|
|
_password = _readPasswordJob->textData();
|
|
return true;
|
|
}
|
|
|
|
_username.clear();
|
|
if (_readPasswordJob->error() != EntryNotFound) {
|
|
qCWarning(lcProxy) << "ReadPasswordJob failed with" << _readPasswordJob->errorString();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ProxyAuthHandler::storeCredsInKeychain()
|
|
{
|
|
if (_waitingForKeychain) {
|
|
return;
|
|
}
|
|
|
|
qCInfo(lcProxy) << "storing" << _proxy;
|
|
|
|
_settings->setValue(keychainUsernameKey(), _username);
|
|
|
|
auto job = new WritePasswordJob(Theme::instance()->appName(), this);
|
|
job->setSettings(_settings.data());
|
|
job->setInsecureFallback(false);
|
|
job->setKey(keychainPasswordKey());
|
|
job->setTextData(_password);
|
|
job->setAutoDelete(false);
|
|
job->start();
|
|
|
|
execAwait(job,
|
|
&QKeychain::Job::finished,
|
|
_waitingForKeychain);
|
|
|
|
job->deleteLater();
|
|
if (job->error() != NoError) {
|
|
qCWarning(lcProxy) << "WritePasswordJob failed with" << job->errorString();
|
|
}
|
|
}
|
|
|
|
QString ProxyAuthHandler::keychainUsernameKey() const
|
|
{
|
|
return QString::fromLatin1("%1/username").arg(_proxy);
|
|
}
|
|
|
|
QString ProxyAuthHandler::keychainPasswordKey() const
|
|
{
|
|
return QString::fromLatin1("%1/password").arg(_proxy);
|
|
}
|