Tidy networking code

This commit is contained in:
David Rowe 2020-08-03 09:17:12 +12:00
parent dfbad4efc0
commit 43b6e77235
5 changed files with 54 additions and 49 deletions

View file

@ -85,9 +85,9 @@
"backup": false "backup": false
}, },
{ {
"name": "oauth2_url_base", "name": "oauth2_url_path",
"label": "Authentication URL Base", "label": "Authentication URL",
"help": "The URL base that the Interface will use to login via OAuth2.", "help": "The URL that the Interface will use to login via OAuth2.",
"advanced": true "advanced": true
}, },
{ {

View file

@ -450,7 +450,7 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
} }
const QString AUTHENTICATION_ENAABLED = "authentication.enable_oauth2"; const QString AUTHENTICATION_ENAABLED = "authentication.enable_oauth2";
const QString AUTHENTICATION_OAUTH2_URL_BASE = "authentication.oauth2_url_base"; const QString AUTHENTICATION_OAUTH2_URL_PATH = "authentication.oauth2_url_path";
const QString AUTHENTICATION_WORDPRESS_URL_BASE = "authentication.wordpress_url_base"; const QString AUTHENTICATION_WORDPRESS_URL_BASE = "authentication.wordpress_url_base";
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location"; const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location";
@ -548,10 +548,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
if (domainHasLogin()) { if (domainHasLogin()) {
QString domainAuthURL; QString domainAuthURL;
auto domainAuthURLVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_BASE); auto domainAuthURLVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_PATH);
if (domainAuthURLVariant.canConvert<QString>()) { if (domainAuthURLVariant.canConvert<QString>()) {
domainAuthURL = domainAuthURLVariant.toString(); domainAuthURL = domainAuthURLVariant.toString();
qDebug() << "Domain authorization URL:" << domainAuthURL;
} }
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain, domainAuthURL); nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain, domainAuthURL);
@ -1220,7 +1219,7 @@ bool DomainGatekeeper::domainHasLogin() {
// The domain may have its own users and groups in a WordPress site. // The domain may have its own users and groups in a WordPress site.
// ####### TODO: Add checks of any further domain server settings used. // ####### TODO: Add checks of any further domain server settings used.
return _server->_settingsManager.valueForKeyPath(AUTHENTICATION_ENAABLED).toBool() return _server->_settingsManager.valueForKeyPath(AUTHENTICATION_ENAABLED).toBool()
&& !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_BASE).toString().isEmpty() && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_PATH).toString().isEmpty()
&& !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString().isEmpty(); && !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString().isEmpty();
} }
@ -1233,7 +1232,7 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString&
} }
if (_inFlightDomainUserIdentityRequests.contains(username)) { if (_inFlightDomainUserIdentityRequests.contains(username)) {
// Domain identify request for this username is already flight. // Domain identify request for this username is already in progress.
return; return;
} }
_inFlightDomainUserIdentityRequests.insert(username, QPair<QString, QString>(accessToken, refreshToken)); _inFlightDomainUserIdentityRequests.insert(username, QPair<QString, QString>(accessToken, refreshToken));
@ -1248,9 +1247,9 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString&
} }
// Get data pertaining to "me", the user who generated the access token. // Get data pertaining to "me", the user who generated the access token.
// ####### TODO: Confirm API_ROUTE w.r.t. OAuth2 plugin's capabilities. // ####### TODO: Confirm API route and data w.r.t. OAuth2 plugin's capabilities. [plugin]
QString API_ROUTE = "wp/v2/users/me?context=edit&_fields=id,username,roles"; const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me?context=edit&_fields=id,username,roles";
QUrl domainUserURL = apiBase + API_ROUTE; QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE;
// ####### TODO: Append a random key to check in response? // ####### TODO: Append a random key to check in response?
@ -1258,7 +1257,6 @@ void DomainGatekeeper::requestDomainUser(const QString& username, const QString&
request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT); request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
// ####### TODO: WordPress plugin's authorization requirements.
request.setRawHeader(QByteArray("Authorization"), QString("Bearer " + accessToken).toUtf8()); request.setRawHeader(QByteArray("Authorization"), QString("Bearer " + accessToken).toUtf8());
QByteArray formData; // No data to send. QByteArray formData; // No data to send.
@ -1278,12 +1276,13 @@ void DomainGatekeeper::requestDomainUserFinished() {
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender()); QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
const QJsonObject& rootObject = jsonResponse.object();
auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (200 <= httpStatus && httpStatus < 300) { if (200 <= httpStatus && httpStatus < 300) {
// Success. // Success.
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); // ####### TODO: Verify Expected response. [plugin]
const QJsonObject& rootObject = jsonResponse.object();
// ####### Expected response:
/* /*
{ {
id: 2, id: 2,
@ -1292,22 +1291,24 @@ void DomainGatekeeper::requestDomainUserFinished() {
} }
*/ */
// ####### TODO: Handle invalid / unexpected response.
QString username = rootObject["username"].toString().toLower(); QString username = rootObject["username"].toString().toLower();
// ####### TODO: Handle invalid username or one that isn't in the _inFlight list.
if (_inFlightDomainUserIdentityRequests.contains(username)) { if (_inFlightDomainUserIdentityRequests.contains(username)) {
// Success! Verified user. // Success! Verified user.
_verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username)); _verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username));
_inFlightDomainUserIdentityRequests.remove(username); _inFlightDomainUserIdentityRequests.remove(username);
} else { } else {
// Unexpected response. // Failure.
// ####### TODO qDebug() << "Unexpected username in response for user details -" << username;
} }
// ####### TODO: Handle roles if available. [plugin]
} else { } else {
// Failure. // Failure.
// ####### TODO: Error fields to report. [plugin]
qDebug() << "Error in response for user details -" << rootObject["error"].toString();
// ####### TODO: Is this the best way to handle _inFlightDomainUserIdentityRequests? // ####### TODO: Is this the best way to handle _inFlightDomainUserIdentityRequests?
// If there's a brief network glitch will it recover? // If there's a brief network glitch will it recover?
// Perhaps clear on a timer? Cancel timer upon subsequent successful responses? // Perhaps clear on a timer? Cancel timer upon subsequent successful responses?

View file

@ -155,7 +155,7 @@ private:
void requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken); void requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken);
typedef QHash<QString, QPair<QString, QString>> DomainUserIdentities; // <domainUserName, <access_token, refresh_token>> typedef QHash<QString, QPair<QString, QString>> DomainUserIdentities; // <domainUserName, <access_token, refresh_token>>
DomainUserIdentities _inFlightDomainUserIdentityRequests; // Keep track of domain user identity requests in progress. DomainUserIdentities _inFlightDomainUserIdentityRequests; // Domain user identity requests currently in progress.
DomainUserIdentities _verifiedDomainUserIdentities; // Verified domain users. DomainUserIdentities _verifiedDomainUserIdentities; // Verified domain users.
void getDomainGroupMemberships(const QString& domainUserName); void getDomainGroupMemberships(const QString& domainUserName);

View file

@ -60,7 +60,12 @@ void DomainAccountManager::setAuthURL(const QUrl& authURL) {
qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString()); qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString());
// ####### TODO: See AccountManager::setAuthURL(). _access_token = "";
_refresh_token = "";
// ####### TODO: Restore and refresh OAuth2 tokens if have them for this domain.
// ####### TODO: Handle "keep me logged in".
} }
} }
@ -84,9 +89,7 @@ void DomainAccountManager::requestAccessToken(const QString& username, const QSt
formData.append("scope=" + DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE); formData.append("scope=" + DOMAIN_ACCOUNT_MANAGER_REQUESTED_SCOPE);
// ####### TODO: Include state? // ####### TODO: Include state?
QUrl domainAuthURL = _authURL; request.setUrl(_authURL);
domainAuthURL.setPath("/token"); // ####### TODO: miniOrange-mandated URL. ####### TODO: Should this be included in the server settings value?
request.setUrl(domainAuthURL);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
@ -96,40 +99,40 @@ void DomainAccountManager::requestAccessToken(const QString& username, const QSt
} }
void DomainAccountManager::requestAccessTokenFinished() { void DomainAccountManager::requestAccessTokenFinished() {
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender()); QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
const QJsonObject& rootObject = jsonResponse.object(); const QJsonObject& rootObject = jsonResponse.object();
// ####### TODO: Test HTTP response codes rather than object contains "error". auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
// #### reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200 if (200 <= httpStatus && httpStatus < 300) {
if (!rootObject.contains("error")) {
// ####### TODO: Process response scope?
// ####### TODO: Process response state?
// ####### TODO: Check that token type == "Bearer"? // ####### TODO: Check that token type == "Bearer"?
// ####### TODO: Process response state?
// ####### TODO: Process response scope?
if (!rootObject.contains("access_token") if (rootObject.contains("access_token")) {
// ####### TODO: Does WordPRess plugin provide "expires_in"? // Success.
// If so, handle here, or is it just the domain server that needs to use it?
//|| !rootObject.contains("expires_in")
|| !rootObject.contains("token_type")) {
qCDebug(networking) << "Received a response for password grant that is missing one or more expected values.";
emit loginFailed();
} else {
// clear the path from the response URL so we have the right root URL for this access token
QUrl rootURL = requestReply->url(); QUrl rootURL = requestReply->url();
rootURL.setPath(""); rootURL.setPath("");
qCDebug(networking) << "Storing a domain account with access-token for" << qPrintable(rootURL.toString()); setTokensFromJSON(rootObject, rootURL);
setAccessTokenFromJSON(rootObject);
emit loginComplete(rootURL); emit loginComplete();
} else {
// Failure.
qCDebug(networking) << "Received a response for password grant that is missing one or more expected values.";
emit loginFailed();
} }
} else { } else {
qCDebug(networking) << "Error in response for password grant -" << rootObject["error_description"].toString(); // Failure.
// ####### TODO: Error object fields to report. [plugin]
qCDebug(networking) << "Error in response for password grant -" << rootObject["error"].toString();
emit loginFailed(); emit loginFailed();
} }
} }
@ -171,13 +174,14 @@ bool DomainAccountManager::hasValidAccessToken() {
} }
void DomainAccountManager::setAccessTokenFromJSON(const QJsonObject& jsonObject) { void DomainAccountManager::setTokensFromJSON(const QJsonObject& jsonObject, const QUrl& url) {
_access_token = jsonObject["access_token"].toString(); _access_token = jsonObject["access_token"].toString();
_refresh_token = jsonObject["refresh_token"].toString(); _refresh_token = jsonObject["refresh_token"].toString();
// ####### TODO: Enable and use these. // ####### TODO: Enable and use these.
// ####### TODO: Protect these per AccountManager? // ####### TODO: Protect these per AccountManager?
/* /*
qCDebug(networking) << "Storing a domain account with access-token for" << qPrintable(url.toString());
domainAccessToken.set(jsonObject["access_token"].toString()); domainAccessToken.set(jsonObject["access_token"].toString());
domainAccessRefreshToken.set(jsonObject["refresh_token"].toString()); domainAccessRefreshToken.set(jsonObject["refresh_token"].toString());
domainAccessTokenExpiresIn.set(QDateTime::currentMSecsSinceEpoch() + (jsonObject["expires_in"].toDouble() * 1000)); domainAccessTokenExpiresIn.set(QDateTime::currentMSecsSinceEpoch() + (jsonObject["expires_in"].toDouble() * 1000));

View file

@ -37,7 +37,7 @@ public slots:
void requestAccessTokenFinished(); void requestAccessTokenFinished();
signals: signals:
void authRequired(); void authRequired();
void loginComplete(const QUrl& authURL); void loginComplete();
void loginFailed(); void loginFailed();
void logoutComplete(); void logoutComplete();
void newTokens(); void newTokens();
@ -47,7 +47,7 @@ private slots:
private: private:
bool hasValidAccessToken(); bool hasValidAccessToken();
bool accessTokenIsExpired(); bool accessTokenIsExpired();
void setAccessTokenFromJSON(const QJsonObject&); void setTokensFromJSON(const QJsonObject&, const QUrl& url);
void sendInterfaceAccessTokenToServer(); void sendInterfaceAccessTokenToServer();
QUrl _authURL; QUrl _authURL;