2015-07-16 15:21:51 +03:00
|
|
|
/*
|
|
|
|
* 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
|
2016-10-25 12:00:07 +03:00
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
2015-07-16 15:21:51 +03:00
|
|
|
*
|
|
|
|
* 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"
|
2015-07-17 13:46:04 +03:00
|
|
|
#include "account.h"
|
2015-07-16 15:21:51 +03:00
|
|
|
|
|
|
|
#include <QApplication>
|
|
|
|
|
|
|
|
#include <keychain.h>
|
|
|
|
|
|
|
|
using namespace OCC;
|
|
|
|
|
2017-12-28 22:33:10 +03:00
|
|
|
Q_LOGGING_CATEGORY(lcProxy, "nextcloud.gui.credentials.proxy", QtInfoMsg)
|
2017-05-09 15:24:11 +03:00
|
|
|
|
2015-07-16 15:21:51 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-02-23 16:54:17 +03:00
|
|
|
QString key = proxy.hostName() + QLatin1Char(':') + QString::number(proxy.port());
|
2015-07-16 15:21:51 +03:00
|
|
|
|
|
|
|
// If the proxy server has changed, forget what we know.
|
|
|
|
if (key != _proxy) {
|
|
|
|
_proxy = key;
|
|
|
|
_username.clear();
|
|
|
|
_password.clear();
|
|
|
|
_blocked = false;
|
2015-07-17 13:46:04 +03:00
|
|
|
_gaveCredentialsTo.clear();
|
2015-07-16 15:21:51 +03:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2015-07-17 13:46:04 +03:00
|
|
|
// Find the responsible QNAM if possible.
|
2018-11-11 12:56:22 +03:00
|
|
|
QNetworkAccessManager *sending_qnam = nullptr;
|
2017-04-20 10:21:33 +03:00
|
|
|
QWeakPointer<QNetworkAccessManager> qnam_alive;
|
2020-05-18 21:54:23 +03:00
|
|
|
if (auto *account = qobject_cast<Account *>(sender())) {
|
2017-04-20 10:21:33 +03:00
|
|
|
// 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.
|
|
|
|
qnam_alive = account->sharedNetworkAccessManager();
|
|
|
|
sending_qnam = qnam_alive.data();
|
2015-07-17 13:46:04 +03:00
|
|
|
}
|
|
|
|
if (!sending_qnam) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcProxy) << "Could not get the sending QNAM for" << sender();
|
2015-07-17 13:46:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcProxy) << "Proxy auth required for" << key << proxy.type();
|
2015-07-16 15:21:51 +03:00
|
|
|
|
|
|
|
// If we already had a username but auth still failed,
|
2015-07-17 13:46:04 +03:00
|
|
|
// 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.
|
2015-07-16 15:21:51 +03:00
|
|
|
bool invalidated = false;
|
2015-07-17 13:46:04 +03:00
|
|
|
if (!_waitingForDialog && !_waitingForKeychain && (!authenticator->user().isEmpty()
|
|
|
|
|| (sending_qnam && _gaveCredentialsTo.contains(sending_qnam)))) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcProxy) << "invalidating old creds" << key;
|
2015-07-16 15:21:51 +03:00
|
|
|
_username.clear();
|
|
|
|
_password.clear();
|
|
|
|
invalidated = true;
|
2015-07-17 13:46:04 +03:00
|
|
|
_gaveCredentialsTo.clear();
|
2015-07-16 15:21:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (_username.isEmpty() || _waitingForKeychain) {
|
|
|
|
if (invalidated || !getCredsFromKeychain()) {
|
|
|
|
if (getCredsFromDialog()) {
|
|
|
|
storeCredsInKeychain();
|
|
|
|
} else {
|
|
|
|
// dialog was cancelled, never ask for that proxy again
|
|
|
|
_blocked = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcProxy) << "got creds for" << _proxy;
|
2015-07-16 15:21:51 +03:00
|
|
|
authenticator->setUser(_username);
|
|
|
|
authenticator->setPassword(_password);
|
2017-04-20 10:21:33 +03:00
|
|
|
sending_qnam = qnam_alive.data();
|
2015-07-17 13:46:04 +03:00
|
|
|
if (sending_qnam) {
|
|
|
|
_gaveCredentialsTo.insert(sending_qnam);
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(sending_qnam, &QObject::destroyed,
|
|
|
|
this, &ProxyAuthHandler::slotSenderDestroyed);
|
2015-07-17 13:46:04 +03:00
|
|
|
}
|
2015-07-16 15:21:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void ProxyAuthHandler::slotKeychainJobDone()
|
|
|
|
{
|
|
|
|
_keychainJobRunning = false;
|
|
|
|
}
|
|
|
|
|
2015-07-17 13:46:04 +03:00
|
|
|
void ProxyAuthHandler::slotSenderDestroyed(QObject *obj)
|
|
|
|
{
|
|
|
|
_gaveCredentialsTo.remove(obj);
|
|
|
|
}
|
|
|
|
|
2015-07-16 15:21:51 +03:00
|
|
|
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.
|
|
|
|
++_waitingForDialog;
|
|
|
|
while (_dialog && _dialog->isVisible()) {
|
|
|
|
QApplication::processEvents(QEventLoop::ExcludeSocketNotifiers, 200);
|
|
|
|
}
|
|
|
|
--_waitingForDialog;
|
|
|
|
|
|
|
|
if (_dialog && _dialog->result() == QDialog::Accepted) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcProxy) << "got creds for" << _proxy << "from dialog";
|
2015-07-16 15:21:51 +03:00
|
|
|
_username = _dialog->username();
|
|
|
|
_password = _dialog->password();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ProxyAuthHandler::getCredsFromKeychain()
|
|
|
|
{
|
|
|
|
using namespace QKeychain;
|
|
|
|
|
|
|
|
if (_waitingForDialog) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-05-09 15:24:11 +03:00
|
|
|
qCDebug(lcProxy) << "trying to load" << _proxy;
|
2015-07-16 15:21:51 +03:00
|
|
|
|
|
|
|
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);
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(_readPasswordJob.data(), &QKeychain::Job::finished,
|
|
|
|
this, &ProxyAuthHandler::slotKeychainJobDone);
|
2015-07-16 15:21:51 +03:00
|
|
|
_keychainJobRunning = true;
|
|
|
|
_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.
|
|
|
|
++_waitingForKeychain;
|
|
|
|
_keychainJobRunning = true;
|
|
|
|
while (_keychainJobRunning) {
|
|
|
|
QApplication::processEvents(QEventLoop::AllEvents, 200);
|
|
|
|
}
|
|
|
|
--_waitingForKeychain;
|
|
|
|
|
|
|
|
if (_readPasswordJob->error() == NoError) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcProxy) << "got creds for" << _proxy << "from keychain";
|
2015-07-16 15:21:51 +03:00
|
|
|
_password = _readPasswordJob->textData();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
_username.clear();
|
|
|
|
if (_readPasswordJob->error() != EntryNotFound) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcProxy) << "ReadPasswordJob failed with" << _readPasswordJob->errorString();
|
2015-07-16 15:21:51 +03:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ProxyAuthHandler::storeCredsInKeychain()
|
|
|
|
{
|
|
|
|
using namespace QKeychain;
|
|
|
|
|
|
|
|
if (_waitingForKeychain) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-03-30 14:46:20 +03:00
|
|
|
qCInfo(lcProxy) << "storing" << _proxy;
|
2015-07-16 15:21:51 +03:00
|
|
|
|
|
|
|
_settings->setValue(keychainUsernameKey(), _username);
|
|
|
|
|
2020-05-18 21:54:23 +03:00
|
|
|
auto *job = new WritePasswordJob(Theme::instance()->appName(), this);
|
2015-07-16 15:21:51 +03:00
|
|
|
job->setSettings(_settings.data());
|
|
|
|
job->setInsecureFallback(false);
|
|
|
|
job->setKey(keychainPasswordKey());
|
|
|
|
job->setTextData(_password);
|
|
|
|
job->setAutoDelete(false);
|
2017-09-20 11:14:48 +03:00
|
|
|
connect(job, &QKeychain::Job::finished, this, &ProxyAuthHandler::slotKeychainJobDone);
|
2015-07-16 15:21:51 +03:00
|
|
|
_keychainJobRunning = true;
|
|
|
|
job->start();
|
|
|
|
|
|
|
|
++_waitingForKeychain;
|
|
|
|
_keychainJobRunning = true;
|
|
|
|
while (_keychainJobRunning) {
|
|
|
|
QApplication::processEvents(QEventLoop::AllEvents, 200);
|
|
|
|
}
|
|
|
|
--_waitingForKeychain;
|
|
|
|
|
|
|
|
job->deleteLater();
|
|
|
|
if (job->error() != NoError) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcProxy) << "WritePasswordJob failed with" << job->errorString();
|
2015-07-16 15:21:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString ProxyAuthHandler::keychainUsernameKey() const
|
|
|
|
{
|
|
|
|
return QString::fromLatin1("%1/username").arg(_proxy);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString ProxyAuthHandler::keychainPasswordKey() const
|
|
|
|
{
|
|
|
|
return QString::fromLatin1("%1/password").arg(_proxy);
|
|
|
|
}
|