File indexing completed on 2024-05-19 04:50:10

0001 /****************************************************************************************
0002  * Copyright (c) 2007 Nikolaj Hald Nielsen <nhn@kde.org>                                *
0003  *           (c) 2010 Ian Monroe <ian@monroe.nu>                                        *
0004  *           (c) 2013 Ralf Engels <ralf-engels@gmx.de>                                  *
0005  *                                                                                      *
0006  * This program is free software; you can redistribute it and/or modify it under        *
0007  * the terms of the GNU General Public License as published by the Free Software        *
0008  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
0009  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
0010  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
0011  * version 3 of the license.                                                            *
0012  *                                                                                      *
0013  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0014  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0015  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0016  *                                                                                      *
0017  * You should have received a copy of the GNU General Public License along with         *
0018  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0019  ****************************************************************************************/
0020 
0021 
0022 #include "AmpacheAccountLogin.h"
0023 
0024 #include "core/support/Amarok.h"
0025 #include "core/support/Components.h"
0026 #include "core/support/Debug.h"
0027 
0028 #include <QCryptographicHash>
0029 #include <QDomDocument>
0030 #include <QNetworkRequest>
0031 #include <QUrlQuery>
0032 
0033 #include <KLocalizedString>
0034 #include <KPasswordDialog>
0035 #include <KMessageBox>
0036 
0037 
0038 AmpacheAccountLogin::AmpacheAccountLogin( const QUrl& url, const QString& username, const QString& password, QWidget* parent )
0039     : QObject(parent)
0040     , m_authenticated( false )
0041     , m_server( url )
0042     , m_username( username )
0043     , m_password( password )
0044     , m_authRequest( nullptr )
0045     , m_pingRequest( nullptr )
0046 {
0047     reauthenticate();
0048 }
0049 
0050 
0051 AmpacheAccountLogin::~AmpacheAccountLogin()
0052 {
0053 }
0054 
0055 void
0056 AmpacheAccountLogin::reauthenticate()
0057 {
0058     DEBUG_BLOCK
0059 
0060     // We need to check the version of Ampache we are attempting to authenticate against, as this changes how we deal with it
0061     QUrl url = getRequestUrl( "ping" );
0062 
0063     debug() << "Verifying Ampache Version Using: " << url.url();
0064 
0065     m_pingRequest = The::networkAccessManager()->getData( url, this, &AmpacheAccountLogin::authenticate );
0066 
0067     if( !m_pingRequest )
0068         Q_EMIT finished();
0069 }
0070 
0071 void
0072 AmpacheAccountLogin::authenticate( const QUrl &requestUrl, const QByteArray &data, const NetworkAccessManagerProxy::Error &e )
0073 {
0074     if( !m_pingRequest )
0075         return;
0076 
0077     DEBUG_BLOCK
0078     Q_UNUSED( requestUrl );
0079 
0080     QDomDocument doc;
0081     doc.setContent( data );
0082 
0083     if( !generalVerify( m_pingRequest, doc, e ) )
0084         return;
0085 
0086     // so lets figure out what we got here:
0087     debug() << "Version reply: " << data;
0088     int version = getVersion( doc );
0089 
0090     QUrl url = getRequestUrl( "handshake" );
0091     QUrlQuery query( url );
0092     QString timestamp = QString::number( QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000 );
0093     QString passPhrase;
0094 
0095     // We need to use different authentication strings depending on the version of ampache
0096     if( version > 350000 )
0097     {
0098         debug() << "New Password Scheme " << version;
0099         query.addQueryItem( "version", "350001" );
0100 
0101         QCryptographicHash sha256Hash( QCryptographicHash::Sha256 );
0102         sha256Hash.addData( m_password.toUtf8() );
0103         QString hashedPassword = sha256Hash.result().toHex();
0104 
0105         QString rawHandshake = timestamp + hashedPassword;
0106         sha256Hash.reset();
0107         sha256Hash.addData( rawHandshake.toUtf8() );
0108 
0109         passPhrase = sha256Hash.result().toHex();
0110 
0111     }
0112     else
0113     {
0114         debug() << "Version Older than 35001 Generated MD5 Auth " << version;
0115 
0116         QString rawHandshake = timestamp + m_password;
0117         QCryptographicHash md5Hash( QCryptographicHash::Md5 );
0118 
0119         md5Hash.addData( rawHandshake.toUtf8() );
0120         passPhrase = md5Hash.result().toHex();
0121     }
0122 
0123     query.addQueryItem( "timestamp", timestamp );
0124     query.addQueryItem( "auth", passPhrase );
0125     url.setQuery( query );
0126 
0127     debug() << "Authenticating with string: " << url.url() << passPhrase;
0128 
0129     // TODO: Amarok::Logger::newProgressOperation( m_xmlDownloadJob, i18n( "Authenticating with Ampache" ) );
0130     m_authRequest = The::networkAccessManager()->getData( url, this, &AmpacheAccountLogin::authenticationComplete );
0131 
0132     if( !m_authRequest )
0133         Q_EMIT finished();
0134 }
0135 
0136 void AmpacheAccountLogin::authenticationComplete( const QUrl &requestUrl, const QByteArray &data, const NetworkAccessManagerProxy::Error &e )
0137 {
0138     Q_UNUSED( requestUrl );
0139 
0140     if( !m_authRequest )
0141         return;
0142 
0143     DEBUG_BLOCK
0144 
0145     QDomDocument doc;
0146     doc.setContent( data );
0147 
0148     if( !generalVerify( m_authRequest, doc, e ) )
0149         return;
0150 
0151     // so lets figure out what we got here:
0152     debug() << "Authentication reply: " << data;
0153     QDomElement root = doc.firstChildElement("root");
0154 
0155     //find status code:
0156     QDomElement element = root.firstChildElement("auth");
0157     if( element.isNull() )
0158     {
0159         // Default the Version down if it didn't work
0160         debug() << "authenticationComplete failed";
0161         KMessageBox::error( qobject_cast<QWidget*>(parent()),
0162                             i18n( "Authentication failed." ),
0163                             i18n( "Authentication Error" ) );
0164         Q_EMIT finished();
0165         return;
0166     }
0167 
0168     m_sessionId = element.text();
0169     m_authenticated = true;
0170 
0171     Q_EMIT loginSuccessful();
0172     Q_EMIT finished();
0173 }
0174 
0175 int
0176 AmpacheAccountLogin::getVersion( const QDomDocument& doc ) const
0177 {
0178     DEBUG_BLOCK
0179 
0180     QDomElement root = doc.firstChildElement("root");
0181     //is this an error?
0182     QDomElement error = root.firstChildElement("error");
0183     //find status code:
0184     QDomElement version = root.firstChildElement("version");
0185 
0186     // It's OK if we get a null response from the version, that just means we're dealing with an older version
0187     if( !error.isNull() )
0188     {
0189         // Default the Version down if it didn't work
0190         debug() << "getVersion error: " << error.text();
0191         return 100000;
0192     }
0193     else if( !version.isNull() )
0194     {
0195         debug() << "getVersion returned: " << version.text();
0196         return version.text().toInt();
0197     }
0198     else
0199     {
0200         debug() << "getVersion no version";
0201         return 0;
0202     }
0203 }
0204 
0205 bool
0206 AmpacheAccountLogin::generalVerify( QNetworkReply *reply, const QDomDocument& doc, const NetworkAccessManagerProxy::Error &e )
0207 {
0208     Q_ASSERT( reply );
0209 
0210     if( reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt() != 200 )
0211     {
0212         debug() << "server response code:" <<
0213             reply->attribute( QNetworkRequest::HttpStatusCodeAttribute ).toInt() <<
0214             reply->attribute( QNetworkRequest::HttpReasonPhraseAttribute ).toString();
0215 
0216         Q_EMIT finished();
0217         return false;
0218     }
0219 
0220     if( e.code != QNetworkReply::NoError )
0221     {
0222         debug() << "authenticate Error:" << e.description;
0223         Q_EMIT finished();
0224         return false;
0225     }
0226 
0227     QDomElement root = doc.firstChildElement("root");
0228     QDomElement error = root.firstChildElement("error");
0229 
0230     if( !error.isNull() )
0231     {
0232         // Default the Version down if it didn't work
0233         debug() << "generalVerify error: " << error.text();
0234         KMessageBox::error( qobject_cast<QWidget*>(parent()), error.text(), i18n( "Authentication Error" ) );
0235         Q_EMIT finished();
0236         return false;
0237     }
0238 
0239     return true;
0240 }
0241 
0242 QUrl
0243 AmpacheAccountLogin::getRequestUrl( const QString &action ) const
0244 {
0245     //lets keep this around for now if we want to allow people to add a service that prompts for stuff
0246     /* But comment it out since the AmpacheQueryMaker does not do this
0247     if ( m_server.isEmpty() || m_password.isEmpty() )
0248     {
0249         KPasswordDialog dlg( 0 , KPasswordDialog::ShowUsernameLine );
0250         dlg.setPrompt( i18n( "Enter the server name and a password" ) );
0251         if( !dlg.exec() )
0252             return QUrl(); //the user canceled
0253 
0254         m_server = QUrl( dlg.username() ).url();
0255         m_password = dlg.password();
0256     }
0257     */
0258 
0259     QUrl url = m_server;
0260     url.setPath( m_server.path() + "/server/xml.server.php" );
0261     QString scheme = m_server.scheme();
0262 
0263     if( scheme != "http" && scheme != "https" )
0264         url.setScheme( "http" );
0265 
0266     QUrlQuery query( m_server );
0267 
0268     if( !action.isEmpty() )
0269         query.addQueryItem( "action", action );
0270 
0271     if( !m_username.isEmpty() && action != "ping" )
0272         query.addQueryItem( "user", m_username );
0273 
0274     if( !m_sessionId.isEmpty() && action == "ping" )
0275         query.addQueryItem( "auth", m_sessionId );
0276 
0277     url.setQuery( query );
0278 
0279     return url;
0280 }
0281