mirror-chatterino2/src/common/Credentials.cpp

233 lines
5.7 KiB
C++
Raw Normal View History

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