summaryrefslogtreecommitdiffstats
path: root/src/qrlogin.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qrlogin.cpp')
-rw-r--r--src/qrlogin.cpp167
1 files changed, 167 insertions, 0 deletions
diff --git a/src/qrlogin.cpp b/src/qrlogin.cpp
new file mode 100644
index 0000000..1e44668
--- /dev/null
+++ b/src/qrlogin.cpp
@@ -0,0 +1,167 @@
+#include "qrlogin.h"
+#include "settings.h"
+
+#include <unistd.h>
+
+#include <QDebug>
+#include <QLabel>
+#include <QSvgRenderer>
+#include <QPainter>
+#include <QTimer>
+#include <QElapsedTimer>
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+
+#define SVG_RENDER_SCALE 3
+#define QR_VALID_MS 300000
+
+static QString toBase32(const QByteArray &data);
+
+QrLogin::QrLogin(QObject *parent)
+ : QObject(parent)
+ , dest(nullptr)
+ , timer(new QTimer(this))
+ , nam(new QNetworkAccessManager(this))
+ , elapsed(new QElapsedTimer())
+{
+ QByteArray input;
+ input.append((const char*)this, sizeof(*this));
+ input.append(QString::asprintf("%d %d %d ", QCursor::pos().x(), QCursor::pos().y(), (int)getpid()).toUtf8());
+ input.append(QString::number(QDateTime::currentMSecsSinceEpoch()).toUtf8());
+ token = toBase32(QCryptographicHash::hash(input, QCryptographicHash::Md5).left(10)).left(16);
+ nam->setAutoDeleteReplies(true);
+ nam->setTransferTimeout(5000);
+}
+
+void QrLogin::abort()
+{
+ QObject::disconnect(timer, nullptr, nullptr, nullptr);
+ timer->stop();
+ timer->blockSignals(true);
+ nam->blockSignals(true);
+}
+
+void QrLogin::loadQrCode(QLabel *dest)
+{
+ this->dest = dest;
+ QObject::disconnect(timer, nullptr, nullptr, nullptr);
+ timer->stop();
+ timer->setSingleShot(true);
+ svgData.clear();
+ QNetworkReply *reply =
+ nam->get(QNetworkRequest(Settings::shibUrl() + QLatin1String("?action=qrgen&token=") + token));
+ connect(reply, &QNetworkReply::readyRead, [=]() {
+ svgData.append(reply->readAll());
+ });
+ connect(reply, &QNetworkReply::finished, [reply, this]() {
+ QObject::disconnect(timer, nullptr, nullptr, nullptr);
+ timer->stop();
+ auto err = reply->error();
+ if (err != QNetworkReply::NoError) {
+ qDebug() << err << svgData;
+ emit triggerReset(QLatin1String("QRCode Error: ") + QString::fromUtf8(svgData));
+ return;
+ }
+ // Render at 3x size because QSvgRenderer adds ugly artefacts between pixels/modules
+ QPixmap pm(this->dest->size() * SVG_RENDER_SCALE);
+ pm.fill(Qt::white);
+ QPainter painter(&pm);
+ QRect square = pm.rect();
+ square.setSize(pm.size());
+ int diff = square.width() - square.height();
+ if (diff > 0) {
+ square.setLeft(diff / 2);
+ square.setWidth(square.height());
+ } else if (diff < 0) {
+ square.setTop(-diff / 2);
+ square.setHeight(square.width());
+ }
+ square.adjust(6 * SVG_RENDER_SCALE, 6 * SVG_RENDER_SCALE, -6 * SVG_RENDER_SCALE, -6 * SVG_RENDER_SCALE);
+ QSvgRenderer(svgData).render(&painter, square);
+ painter.end();
+ this->dest->setPixmap(pm.scaled(pm.size() / SVG_RENDER_SCALE, Qt::KeepAspectRatio, Qt::SmoothTransformation));
+ this->dest->update();
+ connect(timer, &QTimer::timeout, [=]() {
+ if (elapsed->hasExpired(300000)) {
+ QObject::disconnect(timer, nullptr, nullptr, nullptr);
+ timer->stop();
+ emit triggerReset(QLatin1String("Timeout: QR Code expired"));
+ return;
+ }
+ QNetworkReply *pr =
+ nam->get(QNetworkRequest(Settings::shibUrl() + QLatin1String("?action=qrpoll&token=") + token));
+ connect(pr, &QNetworkReply::finished, [=]() {
+ int code = pr->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
+ if (pr->error() != QNetworkReply::NoError) {
+ QObject::disconnect(timer, nullptr, nullptr, nullptr);
+ timer->stop();
+ emit triggerReset(QString::asprintf("QR Error(%d): ", code) + QString::fromUtf8(pr->readAll()));
+ return;
+ }
+ if (code == 204) {
+ int rem = (QR_VALID_MS - elapsed->elapsed()) / 1000;
+ if (rem > 60) {
+ emit updateStatus(QString::asprintf("Code valid for: ~%d mins", (rem + 30) / 60));
+ } else {
+ if (rem >= 58) {
+ QPixmap copy = this->dest->pixmap(Qt::ReturnByValue);
+ QImage img = copy.toImage();
+ uchar *b = img.bits();
+ for (int i = 0; i < img.sizeInBytes(); ++i) {
+ if (b[i] < 128) b[i] += 128;
+ }
+ this->dest->setPixmap(QPixmap::fromImage(img));
+ }
+ emit updateStatus(QString::asprintf("Code valid for: %d s", rem));
+ }
+ return;
+ }
+ QObject::disconnect(timer, nullptr, nullptr, nullptr);
+ timer->stop();
+ auto lines = QString::fromUtf8(pr->readAll()).split('\n');
+ if (lines.size() >= 2) {
+ emit startAuthentication(lines[0], QLatin1String("shib=") + lines[1]);
+ } else {
+ emit triggerReset(QLatin1String("QR: Invalid reply received"));
+ }
+ });
+ });
+ elapsed->start();
+ timer->setSingleShot(false);
+ timer->start(2000);
+ });
+ connect(timer, &QTimer::timeout, [=]() {
+ QObject::disconnect(reply, nullptr, nullptr, nullptr);
+ QObject::disconnect(timer, nullptr, nullptr, nullptr);
+ timer->stop();
+ reply->abort();
+ emit triggerReset(QLatin1String("Timeout: Could not get QR Code"));
+ });
+ timer->start(10000);
+}
+
+static QString toBase32(const QByteArray &data)
+{
+ static const QString base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567_-=";
+ QString result;
+ int numBits = 0;
+ quint16 buffer = 0;
+
+ for (int i = 0; i < data.size(); ++i) {
+ buffer = (buffer << 8) | static_cast<quint8>(data[i]);
+ numBits += 8;
+
+ while (numBits >= 5) {
+ numBits -= 5;
+ result += base32Chars[(buffer >> numBits) & 0x1F];
+ }
+ }
+
+ if (numBits > 0) {
+ buffer <<= (5 - numBits);
+ result += base32Chars[buffer & 0x1F];
+ }
+
+ return result;
+}