XMPP con biblioteca Asmack de Java compatible con X-FACEBOOK-PLATFORM

Estoy tratando de hacer un Chat de Facebook en Android con la biblioteca Smack. He leído la API de Chat de Facebook, pero no puedo entender cómo tengo que autenticarme con Facebook utilizando esta biblioteca.

¿Alguien puede indicarme cómo lograr esto?

Actualización : de acuerdo con la respuesta no.good.at.coding, tengo este código adaptado a la biblioteca Asmack. Todo funciona bien excepto que recibo como respuesta al inicio de sesión: no autorizado. Aquí está el código que uso:

public class SASLXFacebookPlatformMechanism extends SASLMechanism { private static final String NAME = "X-FACEBOOK-PLATFORM"; private String apiKey = ""; private String applicationSecret = ""; private String sessionKey = ""; /** * Constructor. */ public SASLXFacebookPlatformMechanism(SASLAuthentication saslAuthentication) { super(saslAuthentication); } @Override protected void authenticate() throws IOException, XMPPException { getSASLAuthentication().send(new AuthMechanism(NAME, "")); } @Override public void authenticate(String apiKeyAndSessionKey, String host, String applicationSecret) throws IOException, XMPPException { if (apiKeyAndSessionKey == null || applicationSecret == null) { throw new IllegalArgumentException("Invalid parameters"); } String[] keyArray = apiKeyAndSessionKey.split("\\|", 2); if (keyArray.length < 2) { throw new IllegalArgumentException( "API key or session key is not present"); } this.apiKey = keyArray[0]; Log.d("API_KEY", apiKey); this.applicationSecret = applicationSecret; Log.d("SECRET_KEY", applicationSecret); this.sessionKey = keyArray[1]; Log.d("SESSION_KEY", sessionKey); this.authenticationId = sessionKey; this.password = applicationSecret; this.hostname = host; String[] mechanisms = { "DIGEST-MD5" }; Map props = new HashMap(); this.sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this); authenticate(); } @Override protected String getName() { return NAME; } @Override public void challengeReceived(String challenge) throws IOException { byte[] response = null; if (challenge != null) { String decodedChallenge = new String(Base64.decode(challenge)); Log.d("DECODED", decodedChallenge); Map parameters = getQueryMap(decodedChallenge); String version = "1.0"; String nonce = parameters.get("nonce"); String method = parameters.get("method"); long callId = new GregorianCalendar().getTimeInMillis() / 1000L; String sig = "api_key=" + apiKey + "call_id=" + callId + "method=" + method + "nonce=" + nonce + "session_key=" + sessionKey + "v=" + version + applicationSecret; try { sig = md5(sig); sig = sig.toUpperCase(); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); } String composedResponse = "api_key=" + URLEncoder.encode(apiKey, "utf-8") + "&call_id=" + callId + "&method=" + URLEncoder.encode(method, "utf-8") + "&nonce=" + URLEncoder.encode(nonce, "utf-8") + "&session_key=" + URLEncoder.encode(sessionKey, "utf-8") + "&v=" + URLEncoder.encode(version, "utf-8") + "&sig=" + URLEncoder.encode(sig, "utf-8"); Log.d("COMPOSED", composedResponse); response = composedResponse.getBytes("utf-8"); } String authenticationText = ""; if (response != null) { authenticationText = Base64.encodeBytes(response, Base64.DONT_BREAK_LINES); } // Send the authentication to the server getSASLAuthentication().send(new Response(authenticationText)); } private Map getQueryMap(String query) { Map map = new HashMap(); String[] params = query.split("\\&"); for (String param : params) { String[] fields = param.split("=", 2); map.put(fields[0], (fields.length > 1 ? fields[1] : null)); } return map; } private String md5(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(text.getBytes("utf-8"), 0, text.length()); return convertToHex(md.digest()); } private String convertToHex(byte[] data) { StringBuilder buf = new StringBuilder(); int len = data.length; for (int i = 0; i >> 4) & 0xF; int twoHalfs = 0; do { if (0 <= halfByte && halfByte <= 9) { buf.append((char) ('0' + halfByte)); } else { buf.append((char) ('a' + halfByte - 10)); } halfByte = data[i] & 0xF; } while (twoHalfs++ < 1); } return buf.toString(); } } 

Y esto, es la comunicación con el servidor con los mensajes enviados y recibidos:

 PM SENT (1132418216):  PM RCV (1132418216): X-FACEBOOK-PLATFORMDIGEST-MD5 PM SENT (1132418216):  PM RCV (1132418216): dmVyc2lvbj0xJm1ldGhvZD1hdXRoLnhtcHBfbG9naW4mbm9uY2U9NzFGNkQ3Rjc5QkIyREJCQ0YxQTkwMzA0QTg3OTlBMzM= PM SENT (1132418216): YXBpX2tleT0zMWYzYjg1ZjBjODYwNjQ3NThiZTZhOTQyNjVjZmNjMCZjYWxsX2lkPTEzMDA0NTYxMzUmbWV0aG9kPWF1dGgueG1wcF9sb2dpbiZub25jZT03MUY2RDdGNzlCQjJEQkJDRjFBOTAzMDRBODc5OUEzMyZzZXNzaW9uX2tleT0yNjUzMTg4ODNkYWJhOGRlOTRiYTk4ZDYtMTAwMDAwNTAyNjc2Nzc4JnY9MS4wJnNpZz04RkRDRjRGRTgzMENGOEQ3QjgwNjdERUQyOEE2RERFQw== PM RCV (1132418216):  

Según se lee en el foro de Facebook de los desarrolladores , es necesario desactivar la configuración “Deshabilitar métodos de autenticación en desuso” en la página de configuración de Facebook de su aplicación. Pero, incluso haciendo eso, no puedo iniciar sesión. Y la clave de sesión es la segunda parte del token de OAuth en el formato AAA | BBB | CCC, quiero decir, BBB.

Finalmente, gracias al código no.good.at.coding y la sugerencia de harism, he podido conectarme al chat de Facebook. Este código es el mecanismo para la biblioteca Asmack (el puerto Smack para Android). Para la biblioteca Smack es necesario utilizar el mecanismo no.good.at.coding.

SASLXFacebookPlatformMechanism.java:

 import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; import org.apache.harmony.javax.security.auth.callback.CallbackHandler; import org.apache.harmony.javax.security.sasl.Sasl; import org.jivesoftware.smack.SASLAuthentication; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.sasl.SASLMechanism; import org.jivesoftware.smack.util.Base64; public class SASLXFacebookPlatformMechanism extends SASLMechanism { private static final String NAME = "X-FACEBOOK-PLATFORM"; private String apiKey = ""; private String applicationSecret = ""; private String sessionKey = ""; /** * Constructor. */ public SASLXFacebookPlatformMechanism(SASLAuthentication saslAuthentication) { super(saslAuthentication); } @Override protected void authenticate() throws IOException, XMPPException { getSASLAuthentication().send(new AuthMechanism(NAME, "")); } @Override public void authenticate(String apiKeyAndSessionKey, String host, String applicationSecret) throws IOException, XMPPException { if (apiKeyAndSessionKey == null || applicationSecret == null) { throw new IllegalArgumentException("Invalid parameters"); } String[] keyArray = apiKeyAndSessionKey.split("\\|", 2); if (keyArray.length < 2) { throw new IllegalArgumentException( "API key or session key is not present"); } this.apiKey = keyArray[0]; this.applicationSecret = applicationSecret; this.sessionKey = keyArray[1]; this.authenticationId = sessionKey; this.password = applicationSecret; this.hostname = host; String[] mechanisms = { "DIGEST-MD5" }; Map props = new HashMap(); this.sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this); authenticate(); } @Override public void authenticate(String username, String host, CallbackHandler cbh) throws IOException, XMPPException { String[] mechanisms = { "DIGEST-MD5" }; Map props = new HashMap(); this.sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh); authenticate(); } @Override protected String getName() { return NAME; } @Override public void challengeReceived(String challenge) throws IOException { byte[] response = null; if (challenge != null) { String decodedChallenge = new String(Base64.decode(challenge)); Map parameters = getQueryMap(decodedChallenge); String version = "1.0"; String nonce = parameters.get("nonce"); String method = parameters.get("method"); long callId = new GregorianCalendar().getTimeInMillis(); String sig = "api_key=" + apiKey + "call_id=" + callId + "method=" + method + "nonce=" + nonce + "session_key=" + sessionKey + "v=" + version + applicationSecret; try { sig = md5(sig); } catch (NoSuchAlgorithmException e) { throw new IllegalStateException(e); } String composedResponse = "api_key=" + URLEncoder.encode(apiKey, "utf-8") + "&call_id=" + callId + "&method=" + URLEncoder.encode(method, "utf-8") + "&nonce=" + URLEncoder.encode(nonce, "utf-8") + "&session_key=" + URLEncoder.encode(sessionKey, "utf-8") + "&v=" + URLEncoder.encode(version, "utf-8") + "&sig=" + URLEncoder.encode(sig, "utf-8"); response = composedResponse.getBytes("utf-8"); } String authenticationText = ""; if (response != null) { authenticationText = Base64.encodeBytes(response, Base64.DONT_BREAK_LINES); } // Send the authentication to the server getSASLAuthentication().send(new Response(authenticationText)); } private Map getQueryMap(String query) { Map map = new HashMap(); String[] params = query.split("\\&"); for (String param : params) { String[] fields = param.split("=", 2); map.put(fields[0], (fields.length > 1 ? fields[1] : null)); } return map; } private String md5(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(text.getBytes("utf-8"), 0, text.length()); return convertToHex(md.digest()); } private String convertToHex(byte[] data) { StringBuilder buf = new StringBuilder(); int len = data.length; for (int i = 0; i < len; i++) { int halfByte = (data[i] >>> 4) & 0xF; int twoHalfs = 0; do { if (0 < = halfByte && halfByte <= 9) { buf.append((char) ('0' + halfByte)); } else { buf.append((char) ('a' + halfByte - 10)); } halfByte = data[i] & 0xF; } while (twoHalfs++ < 1); } return buf.toString(); } } 

Para usarlo:

 ConnectionConfiguration config = new ConnectionConfiguration("chat.facebook.com", 5222); config.setSASLAuthenticationEnabled(true); XMPPConnection xmpp = new XMPPConnection(config); try { SASLAuthentication.registerSASLMechanism("X-FACEBOOK-PLATFORM", SASLXFacebookPlatformMechanism.class); SASLAuthentication.supportSASLMechanism("X-FACEBOOK-PLATFORM", 0); xmpp.connect(); xmpp.login(apiKey + "|" + sessionKey, sessionSecret, "Application"); } catch (XMPPException e) { xmpp.disconnect(); e.printStackTrace(); } 

apiKey es la clave API que se proporciona en la página de configuración de la aplicación en Facebook. sessionKey es la segunda parte del token de acceso. Si el token está en esta forma, AAA | BBB | CCC, BBB es la clave de sesión. sessionSecret se obtiene utilizando la antigua API REST con el método auth.promoteSession. Para usarlo, es necesario hacer que un HTTP llegue a esta url:

https://api.facebook.com/method/auth.promoteSession?access_token=yourAccessToken

A pesar de que la documentación de Facebook Chat dice que es necesario usar la clave secreta de la aplicación, solo cuando utilicé la clave que devolvió ese método REST, pude hacerlo funcionar. Para hacer que ese método funcione, debe desactivar la opción Deshabilitar métodos de autenticación en desuso en la ficha Avanzado en la configuración de la aplicación.

Lo había usado hace unos 6 meses con Smack (no asmack), así que no estoy seguro de cómo va a aguantar ahora, pero aquí va, ¡espero que ayude!

Encontré una implementación del mecanismo de autenticación X-FACEBOOK-PLATFORM de Facebook en el foro Ignite Realtime Smack donde alguien lo obtuvo del proyecto fbgc . Encontrarás un ZIP con la fuente SASLXFacebookPlatformMechanism.java en la respuesta a la que me he vinculado. Puedes usarlo de la siguiente manera:

 public void login() throws XMPPException { SASLAuthentication.registerSASLMechanism(SASLXFacebookPlatformMechanism.NAME, SASLXFacebookPlatformMechanism.class); SASLAuthentication.supportSASLMechanism(SASLXFacebookPlatformMechanism.NAME, 0); ConnectionConfiguration connConfig = new ConnectionConfiguration(host, port); XMPPConnection connection = new XMPPConnection(connConfig); connection.connect(); log.info("XMPP client connected"); connection.login(Utils.FB_APP_ID + "|" + this.user.sessionId, Utils.FB_APP_SECRET, "app_name"); log.info("XMPP client logged in"); } 

Estaba haciendo esto en el servidor sin un SDK. No recuerdo los detalles (y la documentación de Facebook no es muy buena) pero por lo que puedo decir de mi código, después de lograr que el usuario autorice la aplicación, recibo una solicitud de callback de Facebook con un parámetro de code . Abro URLConnection a https://graph.facebook.com/oauth/access_token?client_id=&redirect_uri=http://myserver/context/path/&client_secret=&code= . La respuesta debe ser el token de acceso donde la identificación de sesión es la parte después del | - algo de la forma XXX| .