don't allow a username and password for domain auth, require access token

This commit is contained in:
Stephen Birarda 2014-09-30 12:23:04 -07:00
parent 31080c41df
commit 3969c8bfe0
7 changed files with 123 additions and 86 deletions

View file

@ -64,7 +64,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
qRegisterMetaType<DomainServerWebSessionData>("DomainServerWebSessionData");
qRegisterMetaTypeStreamOperators<DomainServerWebSessionData>("DomainServerWebSessionData");
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
// we either read a certificate and private key or were not passed one
// and completed login or did not need to
@ -75,7 +75,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
loadExistingSessionsFromSettings();
// check if we have the flag that enables dynamic IP
setupDynamicIPAddressUpdating();
setupDynamicSocketUpdating();
}
}
@ -151,6 +151,17 @@ bool DomainServer::optionallySetupOAuth() {
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
_oauthProviderURL = QUrl(settingsMap.value(OAUTH_PROVIDER_URL_OPTION).toString());
// if we don't have an oauth provider URL then we default to the default node auth url
if (_oauthProviderURL.isEmpty()) {
_oauthProviderURL = DEFAULT_NODE_AUTH_URL;
}
// setup our account manager with that _oauthProviderURL
AccountManager& accountManager = AccountManager::getInstance();
accountManager.disableSettingsFilePersistence();
accountManager.setAuthURL(_oauthProviderURL);
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
_hostname = settingsMap.value(REDIRECT_HOSTNAME_OPTION).toString();
@ -225,41 +236,37 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
addStaticAssignmentsToQueue();
}
const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME";
const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD";
bool DomainServer::hasOAuthProviderAndAuthInformation() {
bool DomainServer::didSetupAccountManagerWithAccessToken() {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.hasValidAccessToken()) {
// we already gave the account manager a valid access token
return true;
}
if (!_oauthProviderURL.isEmpty()) {
// check for an access-token in our settings, can optionally be overidden by env value
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
const QString ENV_ACCESS_TOKEN_KEY = "DOMAIN_SERVER_ACCESS_TOKEN";
static bool hasAttemptedAuthWithOAuthProvider = false;
QString accessToken = QProcessEnvironment::systemEnvironment().value(ENV_ACCESS_TOKEN_KEY);
if (!hasAttemptedAuthWithOAuthProvider) {
AccountManager& accountManager = AccountManager::getInstance();
accountManager.setAuthURL(_oauthProviderURL);
if (accessToken.isEmpty()) {
const QVariant* accessTokenVariant = valueForKeyPath(_settingsManager.getSettingsMap(), ACCESS_TOKEN_KEY_PATH);
if (!accountManager.hasValidAccessToken()) {
// we don't have a valid access token so we need to get one
// check if we have a username and password set via env
QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY);
QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY);
if (!username.isEmpty() && !password.isEmpty()) {
accountManager.requestAccessToken(username, password);
// connect to loginFailed signal from AccountManager so we can quit if that is the case
connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed);
} else {
qDebug() << "Missing access-token or username and password combination. domain-server will now quit.";
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
return false;
}
if (accessTokenVariant->canConvert(QMetaType::QString)) {
accessToken = accessTokenVariant->toString();
} else {
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."
<< "Set an access token via the web interface, in your user or master config"
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
return false;
}
hasAttemptedAuthWithOAuthProvider = true;
}
// give this access token to the AccountManager
accountManager.setAccessTokenForCurrentAuthURL(accessToken);
return true;
} else {
@ -277,7 +284,7 @@ bool DomainServer::optionallySetupAssignmentPayment() {
if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
hasOAuthProviderAndAuthInformation()) {
didSetupAccountManagerWithAccessToken()) {
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
@ -299,20 +306,20 @@ bool DomainServer::optionallySetupAssignmentPayment() {
return true;
}
void DomainServer::setupDynamicIPAddressUpdating() {
const QString ENABLE_DYNAMIC_IP_UPDATING_OPTION = "update-ip";
void DomainServer::setupDynamicSocketUpdating() {
const QString ENABLE_DYNAMIC_SOCKET_UPDATING_KEY_PATH = "metaverse.update_sockets";
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
const QVariant* updateSocketValue = valueForKeyPath(_settingsManager.getSettingsMap(),
ENABLE_DYNAMIC_SOCKET_UPDATING_KEY_PATH);
if (settingsMap.contains(ENABLE_DYNAMIC_IP_UPDATING_OPTION) &&
settingsMap.value(ENABLE_DYNAMIC_IP_UPDATING_OPTION).toBool() &&
hasOAuthProviderAndAuthInformation()) {
if (updateSocketValue && updateSocketValue->canConvert(QMetaType::Bool) && updateSocketValue->toBool()
&& didSetupAccountManagerWithAccessToken()) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
const QUuid& domainID = nodeList->getSessionUUID();
if (!domainID.isNull()) {
qDebug() << "domain-server IP address will be updated for domain with ID"
qDebug() << "domain-server socket will be updated for domain with ID"
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000;

View file

@ -68,9 +68,9 @@ private:
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
bool optionallySetupOAuth();
bool optionallyReadX509KeyAndCertificate();
bool hasOAuthProviderAndAuthInformation();
bool didSetupAccountManagerWithAccessToken();
bool optionallySetupAssignmentPayment();
void setupDynamicIPAddressUpdating();
void setupDynamicSocketUpdating();
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);

View file

@ -52,7 +52,8 @@ JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, co
AccountManager::AccountManager() :
_authURL(),
_pendingCallbackMap(),
_accountInfo()
_accountInfo(),
_shouldPersistToSettingsFile(false)
{
qRegisterMetaType<OAuthAccessToken>("OAuthAccessToken");
qRegisterMetaTypeStreamOperators<OAuthAccessToken>("OAuthAccessToken");
@ -76,14 +77,18 @@ void AccountManager::logout() {
emit balanceChanged(0);
connect(&_accountInfo, &DataServerAccountInfo::balanceChanged, this, &AccountManager::accountInfoBalanceChanged);
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
settings.remove(keyURLString);
qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
if (_shouldPersistToSettingsFile) {
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
QString keyURLString(_authURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE));
settings.remove(keyURLString);
qDebug() << "Removed account info for" << _authURL << "from in-memory accounts and .ini file";
} else {
qDebug() << "Cleared data server account info in account manager.";
}
emit logoutComplete();
// the username has changed to blank
@ -109,28 +114,29 @@ void AccountManager::setAuthURL(const QUrl& authURL) {
if (_authURL != authURL) {
_authURL = authURL;
qDebug() << "URL for node authentication has been changed to" << qPrintable(_authURL.toString());
qDebug() << "Re-setting authentication flow.";
// check if there are existing access tokens to load from settings
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
// take a key copy to perform the double slash replacement
QString keyCopy(key);
QUrl keyURL(keyCopy.replace("slashslash", "//"));
if (keyURL == _authURL) {
// pull out the stored access token and store it in memory
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString());
// profile info isn't guaranteed to be saved too
if (_accountInfo.hasProfile()) {
emit profileChanged();
} else {
requestProfile();
qDebug() << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString());
if (_shouldPersistToSettingsFile) {
// check if there are existing access tokens to load from settings
QSettings settings;
settings.beginGroup(ACCOUNTS_GROUP);
foreach(const QString& key, settings.allKeys()) {
// take a key copy to perform the double slash replacement
QString keyCopy(key);
QUrl keyURL(keyCopy.replace("slashslash", "//"));
if (keyURL == _authURL) {
// pull out the stored access token and store it in memory
_accountInfo = settings.value(key).value<DataServerAccountInfo>();
qDebug() << "Found a data-server access token for" << qPrintable(keyURL.toString());
// profile info isn't guaranteed to be saved too
if (_accountInfo.hasProfile()) {
emit profileChanged();
} else {
requestProfile();
}
}
}
}
@ -307,8 +313,9 @@ void AccountManager::passErrorToCallback(QNetworkReply* requestReply) {
}
bool AccountManager::hasValidAccessToken() {
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qDebug() << "An access token is required for requests to" << qPrintable(_authURL.toString());
}
@ -330,6 +337,19 @@ bool AccountManager::checkAndSignalForAccessToken() {
return hasToken;
}
void AccountManager::setAccessTokenForCurrentAuthURL(const QString& accessToken) {
// clear our current DataServerAccountInfo
_accountInfo = DataServerAccountInfo();
// start the new account info with a new OAuthAccessToken
OAuthAccessToken newOAuthToken;
newOAuthToken.token = accessToken;
qDebug() << "Setting new account manager access token to" << accessToken;
_accountInfo.setAccessToken(newOAuthToken);
}
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
@ -380,12 +400,14 @@ void AccountManager::requestAccessTokenFinished() {
_accountInfo.setAccessTokenFromJSON(rootObject);
emit loginComplete(rootURL);
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
if (_shouldPersistToSettingsFile) {
// store this access token into the local settings
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
requestProfile();
}
@ -429,13 +451,16 @@ void AccountManager::requestProfileFinished() {
// the username has changed to whatever came back
emit usernameChanged(_accountInfo.getUsername());
// store the whole profile into the local settings
QUrl rootURL = profileReply->url();
rootURL.setPath("");
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
if (_shouldPersistToSettingsFile) {
// store the whole profile into the local settings
QUrl rootURL = profileReply->url();
rootURL.setPath("");
QSettings localSettings;
localSettings.beginGroup(ACCOUNTS_GROUP);
localSettings.setValue(rootURL.toString().replace("//", DOUBLE_SLASH_SUBSTITUTE),
QVariant::fromValue(_accountInfo));
}
} else {
// TODO: error handling
qDebug() << "Error in response for profile";

View file

@ -59,10 +59,13 @@ public:
const QUrl& getAuthURL() const { return _authURL; }
void setAuthURL(const QUrl& authURL);
bool hasAuthEndpoint() { return !_authURL.isEmpty(); }
void disableSettingsFilePersistence() { _shouldPersistToSettingsFile = false; }
bool isLoggedIn() { return !_authURL.isEmpty() && hasValidAccessToken(); }
bool hasValidAccessToken();
Q_INVOKABLE bool checkAndSignalForAccessToken();
void setAccessTokenForCurrentAuthURL(const QString& accessToken);
void requestAccessToken(const QString& login, const QString& password);
void requestProfile();
@ -107,6 +110,7 @@ private:
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
DataServerAccountInfo _accountInfo;
bool _shouldPersistToSettingsFile;
};
#endif // hifi_AccountManager_h

View file

@ -27,6 +27,7 @@ public:
DataServerAccountInfo& operator=(const DataServerAccountInfo& otherInfo);
const OAuthAccessToken& getAccessToken() const { return _accessToken; }
void setAccessToken(const OAuthAccessToken& accessToken) { _accessToken = accessToken; }
void setAccessTokenFromJSON(const QJsonObject& jsonObject);
const QString& getUsername() const { return _username; }

View file

@ -16,7 +16,7 @@
OAuthAccessToken::OAuthAccessToken() :
token(),
refreshToken(),
expiryTimestamp(0),
expiryTimestamp(-1),
tokenType()
{

View file

@ -26,7 +26,7 @@ public:
QByteArray authorizationHeaderValue() const { return QString("Bearer %1").arg(token).toUtf8(); }
bool isExpired() const { return expiryTimestamp <= QDateTime::currentMSecsSinceEpoch(); }
bool isExpired() const { return expiryTimestamp != -1 && expiryTimestamp <= QDateTime::currentMSecsSinceEpoch(); }
QString token;
QString refreshToken;