summaryrefslogblamecommitdiffstats
path: root/src/qrlogin.cpp
blob: 1e44668c5caa56c0c621ff4956eea609fa8105b8 (plain) (tree)






































































































































































                                                                                                                      
#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;
}