2019-09-09 22:27:46 +02:00
|
|
|
#include "Credentials.hpp"
|
|
|
|
|
2019-09-14 15:50:05 +02:00
|
|
|
#include "debug/AssertInGuiThread.hpp"
|
2019-09-09 22:27:46 +02:00
|
|
|
#include "keychain.h"
|
|
|
|
#include "singletons/Paths.hpp"
|
2019-09-13 19:26:52 +02:00
|
|
|
#include "singletons/Settings.hpp"
|
|
|
|
#include "util/CombinePath.hpp"
|
2019-09-14 22:58:53 +02:00
|
|
|
#include "util/Overloaded.hpp"
|
2019-09-13 19:26:52 +02:00
|
|
|
|
|
|
|
#include <QSaveFile>
|
2019-09-18 16:01:41 +02:00
|
|
|
#include <boost/variant.hpp>
|
2019-09-09 22:27:46 +02:00
|
|
|
|
|
|
|
#define FORMAT_NAME \
|
|
|
|
([&] { \
|
|
|
|
assert(!provider.contains(":")); \
|
|
|
|
return QString("chatterino:%1:%2").arg(provider).arg(name_); \
|
|
|
|
})()
|
|
|
|
|
|
|
|
namespace chatterino {
|
|
|
|
|
2019-09-13 19:26:52 +02:00
|
|
|
namespace {
|
|
|
|
bool useKeyring()
|
|
|
|
{
|
|
|
|
if (getPaths()->isPortable())
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
#ifdef Q_OS_LINUX
|
|
|
|
return getSettings()->useKeyring;
|
|
|
|
#else
|
|
|
|
return true;
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Insecure storage:
|
|
|
|
QString insecurePath()
|
|
|
|
{
|
|
|
|
return combinePath(getPaths()->settingsDirectory, "credentials.json");
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonDocument loadInsecure()
|
|
|
|
{
|
|
|
|
QFile file(insecurePath());
|
|
|
|
file.open(QIODevice::ReadOnly);
|
|
|
|
return QJsonDocument::fromJson(file.readAll());
|
|
|
|
}
|
|
|
|
|
|
|
|
void storeInsecure(const QJsonDocument &doc)
|
|
|
|
{
|
|
|
|
QSaveFile file(insecurePath());
|
|
|
|
file.open(QIODevice::WriteOnly);
|
|
|
|
file.write(doc.toJson());
|
|
|
|
file.commit();
|
|
|
|
}
|
|
|
|
|
|
|
|
QJsonDocument &insecureInstance()
|
|
|
|
{
|
|
|
|
static auto store = loadInsecure();
|
|
|
|
return store;
|
|
|
|
}
|
|
|
|
|
|
|
|
void queueInsecureSave()
|
|
|
|
{
|
|
|
|
static bool isQueued = false;
|
|
|
|
|
|
|
|
if (!isQueued)
|
|
|
|
{
|
|
|
|
isQueued = true;
|
|
|
|
QTimer::singleShot(200, qApp, [] {
|
|
|
|
storeInsecure(insecureInstance());
|
|
|
|
isQueued = false;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2019-09-14 22:58:53 +02:00
|
|
|
|
|
|
|
// QKeychain runs jobs asyncronously, so we have to assure that set/erase
|
|
|
|
// jobs gets executed in order.
|
|
|
|
struct SetJob {
|
|
|
|
QString name;
|
|
|
|
QString credential;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct EraseJob {
|
|
|
|
QString name;
|
|
|
|
};
|
|
|
|
|
2019-09-18 16:01:41 +02:00
|
|
|
using Job = boost::variant<SetJob, EraseJob>;
|
2019-09-14 22:58:53 +02:00
|
|
|
|
|
|
|
static std::queue<Job> &jobQueue()
|
|
|
|
{
|
|
|
|
static std::queue<Job> jobs;
|
|
|
|
return jobs;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void runNextJob()
|
|
|
|
{
|
|
|
|
auto &&queue = jobQueue();
|
|
|
|
|
|
|
|
if (!queue.empty())
|
|
|
|
{
|
2019-09-18 15:12:23 +02:00
|
|
|
// we were gonna use std::visit here but macos is shit
|
|
|
|
|
|
|
|
auto &&item = queue.front();
|
2019-09-18 16:01:41 +02:00
|
|
|
|
|
|
|
if (item.which() == 0) // set job
|
2019-09-18 15:12:23 +02:00
|
|
|
{
|
2019-09-18 16:01:41 +02:00
|
|
|
auto set = boost::get<SetJob>(item);
|
2019-09-18 15:12:23 +02:00
|
|
|
auto job = new QKeychain::WritePasswordJob("chatterino");
|
|
|
|
job->setAutoDelete(true);
|
|
|
|
job->setKey(set.name);
|
|
|
|
job->setTextData(set.credential);
|
|
|
|
QObject::connect(job, &QKeychain::Job::finished, qApp,
|
|
|
|
[](auto) { runNextJob(); });
|
|
|
|
job->start();
|
|
|
|
}
|
|
|
|
else // erase job
|
|
|
|
{
|
2019-09-18 16:01:41 +02:00
|
|
|
auto erase = boost::get<EraseJob>(item);
|
2019-09-18 15:12:23 +02:00
|
|
|
auto job = new QKeychain::DeletePasswordJob("chatterino");
|
|
|
|
job->setAutoDelete(true);
|
|
|
|
job->setKey(erase.name);
|
|
|
|
QObject::connect(job, &QKeychain::Job::finished, qApp,
|
|
|
|
[](auto) { runNextJob(); });
|
|
|
|
job->start();
|
|
|
|
}
|
2019-09-18 16:01:41 +02:00
|
|
|
|
2019-09-18 15:30:17 +02:00
|
|
|
queue.pop();
|
2019-09-14 22:58:53 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-18 15:30:17 +02:00
|
|
|
static void queueJob(Job &&job)
|
2019-09-18 15:12:23 +02:00
|
|
|
{
|
2019-09-18 15:30:17 +02:00
|
|
|
auto &&queue = jobQueue();
|
|
|
|
|
|
|
|
queue.push(std::move(job));
|
|
|
|
if (queue.size() == 1)
|
|
|
|
{
|
|
|
|
runNextJob();
|
|
|
|
}
|
2019-09-14 22:58:53 +02:00
|
|
|
}
|
2019-09-18 15:30:17 +02:00
|
|
|
} // namespace
|
2019-09-13 19:26:52 +02:00
|
|
|
|
2019-09-09 22:27:46 +02:00
|
|
|
Credentials &Credentials::getInstance()
|
|
|
|
{
|
|
|
|
static Credentials creds;
|
|
|
|
return creds;
|
|
|
|
}
|
|
|
|
|
|
|
|
Credentials::Credentials()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2019-09-14 18:38:09 +02:00
|
|
|
void Credentials::get(const QString &provider, const QString &name_,
|
2019-09-14 20:45:01 +02:00
|
|
|
QObject *receiver,
|
2019-09-14 18:38:09 +02:00
|
|
|
std::function<void(const QString &)> &&onLoaded)
|
2019-09-09 22:27:46 +02:00
|
|
|
{
|
2019-09-13 19:26:52 +02:00
|
|
|
assertInGuiThread();
|
2019-09-09 22:27:46 +02:00
|
|
|
|
2019-09-13 19:26:52 +02:00
|
|
|
auto name = FORMAT_NAME;
|
2019-09-09 22:27:46 +02:00
|
|
|
|
2019-09-13 19:26:52 +02:00
|
|
|
if (useKeyring())
|
2019-09-09 22:27:46 +02:00
|
|
|
{
|
|
|
|
auto job = new QKeychain::ReadPasswordJob("chatterino");
|
|
|
|
job->setAutoDelete(true);
|
|
|
|
job->setKey(name);
|
2019-09-14 20:45:01 +02:00
|
|
|
QObject::connect(job, &QKeychain::Job::finished, receiver,
|
2019-09-09 22:27:46 +02:00
|
|
|
[job, onLoaded = std::move(onLoaded)](auto) mutable {
|
|
|
|
onLoaded(job->textData());
|
2019-09-14 18:38:09 +02:00
|
|
|
},
|
|
|
|
Qt::DirectConnection);
|
2019-09-09 22:27:46 +02:00
|
|
|
job->start();
|
|
|
|
}
|
2019-09-13 19:26:52 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
auto &instance = insecureInstance();
|
|
|
|
|
2019-09-14 18:38:09 +02:00
|
|
|
onLoaded(instance.object().find(name).value().toString());
|
2019-09-13 19:26:52 +02:00
|
|
|
}
|
2019-09-09 22:27:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Credentials::set(const QString &provider, const QString &name_,
|
|
|
|
const QString &credential)
|
|
|
|
{
|
2019-09-13 19:26:52 +02:00
|
|
|
assertInGuiThread();
|
|
|
|
|
|
|
|
/// On linux, we try to use a keychain but show a message to disable it when it fails.
|
|
|
|
/// XXX: add said message
|
|
|
|
|
2019-09-09 22:27:46 +02:00
|
|
|
auto name = FORMAT_NAME;
|
|
|
|
|
2019-09-13 19:26:52 +02:00
|
|
|
if (useKeyring())
|
2019-09-09 22:27:46 +02:00
|
|
|
{
|
2019-09-14 22:58:53 +02:00
|
|
|
queueJob(SetJob{name, credential});
|
2019-09-09 22:27:46 +02:00
|
|
|
}
|
2019-09-13 19:26:52 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
auto &instance = insecureInstance();
|
|
|
|
|
|
|
|
instance.object()[name] = credential;
|
|
|
|
|
|
|
|
queueInsecureSave();
|
|
|
|
}
|
2019-09-09 22:27:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Credentials::erase(const QString &provider, const QString &name_)
|
|
|
|
{
|
2019-09-13 19:26:52 +02:00
|
|
|
assertInGuiThread();
|
|
|
|
|
2019-09-09 22:27:46 +02:00
|
|
|
auto name = FORMAT_NAME;
|
|
|
|
|
2019-09-13 19:26:52 +02:00
|
|
|
if (useKeyring())
|
2019-09-09 22:27:46 +02:00
|
|
|
{
|
2019-09-14 22:58:53 +02:00
|
|
|
queueJob(EraseJob{name});
|
2019-09-09 22:27:46 +02:00
|
|
|
}
|
2019-09-13 19:26:52 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
auto &instance = insecureInstance();
|
|
|
|
|
|
|
|
if (auto it = instance.object().find(name);
|
|
|
|
it != instance.object().end())
|
|
|
|
{
|
|
|
|
instance.object().erase(it);
|
|
|
|
}
|
|
|
|
|
|
|
|
queueInsecureSave();
|
|
|
|
}
|
2019-09-09 22:27:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace chatterino
|