summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/global.cpp23
-rw-r--r--src/global.h4
-rw-r--r--src/loginform.cpp5
-rw-r--r--src/qrlogin.cpp175
-rw-r--r--src/qrlogin.h7
-rw-r--r--src/webview.cpp12
6 files changed, 156 insertions, 70 deletions
diff --git a/src/global.cpp b/src/global.cpp
index 4efccad..ab2519b 100644
--- a/src/global.cpp
+++ b/src/global.cpp
@@ -7,6 +7,8 @@
#include <QDebug>
#include <QCoreApplication>
#include <QStringList>
+#include <QCryptographicHash>
+#include <QRegularExpression>
bool Global::m_testMode = false;
@@ -116,3 +118,24 @@ QStringList Global::urlWhitelist()
return QStringList();
return loadUrlList(path);
}
+
+void Global::writeCowToken(const QString &user, const QString &token)
+{
+ QString userHash = QString::fromLocal8Bit(QCryptographicHash::hash(user.toLocal8Bit(), QCryptographicHash::Md5).toHex());
+ QFile file(QLatin1String("/run/openslx/lightdm/") + userHash);
+ if (file.open(QFile::WriteOnly | QFile::Truncate)) {
+ file.write(token.toLocal8Bit());
+ file.close();
+ file.setPermissions(QFileDevice::ReadOwner | QFileDevice::WriteOwner);
+ }
+}
+
+bool Global::isValidShibCreds(const QString &ustr, const QString &upass)
+{
+ static QRegularExpression R_USER("^[a-z_A-Z][a-zA-Z0-9_@.-]{1,32}$");
+ static QRegularExpression R_PASS("^[a-z0-9]{1,32}$");
+
+ return ustr.contains('@')
+ && R_USER.match(ustr).hasMatch()
+ && R_PASS.match(upass).hasMatch();
+}
diff --git a/src/global.h b/src/global.h
index bdc79cd..d581c07 100644
--- a/src/global.h
+++ b/src/global.h
@@ -52,6 +52,10 @@ public:
static QStringList urlBlacklist();
+ static void writeCowToken(const QString &user, const QString &token);
+
+ static bool isValidShibCreds(const QString &ustr, const QString &upass);
+
private:
static bool m_testMode;
static QLightDM::Greeter *m_Greeter;
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..c562e98 100644
--- a/src/qrlogin.cpp
+++ b/src/qrlogin.cpp
@@ -1,5 +1,6 @@
#include "qrlogin.h"
#include "settings.h"
+#include "global.h"
#include <unistd.h>
@@ -52,6 +53,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 +68,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 +89,105 @@ 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 && !lines[2].isEmpty()) {
+ // Admin token for editing VMs
+ Global::writeCowToken(lines[0], lines[2]);
+ }
+ 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;
diff --git a/src/webview.cpp b/src/webview.cpp
index 54d19eb..9ebc1ba 100644
--- a/src/webview.cpp
+++ b/src/webview.cpp
@@ -15,9 +15,6 @@
#include <QRegularExpression>
#include <QWebPage>
-static QRegularExpression R_USER("^[a-z_A-Z][a-zA-Z0-9_@.-]{1,32}$");
-static QRegularExpression R_PASS("^[a-z0-9]{1,32}$");
-
static QRegularExpression urlListToRegExp(const QStringList &list);
// Override user-agent to make it appear mobile
@@ -137,6 +134,7 @@ void WebView::onLoadFinished(bool ok)
auto pass = this->page()->mainFrame()->documentElement().findFirst("#bwlp-password");
auto err = this->page()->mainFrame()->documentElement().findFirst("#bwlp-error");
auto hash = this->page()->mainFrame()->documentElement().findFirst("#bwlp-hash");
+ auto adminToken = this->page()->mainFrame()->documentElement().findFirst("#bwlp-cow-token");
if (!user.isNull() && !pass.isNull() && !hash.isNull()) {
if (hash.toPlainText() != QCryptographicHash::hash(_token.toLatin1(), QCryptographicHash::Md5).toHex()) {
qDebug() << " *** Invalid security hash ***";
@@ -145,8 +143,14 @@ void WebView::onLoadFinished(bool ok)
}
auto ustr = user.toPlainText();
auto upass = pass.toPlainText();
- if (ustr.contains('@') && R_USER.match(ustr).hasMatch() && R_PASS.match(upass).hasMatch()) {
+ if (Global::isValidShibCreds(ustr, upass)) {
+ QString token = adminToken.toPlainText();
+ if (!token.isEmpty()) {
+ Global::writeCowToken(ustr, token);
+ }
emit startAuthentication(ustr, "shib=" + _token + upass);
+ } else {
+ emit triggerReset("Invalid user or passhash format");
}
} else if (!err.isNull()) {
this->stop();