How to handle multiple Authenticator - android

To authenticate a request, I use Authenticator.setDefault
which is VM wide...
What If I want to separate different webservices
and each one are aware of their authentication credentials.
Do I need to Authenticator.setDefault for each request ?
This may not work if there are concurrent connection with mixed webservices...

Building on Mike's response above I have the following solution, because while I much appreciate the general idea (that's why I've copied it ;-) , I see a few problems with it:
Mike's solution will throw a NullPointerException if the JDK requests the authentication via one of the two static request methods in java.net.Authenticator that do not pass the URL (then getRequestingURL() will return null).
It requires you to pass in an external regex pattern that deconstructs the URL. This is (very) easy to get wrong, and the URL class in the JDK implements this parsing, so I prefer to use that.
It requires that some external class builds the map of PasswordAuthentication objects, and then sets it. It does not implement a registration mechanism that other components in your system can use. I've also turned it into a singleton.
More of a style thing: I don't recommend duplicating class names (Authenticator), so I've renamed it DefaultAuthenticator.
Below solution I think solves these issues.
/**
* Authenticator which keeps credentials to be passed to the requestor based on authority of the requesting URL. The
* authority is <pre>user:password#host:port</pre>, where all parts are optional except the host.
* <p>
* If the configured credentials are not found, the Authenticator will use the credentials embedded in the URL, if
* present. Embedded credentials are in the form of <pre>user:password#host:port</pre>
*
* #author Michael Fortin 2011-09-23
*/
public final class DefaultAuthenticator extends Authenticator {
private static final Logger LOG = Logger.getLogger(DefaultAuthenticator.class.getName());
private static DefaultAuthenticator instance;
private Map<String, PasswordAuthentication> authInfo = new HashMap<String, PasswordAuthentication>();
private DefaultAuthenticator() {
}
public static synchronized DefaultAuthenticator getInstance() {
if (instance == null) {
instance = new DefaultAuthenticator();
Authenticator.setDefault(instance);
}
return instance;
}
// unit testing
static void reset() {
instance = null;
Authenticator.setDefault(null);
}
#Override
protected PasswordAuthentication getPasswordAuthentication() {
String requestorInfo = getRequestorInfo();
LOG.info(getRequestorType() + " at \"" + getRequestingPrompt() + "\" is requesting " + getRequestingScheme()
+ " password authentication for \"" + requestorInfo + "\"");
if (authInfo.containsKey(requestorInfo)) {
return authInfo.get(requestorInfo);
} else {
PasswordAuthentication pa = getEmbeddedCredentials(getRequestingURL());
if (pa == null) {
LOG.warning("No authentication information");
}
return pa;
}
}
/**
* Register the authentication information for a given URL.
*
* #param url - the URL that will request authorization
* #param auth - the {#link PasswordAuthentication} for this URL providing the credentials
*/
public void register(URL url, PasswordAuthentication auth) {
String requestorInfo = getRequestorInfo(url.getHost(), url.getPort());
authInfo.put(requestorInfo, auth);
}
/**
* Get the requestor info based on info provided.
*
* #param host - hostname of requestor
* #param port - TCP/IP port
* #return requestor info string
*/
private String getRequestorInfo(String host, int port) {
String fullHostname;
try {
InetAddress addr = InetAddress.getByName(host);
fullHostname = addr.getCanonicalHostName();
} catch (UnknownHostException e) {
fullHostname = host;
}
if (port == -1) {
return fullHostname;
} else {
return fullHostname + ":" + port;
}
}
/**
* Get the requestor info for the request currently being processed by this Authenticator.
*
* #return requestor info string for current request
*/
private String getRequestorInfo() {
String host;
InetAddress addr = getRequestingSite();
if (addr == null) {
host = getRequestingHost();
} else {
host = addr.getCanonicalHostName();
}
return getRequestorInfo(host, getRequestingPort());
}
/**
* Get the credentials from the requesting URL.
*
* #param url - URL to get the credentials from (can be null, method will return null)
* #return PasswordAuthentication with credentials from URL or null if URL contains no credentials or if URL is
* null itself
*/
PasswordAuthentication getEmbeddedCredentials(URL url) {
if (url == null) {
return null;
}
String userInfo = url.getUserInfo();
int colon = userInfo == null ? -1 : userInfo.indexOf(":");
if (colon == -1) {
return null;
} else {
String userName = userInfo.substring(0, colon);
String pass = userInfo.substring(colon + 1);
return new PasswordAuthentication(userName, pass.toCharArray());
}
}
}
While I'm at it, let me give you my unit tests (JUnit 4).
/**
* #author Paul Balm - May 10 2012
*/
public class DefaultAuthenticatorTest {
private static final Logger LOG = Logger.getLogger(DefaultAuthenticatorTest.class.getName());
#Before
public void setUp() throws Exception {
DefaultAuthenticator.reset();
DefaultAuthenticator.getInstance();
}
#After
public void tearDown() {
DefaultAuthenticator.reset();
}
#Test
public void testRequestAuthenticationFromURL() throws MalformedURLException, UnknownHostException {
Map<String, String[]> urls = generateURLs();
for (String urlStr : urls.keySet()) {
String[] userInfo = urls.get(urlStr);
LOG.info("Testing: " + urlStr);
URL url = new URL(urlStr);
request(userInfo[1], userInfo[2], url, true);
}
}
#Test
public void testRequestAuthenticationRegistered() throws UnknownHostException, MalformedURLException {
Map<String, String[]> urls = generateURLs();
for (String urlStr : urls.keySet()) {
String[] userInfo = urls.get(urlStr);
LOG.info("Testing: " + urlStr);
URL url = new URL(urlStr);
DefaultAuthenticator.reset();
DefaultAuthenticator auth = DefaultAuthenticator.getInstance();
String userName = userInfo[1];
String password = userInfo[2];
if (password != null) {
// You can't register a null password
auth.register(url, new PasswordAuthentication(userName, password.toCharArray()));
}
request(userName, password, url, false);
}
}
/**
* Generate a bunch of URLs mapped to String array. The String array has the following elements:
* - user info part of URL,
* - expected user,
* - expected password
*
* Note that the keys of the maps must be strings and not URL objects, because of the way URL.equals is
* implemented. This method does not consider the credentials.
*
* #throws MalformedURLException
*/
Map<String, String[]> generateURLs() {
String[] hosts = new String[]{ "127.0.0.1", "localhost.localdomain"};
List<String[]> userData = new ArrayList<String[]>();
// normal cases
userData.add(new String[] { "user:pass#", "user", "pass" }); // results in: http://user:pass#[host]
userData.add(new String[] { "", null, null });
// unexpected cases
userData.add(new String[] { "#", null, null });
userData.add(new String[] { ":#", "", "" });
userData.add(new String[] { "user:#", "user", "" });
userData.add(new String[] { ":pass#", "", "pass" });
Map<String, String[]> urls = new HashMap<String, String[]>();
for (String[] userInfo : userData) {
for (String host : hosts) {
String s = "http://" + userInfo[0] + host;
urls.put(s, userInfo);
}
}
LOG.info("" + urls.size() + " URLs prepared");
return urls;
}
private void request(String expectedUser, String expectedPass, URL url, boolean inURL)
throws UnknownHostException {
String host = url.getHost();
InetAddress addr = InetAddress.getAllByName(host)[0];
int port = url.getPort();
String protocol = url.getProtocol();
String prompt = ""; // prompt for the user when asking for the credentials
String scheme = "basic"; // or digest
RequestorType reqType = RequestorType.SERVER;
PasswordAuthentication credentials =
Authenticator.requestPasswordAuthentication(addr, port, protocol, prompt, scheme);
// If the credentials are in the URL, you can't find them using this method because we're not passing the URL
checkCredentials(url, inURL ? null : expectedUser, inURL ? null : expectedPass, credentials);
credentials = Authenticator.requestPasswordAuthentication(host, addr, port, protocol, prompt, scheme);
// If the credentials are in the URL, you can't find them using this method because we're not passing the URL
checkCredentials(url, inURL ? null : expectedUser, inURL ? null : expectedPass, credentials);
credentials = Authenticator.requestPasswordAuthentication(host, addr, port, protocol, prompt, scheme, url, reqType);
checkCredentials(url, expectedUser, expectedPass, credentials);
}
private void checkCredentials(URL url, String expectedUser, String expectedPass, PasswordAuthentication credentials) {
if (expectedUser == null) {
Assert.assertNull(url.toString(), credentials);
} else {
Assert.assertNotNull(url.toString(), credentials);
Assert.assertEquals(url.toString(), expectedUser, credentials.getUserName());
if (expectedPass == null) {
Assert.assertNull(url.toString(), credentials.getPassword());
} else {
Assert.assertArrayEquals(url.toString(), expectedPass.toCharArray(), credentials.getPassword());
}
}
}
}

Here's the solution I've implemented and it works like a charm!
import java.net.*;
import java.util.*;
import java.util.logging.*;
import java.util.regex.*;
/**
* Authenticator which keeps credentials to be passed to the requester
* based on the concatenation of the authority and the URL that requires
* authentication.
*
* If the configured credentials are not found, the Authenticator will
* use the embedded credentials if present.
*
* Embedded credentials are in the form of <pre><b>user</b>:<b>password</b><i>#host:port/<url-path></i></pre>
*
* #author Michael Fortin 2011-09-23
*/
public class Authenticator extends java.net.Authenticator {
private Logger log = Logger.getLogger(this.getClass().getName());
private Map<String, PasswordAuthentication> authInfos;
private Pattern embeddedAuthInfoPattern;
#Override
protected PasswordAuthentication getPasswordAuthentication() {
String requesterInfo = String.format("%s%s", getRequestingURL().getAuthority(), getRequestingURL().getPath());
log.fine(String.format("%s at \"%s\" is requesting %s password authentication for \"%s\"", getRequestorType(), getRequestingPrompt(), getRequestingScheme(), requesterInfo));
PasswordAuthentication pa = null;
if ((pa = authInfos.get(requesterInfo)) == null && (pa = getEmbeddedPA(getRequestingURL().getAuthority())) == null) {
log.warning(String.format("No authentication information for \"%s\"", requesterInfo));
}
return pa;
}
public void setAuthInfos(Map<String, PasswordAuthentication> authInfos) {
this.authInfos = authInfos;
}
public void setEmbeddedAuthInfoPattern(String pattern) {
this.embeddedAuthInfoPattern = Pattern.compile(pattern);
}
private PasswordAuthentication getEmbeddedPA(String authInfo) {
if (authInfo != null) {
Matcher matcher = embeddedAuthInfoPattern.matcher(authInfo);
if (matcher.find()) {
return new PasswordAuthentication(matcher.group(1), matcher.group(2).toCharArray());
}
}
return null;
}
}

Lack of answers tends to mean that nobody knows, which tells me there isn't an answer.
I've been wondering the same thing, and I think the answer is that it can't be done through java.net. I think you either need to limit your http accesses to one server at a time, or look into other packages such as org.apache.http.client.

Related

How to do SNS Push Notification? CreatePlatformEndpointResult returns null

I have followed a tutorial on how to setup SNS Push notification but the CreatePlatformEndpointResult object returns null. I need that so that I can retrieve the endpointArn and send that to the backend. Below is my entire setup. And here is the link to the tutorial: http://www.allcode.com/amazon-sns-push-notification-tutorial-android-using-gcm/
First I retrieve a token from GCM by calling
AWSManager.registerAppToGCM(getApplicationContext())
This is from my AWSManager class
public class AWSManager {
private static final String TAG = AWSManager.class.getSimpleName();
private static final String SNS_ACCESS_KEY_ID = "1234567890"; // I have swapped out the real key
private static final String SNS_SECRET_KEY = "1234567890"; // I have swapped out the real key
private static AmazonSNSClient snsClient;
/**
* Method is used to retrieve SNSClient object
*
* #return snsClient object
*/
public static AmazonSNSClient getSNSClient() {
if (FrameworkUtils.checkIfNull(snsClient)) {
snsClient = new AmazonSNSClient(new BasicAWSCredentials(SNS_ACCESS_KEY_ID, SNS_SECRET_KEY));
snsClient.setRegion(Region.getRegion(Regions.US_WEST_1));
}
return snsClient;
}
/**
* Method is used to register app to GCM
*
* #param context
*/
public static void registerAppToGCM(Context context) {
SharedPref sharedPref = new SharedPref(context, Constants.PREF_FILE_NAME);
String gcmToken = sharedPref.getStringPref(Constants.NOTIFICATION_GCM_TOKEN, "");
if (FrameworkUtils.isStringEmpty(gcmToken)) {
new GCMRegisterTask(context).execute();
}
}
}
Here is the class performing the background task
public class GCMRegisterTask extends AsyncTask<String, Void, Boolean> {
private static final String TAG = GCMRegisterTask.class.getSimpleName();
private Context mContext;
/**
* Constructor
*
* #param context
*/
public GCMRegisterTask(Context context) {
super();
mContext = context;
}
#Override
protected Boolean doInBackground(String... params) {
String token;
try {
token = InstanceID.getInstance(mContext).getToken(mContext.getString(R.string.gcm_project_id), GoogleCloudMessaging.INSTANCE_ID_SCOPE);
SharedPref sharedPref = new SharedPref(mContext, Constants.PREF_FILE_NAME);
sharedPref.setPref(Constants.NOTIFICATION_GCM_TOKEN, token);
Logger.i(TAG, "GCM token successfully stored to prefs: " + token);
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
Once I have successfully retrieved the GCM token. I use new
AWSCreateEndpointTask(mContext).execute(test, token,
"email#gmail.com")
to begin the process of creating the endpoint ARN. test = "arn:aws:sns:region:us-east-1:app/GCM/AppName"
public class AWSCreateEndpointTask extends AsyncTask<String, Void, CreatePlatformEndpointResult> {
Context mContext;
/**
* Constructor
*
* #param context
*/
public AWSCreateEndpointTask(Context context) {
super();
mContext = context;
}
#Override
protected CreatePlatformEndpointResult doInBackground(String[] params) {
if (params.length < 3) {
return null;
}
String arn = params[0];
String gcmToken = params[1];
String userData = params[2];
try {
CreatePlatformEndpointRequest request = new CreatePlatformEndpointRequest();
request.setCustomUserData(userData);
request.setToken(gcmToken);
request.setPlatformApplicationArn(arn);
return AWSManager.getSNSClient().createPlatformEndpoint(request);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
#Override
protected void onPostExecute(CreatePlatformEndpointResult result) {
if (!FrameworkUtils.checkIfNull(result)) {
String endpointArn = result.getEndpointArn();
SharedPref sharedPref = new SharedPref(mContext, Constants.PREF_FILE_NAME);
sharedPref.setPref(Constants.NOTIFICATION_ENDPOINT_ARN, endpointArn);
}
}
Inside of onPostExecute, the returned CreatePlatformEndpointResult object is null. What am I missing or doing incorrectly to cause this?
It turns out that the implementation is correct, the arn value was wrong. For others running into this conflict, make sure the information is correct when trying to get the endpointARN. I updated mine to
arn:aws:sns:us-east-1::app/GCM/
This comes from the developer console.

building, authenticating, and parsing with the Smack library

I am trying to connect to Firebase Cloud Messaging by using smack library.
I don't have much knowledge of the Smack API. After reading the Firebase docs, I see that the connection must be authenticated and there are a series of responses back and forth between "app server" and Cloud Connection Servers. According to the docs, I must create a Sasl Plain authentication. I don't know how to implement this. But after reading some posts by other StackOverFlow users I see that I must create an authentication class. Specifically, I was reviewing the answers and comments on "Gtalk XMPP SASL authentication failed using mechanism X-OAUTH2?" These responses that are between CCS and the "app server" are enclosed in and tags. I dont know how to use Smack to get or build these responses. I do have my connection set up with XMPP, and I've tried setting a addAsyncStanzListener to my XMPP connection with the hopes of getting some of these responses from CCS. But, nothing comes in. The responses between "app server" and CCS could be found in this link https://firebase.google.com/docs/cloud-messaging/server#connecting
Does anyone here know how to proceed from here. I think that Smack is not well documented and having little knowledge of XMPP makes it even worse. There are all these classes, Packets, extensions, IQ class, XML pull parsers etc.
Any rough structure on the set up would be ideal.
Thanks
Here is a snippet of my server code that I use with Firebase. If you want to understand in more detail try this blog post (it is how I figured out how to get everything to work in the end): http://www.grokkingandroid.com/xmpp-server-google-cloud-messaging/
In order to get the code below to work you will need to go to the Firebase console and go to project settings
From here you will need to take note of your Server Key and Sender ID and replace it in the code snippet below (it is near the bottom of the code) and save it in your path as SmackCcsClient.class
After doing that you can compile and run your server through the command promt:
// Set up java file (replace PATH_TO_WHERE_YOUR_CLASS_IS with your own path and make sure to put the json and smack JARs there as well
javac -d PATH_TO_WHERE_YOUR_CLASS_IS -sourcepath src -cp PATH_TO_WHERE_YOUR_CLASS_IS\json-simple-1.1.1.jar;PATH_TO_WHERE_YOUR_CLASS_IS\smack-3.4.1-0cec571.jar PATH_TO_WHERE_YOUR_CLASS_IS\SmackCcsClient.java
// Run
java -cp PATH_TO_WHERE_YOUR_CLASS_IS;PATH_TO_WHERE_YOUR_CLASS_IS\json-simple-1.1.1.jar;PATH_TO_WHERE_YOUR_CLASS_IS\smack-3.4.1-0cec571.jar SmackCcsClient
/*
SmackCcsClient:
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketInterceptor;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.DefaultPacketExtension;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smack.provider.PacketExtensionProvider;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smack.util.StringUtils;
import org.json.simple.JSONValue;
import org.json.simple.parser.ParseException;
import org.xmlpull.v1.XmlPullParser;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.*;
import javax.net.ssl.SSLSocketFactory;
/**
* Sample Smack implementation of a client for GCM Cloud Connection Server.
*
* For illustration purposes only.
*/
public class SmackCcsClient {
static final String REG_ID_STORE = "gcmchat.txt";
static final String MESSAGE_KEY = "SM";
Logger logger = Logger.getLogger("SmackCcsClient");
public static final String GCM_SERVER = "fcm-xmpp.googleapis.com";
public static final int GCM_PORT = 5235;
public static final String GCM_ELEMENT_NAME = "gcm";
public static final String GCM_NAMESPACE = "google:mobile:data";
static Random random = new Random();
XMPPConnection connection;
ConnectionConfiguration config;
/**
* XMPP Packet Extension for GCM Cloud Connection Server.
*/
class GcmPacketExtension extends DefaultPacketExtension {
String json;
public GcmPacketExtension(String json) {
super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
this.json = json;
}
public String getJson() {
return json;
}
#Override
public String toXML() {
return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME,
GCM_NAMESPACE, json, GCM_ELEMENT_NAME);
}
#SuppressWarnings("unused")
public Packet toPacket() {
return new Message() {
// Must override toXML() because it includes a <body>
#Override
public String toXML() {
StringBuilder buf = new StringBuilder();
buf.append("<message");
if (getXmlns() != null) {
buf.append(" xmlns=\"").append(getXmlns()).append("\"");
}
if (getLanguage() != null) {
buf.append(" xml:lang=\"").append(getLanguage())
.append("\"");
}
if (getPacketID() != null) {
buf.append(" id=\"").append(getPacketID()).append("\"");
}
if (getTo() != null) {
buf.append(" to=\"")
.append(StringUtils.escapeForXML(getTo()))
.append("\"");
}
if (getFrom() != null) {
buf.append(" from=\"")
.append(StringUtils.escapeForXML(getFrom()))
.append("\"");
}
buf.append(">");
buf.append(GcmPacketExtension.this.toXML());
buf.append("</message>");
return buf.toString();
}
};
}
}
public SmackCcsClient() {
// Add GcmPacketExtension
ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME,
GCM_NAMESPACE, new PacketExtensionProvider() {
#Override
public PacketExtension parseExtension(XmlPullParser parser)
throws Exception {
String json = parser.nextText();
GcmPacketExtension packet = new GcmPacketExtension(json);
return packet;
}
});
}
/**
* Returns a random message id to uniquely identify a message.
*
* <p>
* Note: This is generated by a pseudo random number generator for
* illustration purpose, and is not guaranteed to be unique.
*
*/
public String getRandomMessageId() {
return "m-" + Long.toString(random.nextLong());
}
/**
* Sends a downstream GCM message.
*/
public void send(String jsonRequest) {
Packet request = new GcmPacketExtension(jsonRequest).toPacket();
connection.sendPacket(request);
}
/**
* Handles an upstream data message from a device application.
*
* <p>
* This sample echo server sends an echo message back to the device.
* Subclasses should override this method to process an upstream message.
*/
public void handleIncomingDataMessage(Map<String, Object> jsonObject) {
String from = jsonObject.get("from").toString();
// PackageName of the application that sent this message.
String category = jsonObject.get("category").toString();
// Use the packageName as the collapseKey in the echo packet
String collapseKey = "echo:CollapseKey";
#SuppressWarnings("unchecked")
Map<String, String> payload = (Map<String, String>) jsonObject
.get("data");
String messageText = payload.get("message_text");
String messageFrom = payload.get("message_userfrom");
String messageTime = payload.get("message_timestamp");
String toUser = payload.get("message_recipient");
payload.put("message_text", messageText);
payload.put("message_userfrom", messageFrom);
payload.put("message_timestamp", messageTime);
String message = createJsonMessage(toUser, getRandomMessageId(),
payload, collapseKey, null, false);
send(message);
}
/**
* Handles an ACK.
*
* <p>
* By default, it only logs a INFO message, but subclasses could override it
* to properly handle ACKS.
*/
public void handleAckReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
logger.log(Level.INFO, "handleAckReceipt() from: " + from
+ ", messageId: " + messageId);
}
/**
* Handles a NACK.
*
* <p>
* By default, it only logs a INFO message, but subclasses could override it
* to properly handle NACKS.
*/
public void handleNackReceipt(Map<String, Object> jsonObject) {
String messageId = jsonObject.get("message_id").toString();
String from = jsonObject.get("from").toString();
logger.log(Level.INFO, "handleNackReceipt() from: " + from
+ ", messageId: " + messageId);
}
/**
* Creates a JSON encoded GCM message.
*
* #param to
* RegistrationId of the target device (Required).
* #param messageId
* Unique messageId for which CCS will send an "ack/nack"
* (Required).
* #param payload
* Message content intended for the application. (Optional).
* #param collapseKey
* GCM collapse_key parameter (Optional).
* #param timeToLive
* GCM time_to_live parameter (Optional).
* #param delayWhileIdle
* GCM delay_while_idle parameter (Optional).
* #return JSON encoded GCM message.
*/
public static String createJsonMessage(String to, String messageId,
Map<String, String> payload, String collapseKey, Long timeToLive,
Boolean delayWhileIdle) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("to", to);
if (collapseKey != null) {
message.put("collapse_key", collapseKey);
}
if (timeToLive != null) {
message.put("time_to_live", timeToLive);
}
if (delayWhileIdle != null && delayWhileIdle) {
message.put("delay_while_idle", true);
}
message.put("message_id", messageId);
message.put("data", payload);
return JSONValue.toJSONString(message);
}
/**
* Creates a JSON encoded ACK message for an upstream message received from
* an application.
*
* #param to
* RegistrationId of the device who sent the upstream message.
* #param messageId
* messageId of the upstream message to be acknowledged to CCS.
* #return JSON encoded ack.
*/
public static String createJsonAck(String to, String messageId) {
Map<String, Object> message = new HashMap<String, Object>();
message.put("message_type", "ack");
message.put("to", to);
message.put("message_id", messageId);
return JSONValue.toJSONString(message);
}
/**
* Connects to GCM Cloud Connection Server using the supplied credentials.
*
* #param username
* GCM_SENDER_ID#gcm.googleapis.com
* #param password
* API Key
* #throws XMPPException
*/
public void connect(String username, String password) throws XMPPException {
config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
config.setSecurityMode(SecurityMode.enabled);
config.setReconnectionAllowed(true);
config.setRosterLoadedAtLogin(false);
config.setSendPresence(false);
config.setSocketFactory(SSLSocketFactory.getDefault());
// NOTE: Set to true to launch a window with information about packets
// sent and received
config.setDebuggerEnabled(true);
// -Dsmack.debugEnabled=true
XMPPConnection.DEBUG_ENABLED = true;
connection = new XMPPConnection(config);
connection.connect();
connection.addConnectionListener(new ConnectionListener() {
#Override
public void reconnectionSuccessful() {
logger.info("Reconnecting..");
}
#Override
public void reconnectionFailed(Exception e) {
logger.log(Level.INFO, "Reconnection failed.. ", e);
}
#Override
public void reconnectingIn(int seconds) {
logger.log(Level.INFO, "Reconnecting in %d secs", seconds);
}
#Override
public void connectionClosedOnError(Exception e) {
logger.log(Level.INFO, "Connection closed on error.");
}
#Override
public void connectionClosed() {
logger.info("Connection closed.");
}
});
// Handle incoming packets
connection.addPacketListener(new PacketListener() {
#Override
public void processPacket(Packet packet) {
logger.log(Level.INFO, "Received: " + packet.toXML());
Message incomingMessage = (Message) packet;
GcmPacketExtension gcmPacket = (GcmPacketExtension) incomingMessage
.getExtension(GCM_NAMESPACE);
String json = gcmPacket.getJson();
try {
#SuppressWarnings("unchecked")
Map<String, Object> jsonObject = (Map<String, Object>) JSONValue
.parseWithException(json);
// present for "ack"/"nack", null otherwise
Object messageType = jsonObject.get("message_type");
if (messageType == null) {
// Normal upstream data message
handleIncomingDataMessage(jsonObject);
// Send ACK to CCS
String messageId = jsonObject.get("message_id")
.toString();
String from = jsonObject.get("from").toString();
String ack = createJsonAck(from, messageId);
send(ack);
} else if ("ack".equals(messageType.toString())) {
// Process Ack
handleAckReceipt(jsonObject);
} else if ("nack".equals(messageType.toString())) {
// Process Nack
handleNackReceipt(jsonObject);
} else {
logger.log(Level.WARNING,
"Unrecognized message type (%s)",
messageType.toString());
}
} catch (ParseException e) {
logger.log(Level.SEVERE, "Error parsing JSON " + json, e);
} catch (Exception e) {
logger.log(Level.SEVERE, "Couldn't send echo.", e);
}
}
}, new PacketTypeFilter(Message.class));
// Log all outgoing packets
connection.addPacketInterceptor(new PacketInterceptor() {
#Override
public void interceptPacket(Packet packet) {
logger.log(Level.INFO, "Sent: {0}", packet.toXML());
}
}, new PacketTypeFilter(Message.class));
connection.login(username, password);
}
public void writeToFile(String name, String regId) throws IOException {
Map<String, String> regIdMap = readFromFile();
regIdMap.put(name, regId);
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
REG_ID_STORE, false)));
for (Map.Entry<String, String> entry : regIdMap.entrySet()) {
out.println(entry.getKey() + "," + entry.getValue());
}
out.println(name + "," + regId);
out.close();
}
public Map<String, String> readFromFile() {
Map<String, String> regIdMap = null;
try {
BufferedReader br = new BufferedReader(new FileReader(REG_ID_STORE));
String regIdLine = "";
regIdMap = new HashMap<String, String>();
while ((regIdLine = br.readLine()) != null) {
String[] regArr = regIdLine.split(",");
regIdMap.put(regArr[0], regArr[1]);
}
br.close();
} catch(IOException ioe) {
}
return regIdMap;
}
public static void main(String [] args) {
final String userName = "Sender ID" + "#gcm.googleapis.com";
final String password = "Server key";
SmackCcsClient ccsClient = new SmackCcsClient();
try {
ccsClient.connect(userName, password);
} catch (XMPPException e) {
e.printStackTrace();
}
}
}

Retrofit async calls with OAuth2

There are two ways to make a Retrofit call synchronous (with methods, returning values) and asynchronous (with callbacks).
Second one, async, works great out-of-the-box. But there is an issue, when it comes to OAuth2 authenticated access.
Can you recommend me a good RestAdapter, compatible with asynchronous retrofit calls.
I tried to use interceptors as follows, but it makes network calls on the main thread, which is not sufficient to me (Android). I am trying to use the following code (not mine).
public class SecuredRestBuilder extends RestAdapter.Builder {
private class OAuthHandler implements RequestInterceptor {
private boolean loggedIn;
private Client client;
private String tokenIssuingEndpoint;
private String username;
private String password;
private String clientId;
private String clientSecret;
private String accessToken;
public OAuthHandler(Client client, String tokenIssuingEndpoint, String username,
String password, String clientId, String clientSecret) {
super();
this.client = client;
this.tokenIssuingEndpoint = tokenIssuingEndpoint;
this.username = username;
this.password = password;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
/**
* Every time a method on the client interface is invoked, this method is
* going to get called. The method checks if the client has previously obtained
* an OAuth 2.0 bearer token. If not, the method obtains the bearer token by
* sending a password grant request to the server.
*
* Once this method has obtained a bearer token, all future invocations will
* automatically insert the bearer token as the "Authorization" header in
* outgoing HTTP requests.
*
*/
#Override
public void intercept(RequestFacade request) {
// If we're not logged in, login and store the authentication token.
if (!loggedIn) {
try {
// This code below programmatically builds an OAuth 2.0 password
// grant request and sends it to the server.
// Encode the username and password into the body of the request.
FormUrlEncodedTypedOutput to = new FormUrlEncodedTypedOutput();
to.addField("username", username);
to.addField("password", password);
// Add the client ID and client secret to the body of the request.
to.addField("client_id", clientId);
to.addField("client_secret", clientSecret);
// Indicate that we're using the OAuth Password Grant Flow
// by adding grant_type=password to the body
to.addField("grant_type", "password");
// The password grant requires BASIC authentication of the client.
// In order to do BASIC authentication, we need to concatenate the
// client_id and client_secret values together with a colon and then
// Base64 encode them. The final value is added to the request as
// the "Authorization" header and the value is set to "Basic "
// concatenated with the Base64 client_id:client_secret value described
// above.
String base64Auth = BaseEncoding.base64().encode(new String(clientId + ":" + clientSecret).getBytes());
// Add the basic authorization header
List<Header> headers = new ArrayList<Header>();
headers.add(new Header("Authorization", "Basic " + base64Auth));
// Create the actual password grant request using the data above
Request req = new Request("POST", tokenIssuingEndpoint, headers, to);
// Request the password grant.
Response resp = client.execute(req);
// Make sure the server responded with 200 OK
if (resp.getStatus() < 200 || resp.getStatus() > 299) {
// If not, we probably have bad credentials
throw new SecuredRestException("Login failure: "
+ resp.getStatus() + " - " + resp.getReason());
} else {
// Extract the string body from the response
String body = IOUtils.toString(resp.getBody().in());
// Extract the access_token (bearer token) from the response so that we
// can add it to future requests.
accessToken = new Gson().fromJson(body, JsonObject.class).get("access_token").getAsString();
// Add the access_token to this request as the "Authorization"
// header.
request.addHeader("Authorization", "Bearer " + accessToken);
// Let future calls know we've already fetched the access token
loggedIn = true;
}
} catch (Exception e) {
throw new SecuredRestException(e);
}
}
else {
// Add the access_token that we previously obtained to this request as
// the "Authorization" header.
request.addHeader("Authorization", "Bearer " + accessToken );
}
}
private String username;
private String password;
private String loginUrl;
private String clientId;
private String clientSecret = "";
private Client client;
#Override
public RestAdapter build() {
if (username == null || password == null) {
throw new SecuredRestException(
"You must specify both a username and password for a "
+ "SecuredRestBuilder before calling the build() method.");
}
if (client == null) {
client = new OkClient();
}
OAuthHandler hdlr = new OAuthHandler(client, loginUrl, username, password, clientId, clientSecret);
setRequestInterceptor(hdlr);
return super.build();
}
// setters and getters here
}
So, I ended up splitting RestAdapter class into two separate classes. The first one gets token. Another one is a RestAdapter class that takes the token as input.
Class for getting token:
public class GetTokenRequest {
public static final String TAG = GetTokenRequest.class.getCanonicalName();
public static final String CLIENT_ID = AccessPoint.CLIENT_ID;
public static final String CLIENT_SECRET = AccessPoint.CLIENT_SECRET;
public static final String ENDPOINT = AccessPoint.ENDPOINT;
public static final String TOKEN_PATH = AccessPoint.TOKEN_PATH;
public interface Listener {
void onGetTokenSucess(String token);
void onGetTokenUnauthorized();
void onGetTokenFailure();
}
public static void getAccessToken(Client client, String username, String password,
final Listener callback) {
try {
// This code below programmatically builds an OAuth 2.0 password
// grant request and sends it to the server.
// Encode the username and password into the body of the request.
FormUrlEncodedTypedOutput to = new FormUrlEncodedTypedOutput();
to.addField("username", username);
to.addField("password", password);
// Add the client ID and client secret to the body of the request.
to.addField("client_id", CLIENT_ID);
to.addField("client_secret", CLIENT_SECRET);
// Indicate that we're using the OAuth Password Grant Flow
// by adding grant_type=password to the body
to.addField("grant_type", "password");
// The password grant requires BASIC authentication of the client.
// In order to do BASIC authentication, we need to concatenate the
// client_id and client_secret values together with a colon and then
// Base64 encode them. The final value is added to the request as
// the "Authorization" header and the value is set to "Basic "
// concatenated with the Base64 client_id:client_secret value described
// above.
String base64Auth = BaseEncoding.base64()
.encode(new String(CLIENT_ID + ":" + CLIENT_SECRET).getBytes());
// Add the basic authorization header
List<Header> headers = new ArrayList<Header>();
headers.add(new Header("Authorization", "Basic " + base64Auth));
// Create the actual password grant request using the data above
Request req = new Request("POST", ENDPOINT + TOKEN_PATH, headers, to);
// Request the password grant.
Response resp = client.execute(req);
if (resp == null) {
Log.e(TAG, "resp is null");
callback.onGetTokenFailure();
return;
}
int status = resp.getStatus();
// Make sure the server responded with 200 OK
if (status >= 200 && status < 300) {
Log.e(TAG, "getToken response code is okay");
// Extract the string body from the response
final String body = IOUtils.toString(resp.getBody().in());
// Extract the access_token (bearer token) from the response so that we
// can add it to future requests.
if (callback instanceof LoginActivity)
((LoginActivity) callback).runOnUiThread(new Runnable() {
#Override
public void run() {
callback.onGetTokenSucess(new Gson().fromJson(body, JsonObject.class)
.get("access_token").getAsString());
}
});
} else if (status == HttpStatus.SC_UNAUTHORIZED
|| status == HttpStatus.SC_BAD_REQUEST) {
Log.e(TAG, "getToken response code is 401");
// Incorrect credentials
if (callback instanceof LoginActivity)
((LoginActivity) callback).runOnUiThread(new Runnable() {
#Override
public void run() {
callback.onGetTokenUnauthorized();
}
});
} else {
// Other error
Log.e(TAG, "getToken response code - other");
if (callback instanceof LoginActivity)
((LoginActivity) callback).runOnUiThread(new Runnable() {
#Override
public void run() {
((LoginActivity) callback).onGetTokenFailure();
}
});
}
} catch (Exception e) {
Log.e(TAG, "Exception caught");
Log.e(TAG, e.toString());
if (callback instanceof LoginActivity)
((LoginActivity) callback).runOnUiThread(new Runnable() {
#Override
public void run() {
callback.onGetTokenFailure();
}
});
}
}
}
RestAdapter class:
public class SecuredRestAdapter extends RestAdapter.Builder {
private class OAuthHandler implements RequestInterceptor {
private boolean loggedIn;
private Client client;
private String tokenIssuingEndpoint;
private String username;
private String password;
private String clientId;
private String clientSecret;
private String accessToken;
public OAuthHandler(Client client, String accessToken) {
super();
this.client = client;
this.accessToken = accessToken;
}
#Override
public void intercept(RequestFacade request) {
// Add the access_token that we previously obtained to this request as
// the "Authorization" header.
request.addHeader("Authorization", "Bearer " + accessToken);
}
}
private String loginUrl;
private Client client;
private String token;
public SecuredRestAdapter setLoginEndpoint(String endpoint){
loginUrl = endpoint;
return this;
}
#Override
public SecuredRestAdapter setEndpoint(String endpoint) {
return (SecuredRestAdapter) super.setEndpoint(endpoint);
}
#Override
public SecuredRestAdapter setEndpoint(Endpoint endpoint) {
return (SecuredRestAdapter) super.setEndpoint(endpoint);
}
#Override
public SecuredRestAdapter setClient(Client client) {
this.client = client;
return (SecuredRestAdapter) super.setClient(client);
}
#Override
public SecuredRestAdapter setClient(Provider clientProvider) {
client = clientProvider.get();
return (SecuredRestAdapter) super.setClient(clientProvider);
}
#Override
public SecuredRestAdapter setErrorHandler(ErrorHandler errorHandler) {
return (SecuredRestAdapter) super.setErrorHandler(errorHandler);
}
#Override
public SecuredRestAdapter setExecutors(Executor httpExecutor,
Executor callbackExecutor) {
return (SecuredRestAdapter) super.setExecutors(httpExecutor,
callbackExecutor);
}
#Override
public SecuredRestAdapter setRequestInterceptor(
RequestInterceptor requestInterceptor) {
return (SecuredRestAdapter) super
.setRequestInterceptor(requestInterceptor);
}
#Override
public SecuredRestAdapter setConverter(Converter converter) {
return (SecuredRestAdapter) super.setConverter(converter);
}
#Override
public SecuredRestAdapter setProfiler(#SuppressWarnings("rawtypes") Profiler profiler) {
return (SecuredRestAdapter) super.setProfiler(profiler);
}
#Override
public SecuredRestAdapter setLog(Log log) {
return (SecuredRestAdapter) super.setLog(log);
}
#Override
public SecuredRestAdapter setLogLevel(LogLevel logLevel) {
return (SecuredRestAdapter) super.setLogLevel(logLevel);
}
public SecuredRestAdapter setToken(String token) {
this.token = token;
return this;
}
#Override
public RestAdapter build() {
if (this.token == null || this.token.equals(""))
throw new SecuredRestAdapterException(
"Token must be provided, when calling SecuredRestAdapter");
if (client == null) {
client = new OkClient();
}
OAuthHandler hdlr = new OAuthHandler(client, token);
setRequestInterceptor(hdlr);
return super.build();
}
}
Exception class:
public class SecuredRestAdapterException extends RuntimeException {
public SecuredRestAdapterException(String message) {
super(message);
}
}

Unable to exchange authorization code for access token and refresh token in Cross Client google oauth2.0

I've been having problems implementing Google Play Services login on my android app and passing the authorisation code to my backend server, so the server will exchange the code for access token and refresh token.
First let me write a few lines what has already been tried/read:
on code.google.com/apis/console I've created a new project, with two clients (WEB client and Android installed client)
read articles onhttps://developers.google.com/+/mobile/android/sign-in#cross-platform_single_sign_on and http://android-developers.blogspot.com/2013/01/verifying-back-end-calls-from-android.html
Here is my code for client side that retrieves the authorization code and IdToken:
package com.google.drive.samples.crossclientoauth2;
import java.util.Arrays;
import java.util.List;
import android.accounts.AccountManager;
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.widget.EditText;
import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
public class MainActivity extends Activity {
final private String CLIENT_ID = MY WEB SERVER'S CLIENT ID;
final private List<String> SCOPES = Arrays.asList(new String[]{
"https://www.googleapis.com/auth/plus.login",
"https://www.googleapis.com/auth/drive",
"https://www.googleapis.com/auth/youtube",
"https://www.googleapis.com/auth/youtube.readonly"
});
// I have modified the above line of code.
private GoogleAccountCredential mCredential;
private EditText mExchangeCodeEditText;
private EditText mIdTokenEditText;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mExchangeCodeEditText = (EditText) findViewById(R.id.editTextExchangeCode);
mIdTokenEditText = (EditText) findViewById(R.id.editTextIdToken);
// initiate a credential object with drive and plus.login scopes
// cross identity is only available for tokens retrieved with plus.login
mCredential = GoogleAccountCredential.usingOAuth2(this, null);
// user needs to select an account, start account picker
startActivityForResult(
mCredential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}
/**
* Handles the callbacks from result returning
* account picker and permission requester activities.
*/
#Override
protected void onActivityResult(
final int requestCode, final int resultCode, final Intent data) {
switch (requestCode) {
// user has returned back from the account picker,
// initiate the rest of the flow with the account he/she has chosen.
case REQUEST_ACCOUNT_PICKER:
String accountName = data.getStringExtra(AccountManager.KEY_ACCOUNT_NAME);
if (accountName != null) {
mCredential.setSelectedAccountName(accountName);
new RetrieveExchangeCodeAsyncTask().execute();
new RetrieveJwtAsyncTask().execute();
}
break;
// user has returned back from the permissions screen,
// if he/she has given enough permissions, retry the the request.
case REQUEST_AUTHORIZATION:
if (resultCode == Activity.RESULT_OK) {
// replay the same operations
new RetrieveExchangeCodeAsyncTask().execute();
new RetrieveJwtAsyncTask().execute();
}
break;
}
}
/**
* Retrieves the exchange code to be sent to the
* server-side component of the app.
*/
public class RetrieveExchangeCodeAsyncTask
extends AsyncTask<Void, Boolean, String> {
#Override
protected String doInBackground(Void... params) {
String scope = String.format("oauth2:server:client_id:%s:api_scope:%s",
CLIENT_ID, TextUtils.join(" ", SCOPES));
try {
return GoogleAuthUtil.getToken(
MainActivity.this, mCredential.getSelectedAccountName(), scope);
} catch (UserRecoverableAuthException e) {
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
} catch (Exception e) {
e.printStackTrace(); // TODO: handle the exception
}
return null;
}
#Override
protected void onPostExecute(String code) {
// exchange code with server-side to retrieve an additional
// access token on the server-side.
Log.v("first One ","code 1 is: "+ code);
mExchangeCodeEditText.setText(code);
}
}
/**
* Retrieves a JWT to identify the user without the
* regular client-side authorization flow. The jwt payload needs to be
* sent to the server-side component.
*/
public class RetrieveJwtAsyncTask
extends AsyncTask<Void, Boolean, String> {
#Override
protected String doInBackground(Void... params) {
String scope = "audience:server:client_id:" + CLIENT_ID;
try {
return GoogleAuthUtil.getToken(
MainActivity.this, mCredential.getSelectedAccountName(), scope);
} catch(UserRecoverableAuthIOException e) {
startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION);
} catch (Exception e) {
e.printStackTrace(); // TODO: handle the exception
}
return null;
}
#Override
protected void onPostExecute(String idToken) {
// exchange encrypted idToken with server-side to identify the user
Log.v("Second One","2222"+ idToken);
mIdTokenEditText.setText(idToken);
}
}
private static final int REQUEST_ACCOUNT_PICKER = 100;
private static final int REQUEST_AUTHORIZATION = 200;
}
The above code gives me two codes:
1.One returned by RetrieveExchangeCodeAsyncTask - named code.
2.Second returned by RetrieveJwtAsyncTask class- named IdToken.
Now in the first place i am confused as to which one from the above do i need to send to my web server where it will be exchanged.
I tried using the first one(the one that starts as "4/....") for exchange at my server side but got A null pointer exception.
Also please specify what redirect URI i need to use.
Here is my server-side code for exchange:
package com.myAuthSample.tial;
import java.io.IOException;
import com.myAuthSample.tial.MyClass.CodeExchangeException;
import com.myAuthSample.tial.MyClass.NoRefreshTokenException;
public class MyMainDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
MyClass.getCredentials("4/...something...", "state"); //passed the retrieved authorization code
} catch (CodeExchangeException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoRefreshTokenException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class MyClass {
// Path to client_secrets.json which should contain a JSON document such as:
// {
// "web": {
// "client_id": "[[YOUR_CLIENT_ID]]",
// "client_secret": "[[YOUR_CLIENT_SECRET]]",
// "auth_uri": "https://accounts.google.com/o/oauth2/auth",
// "token_uri": "https://accounts.google.com/o/oauth2/token"
// }
// }
private static final String CLIENTSECRETS_LOCATION = "/client_secrets_2.json";// client secrets of my android client
// private static final String REDIRECT_URI = "<YOUR_REGISTERED_REDIRECT_URI>";
private static String REDIRECT_URI ="";
private static final List<String> SCOPES = Arrays.asList(
"https://www.googleapis.com/auth/plus.login",
"https://www.googleapis.com/auth/youtube");
private static GoogleAuthorizationCodeFlow flow = null;
/**
* Exception thrown when an error occurred while retrieving credentials.
*/
public static class GetCredentialsException extends Exception {
protected String authorizationUrl;
/**
* Construct a GetCredentialsException.
*
* #param authorizationUrl The authorization URL to redirect the user to.
*/
public GetCredentialsException(String authorizationUrl) {
this.authorizationUrl = authorizationUrl;
}
/**
* Set the authorization URL.
*/
public void setAuthorizationUrl(String authorizationUrl) {
this.authorizationUrl = authorizationUrl;
}
/**
* #return the authorizationUrl
*/
public String getAuthorizationUrl() {
return authorizationUrl;
}
}
/**
* Exception thrown when a code exchange has failed.
*/
public static class CodeExchangeException extends GetCredentialsException {
/**
* Construct a CodeExchangeException.
*
* #param authorizationUrl The authorization URL to redirect the user to.
*/
public CodeExchangeException(String authorizationUrl) {
super(authorizationUrl);
}
}
/**
* Exception thrown when no refresh token has been found.
*/
public static class NoRefreshTokenException extends GetCredentialsException {
/**
* Construct a NoRefreshTokenException.
*
* #param authorizationUrl The authorization URL to redirect the user to.
*/
public NoRefreshTokenException(String authorizationUrl) {
super(authorizationUrl);
}
}
/**
* Exception thrown when no user ID could be retrieved.
*/
private static class NoUserIdException extends Exception {
}
/**
* Retrieved stored credentials for the provided user ID.
*
* #param userId User's ID.
* #return Stored Credential if found, {#code null} otherwise.
*/
static Credential getStoredCredentials(String userId) {
// TODO: Implement this method to work with your database. Instantiate a new
// Credential instance with stored accessToken and refreshToken.
throw new UnsupportedOperationException();
}
/**
* Store OAuth 2.0 credentials in the application's database.
*
* #param userId User's ID.
* #param credentials The OAuth 2.0 credentials to store.
*/
static void storeCredentials(String userId, Credential credentials) {
// TODO: Implement this method to work with your database.
// Store the credentials.getAccessToken() and credentials.getRefreshToken()
// string values in your database.
System.out.println("credentials are : " + credentials.toString());
throw new UnsupportedOperationException();
}
/**
* Build an authorization flow and store it as a static class attribute.
*
* #return GoogleAuthorizationCodeFlow instance.
* #throws IOException Unable to load client_secrets.json.
*/
static GoogleAuthorizationCodeFlow getFlow() throws IOException {
if (flow == null) {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory(); //...this was the original line....
// JsonFactory jsonFactory = new JacksonFactory();
//my code....
Reader clientSecretReader = new InputStreamReader(MyClass.class.getResourceAsStream(CLIENTSECRETS_LOCATION));
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(jsonFactory,clientSecretReader);
REDIRECT_URI =clientSecrets.getDetails().getRedirectUris().get(0);
// my code ends...
/* GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(jsonFactory,
MyClass.class.getResourceAsStream(CLIENTSECRETS_LOCATION));
*/
flow =
new GoogleAuthorizationCodeFlow.Builder(httpTransport, jsonFactory, clientSecrets, SCOPES)
.setAccessType("offline").setApprovalPrompt("force").build();
}
return flow;
}
/**
* Exchange an authorization code for OAuth 2.0 credentials.
*
* #param authorizationCode Authorization code to exchange for OAuth 2.0
* credentials.
* #return OAuth 2.0 credentials.
* #throws CodeExchangeException An error occurred.
*/
static Credential exchangeCode(String authorizationCode)
throws CodeExchangeException {
try {
GoogleAuthorizationCodeFlow flow = getFlow();
GoogleTokenResponse response =
flow.newTokenRequest(authorizationCode).setRedirectUri(REDIRECT_URI).execute();
return flow.createAndStoreCredential(response, null);
} catch (IOException e) {
System.err.println("An error occurred: " + e);
throw new CodeExchangeException(null);
}
}
/**
* Send a request to the UserInfo API to retrieve the user's information.
*
* #param credentials OAuth 2.0 credentials to authorize the request.
* #return User's information.
* #throws NoUserIdException An error occurred.
*/
static Userinfo getUserInfo(Credential credentials)
throws NoUserIdException {
Oauth2 userInfoService =
new Oauth2.Builder(new NetHttpTransport(), new JacksonFactory(), credentials).build();
Userinfo userInfo = null;
try {
userInfo = userInfoService.userinfo().get().execute();
} catch (IOException e) {
System.err.println("An error occurred: " + e);
}
if (userInfo != null && userInfo.getId() != null) {
return userInfo;
} else {
throw new NoUserIdException();
}
}
/**
* Retrieve the authorization URL.
*
* #param emailAddress User's e-mail address.
* #param state State for the authorization URL.
* #return Authorization URL to redirect the user to.
* #throws IOException Unable to load client_secrets.json.
*/
public static String getAuthorizationUrl(String emailAddress, String state) throws IOException {
GoogleAuthorizationCodeRequestUrl urlBuilder =
getFlow().newAuthorizationUrl().setRedirectUri(REDIRECT_URI).setState(state);
urlBuilder.set("user_id", emailAddress);
return urlBuilder.build();
}
/**
* Retrieve credentials using the provided authorization code.
*
* This function exchanges the authorization code for an access token and
* queries the UserInfo API to retrieve the user's e-mail address. If a
* refresh token has been retrieved along with an access token, it is stored
* in the application database using the user's e-mail address as key. If no
* refresh token has been retrieved, the function checks in the application
* database for one and returns it if found or throws a NoRefreshTokenException
* with the authorization URL to redirect the user to.
*
* #param authorizationCode Authorization code to use to retrieve an access
* token.
* #param state State to set to the authorization URL in case of error.
* #return OAuth 2.0 credentials instance containing an access and refresh
* token.
* #throws NoRefreshTokenException No refresh token could be retrieved from
* the available sources.
* #throws IOException Unable to load client_secrets.json.
*/
public static Credential getCredentials(String authorizationCode, String state)
throws CodeExchangeException, NoRefreshTokenException, IOException {
String emailAddress = "";
try {
Credential credentials = exchangeCode(authorizationCode);
Userinfo userInfo = getUserInfo(credentials);
String userId = userInfo.getId();
emailAddress = userInfo.getEmail();
if (credentials.getRefreshToken() != null) {
storeCredentials(userId, credentials);
return credentials;
} else {
credentials = getStoredCredentials(userId);
if (credentials != null && credentials.getRefreshToken() != null) {
return credentials;
}
}
} catch (CodeExchangeException e) {
e.printStackTrace();
// Drive apps should try to retrieve the user and credentials for the current
// session.
// If none is available, redirect the user to the authorization URL.
e.setAuthorizationUrl(getAuthorizationUrl(emailAddress, state));
throw e;
} catch (NoUserIdException e) {
e.printStackTrace();
}
// No refresh token has been retrieved.
String authorizationUrl = getAuthorizationUrl(emailAddress, state);
throw new NoRefreshTokenException(authorizationUrl);
}
}
Also,am i passing the correct client_secret.json file in my server side code(in MyClass)--which is of android client.
Please help!!!
Thanx in advance.
Use from this:
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson.JacksonFactory;
private final val TRANSPORT: HttpTransport = new NetHttpTransport()
private final val JSON_FACTORY: JacksonFactory = new JacksonFactory()
GoogleTokenResponse tokenResponse = new GoogleAuthorizationCodeTokenRequest(TRANSPORT, JSON_FACTORY,
CLIENT_ID, CLIENT_SECRET, code, "postmessage").execute();
GoogleIdToken idToken = tokenResponse.parseIdToken();
String gplusId = idToken.getPayload().getSubject();
you must replace your values of the client_id,client_secret and code with above related variables.
more information is in the flowing link:
https://github.com/googleplus/gplus-quickstart-java/blob/master/src/com/google/plus/samples/quickstart/Signin.java
Also you can get the Google API libraries from this link:
http://repo1.maven.org/maven2/com/google/
You need to exchange the code for an access token indeed. The id_token is meant for your client only and self-contained so it does not need to be exchanged for anything else. The redirect_uri that you need to send along with the code in the exchange request to the token endpoint is the exact same redirect_uri value that you sent originally in the authorization request that went to the authorization endpoint (and that is registered for your client in the Google API Console), so looking at your code it is the one retrieved with clientSecrets.getDetails().getRedirectUris().get(0);

Unable to login to google talk using asmack library for android

I have been trying to login to google talk using the asmack library without success. I don't really know what is happening behind the scenes, just gathered some code snippets from here and there. This is what I have currently for the android activity:
public class MainActivity extends Activity {
public static final String HOST = "talk.google.com";
public static final int PORT = 5222;
public static final String SERVICE = "gmail.com";
public static final String USER = "user#gmail.com";
public static final String PASSWORD = "password";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Context context = getApplicationContext();
SmackAndroid asmk = SmackAndroid.init(context);
SASLAuthentication.registerSASLMechanism("X-OAUTH2", SASLGoogleOAuth2Mechanism.class);
SASLAuthentication.supportSASLMechanism("X-OAUTH2", 0);
ConnectionConfiguration connConfig = new ConnectionConfiguration(HOST, PORT, SERVICE);
connConfig.setSecurityMode(SecurityMode.enabled);
connConfig.setReconnectionAllowed(true);
XMPPTCPConnection connection = new XMPPTCPConnection(connConfig);
try {
connection.connect();
try {
connection.login(USER, PASSWORD);
} catch (XMPPException ex) {
Log.w("XMPPChatDemoActivity", "Failed to log in");
Log.w("XMPPChatDemoActivity", ex.getMessage());
}
} catch (...) {
...
}
}
}
and this is the SASLMechanism:
public class SASLGoogleOAuth2Mechanism extends SASLMechanism {
private static final Logger log = Logger.getLogger("XMPPChatDemoActivity");
public static final String NAME = "X-OAUTH2";
public SASLGoogleOAuth2Mechanism(SASLAuthentication saslAuthentication) {
super(saslAuthentication);
log.info("Creating SASL mechanism for GTalk (X-OAUTH2)");
}
#Override
public void authenticate(String username, String host, String serviceName, String password) throws IOException, SaslException, NotConnectedException {
this.authenticationId = username;
this.hostname = host;
this.password = password;
String[] mechanisms = { "PLAIN" };
Map<String, String> props = new HashMap<String, String>();
this.sc = Sasl.createSaslClient(mechanisms, username, "xmpp", host, props, this);
log.info("sc " + sc);
authenticate();
}
#Override
public void authenticate(String host, CallbackHandler cbh) throws IOException, SaslException, NotConnectedException {
String[] mechanisms = { "PLAIN" };
Map<String, String> props = new HashMap<String, String>();
sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
authenticate();
}
#Override
protected void authenticate() throws IOException, SaslException, NotConnectedException {
String authenticationText = null;
try {
if (sc.hasInitialResponse()) {
byte[] response = sc.evaluateChallenge(new byte[0]);
authenticationText = Base64.encodeBytes(response, Base64.DONT_BREAK_LINES);
}
} catch (SaslException e) {
throw new SaslException("SASL authentication failed", e);
}
// Send the authentication to the server
getSASLAuthentication().send(new GoogleOAuthMechanism(authenticationText));
}
#Override
protected String getName() {
return NAME;
}
/**
* Initiating SASL authentication by select a mechanism.
*/
public static class GoogleOAuthMechanism extends Packet {
private final String authenticationText;
/**
* Create a GoogleOAuthMechanism.
*
* #param authenticationText the authentification token
*
*/
public GoogleOAuthMechanism(final String authenticationText) {
this.authenticationText = authenticationText;
}
#Override
public String toXML() {
StringBuilder stanza = new StringBuilder();
stanza.append("<auth mechanism=\"").append(NAME);
stanza.append("\" xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\" "
+ "auth:service=\"oauth2\" "
+ "xmlns:auth=\"http://www.google.com/talk/protocol/auth\">");
if (authenticationText != null
&& authenticationText.trim().length() > 0) {
stanza.append(authenticationText);
}
stanza.append("</auth>");
return stanza.toString();
}
}
}
The code is ok and i don't get any exception, but I get a <not-authorized> response. The user name and password are correct. I couldn't find any reference code for this library. Any help will be appreciated.
After struggling for a few days and trying every imaginable combination of the snippets that I found on the Internet, I've come across a solution that I'm glad to share with the community.
It appears that instead of passing the password to the XMPPTCPConnection.login() method, we should use an auth token from google. I found a post explaining a way to generate such a token. A similar question to mine exists, but it uses X-GOOGLE-TOKEN mechanism also for authentication, which is a different approach to mine of using X-OAUTH2 mechanism for authentication. Furthermore, all other posts I could find relating to the problem of authenticating to google talk using OAUTH2 are old. I am using the asmack build of smack 4.0.4.
So the only modification required for the code shown in the question to work is this:
AccountManager am = AccountManager.get(this);
Account accounts[] = am.getAccountsByType("com.google");
conn.login(USER, amf.blockingGetAuthToken(accounts[0], GOOGLE_TOKEN_TYPE, true));
As you see, I used an account stored on the device to proof the solution, but you can generate the token by other means, as I commented above.
Finally, as I found the solution by trial and error, I would appreciate anyone explaining what is really happening or any misinformation I could have given, so this answer can be further improved.

Categories

Resources