From bcd99e02640cc35bb9a3f9ec9ad919b51b1c3482 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Wed, 7 Feb 2024 18:25:48 +0100 Subject: Add black/whitelist feature to browser-based login --- src/global.cpp | 36 ++++++++++++++++++++++++++++++++++ src/global.h | 4 ++++ src/nam.cpp | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/nam.h | 38 ++++++++++++++++++++++++++++++++++++ src/settings.h | 2 ++ src/webview.cpp | 60 +++++++++++++++++++++++++++++++++++++++++---------------- 6 files changed, 177 insertions(+), 17 deletions(-) create mode 100644 src/nam.cpp create mode 100644 src/nam.h diff --git a/src/global.cpp b/src/global.cpp index fd6e321..4efccad 100644 --- a/src/global.cpp +++ b/src/global.cpp @@ -80,3 +80,39 @@ QImage Global::getConfigGradient() } return img; } + +QStringList loadUrlList(const QString &file) +{ + QStringList stringList; + QFile textFile(file); + if (!textFile.open(QFile::ReadOnly)) { + QTextStream(stdout) << "Cannot open URL list\n"; + return QStringList(); + } + QTextStream textStream(&textFile); + while (true) + { + QString line = textStream.readLine(); + if (line.isNull()) + break; + else + stringList.append(line); + } + return stringList; +} + +QStringList Global::urlBlacklist() +{ + auto path = Settings::urlBlacklistFile(); + if (!QFile::exists(path)) + return QStringList(); + return loadUrlList(path); +} + +QStringList Global::urlWhitelist() +{ + auto path = Settings::urlWhitelistFile(); + if (!QFile::exists(path)) + return QStringList(); + return loadUrlList(path); +} diff --git a/src/global.h b/src/global.h index 91b878f..bdc79cd 100644 --- a/src/global.h +++ b/src/global.h @@ -48,6 +48,10 @@ public: static QImage getConfigGradient(); + static QStringList urlWhitelist(); + + static QStringList urlBlacklist(); + private: static bool m_testMode; static QLightDM::Greeter *m_Greeter; diff --git a/src/nam.cpp b/src/nam.cpp new file mode 100644 index 0000000..6128c40 --- /dev/null +++ b/src/nam.cpp @@ -0,0 +1,54 @@ +#include "nam.h" + +#include +#include + +SlxDisabledNetworkReply::SlxDisabledNetworkReply(QObject *parent, const QNetworkRequest &req, + QNetworkAccessManager::Operation op) + : QNetworkReply(parent) +{ + setRequest(req); + setUrl(req.url()); + setOperation(op); + setFinished(true); + qRegisterMetaType(); + QString msg = QCoreApplication::translate("QNetworkAccessManager", + "Network access is disabled."); + setError(UnknownNetworkError, msg); + QMetaObject::invokeMethod(this, "error", Qt::QueuedConnection, + Q_ARG(QNetworkReply::NetworkError, UnknownNetworkError)); + QMetaObject::invokeMethod(this, "finished", Qt::QueuedConnection); +} + +QNetworkReply* SlxNetworkAccessManager::createRequest(QNetworkAccessManager::Operation op, + const QNetworkRequest &req, QIODevice *outgoingData) +{ + const QUrl url(req.url()); + //qDebug() << url; + bool ok; + if (url.isLocalFile()) { + ok = true; + } else if (url.scheme() == QLatin1String("qrc") || url.scheme() == QLatin1String("data")) { + ok = true; + } else if (url.host().isEmpty()) { + ok = true; + } else { + auto str = url.toDisplayString(QUrl::NormalizePathSegments); + if (_white.isValid() && _white.match(str).hasMatch()) { + // We have a whitelist, and it matches - allow + ok = true; + } else if (_black.isValid() && _black.match(str).hasMatch()) { + // we have a blacklist, and it matches - deny + ok = false; + } else { + // neither matched; if we have a whitelist: deny; otherwise: allow + ok = !_white.isValid(); + } + } + if (!ok) { + return new SlxDisabledNetworkReply(this, req, op); + } + auto cp(req); + cp.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, QVariant(false)); + return QNetworkAccessManager::createRequest(op, cp, outgoingData); +} diff --git a/src/nam.h b/src/nam.h new file mode 100644 index 0000000..ec2bb17 --- /dev/null +++ b/src/nam.h @@ -0,0 +1,38 @@ +#ifndef NAM_H_ +#define NAM_H_ + +#include +#include +#include + +/** + * Block certain requests based on URL + */ +class SlxNetworkAccessManager : public QNetworkAccessManager +{ +Q_OBJECT +public: + SlxNetworkAccessManager(QRegularExpression blackList, QRegularExpression whiteList, QObject *parent = nullptr) + : QNetworkAccessManager(parent), _black(blackList), _white(whiteList) {} +protected: + QNetworkReply* createRequest(QNetworkAccessManager::Operation op, + const QNetworkRequest &originalReq, QIODevice *outgoingData = nullptr) override; +private: + QRegularExpression _black, _white; +}; + + +class SlxDisabledNetworkReply : public QNetworkReply +{ + Q_OBJECT +public: + SlxDisabledNetworkReply(QObject *parent, const QNetworkRequest &req, + QNetworkAccessManager::Operation op); + + ~SlxDisabledNetworkReply() {} + void abort() override { } +protected: + qint64 readData(char *, qint64) override { return -1; } +}; + +#endif diff --git a/src/settings.h b/src/settings.h index fe5f530..b00bce6 100644 --- a/src/settings.h +++ b/src/settings.h @@ -49,6 +49,8 @@ public: static bool guestSessionEnabled() { return s_settings->value("guest-session-enabled").toBool(); } static bool shibSessionEnabled() { return s_settings->value("shib-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 userSessionButtonText() { return s_settings->value("user-session-button-text").toString(); } static QString guestSessionButtonText() { return s_settings->value("guest-session-button-text").toString(); } diff --git a/src/webview.cpp b/src/webview.cpp index e0b47f3..b178d73 100644 --- a/src/webview.cpp +++ b/src/webview.cpp @@ -1,4 +1,7 @@ #include "webview.h" +#include "nam.h" +#include "global.h" + #include #include #include @@ -11,11 +14,12 @@ #include #include #include -#include 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 class UaWebPage : public QWebPage { @@ -27,19 +31,6 @@ public: } }; -class Nam : public QNetworkAccessManager -{ -public: - explicit Nam(QObject *parent = nullptr) : QNetworkAccessManager(parent) {} -protected: - virtual QNetworkReply *createRequest(Operation op, const QNetworkRequest &request, - QIODevice *outgoingData = nullptr) override { - auto cp(request); - cp.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, QVariant(false)); - return QNetworkAccessManager::createRequest(op, cp, outgoingData); - } -}; - QRegularExpression UaWebPage::re("(\\S+)$"); WebView::WebView(QWidget* parent) @@ -49,12 +40,15 @@ WebView::WebView(QWidget* parent) _inErrorState(false), _timerReset(new QTimer(this)), _firstLoad(false) - { +{ this->setPage(new UaWebPage); _timerAbortMessage->setSingleShot(true); _timerReset->setSingleShot(true); connect(page(), SIGNAL(windowCloseRequested()), this, SLOT(windowCloseRequested())); - page()->setNetworkAccessManager(new Nam(this)); + auto bl = Global::urlBlacklist(); + auto wl = Global::urlWhitelist(); + page()->setNetworkAccessManager(new SlxNetworkAccessManager(urlListToRegExp(bl), + urlListToRegExp(wl), this)); page()->setForwardUnsupportedContent(true); page()->settings()->setAttribute(QWebSettings::LocalStorageEnabled, true); //page()->settings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); @@ -181,7 +175,6 @@ void WebView::reset(const QString baseUrl) q.addQueryItem("token", _token); url.setQuery(q); _urls.clear(); - qDebug() << "Bahnahne"; this->page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar); this->setUrl(url); _firstLoad = true; @@ -191,3 +184,36 @@ void WebView::reset(const QString baseUrl) _timerReset->stop(); }); } + +static QRegularExpression urlListToRegExp(const QStringList &list) +{ + if (list.isEmpty()) + return QRegularExpression("(["); // Return an invalid regex, we use .isValid to check if the list is to be used + // We search in the escaped string, so actually look for \*\* and \* + // Capture char before that because it must not be another backslash, as that + // means the star was already escaped in the list. + // Since these are C strings there are some additional backslashes here. + static const QRegularExpression STARSTAR("(^|[^\\\\])\\\\\\*\\\\\\*"); + static const QRegularExpression STAR("(^|[^\\\\])\\\\\\*"); + static const QRegularExpression QUEST("(^|[^\\\\])\\\\\\?"); + static const QString STARSTAR_REP("\\1.*"); + static const QString STAR_REP("\\1[^/]*"); + static const QString QUEST_REP("\\1.?"); + QStringList regexes; // All my regex's live in Regtexas + for (const QString &str : list) { + QString mangled; + if (str.contains(QLatin1String("//"))) { + mangled = str; + } else if (str.contains(QLatin1Char('/')) || str.contains(QLatin1String("**"))) { + mangled = "*//" + str; + } else { + mangled = "*//" + str + "/**"; + } + mangled = QRegularExpression::escape(mangled); + mangled = mangled.replace(STARSTAR, STARSTAR_REP).replace(STAR, STAR_REP) + .replace(QUEST, QUEST_REP); + regexes << mangled; + } + qDebug() << regexes; + return QRegularExpression("^(" + regexes.join('|') + ")$"); +} -- cgit v1.2.3-55-g7522