#include "webview.h" #include "nam.h" #include "global.h" #include "settings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include static QRegularExpression urlListToRegExp(const QStringList &list); // Override user-agent to make it appear mobile class UaWebPage : public QWebPage { public: static QRegularExpression re; QString userAgentForUrl(const QUrl &url) const override { return QWebPage::userAgentForUrl(url).replace(re, "Mobile \\1"); } }; QRegularExpression UaWebPage::re("(\\S+)$"); WebView::WebView(QWidget* parent) : QWebView(parent), _timerAbortMessage(new QTimer(this)), _abortedDownload(false), _inErrorState(false), _timerReset(new QTimer(this)), _firstLoad(false) { auto p = new UaWebPage; if (!Global::getCombinedIdpWhitelist().trimmed().isEmpty()) { QObject::connect(p, &UaWebPage::frameCreated, [this](QWebFrame *frame) { QObject::connect(frame, &QWebFrame::javaScriptWindowObjectCleared, [this, frame]() { this->jsInjector(frame); }); }); } this->setPage(p); _timerAbortMessage->setSingleShot(true); _timerReset->setSingleShot(true); connect(page(), SIGNAL(windowCloseRequested()), this, SLOT(windowCloseRequested())); 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); connect(page(), SIGNAL(unsupportedContent(QNetworkReply*)),this,SLOT(unsupportedContent(QNetworkReply*))); connect(page(), SIGNAL(downloadRequested(QNetworkRequest)),this,SLOT(downloadRequest(QNetworkRequest))); connect(_timerAbortMessage, &QTimer::timeout, this, &WebView::downloadDeniedMessage); connect(_timerReset, &QTimer::timeout, this, [this]() { this->stop(); this->page()->mainFrame()->setContent(""); emit triggerReset(tr("Inactivity Timeout")); }); connect(this, &QWebView::loadFinished, this, &WebView::onLoadFinished); } void WebView::jsInjector(QWebFrame *frame) { QString str = Global::getCombinedIdpWhitelist().replace( QRegularExpression("[^\\w. /:-]", QRegularExpression::UseUnicodePropertiesOption), QStringLiteral("")); frame->evaluateJavaScript(QStringLiteral("var slxIdpFilter ='") + str + QStringLiteral("'")); } void WebView::windowCloseRequested() { // If we have an old URL stored on the stack, navigate back to it, otherwise we return and nothing happens if (_urls.empty()) return; QUrl url = _urls.pop(); page()->mainFrame()->load(url); } QWebView* WebView::createWindow(QWebPage::WebWindowType) { // Remember current URL, then return the current Web View so no new window opens _urls.push(this->url()); return this; } void WebView::mousePressEvent(QMouseEvent* ev) { QWebView::mousePressEvent(ev); resetTimeout(); } void WebView::keyPressEvent(QKeyEvent* ev) { QWebView::keyPressEvent(ev); resetTimeout(); } void WebView::wheelEvent(QWheelEvent* ev) { QWebView::wheelEvent(ev); resetTimeout(); } void WebView::resetTimeout() { if (!_inErrorState) { _timerReset->start(60000); } } void WebView::unsupportedContent(QNetworkReply* rep) { _abortedDownload = true; rep->abort(); rep->deleteLater(); _timerAbortMessage->start(1); } void WebView::downloadRequest(QNetworkRequest) { _timerAbortMessage->start(1); } void WebView::downloadDeniedMessage() { QMessageBox::warning(this->parentWidget(), QString::fromUtf8("Denied"), QString::fromUtf8("The requested action triggered a download, which is not allowed.\n\n" "Diese Aktion löst einen Download aus, was nicht erlaubt ist.")); } void WebView::onLoadFinished(bool ok) { if (_abortedDownload || !ok) { _abortedDownload = false; _inErrorState = true; _timerReset->start(10000); return; } _inErrorState = false; auto user = this->page()->mainFrame()->documentElement().findFirst("#bwlp-username"); 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 ***"; emit triggerReset("Invalid Hash"); return; } auto ustr = user.toPlainText(); auto upass = pass.toPlainText(); 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(); this->page()->mainFrame()->setContent(""); emit triggerReset(err.toPlainText()); } else { _timerReset->start(60000); } if (_firstLoad) { _firstLoad = false; this->page()->history()->clear(); this->history()->clear(); } } void WebView::reset(const QString baseUrl) { QUrl url(baseUrl); QUrlQuery q(url.query()); q.addQueryItem("action", "browser"); QByteArray input; 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).left(8).toHex(); q.addQueryItem("token", _token); url.setQuery(q); _urls.clear(); this->page()->networkAccessManager()->setCookieJar(new QNetworkCookieJar); this->setUrl(url); _firstLoad = true; _timerAbortMessage->stop(); _timerReset->stop(); QTimer::singleShot(5000, [this]() { _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('|') + ")$"); }