From 9fe2f6126dc7b9396d188b57ccb097f0035f57b7 Mon Sep 17 00:00:00 2001 From: Silarn Date: Mon, 20 Aug 2018 18:34:31 -0500 Subject: Initial Nexus API changes: - Switch to SSO with WebSocket - Update endpoints (all but version checking) --- src/nxmaccessmanager.cpp | 615 ++++++++++++++++++++--------------------------- 1 file changed, 264 insertions(+), 351 deletions(-) (limited to 'src/nxmaccessmanager.cpp') diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 912eab30..dc1eb2cb 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -1,351 +1,264 @@ -/* -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("https://legacy-api.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(), - qUtf8Printable(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", qUtf8Printable(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 << "Nexus Client v" + m_NMMVersion; - if (!subModule.isEmpty()) { - comments << "module: " + subModule; - } - - return QString("Mod Organizer/%1 (%2)").arg(m_MOVersion, comments.join("; ")); -} - - -void NXMAccessManager::pageLogin() -{ - qDebug("logging %s in on Nexus", qUtf8Printable(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; -} +/* +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 nexusBaseUrl("https://api.nexusmods.com/v1"); +} + +NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) + : QNetworkAccessManager(parent) + , m_ValidateReply(nullptr) + , m_MOVersion(moVersion) +{ + m_ValidateTimeout.setSingleShot(true); + m_ValidateTimeout.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_ValidateReply != nullptr) { + m_ValidateReply->deleteLater(); + m_ValidateReply = 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(nexusBaseUrl + "/"); + for (const QNetworkCookie &cookie : cookieJar()->cookiesForUrl(url)) { + qDebug("%s - %s (expires: %s)", + cookie.name().constData(), cookie.value().constData(), + qUtf8Printable(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::startValidationCheck() +{ + qDebug("Checking Nexus API Key..."); + QString requestString = nexusBaseUrl + "/users/validate"; + + QNetworkRequest request(requestString); + request.setRawHeader("APIKEY", m_ApiKey.toUtf8()); + request.setRawHeader("User-Agent", userAgent().toUtf8()); + + m_ProgressDialog = new QProgressDialog(nullptr); + m_ProgressDialog->setLabelText(tr("Validating Nexus Connection")); + 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_ValidateReply = get(request); + m_ValidateTimeout.start(); + m_ValidateState = VALIDATE_CHECKING; + connect(m_ValidateReply, SIGNAL(finished()), this, SLOT(validateFinished())); + connect(m_ValidateReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(validateError(QNetworkReply::NetworkError))); +} + + +bool NXMAccessManager::validated() const +{ + if (m_ValidateState == VALIDATE_CHECKING) { + QProgressDialog progress; + progress.setLabelText(tr("Verifying Nexus login")); + progress.show(); + while (m_ValidateState == VALIDATE_CHECKING) { + QCoreApplication::processEvents(); + QThread::msleep(100); + } + progress.hide(); + } + + return m_ValidateState == VALIDATE_VALID; +} + + +void NXMAccessManager::refuseValidation() +{ + m_ValidateState = VALIDATE_REFUSED; +} + + +bool NXMAccessManager::validateAttempted() const +{ + return m_ValidateState != VALIDATE_NOT_CHECKED; +} + + +bool NXMAccessManager::validateWaiting() const +{ + return m_ValidateReply != nullptr; +} + + +void NXMAccessManager::apiCheck(const QString &apiKey) +{ + if (m_ValidateReply != nullptr) { + return; + } + + if (m_ValidateState == VALIDATE_VALID) { + emit validateSuccessful(false); + return; + } + m_ApiKey = apiKey; + startValidationCheck(); +} + + +QString NXMAccessManager::userAgent(const QString &subModule) const +{ + QStringList comments; + comments << "Nexus Client v" + m_NMMVersion; + if (!subModule.isEmpty()) { + comments << "module: " + subModule; + } + + return QString("Mod Organizer/%1 (%2)").arg(m_MOVersion, comments.join("; ")); +} + + +QString NXMAccessManager::apiKey() const +{ + return m_ApiKey; +} + + +void NXMAccessManager::validateTimeout() +{ + m_ValidateReply->deleteLater(); + m_ValidateReply = nullptr; + m_ValidateAttempted = false; // this usually means we might have success later + m_ApiKey.clear(); + m_ValidateState = VALIDATE_NOT_VALID; + + emit validateFailed(tr("timeout")); +} + + +void NXMAccessManager::validateError(QNetworkReply::NetworkError) +{ + qDebug("login error"); + if (m_ProgressDialog != nullptr) { + m_ProgressDialog->deleteLater(); + m_ProgressDialog = nullptr; + } + m_ApiKey.clear(); + m_ValidateState = VALIDATE_NOT_VALID; + + if (m_ValidateReply != nullptr) { + emit validateFailed(m_ValidateReply->errorString()); + m_ValidateReply->deleteLater(); + m_ValidateReply = nullptr; + } else { + emit validateFailed(tr("Unknown error")); + } +} + + +void NXMAccessManager::validateFinished() +{ + if (m_ProgressDialog != nullptr) { + m_ProgressDialog->deleteLater(); + m_ProgressDialog = nullptr; + } + + if (m_ValidateReply != nullptr) { + QJsonDocument jdoc = QJsonDocument::fromJson(m_ValidateReply->readAll()); + QJsonObject credentialsData = jdoc.object(); + QString test = jdoc.toJson(); + QString name = credentialsData.value("name").toString(); + bool premium = credentialsData.value("is_premium?").toBool(); + emit credentialsReceived(credentialsData.value("name").toString(), + credentialsData.value("is_premium?").toBool()); + + m_ValidateReply->deleteLater(); + m_ValidateReply = nullptr; + + m_ValidateState = VALIDATE_VALID; + emit validateSuccessful(true); + } +} -- cgit v1.3.1