#include "webview.h"
#include "nam.h"
#include "global.h"
#include <QWebFrame>
#include <QNetworkReply>
#include <QMessageBox>
#include <QTimer>
#include <QUrlQuery>
#include <QCryptographicHash>
#include <QCursor>
#include <QWebHistory>
#include <QNetworkCookieJar>
#include <QWebElement>
#include <QRegularExpression>
#include <QWebPage>
#include <QWebFrame>
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->resetBrowserAndTimeout();
emit triggerReset(tr("Inactivity Timeout"));
});
connect(this, &QWebView::loadFinished, this, &WebView::onLoadFinished);
}
void WebView::resetBrowserAndTimeout()
{
_timerReset->stop();
_timerAbortMessage->stop();
this->stop();
this->page()->mainFrame()->setContent("");
}
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::asprintf("%d %d", QCursor::pos().x(), QCursor::pos().y()).toUtf8());
input.append(QString::number(QDateTime::currentMSecsSinceEpoch()).toUtf8());
_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();
}
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('|') + ")$");
}