1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-08-04 21:27:58 +02:00

Update Hatchet account type to current protocol workflow.

This commit is contained in:
Jeff Mitchell
2014-01-16 17:59:07 -05:00
parent 37ec3466ab
commit 1e178ec5bc
4 changed files with 187 additions and 93 deletions

View File

@@ -86,7 +86,7 @@ HatchetAccount::HatchetAccount( const QString& accountId )
setAccountServiceName( "Hatchet" ); setAccountServiceName( "Hatchet" );
// We're connecting peers. // We're connecting peers.
setTypes( SipType ); setTypes( SipType );
/*
QFile pemFile( ":/hatchet-account/mandella.pem" ); QFile pemFile( ":/hatchet-account/mandella.pem" );
pemFile.open( QIODevice::ReadOnly ); pemFile.open( QIODevice::ReadOnly );
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "certs/mandella.pem: " << pemFile.readAll(); tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "certs/mandella.pem: " << pemFile.readAll();
@@ -100,6 +100,7 @@ HatchetAccount::HatchetAccount( const QString& accountId )
return; return;
} }
m_publicKey = new QCA::PublicKey( publicKey ); m_publicKey = new QCA::PublicKey( publicKey );
*/
} }
@@ -223,66 +224,128 @@ uint
HatchetAccount::refreshTokenExpiration() const HatchetAccount::refreshTokenExpiration() const
{ {
bool ok; bool ok;
return credentials().value( "expiration" ).toUInt( &ok ); return credentials().value( "refresh_token_expiration" ).toUInt( &ok );
}
QByteArray
HatchetAccount::mandellaAccessToken() const
{
return credentials().value( "mandella_access_token" ).toByteArray();
}
uint
HatchetAccount::mandellaAccessTokenExpiration() const
{
bool ok;
return credentials().value( "mandella_access_token_expiration" ).toUInt( &ok );
}
QByteArray
HatchetAccount::mandellaTokenType() const
{
return credentials().value( "mandella_token_type" ).toByteArray();
} }
void void
HatchetAccount::loginWithPassword( const QString& username, const QString& password, const QString &otp ) HatchetAccount::loginWithPassword( const QString& username, const QString& password, const QString &otp )
{ {
if ( username.isEmpty() || password.isEmpty() || !m_publicKey ) //if ( username.isEmpty() || password.isEmpty() || !m_publicKey )
if ( username.isEmpty() || password.isEmpty() )
{ {
tLog() << "No tomahawk account username or pw or public key, not logging in"; tLog() << "No tomahawk account username or pw or public key, not logging in";
return; return;
} }
/*
m_uuid = QUuid::createUuid().toString(); m_uuid = QUuid::createUuid().toString();
QCA::SecureArray sa( m_uuid.toLatin1() ); QCA::SecureArray sa( m_uuid.toLatin1() );
QCA::SecureArray result = m_publicKey->encrypt( sa, QCA::EME_PKCS1_OAEP ); QCA::SecureArray result = m_publicKey->encrypt( sa, QCA::EME_PKCS1_OAEP );
QVariantMap params;
params[ "password" ] = password;
params[ "username" ] = username;
if ( !otp.isEmpty() )
params[ "otp" ] = otp;
params[ "client" ] = "Tomahawk (" + QHostInfo::localHostName() + ")";
params[ "nonce" ] = QString( result.toByteArray().toBase64() ); params[ "nonce" ] = QString( result.toByteArray().toBase64() );
*/
QJson::Serializer s; QNetworkRequest req( QUrl( c_loginServer + "/authentication/password") );
const QByteArray msgJson = s.serialize( params ); req.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded" );
QUrl params;
params.addQueryItem( "username", username );
params.addQueryItem( "password", password );
params.addQueryItem( "grant_type", "password" );
if ( !otp.isEmpty() )
params.addQueryItem( "otp", otp );
QNetworkRequest req( QUrl( c_loginServer + "/auth/credentials") ); QByteArray data = params.encodedQuery();
req.setHeader( QNetworkRequest::ContentTypeHeader, "application/json; charset=utf-8" );
QNetworkReply* reply = Tomahawk::Utils::nam()->post( req, msgJson ); QNetworkReply* reply = Tomahawk::Utils::nam()->post( req, data );
NewClosure( reply, SIGNAL( finished() ), this, SLOT( onPasswordLoginFinished( QNetworkReply*, const QString& ) ), reply, username ); NewClosure( reply, SIGNAL( finished() ), this, SLOT( onPasswordLoginFinished( QNetworkReply*, const QString& ) ), reply, username );
} }
void void
HatchetAccount::fetchAccessTokens( const QString& type ) HatchetAccount::fetchAccessToken( const QString& type )
{ {
if ( username().isEmpty() || refreshToken().isEmpty() ) if ( username().isEmpty() )
{ {
tLog() << "No refresh token, not logging in"; tLog() << "No username, not logging in";
return;
}
if ( mandellaAccessToken().isEmpty() ||
(mandellaAccessTokenExpiration() < QDateTime::currentDateTime().toTime_t() &&
(refreshToken().isEmpty() ||
(refreshTokenExpiration() != 0 && refreshTokenExpiration() < QDateTime::currentDateTime().toTime_t()))) )
{
tLog() << "No valid combination of access/refresh tokens, not logging in";
tLog() << "Mandella access token expiration:" << mandellaAccessTokenExpiration() << ", refresh token expiration:" << refreshTokenExpiration();
emit authError( "No valid credentials are stored locally, please log in again.", 401, QVariantMap() );
return; return;
} }
if ( refreshTokenExpiration() < ( QDateTime::currentMSecsSinceEpoch() / 1000 ) ) uint matExpiration = mandellaAccessTokenExpiration();
tLog() << "Refresh token has expired, but may still be valid on the server"; bool interceptionNeeded = false;
tLog() << "Fetching access tokens"; if ( matExpiration < QDateTime::currentDateTime().toTime_t() )
QNetworkRequest req( QUrl( c_accessTokenServer + "/tokens/" + type + "?username=" + username() + "&refresh_token=" + refreshToken() ) ); {
interceptionNeeded = true;
tLog() << "Mandella access token has expired, fetching new ones first";
}
else
{
tLog() << "Fetching access tokens of type" << type;
}
QNetworkReply* reply = Tomahawk::Utils::nam()->get( req ); QNetworkRequest req( QUrl( c_accessTokenServer + "/tokens/" + (interceptionNeeded ? "refresh/" + QString::fromUtf8(mandellaTokenType()).toLower() : "fetch/" + type) ) );
QNetworkReply* reply;
connect( reply, SIGNAL( finished() ), this, SLOT( onFetchAccessTokensFinished() ) ); if ( interceptionNeeded )
{
tLog() << "Intercepting; new mandella access token needed";
req.setHeader( QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded" );
QUrl params;
params.addQueryItem( "grant_type", "refresh_token" );
params.addQueryItem( "refresh_token", refreshToken() );
QByteArray data = params.encodedQuery();
reply = Tomahawk::Utils::nam()->post( req, data );
reply->setProperty( "originalType", type );
}
else
{
tLog() << "Fetching token of type" << type;
req.setRawHeader( "Authorization", QString( mandellaTokenType() + " " + mandellaAccessToken()).toUtf8() );
reply = Tomahawk::Utils::nam()->get( req );
}
NewClosure( reply, SIGNAL( finished() ), this, SLOT( onFetchAccessTokenFinished( QNetworkReply*, const QString& ) ), reply, type );
} }
void void
HatchetAccount::onPasswordLoginFinished( QNetworkReply* reply, const QString& username ) HatchetAccount::onPasswordLoginFinished( QNetworkReply* reply, const QString& username )
{ {
tLog() << Q_FUNC_INFO;
Q_ASSERT( reply ); Q_ASSERT( reply );
bool ok; bool ok;
int statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt( &ok ); int statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt( &ok );
@@ -307,12 +370,13 @@ HatchetAccount::onPasswordLoginFinished( QNetworkReply* reply, const QString& us
} }
if ( statusCode >= 400 ) if ( statusCode >= 400 )
{ {
QString errString = resp.value( "result" ).toMap().value( "errorinfo" ).toMap().value( "description" ).toString(); QString errString = resp.value( "error_description" ).toString();
tLog() << Q_FUNC_INFO << "An error was returned from the authentication server: " << errString; tLog() << Q_FUNC_INFO << "An error was returned from the authentication server: " << errString;
emit authError( errString, statusCode, resp ); emit authError( errString, statusCode, resp );
return; return;
} }
/*
const QString nonce = resp.value( "result" ).toMap().value( "nonce" ).toString(); const QString nonce = resp.value( "result" ).toMap().value( "nonce" ).toString();
if ( nonce != m_uuid ) if ( nonce != m_uuid )
{ {
@@ -320,32 +384,59 @@ HatchetAccount::onPasswordLoginFinished( QNetworkReply* reply, const QString& us
emit authError( "The nonce value was incorrect. YOUR ACCOUNT MAY BE COMPROMISED.", statusCode, resp ); emit authError( "The nonce value was incorrect. YOUR ACCOUNT MAY BE COMPROMISED.", statusCode, resp );
return; return;
} }
*/
const QByteArray refreshTokenBytes = resp.value( "result" ).toMap().value( "refresh_token" ).toByteArray(); const QByteArray refreshTokenBytes = resp.value( "refresh_token" ).toByteArray();
uint expiration = resp.value( "result" ).toMap().value( "expiration" ).toUInt( &ok ); uint refreshTokenExpiration = resp.value( "refresh_token_expires_in" ).toUInt( &ok );
if ( refreshTokenBytes.isEmpty() || !ok )
{
tLog() << Q_FUNC_INFO << "Error reading refresh token or its expiration";
emit authError( "An error encountered parsing the authentication server's response", 0, QVariantMap() );
return;
}
const QByteArray accessTokenBytes = resp.value( "access_token" ).toByteArray();
uint accessTokenExpiration = resp.value( "expires_in" ).toUInt( &ok );
if ( accessTokenBytes.isEmpty() || !ok )
{
tLog() << Q_FUNC_INFO << "Error reading access token or its expiration";
emit authError( "An error encountered parsing the authentication server's response", 0, QVariantMap() );
return;
}
const QByteArray tokenTypeBytes = resp.value( "token_type" ).toByteArray();
if ( tokenTypeBytes.isEmpty() )
{
tLog() << Q_FUNC_INFO << "Error reading access token type";
emit authError( "An error encountered parsing the authentication server's response", 0, QVariantMap() );
return;
}
QVariantHash creds = credentials(); QVariantHash creds = credentials();
creds[ "username" ] = username; creds[ "username" ] = username;
creds[ "refresh_token" ] = refreshTokenBytes; creds[ "refresh_token" ] = refreshTokenBytes;
creds[ "expiration" ] = expiration; creds[ "refresh_token_expiration" ] = refreshTokenExpiration == 0 ? 0 : QDateTime::currentDateTime().toTime_t() + refreshTokenExpiration;
creds[ "mandella_access_token" ] = accessTokenBytes;
creds[ "mandella_access_token_expiration" ] = QDateTime::currentDateTime().toTime_t() + accessTokenExpiration;
creds[ "mandella_token_type" ] = tokenTypeBytes;
setCredentials( creds ); setCredentials( creds );
syncConfig(); syncConfig();
if ( !refreshTokenBytes.isEmpty() ) if ( sipPlugin() )
{ sipPlugin()->connectPlugin();
if ( sipPlugin() )
sipPlugin()->connectPlugin();
}
} }
void void
HatchetAccount::onFetchAccessTokensFinished() HatchetAccount::onFetchAccessTokenFinished( QNetworkReply* reply, const QString& type )
{ {
tLog() << Q_FUNC_INFO; tLog() << Q_FUNC_INFO;
QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() );
Q_ASSERT( reply ); Q_ASSERT( reply );
QString originalType;
if ( reply->property( "originalType" ).isValid() )
{
originalType = reply->property( "originalType" ).toString();
}
bool ok; bool ok;
int statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt( &ok ); int statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt( &ok );
if ( !ok ) if ( !ok )
@@ -369,36 +460,59 @@ HatchetAccount::onFetchAccessTokensFinished()
} }
if ( statusCode >= 400 ) if ( statusCode >= 400 )
{ {
QString errString = resp.value( "result" ).toMap().value( "errorinfo" ).toMap().value( "description" ).toString(); QString errString = resp.value( "error_description" ).toString();
tLog() << Q_FUNC_INFO << "An error was returned from the authentication server: " << errString; tLog() << Q_FUNC_INFO << "An error was returned from the authentication server: " << errString;
emit authError( errString, statusCode, resp ); emit authError( errString, statusCode, resp );
return; return;
} }
QVariantHash creds = credentials(); QVariantHash creds = credentials();
QStringList tokenTypesFound;
tDebug() << Q_FUNC_INFO << "resp: " << resp; if ( !originalType.isEmpty() )
if ( resp[ "result" ].toMap().contains( "refresh_token_expiration" ) )
{ {
bool ok; const QByteArray accessTokenBytes = resp.value( "access_token" ).toByteArray();
uint expiration = resp.value( "result" ).toMap().value( "refresh_token_expiration" ).toUInt( &ok ); uint accessTokenExpiration = resp.value( "expires_in" ).toUInt( &ok );
creds[ "expiration" ] = expiration; if ( accessTokenBytes.isEmpty() || !ok )
}
foreach( QVariant tokenVariant, resp[ "result" ].toMap()[ "tokens" ].toList() )
{
QVariantMap tokenMap = tokenVariant.toMap();
QString tokenTypeName = tokenMap[ "type" ].toString() + "tokens";
if ( !tokenTypesFound.contains( tokenTypeName ) )
{ {
creds[ tokenTypeName ] = QVariantList(); tLog() << Q_FUNC_INFO << "Error reading access token or its expiration";
tokenTypesFound.append( tokenTypeName ); emit authError( "An error encountered parsing the authentication server's response", 0, QVariantMap() );
return;
} }
creds[ tokenTypeName ] = creds[ tokenTypeName ].toList() << tokenMap; const QByteArray tokenTypeBytes = resp.value( "token_type" ).toByteArray();
if ( tokenTypeBytes.isEmpty() )
{
tLog() << Q_FUNC_INFO << "Error reading access token type";
emit authError( "An error encountered parsing the authentication server's response", 0, QVariantMap() );
return;
}
creds[ "mandella_access_token" ] = accessTokenBytes;
creds[ "mandella_access_token_expiration" ] = QDateTime::currentDateTime().toTime_t() + accessTokenExpiration;
creds[ "mandella_token_type" ] = tokenTypeBytes;
setCredentials( creds );
syncConfig();
fetchAccessToken( originalType );
return;
} }
const QByteArray accessTokenBytes = resp.value( "access_token" ).toByteArray();
uint accessTokenExpiration = resp.value( "expires_in" ).toUInt( &ok );
if ( accessTokenBytes.isEmpty() || !ok )
{
tLog() << Q_FUNC_INFO << "Error reading access token or its expiration";
emit authError( "An error encountered parsing the authentication server's response", 0, QVariantMap() );
return;
}
const QByteArray tokenTypeBytes = resp.value( "token_type" ).toByteArray();
if ( tokenTypeBytes.isEmpty() )
{
tLog() << Q_FUNC_INFO << "Error reading access token type";
emit authError( "An error encountered parsing the authentication server's response", 0, QVariantMap() );
return;
}
creds[ type + "_access_token" ] = accessTokenBytes;
tDebug() << Q_FUNC_INFO << "Creds: " << creds; tDebug() << Q_FUNC_INFO << "Creds: " << creds;
setCredentials( creds ); setCredentials( creds );
@@ -406,7 +520,7 @@ HatchetAccount::onFetchAccessTokensFinished()
tLog() << Q_FUNC_INFO << "Access tokens fetched successfully"; tLog() << Q_FUNC_INFO << "Access tokens fetched successfully";
emit accessTokensFetched(); emit accessTokenFetched();
} }

View File

@@ -79,7 +79,6 @@ public:
void setConnectionState( Account::ConnectionState connectionState ); void setConnectionState( Account::ConnectionState connectionState );
ConnectionState connectionState() const; ConnectionState connectionState() const;
virtual Tomahawk::InfoSystem::InfoPluginPtr infoPlugin() { return Tomahawk::InfoSystem::InfoPluginPtr(); } virtual Tomahawk::InfoSystem::InfoPluginPtr infoPlugin() { return Tomahawk::InfoSystem::InfoPluginPtr(); }
SipPlugin* sipPlugin( bool create = true ); SipPlugin* sipPlugin( bool create = true );
@@ -88,24 +87,29 @@ public:
QString username() const; QString username() const;
void fetchAccessTokens( const QString& type = "dreamcatcher" ); void fetchAccessToken( const QString& type = "dreamcatcher" );
QString authUrlForService( const Service& service ) const; QString authUrlForService( const Service& service ) const;
signals: signals:
void authError( QString error, int statusCode, const QVariantMap ); void authError( QString error, int statusCode, const QVariantMap );
void deauthenticated(); void deauthenticated();
void accessTokensFetched(); void accessTokenFetched();
private slots: private slots:
void onPasswordLoginFinished( QNetworkReply*, const QString& username ); void onPasswordLoginFinished( QNetworkReply*, const QString& username );
void onFetchAccessTokensFinished(); void onFetchAccessTokenFinished( QNetworkReply*, const QString& type );
void authUrlDiscovered( Tomahawk::Accounts::HatchetAccount::Service service, const QString& authUrl ); void authUrlDiscovered( Tomahawk::Accounts::HatchetAccount::Service service, const QString& authUrl );
private: private:
QByteArray refreshToken() const; QByteArray refreshToken() const;
uint refreshTokenExpiration() const; uint refreshTokenExpiration() const;
QByteArray mandellaAccessToken() const;
uint mandellaAccessTokenExpiration() const;
QByteArray mandellaTokenType() const;
void loginWithPassword( const QString& username, const QString& password, const QString &otp ); void loginWithPassword( const QString& username, const QString& password, const QString &otp );
QVariantMap parseReply( QNetworkReply* reply, bool& ok ) const; QVariantMap parseReply( QNetworkReply* reply, bool& ok ) const;

View File

@@ -56,7 +56,7 @@ HatchetAccountConfig::HatchetAccountConfig( HatchetAccount* account )
connect( m_account, SIGNAL( authError( QString, int, QVariantMap ) ), this, SLOT( authError( QString, int, QVariantMap ) ) ); connect( m_account, SIGNAL( authError( QString, int, QVariantMap ) ), this, SLOT( authError( QString, int, QVariantMap ) ) );
connect( m_account, SIGNAL( deauthenticated() ), this, SLOT( showLoggedOut() ) ); connect( m_account, SIGNAL( deauthenticated() ), this, SLOT( showLoggedOut() ) );
connect( m_account, SIGNAL( accessTokensFetched() ), this, SLOT( accountInfoUpdated() ) ); connect( m_account, SIGNAL( accessTokenFetched() ), this, SLOT( accountInfoUpdated() ) );
if ( !m_account->refreshToken().isEmpty() ) if ( !m_account->refreshToken().isEmpty() )
accountInfoUpdated(); accountInfoUpdated();
@@ -170,7 +170,7 @@ HatchetAccountConfig::accountInfoUpdated()
void void
HatchetAccountConfig::authError( const QString &error, int statusCode, const QVariantMap& resp ) HatchetAccountConfig::authError( const QString &error, int statusCode, const QVariantMap& resp )
{ {
if ( statusCode == 401 && resp["result"].toMap()["errorinfo"].toMap().contains("missingotp") ) if ( statusCode == 400 && error == "otp_needed" )
{ {
m_ui->usernameLabel->hide(); m_ui->usernameLabel->hide();
m_ui->usernameEdit->hide(); m_ui->usernameEdit->hide();
@@ -182,7 +182,7 @@ HatchetAccountConfig::authError( const QString &error, int statusCode, const QVa
return; return;
} }
if ( statusCode == 401 ) if ( statusCode == 401 )
m_account->deauthenticate(); m_account->deauthenticate();
QMessageBox::critical( this, "An error was encountered:", error ); QMessageBox::critical( this, "An error was encountered:", error );
} }

View File

@@ -48,7 +48,7 @@ HatchetSipPlugin::HatchetSipPlugin( Tomahawk::Accounts::Account *account )
{ {
tLog() << Q_FUNC_INFO; tLog() << Q_FUNC_INFO;
connect( m_account, SIGNAL( accessTokensFetched() ), this, SLOT( connectWebSocket() ) ); connect( m_account, SIGNAL( accessTokenFetched() ), this, SLOT( connectWebSocket() ) );
connect( Servent::instance(), SIGNAL( dbSyncTriggered() ), this, SLOT( dbSyncTriggered() )); connect( Servent::instance(), SIGNAL( dbSyncTriggered() ), this, SLOT( dbSyncTriggered() ));
QFile pemFile( ":/hatchet-account/dreamcatcher.pem" ); QFile pemFile( ":/hatchet-account/dreamcatcher.pem" );
@@ -113,7 +113,7 @@ HatchetSipPlugin::connectPlugin()
} }
hatchetAccount()->setConnectionState( Tomahawk::Accounts::Account::Connecting ); hatchetAccount()->setConnectionState( Tomahawk::Accounts::Account::Connecting );
hatchetAccount()->fetchAccessTokens(); hatchetAccount()->fetchAccessToken( "dreamcatcher" );
} }
@@ -156,42 +156,18 @@ HatchetSipPlugin::connectWebSocket()
return; return;
} }
m_token.clear(); m_token = m_account->credentials()[ "dreamcatcher_access_token" ].toString();
QVariantList tokensCreds = m_account->credentials()[ "dreamcatchertokens" ].toList();
//FIXME: Don't blindly pick the first one that matches? Most likely, cycle through if the first one fails if ( m_token.isEmpty() )
QVariantMap connectVals;
foreach ( QVariant credObj, tokensCreds )
{ {
QVariantMap creds = credObj.toMap(); tLog() << Q_FUNC_INFO << "Unable to find an access token";
if ( creds.contains( "type" ) && creds[ "type" ].toString() == "dreamcatcher" )
{
connectVals = creds;
m_token = creds[ "token" ].toString();
break;
}
}
QString url;
if ( !connectVals.isEmpty() )
{
QString port = connectVals[ "port" ].toString();
if ( port == "443" )
url = "wss://";
else
url = "ws://";
url += connectVals[ "host" ].toString() + ':' + connectVals[ "port" ].toString();
}
if ( url.isEmpty() || m_token.isEmpty() )
{
tLog() << Q_FUNC_INFO << "Unable to find a proper connection endpoint; bailing";
disconnectPlugin(); disconnectPlugin();
return; return;
} }
else
tLog() << Q_FUNC_INFO << "Connecting to Dreamcatcher endpoint at: " << url; QString url( "wss://dreamcatcher.hatchet.is:443" );
tLog() << Q_FUNC_INFO << "Connecting to Dreamcatcher endpoint at: " << url;
m_webSocketThreadController->setUrl( url ); m_webSocketThreadController->setUrl( url );
m_webSocketThreadController->start(); m_webSocketThreadController->start();