summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2024-03-25 17:27:15 +0100
committerSimon Rettberg2024-03-25 17:27:15 +0100
commit80917a8b79b0052d07060143fff346eddd189786 (patch)
treef4fc7f7cf90c5280dcda30bddbe3b315b9310de1
parentAdd black/whitelist feature to browser-based login (diff)
downloadslxgreeter-master.tar.gz
slxgreeter-master.tar.xz
slxgreeter-master.zip
Add support for QRCode loginHEADmaster
-rw-r--r--src/loginform.cpp60
-rw-r--r--src/loginform.h6
-rw-r--r--src/loginform.ui37
-rw-r--r--src/qrlogin.cpp167
-rw-r--r--src/qrlogin.h33
-rw-r--r--src/settings.h2
-rw-r--r--src/snake.cpp65
-rw-r--r--src/snake.h1
-rw-r--r--src/webview.cpp2
9 files changed, 364 insertions, 9 deletions
diff --git a/src/loginform.cpp b/src/loginform.cpp
index bb4fdb9..230f408 100644
--- a/src/loginform.cpp
+++ b/src/loginform.cpp
@@ -17,6 +17,7 @@
#include "global.h"
#include "namereplace.h"
#include "loginrpc.h"
+#include "qrlogin.h"
#undef KeyPress
#undef KeyRelease
#undef FocusIn
@@ -44,7 +45,9 @@ LoginForm::LoginForm(QWidget *parent) :
browser(nullptr),
clearMsg(false),
capsOn(-1),
- pageCount(0)
+ pageCount(0),
+ qrcode(nullptr),
+ qrlogin(nullptr)
{
ui->setupUi(this);
origSize = shibSize = sizeHint();
@@ -160,6 +163,8 @@ void LoginForm::initialize()
// timer to reset the form to its original state
if (Settings::resetForm() > 0) {
connect(&resetFormTimer, &QTimer::timeout, [this]() {
+ if (ui->loginChooser->currentWidget() == ui->qrPage)
+ return;
int idleTime = static_cast<int>(getIdleTime(QX11Info::display()));
int remaining = Settings::resetForm() * 1000 - idleTime;
if (remaining <= 0) {
@@ -210,6 +215,18 @@ void LoginForm::initialize()
ui->shibButton->hide();
}
+ if (Settings::qrSessionEnabled()) {
+ pageCount += 2; // Fake this so we always return on timeout;
+ // otherwise, the qr code could expire after some time, breaking
+ // the login process.
+ if (!Settings::qrSessionButtonText().isEmpty()) {
+ ui->qrButton->setText(Settings::qrSessionButtonText());
+ }
+ connect(ui->qrButton, &QAbstractButton::released, this, &LoginForm::showQrWindow);
+ } else {
+ ui->qrButton->hide();
+ }
+
if (Settings::userSessionEnabled()) {
pageCount++;
} else {
@@ -296,6 +313,31 @@ void LoginForm::showShibWindow() {
setBrowserSize();
}
+void LoginForm::showQrWindow() {
+ if (qrcode == nullptr) {
+ qrcode = new QLabel(this);
+ ui->vlQrCode->addWidget(qrcode);
+ qrcode->setBackgroundRole(QPalette::Shadow);
+ //connect(browser, &WebView::startAuthentication, this, &LoginForm::startAuthAs);
+ }
+ if (qrlogin != nullptr) {
+ disconnect(qrlogin);
+ qrlogin->deleteLater();
+ }
+ qrlogin = new QrLogin(this);
+ connect(qrlogin, &QrLogin::startAuthentication, this, &LoginForm::startAuthAs);
+ connect(qrlogin, &QrLogin::updateStatus, this, &LoginForm::showLowPrioMessage);
+ connect(qrlogin, &QrLogin::triggerReset, [this](const QString &message) {
+ this->showMessage(message, true);
+ ui->loginChooser->setCurrentWidget(ui->welcomePage);
+ });
+ QPixmap pm(20, 20);
+ pm.fill(Qt::red);
+ qrcode->setPixmap(pm);
+ qrlogin->loadQrCode(qrcode);
+ ui->loginChooser->setCurrentWidget(ui->qrPage);
+}
+
void LoginForm::checkCaps()
{
unsigned int mask = getKeyMask(QX11Info::display());
@@ -407,6 +449,9 @@ void LoginForm::cancelLogin()
std::cerr << "Was in authentication" << std::endl;
Global::greeter()->cancelAuthentication();
}
+ if (ui->loginChooser->currentWidget() != ui->loginPage && pageCount > 1) {
+ ui->loginChooser->setCurrentWidget(ui->welcomePage);
+ }
cancelLoginTimer.stop();
ui->passwordInput->clear();
enableInputs(true);
@@ -425,11 +470,20 @@ void LoginForm::enableInputs(bool enable)
ui->passwordInput->setEnabled(enable);
ui->backButton->setEnabled(enable);
ui->guestButton->setEnabled(enable);
+ ui->shibButton->setEnabled(enable);
+ ui->qrButton->setEnabled(enable);
if (browser != nullptr) {
browser->setEnabled(enable);
}
}
+void LoginForm::showLowPrioMessage(QString message)
+{
+ if (!ui->messageLabel->text().isEmpty() && !ui->messageLabel->styleSheet().isEmpty())
+ return;
+ showMessage(message, false);
+}
+
void LoginForm::showMessage(QString message, bool error)
{
hideMessageTimer.stop();
@@ -478,6 +532,10 @@ void LoginForm::keyPressEvent(QKeyEvent *event)
void LoginForm::resetForm()
{
std::cerr << "PageCount: " << pageCount << std::endl;
+ if (ui->loginChooser->currentWidget() == ui->qrPage && qrlogin != nullptr) {
+ qrlogin->abort();
+ hideMessage();
+ }
if (pageCount > 1) {
ui->loginChooser->setCurrentWidget(ui->welcomePage);
}
diff --git a/src/loginform.h b/src/loginform.h
index 10e1d2b..6a05ea9 100644
--- a/src/loginform.h
+++ b/src/loginform.h
@@ -27,6 +27,8 @@ class LoginForm;
}
class WebView;
+class QLabel;
+class QrLogin;
class LoginForm : public QWidget
{
@@ -50,6 +52,8 @@ public slots:
private slots:
void setBrowserSize();
void showShibWindow();
+ void showQrWindow();
+ void showLowPrioMessage(QString message);
signals:
void resized();
@@ -79,6 +83,8 @@ private:
QSize shibSize, origSize;
WebView *browser;
+ QLabel *qrcode;
+ QrLogin *qrlogin;
bool clearMsg;
int capsOn;
diff --git a/src/loginform.ui b/src/loginform.ui
index 6a0fa63..66e47fa 100644
--- a/src/loginform.ui
+++ b/src/loginform.ui
@@ -290,7 +290,7 @@ QComboBox QAbstractItemView::item {
</property>
</widget>
</item>
- <item row="4" column="0">
+ <item row="5" column="0">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
@@ -303,6 +303,25 @@ QComboBox QAbstractItemView::item {
</property>
</spacer>
</item>
+ <item row="4" column="0">
+ <widget class="QPushButton" name="qrButton">
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>50</height>
+ </size>
+ </property>
+ <property name="font">
+ <font>
+ <pointsize>10</pointsize>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string>QRCode-Login</string>
+ </property>
+ </widget>
+ </item>
</layout>
</widget>
<widget class="QWidget" name="loginPage">
@@ -531,6 +550,22 @@ QComboBox QAbstractItemView::item {
</property>
</layout>
</widget>
+ <widget class="QWidget" name="qrPage">
+ <layout class="QVBoxLayout" name="vlQrCode">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
</widget>
</item>
<item>
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;
+}
diff --git a/src/qrlogin.h b/src/qrlogin.h
new file mode 100644
index 0000000..fa8cc82
--- /dev/null
+++ b/src/qrlogin.h
@@ -0,0 +1,33 @@
+#ifndef _QRLOGIN_H_
+#define _QRLOGIN_H_
+
+#include <QObject>
+
+class QLabel;
+class QTimer;
+class QElapsedTimer;
+class QNetworkAccessManager;
+
+class QrLogin : public QObject
+{
+ Q_OBJECT
+public:
+ explicit QrLogin(QObject *parent);
+ void loadQrCode(QLabel *dest);
+ void abort();
+
+signals:
+ void triggerReset(const QString &message);
+ void startAuthentication(const QString &user, const QString &pass);
+ void updateStatus(const QString &msg);
+
+private:
+ QLabel *dest;
+ QTimer *timer;
+ QNetworkAccessManager *nam;
+ QString token;
+ QByteArray svgData;
+ QElapsedTimer *elapsed;
+};
+
+#endif
diff --git a/src/settings.h b/src/settings.h
index b00bce6..5bda9b0 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -48,10 +48,12 @@ public:
static bool userSessionEnabled() { return s_settings->value("user-session-enabled", "1").toBool(); }
static bool guestSessionEnabled() { return s_settings->value("guest-session-enabled").toBool(); }
static bool shibSessionEnabled() { return s_settings->value("shib-session-enabled").toBool(); }
+ static bool qrSessionEnabled() { return s_settings->value("qr-session-enabled").toBool(); }
static QString shibUrl() { return s_settings->value("shib-url").toString(); }
static QString urlWhitelistFile() { return s_settings->value("shib-url-whitelist").toString(); }
static QString urlBlacklistFile() { return s_settings->value("shib-url-blacklist").toString(); }
static QString shibSessionButtonText() { return s_settings->value("shib-session-button-text").toString(); }
+ static QString qrSessionButtonText() { return s_settings->value("qr-session-button-text").toString(); }
static QString userSessionButtonText() { return s_settings->value("user-session-button-text").toString(); }
static QString guestSessionButtonText() { return s_settings->value("guest-session-button-text").toString(); }
static QString guestSessionStartText() { return s_settings->value("guest-session-start-text").toString(); }
diff --git a/src/snake.cpp b/src/snake.cpp
index e855250..8d45dd9 100644
--- a/src/snake.cpp
+++ b/src/snake.cpp
@@ -22,11 +22,13 @@
#define CELL_BREAKOUT_BRICK 4
#define CELL_SNAKEBRICK 5
#define CELL_PADDLE 6
+#define CELL_GOL 7
#define AXIS_X 0
#define AXIS_Y 1
#define FIELD(x,y) _field[(x) + ((y) * _width)]
+#define FIELD2(x,y) _field2[(x) + ((y) * _width)]
struct Paddle;
@@ -38,15 +40,17 @@ struct Cell
Cell(int t, QColor c) : type(t), color(QBrush(c)), player(nullptr) {}
bool isFood() const { return type == CELL_FOOD; }
bool willKill() const { return type == CELL_PADDLE_BACKING | type == CELL_PADDLE || type == CELL_SNAKE || type == CELL_SNAKEBRICK; }
- bool isFree() const { return type == CELL_FREE; }
- bool isPaddleFree() const { return type == CELL_FREE || type == CELL_SNAKEBRICK || type == CELL_BREAKOUT_BRICK || type == CELL_FOOD; }
+ bool isFree() const { return type == CELL_FREE || type == CELL_GOL; }
+ bool isPaddleFree() const { return type == CELL_FREE || type == CELL_SNAKEBRICK || type == CELL_BREAKOUT_BRICK || type == CELL_FOOD || type == CELL_GOL; }
bool isBrick() const { return type == CELL_BREAKOUT_BRICK || type == CELL_SNAKEBRICK; }
- bool canSpawnFood() const { return type == CELL_SNAKE || type == CELL_FREE || type == CELL_BREAKOUT_BRICK; }
- bool ballWillDestroy() const { return isFood() || isBrick(); }
+ bool canSpawnFood() const { return type == CELL_SNAKE || type == CELL_FREE || type == CELL_BREAKOUT_BRICK || type == CELL_GOL; }
+ bool ballWillDestroy() const { return isFood() || isBrick() || type == CELL_GOL; }
+ bool isAlive() const { return type == CELL_FOOD || type == CELL_BREAKOUT_BRICK || type == CELL_GOL; }
};
static const Cell empty(CELL_FREE, QColor());
static const Cell snakebrick(CELL_SNAKEBRICK, QColor::fromRgb(200, 200, 200));
+static const Cell golBrick(CELL_GOL, QColor::fromRgb(180, 200, 220));
static Cell breakoutCenterBrick(CELL_BREAKOUT_BRICK, QColor::fromRgb(255, 255, 255));
static const Cell food[] = {
Cell(CELL_FOOD, QColor::fromRgb(0, 255, 0)),
@@ -133,6 +137,8 @@ public:
};
void GameCore::setField(int x, int y, const Cell *val) {
+ if (_field[x + _width * y] == val)
+ return;
_field[x + _width * y] = val;
_widget->update(x * SCALING, y * SCALING, SCALING, SCALING);
}
@@ -151,6 +157,7 @@ GameCore::GameCore(QWidget *widget)
return;
int cellCount = _width * _height;
_field = (const Cell**)calloc(cellCount, sizeof(*_field));
+ _field2 = (const Cell**)calloc(cellCount, sizeof(*_field));
for (int i = 0; i < cellCount; ++i) {
_field[i] = &empty;
}
@@ -462,7 +469,53 @@ GameCore::GameCore(QWidget *widget)
}
} // Done with movement logic
} // End loop over paddles
- }
+ } // End paddles
+ // Game of life
+ if (tick % 13 == 0) {
+ auto **tmp = _field;
+ _field = _field2;
+ _field2 = tmp;
+ memcpy(_field, _field2, _width * _height * sizeof(void*));
+ bool killall = ((tick / 4000) % 2);
+ int killed = 0;
+ for (int y = 0; y < _height; ++y) {
+ for (int x = 0; x < _width; ++x) {
+ if (FIELD2(x,y) != &golBrick && FIELD2(x,y) != &empty) {
+ FIELD(x,y) = FIELD2(x,y);
+ continue;
+ }
+ if (killall) {
+ if (FIELD2(x,y) == &golBrick) {
+ setField(x,y, &empty);
+ if (++killed > 5) {
+ goto endgol;
+ }
+ }
+ continue;
+ }
+ int alive = 0;
+ // Direct
+ if (x > 0 && FIELD2(x-1,y)->isAlive()) alive++;
+ if (y > 0 && FIELD2(x,y-1)->isAlive()) alive++;
+ if (x+1 < _width && FIELD2(x+1,y)->isAlive()) alive++;
+ if (y+1 < _height && FIELD2(x,y+1)->isAlive()) alive++;
+ // Diagonal
+ if (x > 0 && y > 0 && FIELD2(x-1,y-1)->isAlive()) alive++;
+ if (x > 0 && y+1 < _height && FIELD2(x-1,y+1)->isAlive()) alive++;
+ if (y > 0 && x+1 < _width && FIELD2(x+1,y-1)->isAlive()) alive++;
+ if (x+1 < _width && y+1 < _height && FIELD2(x+1,y+1)->isAlive()) alive++;
+ // Check
+ if (alive < 2 || alive > 3) {
+ setField(x,y, &empty);
+ } else if (alive == 3) {
+ setField(x,y, &golBrick);
+ } else {
+ FIELD(x,y) = FIELD2(x,y);
+ }
+ }
+ }
+endgol:;
+ } // End GOL
});
}
@@ -623,7 +676,7 @@ void GameCore::paint(QPaintEvent *event)
if (x < 0)
continue;
const Cell *c = FIELD(x, y);
- if (c->isFree())
+ if (c == &empty)
continue;
p.setBrush(c->color);
p.drawRect(x * SCALING, y * SCALING, SCALING-1, SCALING-1);
diff --git a/src/snake.h b/src/snake.h
index 538a944..b3b8433 100644
--- a/src/snake.h
+++ b/src/snake.h
@@ -27,6 +27,7 @@ private:
qint64 _lastMeal;
qint64 _lastPaddle;
const Cell **_field;
+ const Cell **_field2;
public:
const Cell *field(int x, int y) const {
diff --git a/src/webview.cpp b/src/webview.cpp
index b178d73..54d19eb 100644
--- a/src/webview.cpp
+++ b/src/webview.cpp
@@ -171,7 +171,7 @@ void WebView::reset(const QString baseUrl)
input.append((const char*)this, sizeof(*this));
input.append(QString().sprintf("%d %d", QCursor::pos().x(), QCursor::pos().y()));
input.append(QString::number(QDateTime::currentMSecsSinceEpoch()));
- _token = QCryptographicHash::hash(input, QCryptographicHash::Md5).chopped(8).toHex();
+ _token = QCryptographicHash::hash(input, QCryptographicHash::Md5).left(8).toHex();
q.addQueryItem("token", _token);
url.setQuery(q);
_urls.clear();