/* Copyright (C) 2012 Sebastian Herbord. All rights reserved. This file is part of Mod Organizer. Mod Organizer is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Mod Organizer is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ #include "nxmaccessmanager.h" #include "iplugingame.h" #include "nxmurl.h" #include "report.h" #include "utility.h" #include "selfupdater.h" #include "persistentcookiejar.h" #include "settings.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace MOBase; namespace { QString const Nexus_Management_URL("http://nmm.nexusmods.com"); } // unfortunately Nexus doesn't seem to document these states, all I know is all these listed // are considered premium (27 should be lifetime premium) const std::set NXMAccessManager::s_PremiumAccountStates { 4, 6, 13, 27, 31, 32 }; NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) : QNetworkAccessManager(parent) , m_LoginReply(nullptr) , m_MOVersion(moVersion) { m_LoginTimeout.setSingleShot(true); m_LoginTimeout.setInterval(30000); setCookieJar(new PersistentCookieJar( QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/nexus_cookies.dat"))); if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) { // why is this necessary all of a sudden? setNetworkAccessible(QNetworkAccessManager::Accessible); } } NXMAccessManager::~NXMAccessManager() { if (m_LoginReply != nullptr) { m_LoginReply->deleteLater(); m_LoginReply = nullptr; } } void NXMAccessManager::setNMMVersion(const QString &nmmVersion) { m_NMMVersion = nmmVersion; } QNetworkReply *NXMAccessManager::createRequest( QNetworkAccessManager::Operation operation, const QNetworkRequest &request, QIODevice *device) { if (request.url().scheme() != "nxm") { return QNetworkAccessManager::createRequest(operation, request, device); } if (operation == GetOperation) { emit requestNXMDownload(request.url().toString()); // eat the request, everything else will be done by the download manager return QNetworkAccessManager::createRequest(QNetworkAccessManager::GetOperation, QNetworkRequest(QUrl())); } else if (operation == PostOperation) { return QNetworkAccessManager::createRequest(operation, request, device);; } else { return QNetworkAccessManager::createRequest(operation, request, device); } } void NXMAccessManager::showCookies() const { QUrl url(Nexus_Management_URL + "/"); for (const QNetworkCookie &cookie : cookieJar()->cookiesForUrl(url)) { qDebug("%s - %s (expires: %s)", cookie.name().constData(), cookie.value().constData(), qPrintable(cookie.expirationDate().toString())); } } void NXMAccessManager::clearCookies() { PersistentCookieJar *jar = qobject_cast(cookieJar()); if (jar != nullptr) { jar->clear(); } else { qWarning("failed to clear cookies, invalid cookie jar"); } } void NXMAccessManager::startLoginCheck() { if (hasLoginCookies()) { qDebug("validating login cookies"); QNetworkRequest request(Nexus_Management_URL + "/Sessions/?Validate"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setRawHeader("User-Agent", userAgent().toUtf8()); m_LoginReply = get(request); m_LoginTimeout.start(); m_LoginState = LOGIN_CHECKING; connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginChecked())); connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(loginError(QNetworkReply::NetworkError))); } } void NXMAccessManager::retrieveCredentials() { qDebug("retrieving credentials"); QNetworkRequest request(Nexus_Management_URL + "/Core/Libs/Flamework/Entities/User?GetCredentials"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setRawHeader("User-Agent", userAgent().toUtf8()); QNetworkReply *reply = get(request); QTimer timeout; connect(&timeout, &QTimer::timeout, [reply] () { reply->deleteLater(); }); timeout.start(); connect(reply, &QNetworkReply::finished, [reply, this] () { QJsonDocument jdoc = QJsonDocument::fromJson(reply->readAll()); QJsonArray credentialsData = jdoc.array(); emit credentialsReceived(credentialsData.at(2).toString(), s_PremiumAccountStates.find(credentialsData.at(1).toInt()) != s_PremiumAccountStates.end()); reply->deleteLater(); }); connect(reply, static_cast(&QNetworkReply::error), [=] (QNetworkReply::NetworkError) { qDebug("failed to retrieve account credentials: %s", qPrintable(reply->errorString())); reply->deleteLater(); }); } bool NXMAccessManager::loggedIn() const { if (m_LoginState == LOGIN_CHECKING) { QProgressDialog progress; progress.setLabelText(tr("Verifying Nexus login")); progress.show(); while (m_LoginState == LOGIN_CHECKING) { QCoreApplication::processEvents(); QThread::msleep(100); } progress.hide(); } return m_LoginState == LOGIN_VALID; } void NXMAccessManager::refuseLogin() { m_LoginState = LOGIN_REFUSED; } bool NXMAccessManager::loginAttempted() const { return m_LoginState != LOGIN_NOT_CHECKED; } bool NXMAccessManager::loginWaiting() const { return m_LoginReply != nullptr; } void NXMAccessManager::login(const QString &username, const QString &password) { if (m_LoginReply != nullptr) { return; } if (m_LoginState == LOGIN_VALID) { emit loginSuccessful(false); return; } m_Username = username; m_Password = password; pageLogin(); } QString NXMAccessManager::userAgent(const QString &subModule) const { QStringList comments; comments << "compatible to Nexus Client v" + m_NMMVersion; if (!subModule.isEmpty()) { comments << "module: " + subModule; } return QString("Mod Organizer v%1 (%2)").arg(m_MOVersion, comments.join("; ")); } void NXMAccessManager::pageLogin() { qDebug("logging %s in on Nexus", qPrintable(m_Username)); QString requestString = (Nexus_Management_URL + "/Sessions/?Login&uri=%1") .arg(QString(QUrl::toPercentEncoding(Nexus_Management_URL))); QNetworkRequest request(requestString); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QByteArray postDataQuery; QUrlQuery postData; postData.addQueryItem("username", m_Username); postData.addQueryItem("password", QUrl::toPercentEncoding(m_Password)); postDataQuery = postData.query(QUrl::EncodeReserved).toUtf8(); request.setRawHeader("User-Agent", userAgent().toUtf8()); m_ProgressDialog = new QProgressDialog(nullptr); m_ProgressDialog->setLabelText(tr("Logging into Nexus")); QList buttons = m_ProgressDialog->findChildren(); buttons.at(0)->setEnabled(false); m_ProgressDialog->show(); QCoreApplication::processEvents(); // for some reason the whole app hangs during the login. This way the user has at least a little feedback m_LoginReply = post(request, postDataQuery); m_LoginTimeout.start(); m_LoginState = LOGIN_CHECKING; connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginFinished())); connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(loginError(QNetworkReply::NetworkError))); } void NXMAccessManager::loginTimeout() { m_LoginReply->deleteLater(); m_LoginReply = nullptr; m_LoginAttempted = false; // this usually means we might have success later m_Username.clear(); m_Password.clear(); m_LoginState = LOGIN_NOT_VALID; emit loginFailed(tr("timeout")); } void NXMAccessManager::loginError(QNetworkReply::NetworkError) { qDebug("login error"); if (m_ProgressDialog != nullptr) { m_ProgressDialog->deleteLater(); m_ProgressDialog = nullptr; } m_Username.clear(); m_Password.clear(); m_LoginState = LOGIN_NOT_VALID; if (m_LoginReply != nullptr) { emit loginFailed(m_LoginReply->errorString()); m_LoginReply->deleteLater(); m_LoginReply = nullptr; } else { emit loginFailed(tr("Unknown error")); } } bool NXMAccessManager::hasLoginCookies() const { QUrl url(Nexus_Management_URL + "/"); QList cookies = cookieJar()->cookiesForUrl(url); for (const QNetworkCookie &cookie : cookies) { if (cookie.name() == "sid") { return true; } } return false; } void NXMAccessManager::loginFinished() { if (m_ProgressDialog != nullptr) { m_ProgressDialog->deleteLater(); m_ProgressDialog = nullptr; } m_LoginReply->deleteLater(); m_LoginReply = nullptr; m_Username.clear(); m_Password.clear(); if (hasLoginCookies()) { m_LoginState = LOGIN_VALID; retrieveCredentials(); emit loginSuccessful(true); } else { m_LoginState = LOGIN_NOT_VALID; emit loginFailed(tr("Please check your password")); } } void NXMAccessManager::loginChecked() { QNetworkReply *reply = static_cast(sender()); QByteArray data = reply->readAll(); m_LoginState = data == "null" ? LOGIN_NOT_VALID : LOGIN_VALID; if (m_LoginState == LOGIN_VALID) { retrieveCredentials(); } else { qDebug("cookies seem to be invalid"); } m_LoginReply->deleteLater(); m_LoginReply = nullptr; }