1
0
mirror of https://github.com/tomahawk-player/tomahawk.git synced 2025-04-17 22:43:21 +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" );
// We're connecting peers.
setTypes( SipType );
/*
QFile pemFile( ":/hatchet-account/mandella.pem" );
pemFile.open( QIODevice::ReadOnly );
tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "certs/mandella.pem: " << pemFile.readAll();
@ -100,6 +100,7 @@ HatchetAccount::HatchetAccount( const QString& accountId )
return;
}
m_publicKey = new QCA::PublicKey( publicKey );
*/
}
@ -223,66 +224,128 @@ uint
HatchetAccount::refreshTokenExpiration() const
{
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
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";
return;
}
/*
m_uuid = QUuid::createUuid().toString();
QCA::SecureArray sa( m_uuid.toLatin1() );
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() );
*/
QJson::Serializer s;
const QByteArray msgJson = s.serialize( params );
QNetworkRequest req( QUrl( c_loginServer + "/authentication/password") );
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") );
req.setHeader( QNetworkRequest::ContentTypeHeader, "application/json; charset=utf-8" );
QNetworkReply* reply = Tomahawk::Utils::nam()->post( req, msgJson );
QByteArray data = params.encodedQuery();
QNetworkReply* reply = Tomahawk::Utils::nam()->post( req, data );
NewClosure( reply, SIGNAL( finished() ), this, SLOT( onPasswordLoginFinished( QNetworkReply*, const QString& ) ), reply, username );
}
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;
}
if ( refreshTokenExpiration() < ( QDateTime::currentMSecsSinceEpoch() / 1000 ) )
tLog() << "Refresh token has expired, but may still be valid on the server";
uint matExpiration = mandellaAccessTokenExpiration();
bool interceptionNeeded = false;
tLog() << "Fetching access tokens";
QNetworkRequest req( QUrl( c_accessTokenServer + "/tokens/" + type + "?username=" + username() + "&refresh_token=" + refreshToken() ) );
if ( matExpiration < QDateTime::currentDateTime().toTime_t() )
{
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
HatchetAccount::onPasswordLoginFinished( QNetworkReply* reply, const QString& username )
{
tLog() << Q_FUNC_INFO;
Q_ASSERT( reply );
bool ok;
int statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt( &ok );
@ -307,12 +370,13 @@ HatchetAccount::onPasswordLoginFinished( QNetworkReply* reply, const QString& us
}
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;
emit authError( errString, statusCode, resp );
return;
}
/*
const QString nonce = resp.value( "result" ).toMap().value( "nonce" ).toString();
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 );
return;
}
*/
const QByteArray refreshTokenBytes = resp.value( "result" ).toMap().value( "refresh_token" ).toByteArray();
uint expiration = resp.value( "result" ).toMap().value( "expiration" ).toUInt( &ok );
const QByteArray refreshTokenBytes = resp.value( "refresh_token" ).toByteArray();
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();
creds[ "username" ] = username;
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 );
syncConfig();
if ( !refreshTokenBytes.isEmpty() )
{
if ( sipPlugin() )
sipPlugin()->connectPlugin();
}
if ( sipPlugin() )
sipPlugin()->connectPlugin();
}
void
HatchetAccount::onFetchAccessTokensFinished()
HatchetAccount::onFetchAccessTokenFinished( QNetworkReply* reply, const QString& type )
{
tLog() << Q_FUNC_INFO;
QNetworkReply* reply = qobject_cast< QNetworkReply* >( sender() );
Q_ASSERT( reply );
QString originalType;
if ( reply->property( "originalType" ).isValid() )
{
originalType = reply->property( "originalType" ).toString();
}
bool ok;
int statusCode = reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt( &ok );
if ( !ok )
@ -369,36 +460,59 @@ HatchetAccount::onFetchAccessTokensFinished()
}
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;
emit authError( errString, statusCode, resp );
return;
}
QVariantHash creds = credentials();
QStringList tokenTypesFound;
tDebug() << Q_FUNC_INFO << "resp: " << resp;
if ( resp[ "result" ].toMap().contains( "refresh_token_expiration" ) )
if ( !originalType.isEmpty() )
{
bool ok;
uint expiration = resp.value( "result" ).toMap().value( "refresh_token_expiration" ).toUInt( &ok );
creds[ "expiration" ] = expiration;
}
foreach( QVariant tokenVariant, resp[ "result" ].toMap()[ "tokens" ].toList() )
{
QVariantMap tokenMap = tokenVariant.toMap();
QString tokenTypeName = tokenMap[ "type" ].toString() + "tokens";
if ( !tokenTypesFound.contains( tokenTypeName ) )
const QByteArray accessTokenBytes = resp.value( "access_token" ).toByteArray();
uint accessTokenExpiration = resp.value( "expires_in" ).toUInt( &ok );
if ( accessTokenBytes.isEmpty() || !ok )
{
creds[ tokenTypeName ] = QVariantList();
tokenTypesFound.append( tokenTypeName );
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;
}
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;
setCredentials( creds );
@ -406,7 +520,7 @@ HatchetAccount::onFetchAccessTokensFinished()
tLog() << Q_FUNC_INFO << "Access tokens fetched successfully";
emit accessTokensFetched();
emit accessTokenFetched();
}

View File

@ -79,7 +79,6 @@ public:
void setConnectionState( Account::ConnectionState connectionState );
ConnectionState connectionState() const;
virtual Tomahawk::InfoSystem::InfoPluginPtr infoPlugin() { return Tomahawk::InfoSystem::InfoPluginPtr(); }
SipPlugin* sipPlugin( bool create = true );
@ -88,24 +87,29 @@ public:
QString username() const;
void fetchAccessTokens( const QString& type = "dreamcatcher" );
void fetchAccessToken( const QString& type = "dreamcatcher" );
QString authUrlForService( const Service& service ) const;
signals:
void authError( QString error, int statusCode, const QVariantMap );
void deauthenticated();
void accessTokensFetched();
void accessTokenFetched();
private slots:
void onPasswordLoginFinished( QNetworkReply*, const QString& username );
void onFetchAccessTokensFinished();
void onFetchAccessTokenFinished( QNetworkReply*, const QString& type );
void authUrlDiscovered( Tomahawk::Accounts::HatchetAccount::Service service, const QString& authUrl );
private:
QByteArray refreshToken() const;
uint refreshTokenExpiration() const;
QByteArray mandellaAccessToken() const;
uint mandellaAccessTokenExpiration() const;
QByteArray mandellaTokenType() const;
void loginWithPassword( const QString& username, const QString& password, const QString &otp );
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( 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() )
accountInfoUpdated();
@ -170,7 +170,7 @@ HatchetAccountConfig::accountInfoUpdated()
void
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->usernameEdit->hide();
@ -182,7 +182,7 @@ HatchetAccountConfig::authError( const QString &error, int statusCode, const QVa
return;
}
if ( statusCode == 401 )
m_account->deauthenticate();
m_account->deauthenticate();
QMessageBox::critical( this, "An error was encountered:", error );
}

View File

@ -48,7 +48,7 @@ HatchetSipPlugin::HatchetSipPlugin( Tomahawk::Accounts::Account *account )
{
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() ));
QFile pemFile( ":/hatchet-account/dreamcatcher.pem" );
@ -113,7 +113,7 @@ HatchetSipPlugin::connectPlugin()
}
hatchetAccount()->setConnectionState( Tomahawk::Accounts::Account::Connecting );
hatchetAccount()->fetchAccessTokens();
hatchetAccount()->fetchAccessToken( "dreamcatcher" );
}
@ -156,42 +156,18 @@ HatchetSipPlugin::connectWebSocket()
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
QVariantMap connectVals;
foreach ( QVariant credObj, tokensCreds )
if ( m_token.isEmpty() )
{
QVariantMap creds = credObj.toMap();
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";
tLog() << Q_FUNC_INFO << "Unable to find an access token";
disconnectPlugin();
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->start();