How to authenticate on Google Talk with the AccountManager authentication token using Smack API?

advertisements

This question is similar to: Authenticate to Google Talk (XMPP, Smack) using an authToken

  1. I have android.accounts.AccountManager class and its methods to get authentication token for Google account:

    public AccountManagerFuture<Bundle> getAuthToken (Account account,
           String authTokenType, Bundle options, Activity activity,
           AccountManagerCallback<Bundle> callback, Handler handler)
    
    
  2. I know how to prepare authentication XML:

    jidAndToken ="\0" + UTF8([email protected]) + "\0" + Auth
    
    

    (where "\0" is intended to be a single octet with value zero). Use this in the initial SASL auth:

    <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl'
          mechanism='X-GOOGLE-TOKEN'>Base64(jidAndToken)</auth>
    
    

But I failed to integrate it with Smack API like someone did for facebook chat here: XMPP with Java Asmack library supporting X-FACEBOOK-PLATFORM

Can someone help me?


Vijay,

Your code helped me a lot thanks! I am posting here to offer my solution to the problem of using AccountManager to log into Google talk. So far I have not found a full solution out there, but I have developed mine based on the above code and correcting a few lines that do not work.

There are two parts to the solution. The first is based on the above idea and code. It is to create a subclass of SASLMechanism:

import java.io.IOException;
import java.net.URLEncoder;

import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.sasl.SASLMechanism;

import android.util.Base64;
import android.util.Log;

public class GTalkOAuth2 extends SASLMechanism {
public static final String NAME="X-GOOGLE-TOKEN";

public GTalkOAuth2(SASLAuthentication saslAuthentication) {
    super(saslAuthentication);
}

@Override
protected String getName() {
    return NAME;
}

static void enable() { }

@Override
protected void authenticate() throws IOException, XMPPException
{
    String authCode = password;
    String jidAndToken = "\0" + URLEncoder.encode( authenticationId, "utf-8" ) + "\0" + authCode;

    StringBuilder stanza = new StringBuilder();
    stanza.append( "<auth mechanism=\"" ).append( getName() );
    stanza.append( "\" xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">" );
    stanza.append( new String(Base64.encode( jidAndToken.getBytes( "UTF-8" ), Base64.DEFAULT ) ) );

    stanza.append( "</auth>" );

    Log.v("BlueTalk", "Authentication text is "+stanza);
    // Send the authentication to the server
    getSASLAuthentication().send( new Auth2Mechanism(stanza.toString()) );
}

public class Auth2Mechanism extends Packet {
    String stanza;
    public Auth2Mechanism(String txt) {
        stanza = txt;
    }
    public String toXML() {
        return stanza;
    }
}

/**
 * Initiating SASL authentication by select a mechanism.
 */
public class AuthMechanism extends Packet {
    final private String name;
    final private String authenticationText;

    public AuthMechanism(String name, String authenticationText) {
        if (name == null) {
            throw new NullPointerException("SASL mechanism name shouldn't be null.");
        }
        this.name = name;
        this.authenticationText = authenticationText;
    }

    public String toXML() {
        StringBuilder stanza = new StringBuilder();
        stanza.append("<auth mechanism=\"").append(name);
        stanza.append("\" xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
        if (authenticationText != null &&
                authenticationText.trim().length() > 0) {
            stanza.append(authenticationText);
        }
        stanza.append("</auth>");
        return stanza.toString();
    }
    }
}

The second part is the use of it. The big thing that no other example gave me is that when getting the token from the AccountManager system, the token type is not "ah" but "mail". The idea was out there in examples doing a direct communication with google servers to get the token but not in requesting it from AccountManager. Putting them together gives that you need to do the following in your driver code. Create a function to get the token:

public String getAuthToken(String name)
{
    Context context = getApplicationContext();
    Activity activity = this;
    String retVal = "";
    Account account = new Account(name, "com.google");
    AccountManagerFuture<Bundle> accFut = AccountManager.get(context).getAuthToken(account, "mail", null, activity, null, null);
    try
    {
        Bundle authTokenBundle = accFut.getResult();
        retVal = authTokenBundle.get(AccountManager.KEY_AUTHTOKEN).toString();
    }
    catch (OperationCanceledException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    catch (AuthenticatorException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    catch (IOException e)
    {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return retVal;
}

And then call it after making sure the right SASL system will be used:

SASLAuthentication.registerSASLMechanism( GTalkOAuth2.NAME, GTalkOAuth2.class );
SASLAuthentication.supportSASLMechanism( GTalkOAuth2.NAME, 0 );
config.setSASLAuthenticationEnabled(true);

String saslAuthString = getAuthToken(acct.name);
connection = new XMPPConnection(config);
try {
    connection.connect();
    connection.login(name, saslAuthString);
} catch (XMPPException e) {
    // Most likely an expired token
    // Invalidate the token and start over. There are example of this available
}

Happy Google Talking!