Logo Search packages:      
Sourcecode: kmess version File versions  Download package

passportloginservice.cpp

/***************************************************************************
                          passportloginservice.cpp  -  description
                             -------------------
    begin                : Sun Apr 22 2007
    copyright            : (C) 2007 by Diederik van der Boor
    email                : "vdboor" --at-- "codingdomain.com"
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "passportloginservice.h"

#include <qstringlist.h>
#include <qregexp.h>
#include <qsocket.h>
#include <qurl.h>
#include <qdom.h>
#include <qtextstream.h>

#include <kdebug.h>
#include <kextsock.h>
#include <kssl.h>
#include <kurl.h>
#include <klocale.h>

#include "../../kmessdebug.h"
#include "../mimemessage.h"
#include "../extra/xmlfunctions.h"


/*
 * Great documentation about this can be found at
 * http://msnpiki.msnfanatic.com/index.php/MSNP13:SOAPTweener
 */


/**
 * @brief The constructor
 *
 * Initializes the client to connect to the passport login (RST) service.
 */
00049 PassportLoginService::PassportLoginService( QObject *parent )
: HttpSoapConnection("https://loginnet.passport.com/RST.srf", parent, "PassportLoginService")
, redirects_(0)
{
  // RST stands for "Request Security Token"
  // This class is called "PassportLoginService" since "RequestSecurityTokenService" is too hard to understand.
}



/**
 * @brief The destructor
 */
00062 PassportLoginService::~PassportLoginService()
{

}



/**
 * @brief Start the login process
 *
 * The loginSucceeded() signal is fired when the webservice accepted the login data.
 * The loginIncorrect() signal is fired when the login data is incorrect.
 *
 * @param  parameters  The login parameters found in the <code>USR</code> command of notification server.
 * @param  handle      The account handle; the e-mail address.
 * @param  password    The account password in plain text.
 */
00079 void PassportLoginService::login( const QString &parameters, const QString &handle, const QString &password )
{
#ifdef KMESSDEBUG_PASSPORTLOGINSERVICE
  kdDebug() << "PassportLoginService - Starting login with parameters " << parameters << "." << endl;
#endif

  // Store the given data
  authenticationParameters_ = parameters;
  handle_                   = handle;
  password_                 = password;

  // Create the request
  requestMultipleSecurityTokens();
}



/**
 * @brief Internal method to parse the HTTP header.
 *
 * This method is overwritten to relax the allowed HTTP <code>Content-Type</code> values.
 * For some reason, the response is marked as <code>text/html</code>.
 *
 * @param preamble  The HTTP preamble, something like "HTTP/1.1 200 OK".
 * @param header    The fields of the HTTP header.
 */
00105 bool PassportLoginService::parseHttpHeader( const QString &/*preamble*/, const MimeMessage &header )
{
  int statusCode = getHttpStatusCode();

  // Check the response code.
  if( statusCode != 200 )
  {
    kdWarning() << "PassportLoginService: Received HTTP status line: " << statusCode << endl;
    return false;   // Parse error!
  }

  // Parse the Content-Type
  QString contentType = header.getValue("Content-Type");
  if( contentType.contains(";") )
  {
    contentType = contentType.section(';', 0, 0);
  }

  // Content-Type should be text/xml, but the service returns text/html too for SOAP responses..!
  if( contentType != "text/xml"
  &&  contentType != "text/html" )
  {
    kdWarning() << "PassportLoginService: Invalid Content-Type received: " << contentType << ", "
                << "expected text/xml or text/html (endpoint=" << getEndpoint() << ")!" << endl;
    return false;
  }

  return true;  // no parse error.
}



/**
 * @brief Internal function to parse the SOAP fault.
 *
 * This method detects the faultcode <code>wsse:FailedAuthentication</code>,
 * to fire a loginIncorrect() signal instead of requestFailed().
 *
 * It also traps SOAP fauls which are used to redirect the client.
 * So far this has been observed with @msn.com passport accounts.
 *
 * The SOAP fault for an incorrect login looks like:
 * @code
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope
  xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext"
  xmlns:psf="http://schemas.microsoft.com/Passport/SoapServices/SOAPFault">
  <S:Header>
    <psf:pp xmlns:psf="http://schemas.microsoft.com/Passport/SoapServices/SOAPFault">
      <psf:serverVersion>1</psf:serverVersion>
      <psf:authstate>0x80048800</psf:authstate>
      <psf:reqstatus>0x80048821</psf:reqstatus>
      <psf:serverInfo Path="Live1" RollingUpgradeState="ExclusiveNew" LocVersion="0"
                      ServerTime="2007-04-22T21:22:38Z">BAYPPLOGN2B32 2006.10.31.02.02.43</psf:serverInfo>
      <psf:cookies/>
      <psf:response/>
    </psf:pp>
  </S:Header>
  <S:Fault>
    <faultcode>wsse:FailedAuthentication</faultcode>
    <faultstring>Authentication Failure</faultstring>
  </S:Fault>
</S:Envelope>
 * @endcode
 *
 * The SOAP fault for a redirect looks like:
 * @code
<?xml version='1.0' encoding='UTF-8'?>
<S:Envelope 
  xmlns:S="http://schemas.xmlsoap.org/soap/envelope/" 
  xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext" 
  xmlns:psf="http://schemas.microsoft.com/Passport/SoapServices/SOAPFault">
  <S:Header>
    <psf:pp xmlns:psf="http://schemas.microsoft.com/Passport/SoapServices/SOAPFault">
      <psf:serverVersion>1</psf:serverVersion>
      <psf:authstate>0x80048800</psf:authstate>
      <psf:reqstatus>0x80048852</psf:reqstatus>
      <psf:serverInfo Path="Live1" RollingUpgradeState="ExclusiveNew" LocVersion="0"
                      ServerTime="2007-05-05T13:49:58Z">BAYPPLOGN2A01 2007.04.23.14.59.31</psf:serverInfo>
      <psf:cookies/>
      <psf:response/>
    </psf:pp>
  </S:Header>
  <S:Fault>
    <faultcode>psf:Redirect</faultcode>
    <psf:redirectUrl>https://msnia.login.live.com/pp450/RST.srf</psf:redirectUrl>
    <faultstring>Authentication Failure</faultstring>
  </S:Fault>
</S:Envelope>
@endcode
 *
 * Note the <code>Fault</code> object is not in
 * a <code>Body</code> but <code>Envelope</code> element.
 *
 * @param  faultNode   The XML node which contains the SOAP fault.
 * @param  headerNode  The XML node which contains the SOAP header.
 */
00203 void PassportLoginService::parseSoapFault( const QDomElement &faultNode, const QDomElement &headerNode )
{
  // Detect if the login failed.
  QString faultCode = XmlFunctions::getNodeValue( faultNode, "faultcode" );
  if( faultCode == "wsse:FailedAuthentication" )
  {
    emit loginIncorrect();
  }
  else if( faultCode == "psf:Redirect" )
  {
    QString redirectUrl = XmlFunctions::getNodeValue( faultNode, "redirectUrl" );
#ifdef KMESSDEBUG_PASSPORTLOGINSERVICE
    kdDebug() << "PassportLoginService: received redirect to " << redirectUrl << endl;
#endif

    // Limit the number of redirects.
    if( redirects_ > 5 )
    {
      emit requestFailed( this, faultCode, i18n("Too many redirects by login service"), faultNode );
      return;
    }

    // Switch to different end point and retry.
    redirects_++;
    setEndpoint( redirectUrl );
    requestMultipleSecurityTokens();
  }
  else
  {
    HttpSoapConnection::parseSoapFault( faultNode, headerNode );
  }
}



/**
 * @brief Internal function to process the server response.
 *
 * This handles the <code>wst:RequestSecurityTokenResponseCollection</code> response node.
 * It ignores most of the XML by only looking for the <code>BinarySecurityToken</code> element.
 *
 * The result messsage looks like:
 * @code
<?xml version="1.0" encoding="utf-8" ?>
<S:Envelope xmlns:S="http://schemas.xmlsoap.org/soap/envelope/">
  <S:Header>
    <psf:pp xmlns:psf="http://schemas.microsoft.com/Passport/SoapServices/SOAPFault">
      <psf:serverVersion>1</psf:serverVersion>
      <psf:PUID>0006000087144D8E</psf:PUID>
      <psf:configVersion>3.100.2199.0</psf:configVersion>
      <psf:uiVersion>3.0.869.0</psf:uiVersion>
      <psf:authstate>0x48803</psf:authstate>
      <psf:reqstatus>0x0</psf:reqstatus>
      <psf:serverInfo Path="Live1" RollingUpgradeState="ExclusiveNew" LocVersion="0" ServerTime="2007-04-29T11:30:36Z">BAYPPLOGN2B29 2007.04.23.14.59.31</psf:serverInfo>
      <psf:cookies/>
      <psf:browserCookies>
        <psf:browserCookie Name="MH" URL="http://www.msn.com">MH=; path=/; domain=.msn.com; expires=Wed, 30-Dec-2037 16:00:00 GMT</psf:browserCookie>
        <psf:browserCookie Name="MHW" URL="http://www.msn.com">MHW=; path=/; domain=.msn.com; expires=Thu, 30-Oct-1980 16:00:00 GMT</psf:browserCookie>
        <psf:browserCookie Name="MH" URL="http://www.live.com">MH=; path=/; domain=.live.com; expires=Wed, 30-Dec-2037 16:00:00 GMT</psf:browserCookie>
        <psf:browserCookie Name="MHW" URL="http://www.live.com">MHW=; path=/; domain=.live.com; expires=Thu, 30-Oct-1980 16:00:00 GMT</psf:browserCookie>
      </psf:browserCookies>
      <psf:credProperties>
        <psf:credProperty Name="MainBrandID"></psf:credProperty>
        <psf:credProperty Name="BrandIDList"></psf:credProperty>
        <psf:credProperty Name="IsWinLiveUser">true</psf:credProperty>
        <psf:credProperty Name="CID">e95c54c3221ebb92</psf:credProperty>
      </psf:credProperties>
      <psf:extProperties></psf:extProperties>
      <psf:response/>
    </psf:pp>
  </S:Header>
  <S:Body>
    <wst:RequestSecurityTokenResponseCollection
      xmlns:S="http://schemas.xmlsoap.org/soap/envelope/"
      xmlns:wst="http://schemas.xmlsoap.org/ws/2004/04/trust"
      xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext"
      xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
      xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
      xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"
      xmlns:psf="http://schemas.microsoft.com/Passport/SoapServices/SOAPFault">
      <wst:RequestSecurityTokenResponse>
        <wst:TokenType>urn:passport:legacy</wst:TokenType>
        <wsp:AppliesTo xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing">
          <wsa:EndpointReference>
            <wsa:Address>http://Passport.NET/tb</wsa:Address>
          </wsa:EndpointReference>
        </wsp:AppliesTo>
        <wst:LifeTime>
          <wsu:Created>2007-04-29T11:30:36Z</wsu:Created>
          <wsu:Expires>2007-04-30T11:30:36Z</wsu:Expires>
        </wst:LifeTime>
        <wst:RequestedSecurityToken>
          <EncryptedData xmlns="http://www.w3.org/2001/04/xmlenc#"
            Id="BinaryDAToken0" Type="http://www.w3.org/2001/04/xmlenc#Element">
            <EncryptionMethod Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc"></EncryptionMethod>
            <ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
              <ds:KeyName>http://Passport.NET/STS</ds:KeyName>
            </ds:KeyInfo>
            <CipherData>
              <CipherValue>...some kind of long key...</CipherValue>
            </CipherData>
          </EncryptedData>
        </wst:RequestedSecurityToken>
        <wst:RequestedTokenReference>
          <wsse:KeyIdentifier ValueType="urn:passport"></wsse:KeyIdentifier>
          <wsse:Reference URI="#BinaryDAToken0"></wsse:Reference>
        </wst:RequestedTokenReference>
        <wst:RequestedProofToken>
          <wst:BinarySecret>...some kind of key...</wst:BinarySecret>
        </wst:RequestedProofToken>
      </wst:RequestSecurityTokenResponse>
      <wst:RequestSecurityTokenResponse>
        <wst:TokenType>urn:passport:legacy</wst:TokenType>
        <wsp:AppliesTo xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing">
          <wsa:EndpointReference>
            <wsa:Address>messenger.msn.com</wsa:Address>
          </wsa:EndpointReference>
        </wsp:AppliesTo>
        <wst:LifeTime>
          <wsu:Created>2007-04-29T11:30:36Z</wsu:Created>
          <wsu:Expires>2007-04-29T11:38:56Z</wsu:Expires>
        </wst:LifeTime>
        <wst:RequestedSecurityToken>
          <wsse:BinarySecurityToken Id="PPToken1">...the token for the USR command...</wsse:BinarySecurityToken>
        </wst:RequestedSecurityToken>
        <wst:RequestedTokenReference>
          <wsse:KeyIdentifier ValueType="urn:passport"></wsse:KeyIdentifier>
          <wsse:Reference URI="#PPToken1"></wsse:Reference>
        </wst:RequestedTokenReference>
      </wst:RequestSecurityTokenResponse>
    </wst:RequestSecurityTokenResponseCollection>
  </S:Body>
</S:Envelope>
@endcode
 *
 * @param resultRoot  The result root of the SOAP response.
 * @param headerNode  The SOAP header node.
 */
00341 void PassportLoginService::parseSoapResult( const QDomElement &resultRoot, const QDomElement &/*headerNode*/ )
{
  QString resultType = resultRoot.nodeName();

#ifdef KMESSDEBUG_PASSPORTLOGINSERVICE
  kdDebug() << "PassportLoginService::slotRequestFinished: Parsing result node '" << resultType << "'" << endl;
#endif

  // Close first so crashes in TcpConnectionBase::slotSocketReadyRead() are avoided.
  // That would happen when the listeners destroy this object.
  closeConnection();

  // Determine the object type
  if( resultType == "wst:RequestSecurityTokenResponseCollection" )
  {
    // Received the response for our "RequestMultipleSecurityTokens" request. 
    QDomNodeList authTokens = resultRoot.elementsByTagName("BinarySecurityToken");
    if( authTokens.count() == 0 )
    {
      // Likely an login error.
      emit loginIncorrect();
    }
    else
    {
      if( authTokens.count() > 1 )
      {
        kdWarning() << "PassportLoginService::slotRequestFinished: Multiple authentication tokens found, "
                    << "using first one!" << endl;
      }

      // The 't=...&p=..." like should be in the "BinarySecurityToken" element.
      emit loginSucceeded( authTokens.item(0).toElement().text() );
    }
  }
  else
  {
    kdWarning() << "PassportLoginService::slotRequestFinished: Could not parse response "
                << "(type=" << resultType
                << " endpoint=" << getEndpoint() << ")" << endl;
    emit requestFailed( this, "Unexpected response type" );  // not translated, see MsnNotificationConnection::soapRequestFailed
  }
}



/**
 * @brief SOAP call to request the login tokens.
 *
 * This method is called by login().
 * It sends a huge binary blob which looks like this:
 * @code
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema"
  xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Header>
    <ps:AuthInfo Id="PPAuthInfo"
      xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL">
      <ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>
      <ps:BinaryVersion>4</ps:BinaryVersion>
      <ps:UIVersion>1</ps:UIVersion>
      <ps:Cookies></ps:Cookies>
      <ps:RequestParams>AQAAAAIAAABsYwQAAAAzMDg0</ps:RequestParams>
    </ps:AuthInfo>
    <wsse:Security xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext">
      <wsse:UsernameToken Id="user">
        <wsse:Username>...the login() username...</wsse:Username>
        <wsse:Password>...the login() password...</wsse:Password>
      </wsse:UsernameToken>
    </wsse:Security>
  </soap:Header>
  <soap:Body>
    <ps:RequestMultipleSecurityTokens Id="RSTS"
      xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL"
      xmlns:wst="http://schemas.xmlsoap.org/ws/2004/04/trust"
      xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"
      xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing"
      xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext">
      <wst:RequestSecurityToken Id="RST0">
        <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
        <wsp:AppliesTo>
          <wsa:EndpointReference>
            <wsa:Address>http://Passport.NET/tb</wsa:Address>
          </wsa:EndpointReference>
        </wsp:AppliesTo>
      </wst:RequestSecurityToken>
      <wst:RequestSecurityToken Id="RST1">
        <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>
        <wsp:AppliesTo>
          <wsa:EndpointReference>
            <wsa:Address>messenger.msn.com</wsa:Address>
          </wsa:EndpointReference>
        </wsp:AppliesTo>
        <wsse:PolicyReference URI="...the login() parameters..."></wsse:PolicyReference>
      </wst:RequestSecurityToken>
    </ps:RequestMultipleSecurityTokens>
  </soap:Body>
</soap:Envelope> 
@endcode
 */
00442 void PassportLoginService::requestMultipleSecurityTokens()
{
#ifdef KMESSDEBUG_PASSPORTLOGINSERVICE
  kdDebug() << "PassportLoginService - Step 1. Requesting authentication data." << endl;
#endif

  QString authParams = KURL::decode_string( authenticationParameters_ )
                       .replace(",", "&");

  QString header = "<ps:AuthInfo Id=\"PPAuthInfo\""
                   " xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\">\n"
                   "  <ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>\n"
                   "  <ps:BinaryVersion>4</ps:BinaryVersion>\n"
                   "  <ps:UIVersion>1</ps:UIVersion>\n"
                   "  <ps:Cookies></ps:Cookies>\n"
                   "<ps:RequestParams>AQAAAAIAAABsYwQAAAAzMDg0</ps:RequestParams>\n"
                   "</ps:AuthInfo>\n"
                   "<wsse:Security"
                   " xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2003/06/secext\">\n"
                   "  <wsse:UsernameToken Id=\"user\">\n"
                   "    <wsse:Username>" + escapeString( handle_ ) + "</wsse:Username>\n"
                   "    <wsse:Password>" + escapeString( password_ ) + "</wsse:Password>\n"
                   "  </wsse:UsernameToken>\n"
                   "</wsse:Security>";

  QString body = "<ps:RequestMultipleSecurityTokens Id=\"RSTS\""
                 " xmlns:ps=\"http://schemas.microsoft.com/Passport/SoapServices/PPCRL\""
                 " xmlns:wst=\"http://schemas.xmlsoap.org/ws/2004/04/trust\""
                 " xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2002/12/policy\""
                 " xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/03/addressing\""
                 " xmlns:wsse=\"http://schemas.xmlsoap.org/ws/2003/06/secext\">\n"

                 "  <wst:RequestSecurityToken Id=\"RST0\">\n"
                 "    <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\n"
                 "    <wsp:AppliesTo>\n"
                 "      <wsa:EndpointReference>\n"
                 "        <wsa:Address>http://Passport.NET/tb</wsa:Address>\n"
                 "      </wsa:EndpointReference>\n"
                 "    </wsp:AppliesTo>\n"
                 "  </wst:RequestSecurityToken>\n"

                 "  <wst:RequestSecurityToken Id=\"RST1\">\n"
                 "    <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\n"
                 "    <wsp:AppliesTo>\n"
                 "      <wsa:EndpointReference>\n"
                 "        <wsa:Address>messenger.msn.com</wsa:Address>\n"
                 "      </wsa:EndpointReference>\n"
                 "    </wsp:AppliesTo>\n"
                 "    <wsse:PolicyReference URI=\"?" + escapeString( authParams ) + "\"></wsse:PolicyReference>\n"
                 "  </wst:RequestSecurityToken>\n"

                 "</ps:RequestMultipleSecurityTokens>";

  sendRequest( QString::null, body, header );
}


#include "passportloginservice.moc"

Generated by  Doxygen 1.6.0   Back to index