diff options
author | Simon Rettberg | 2024-05-03 17:06:04 +0200 |
---|---|---|
committer | Simon Rettberg | 2024-05-03 17:06:04 +0200 |
commit | 54ec5fe857502ae80355c4e31c973b7e7afc6ade (patch) | |
tree | 591b655ae4477f43f6236052c6a4348b8dc448b7 | |
parent | Add support for QRCode login (diff) | |
download | slxgreeter-54ec5fe857502ae80355c4e31c973b7e7afc6ade.tar.gz slxgreeter-54ec5fe857502ae80355c4e31c973b7e7afc6ade.tar.xz slxgreeter-54ec5fe857502ae80355c4e31c973b7e7afc6ade.zip |
Handle cow-token on QRCode login
-rw-r--r-- | src/loginform.cpp | 5 | ||||
-rw-r--r-- | src/qrlogin.cpp | 180 | ||||
-rw-r--r-- | src/qrlogin.h | 7 |
3 files changed, 126 insertions, 66 deletions
diff --git a/src/loginform.cpp b/src/loginform.cpp index 230f408..3ca0ec4 100644 --- a/src/loginform.cpp +++ b/src/loginform.cpp @@ -409,11 +409,12 @@ void LoginForm::leaveDropDownActivated(int index) void LoginForm::onMessage(QString message, QLightDM::Greeter::MessageType type) { - if (type == QLightDM::Greeter::MessageType::MessageTypeError) { + bool err = type == QLightDM::Greeter::MessageType::MessageTypeError; + if (err) { ui->passwordInput->clear(); } std::cerr << "Message: " << message.toStdString() << std::endl; - showMessage(message, false); + showMessage(QLatin1String("[G] ") + message, err); clearMsg = true; } diff --git a/src/qrlogin.cpp b/src/qrlogin.cpp index 1e44668..22ea7e9 100644 --- a/src/qrlogin.cpp +++ b/src/qrlogin.cpp @@ -52,6 +52,10 @@ void QrLogin::loadQrCode(QLabel *dest) QNetworkReply *reply = nam->get(QNetworkRequest(Settings::shibUrl() + QLatin1String("?action=qrgen&token=") + token)); connect(reply, &QNetworkReply::readyRead, [=]() { + if (svgData.size() > 1024000) { + reply->abort(); + return; + } svgData.append(reply->readAll()); }); connect(reply, &QNetworkReply::finished, [reply, this]() { @@ -63,74 +67,17 @@ void QrLogin::loadQrCode(QLabel *dest) 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")); - } - }); - }); + + renderReceivedSvg(); + + connect(timer, &QTimer::timeout, this, &QrLogin::pollQrCodeStatus); + elapsed->start(); timer->setSingleShot(false); timer->start(2000); }); + + // Safeguard: 10s timeout in case the NAMs transferTimeout doesn't work connect(timer, &QTimer::timeout, [=]() { QObject::disconnect(reply, nullptr, nullptr, nullptr); QObject::disconnect(timer, nullptr, nullptr, nullptr); @@ -141,6 +88,111 @@ void QrLogin::loadQrCode(QLabel *dest) timer->start(10000); } +/** + * While the QR Login is active, i.e. the code still visible on screen, + * poll the status from the server every 2 seconds. Also, timeout and + * fall back to login screen after 5 minutes. + */ +void QrLogin::pollQrCodeStatus() +{ + 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) { + handleNoContentReceived(); + return; + } + QObject::disconnect(timer, nullptr, nullptr, nullptr); + timer->stop(); + auto lines = QString::fromUtf8(pr->readAll()).split('\n'); + handleAuthReceived(lines); + }); +} + +void QrLogin::renderReceivedSvg() +{ + // 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()); + } + // Add quiet zone + 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(); +} + +/** + * Received auth data for QR Code, trigger login. + */ +void QrLogin::handleAuthReceived(const QStringList &lines) +{ + if (lines.size() >= 2) { + if (lines.size() >= 3) { + // Admin token for editing VMs + QString userHash = QString::fromLocal8Bit(QCryptographicHash::hash(lines[0].toLocal8Bit(), QCryptographicHash::Md5).toHex()); + QFile file(QLatin1String("/run/openslx/lightdm/") + userHash); + if (file.open(QFile::WriteOnly | QFile::Truncate)) { + file.write(lines[2].toLocal8Bit()); + file.close(); + file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner); + } + } + emit startAuthentication(lines[0], QLatin1String("shib=") + lines[1]); + } else { + emit triggerReset(QLatin1String("QR: Invalid reply received")); + } +} + +/** + * Received HTTP 204 No Content, i.e. QR Code is still valid, but nobody logged in. + */ +void QrLogin::handleNoContentReceived() +{ + 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)); + } +} + static QString toBase32(const QByteArray &data) { static const QString base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567_-="; diff --git a/src/qrlogin.h b/src/qrlogin.h index fa8cc82..3c3c61d 100644 --- a/src/qrlogin.h +++ b/src/qrlogin.h @@ -21,7 +21,14 @@ signals: void startAuthentication(const QString &user, const QString &pass); void updateStatus(const QString &msg); +private slots: + void pollQrCodeStatus(); + private: + void handleAuthReceived(const QStringList &lines); + void handleNoContentReceived(); + void renderReceivedSvg(); + QLabel *dest; QTimer *timer; QNetworkAccessManager *nam; |