summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2024-05-03 17:06:04 +0200
committerSimon Rettberg2024-05-03 17:06:04 +0200
commit54ec5fe857502ae80355c4e31c973b7e7afc6ade (patch)
tree591b655ae4477f43f6236052c6a4348b8dc448b7
parentAdd support for QRCode login (diff)
downloadslxgreeter-54ec5fe857502ae80355c4e31c973b7e7afc6ade.tar.gz
slxgreeter-54ec5fe857502ae80355c4e31c973b7e7afc6ade.tar.xz
slxgreeter-54ec5fe857502ae80355c4e31c973b7e7afc6ade.zip
Handle cow-token on QRCode login
-rw-r--r--src/loginform.cpp5
-rw-r--r--src/qrlogin.cpp180
-rw-r--r--src/qrlogin.h7
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;