package com.nostra13.socialsharing.twitter.extpack.winterwell.jtwitter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.io.StringBufferInputStream; import java.lang.reflect.Method; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.util.Map; import com.nostra13.socialsharing.twitter.extpack.oauth.signpost.AbstractOAuthConsumer; import com.nostra13.socialsharing.twitter.extpack.oauth.signpost.OAuthConsumer; import com.nostra13.socialsharing.twitter.extpack.oauth.signpost.basic.DefaultOAuthProvider; import com.nostra13.socialsharing.twitter.extpack.oauth.signpost.basic.HttpURLConnectionRequestAdapter; import com.nostra13.socialsharing.twitter.extpack.oauth.signpost.commonshttp.CommonsHttpOAuthProvider; import com.nostra13.socialsharing.twitter.extpack.oauth.signpost.exception.OAuthException; import com.nostra13.socialsharing.twitter.extpack.oauth.signpost.http.HttpRequest; import com.nostra13.socialsharing.twitter.extpack.winterwell.jtwitter.Twitter.IHttpClient; /** * OAuth based login using Signpost (http://code.google.com/p/oauth-signpost/). * This is the "official" JTwitter OAuth support. * <p> * First download the Signpost jar and add it to your classpath, as it isn't * included in the JTwitter download. * <p> * Example Usage #1 (out-of-bounds, desktop based): * * <pre> * <code> * OAuthSignpostClient client = new OAuthSignpostClient(JTWITTER_OAUTH_KEY, JTWITTER_OAUTH_SECRET, "oob"); * Twitter jtwit = new Twitter("yourtwittername", client); * // open the authorisation page in the user's browser * // This is a convenience method for directing the user to client.authorizeUrl() * client.authorizeDesktop(); * // get the pin * String v = client.askUser("Please enter the verification PIN from Twitter"); * client.setAuthorizationCode(v); * // Optional: store the authorisation token details * Object accessToken = client.getAccessToken(); * // use the API! * jtwit.setStatus("Messing about in Java"); * </code> * </pre> * * <p> * Example Usage #2 (using callbacks):<br> * If you can handle callbacks, then this can be streamlined. * * On Android, you can use Intents to launch a web page, & to catch the resulting * callback. * On a desktop, you need a webserver and a servlet (eg. use Jetty or Tomcat) * to handle callbacks. * <p> * Replace "oob" with your callback url. Direct the user to * client.authorizeUrl(). Twitter will then call your callback with the request * token and verifier (authorisation code). * * <pre> * <code> * OAuthSignpostClient client = new OAuthSignpostClient(JTWITTER_OAUTH_KEY, JTWITTER_OAUTH_SECRET, myCallbackUrl); * Twitter jtwit = new Twitter("yourtwittername", client); * URI url = client.authorizeUrl(); * // Direct the user to this url! * </code> * </pre> * * Now we wait for the callback... * * <pre> * <code> * HttpServletRequest request = from your servlet; * // get the pin * String verifier = request.getParameter("oauth_verifier"); * client.setAuthorizationCode(verifier); * * // The client is now good for use. But wait: if you get an access token * // and secret, you can store them for next time: * String[] accessTokenAndSecret = client.getAccessToken(); * // Then you can in future use * // OAuthSignpostClient client = new OAuthSignpostClient(APP_KEY, APP_SECRET, ACCESS_TOKEN, ACCESS_TOKEN_SECRET); * * // use the API! * jtwit.setStatus("Messing about in Java"); * </code> * </pre> * * <p> * There are alternative OAuth libraries you can use: * * @see OAuthHttpClient * @see OAuthScribeClient * @author Daniel */ public class OAuthSignpostClient extends URLConnectionHttpClient implements IHttpClient, Serializable { /** * Use with #setProvider() to make this a foursquare OAuth client */ private static final CommonsHttpOAuthProvider FOURSQUARE_PROVIDER = new CommonsHttpOAuthProvider( "https://api.twitter.com/oauth/request_token", "https://api.twitter.com/oauth/access_token", "https://api.twitter.com/oauth/authorize"); /** * This consumer key (and secret) allows you to get up and running fast. * However you are strongly advised to register your own app at * http://dev.twitter.com Then use your own key and secret. This will be * less confusing for users, and it protects you incase the JTwitter key * gets changed. */ public static final String JTWITTER_OAUTH_KEY = "Cz8ZLgitPR2jrQVaD6ncw"; /** * For use with {@link #JTWITTER_OAUTH_KEY} */ public static final String JTWITTER_OAUTH_SECRET = "9FFYaWJSvQ6Yi5tctN30eN6DnXWmdw0QgJMl7V6KGI"; private static final long serialVersionUID = 1L; /** * <p> * <i>Convenience method for desktop apps only - does not work in * Android</i> * </p> * * Opens a popup dialog asking the user to enter the verification code. (you * would then call {@link #setAuthorizationCode(String)}). This is only * relevant when using out-of-band instead of a callback-url. This is a * convenience method -- you will probably want to build your own UI around * this. * <p> * <i>This method requires Swing. It will not work on Android devices!</i> * * @param question * e.g. "Please enter the authorisation code from Twitter" * @return */ public static String askUser(String question) { // This cumbersome approach avoids importing Swing classes // It will create a runtime exception on Android // -- but will allow the rest of the class to be used. // JOptionPane.showInputDialog(question); try { Class<?> JOptionPaneClass = Class .forName("javax.swing.JOptionPane"); Method showInputDialog = JOptionPaneClass.getMethod( "showInputDialog", Object.class); return (String) showInputDialog.invoke(null, question); } catch (Exception e) { throw new RuntimeException(e); } } private String accessToken; private String accessTokenSecret; private String callbackUrl; private OAuthConsumer consumer; private String consumerKey; private String consumerSecret; private CommonsHttpOAuthProvider provider; /** * * @param consumerKey * @param consumerSecret * @param callbackUrl * Servlet that will get the verifier sent to it, or "oob" for * out-of-band (user copies and pastes the pin to you) */ public OAuthSignpostClient(String consumerKey, String consumerSecret, String callbackUrl) { assert consumerKey != null && consumerSecret != null && callbackUrl != null; this.consumerKey = consumerKey; this.consumerSecret = consumerSecret; this.callbackUrl = callbackUrl; init(); } /** * Use this if you already have an accessToken for the user. You can then go * straight to using the API without having to authorise again. * * @param consumerKey * @param consumerSecret * @param accessToken */ public OAuthSignpostClient(String consumerKey, String consumerSecret, String accessToken, String accessTokenSecret) { this.consumerKey = consumerKey; this.consumerSecret = consumerSecret; this.accessToken = accessToken; this.accessTokenSecret = accessTokenSecret; init(); } /** * Redirect the user's browser to Twitter's authorise page. You will need to * collect the verifier pin - either from the callback servlet, or from the * user (out-of-band). * <p> * <i>This method requires Swing. It will not work on Android!</i> * * @see #authorizeUrl() */ @Deprecated // this is convenient for getting started, but probably you should build // your own GUI. public void authorizeDesktop() { URI uri = authorizeUrl(); try { // This cumbersome approach avoids importing Swing classes // It will create a runtime exception on Android // -- but will allow the rest of the class to be used. // Desktop d = Desktop.getDesktop(); Class<?> desktopClass = Class.forName("java.awt.Desktop"); Method getDesktop = desktopClass.getMethod("getDesktop", (Class[]) null); Object d = getDesktop.invoke(null, (Object[]) null); // d.browse(uri); Method browse = desktopClass.getMethod("browse", URI.class); browse.invoke(d, uri); } catch (Exception e) { throw new RuntimeException(e); } } /** * @return url to direct the user to for authorisation. Send the user to * this url. They click "OK", then get redirected to your callback * url. */ public URI authorizeUrl() { try { String url = provider.retrieveRequestToken(consumer, callbackUrl); return new URI(url); } catch (Exception e) { // Why does this happen? throw new TwitterException(e); } } @Override public boolean canAuthenticate() { return consumer.getToken() != null; } @Override public IHttpClient copy() { OAuthSignpostClient c; if (accessToken != null) { c = new OAuthSignpostClient(consumerKey, consumerSecret, accessToken, accessTokenSecret); c.callbackUrl = callbackUrl; } else { c = new OAuthSignpostClient(consumerKey, consumerSecret, callbackUrl); } c.setTimeout(timeout); c.setRetryOnError(retryOnError); c.setMinRateLimit(minRateLimit); c.rateLimits.putAll(rateLimits); return c; } /** * @return the access token and access token secret - if this client was * constructed with an access token, or has successfully * authenticated and got one. null otherwise. */ public String[] getAccessToken() { if (accessToken == null) return null; return new String[] { accessToken, accessTokenSecret }; } @Override String getName() { // avoid returning null, cos there always is a user, we just don't know // their name return name == null ? "?user" : name; } private void init() { // The default consumer can't do post requests! // TODO override AbstractAuthConsumer.collectBodyParameters() which // would be more efficient consumer = new AbstractOAuthConsumer(consumerKey, consumerSecret) { /** * */ private static final long serialVersionUID = 1L; @Override protected HttpRequest wrap(final Object request) { if (request instanceof HttpRequest) return (HttpRequest) request; return new HttpURLConnectionRequestAdapter( (HttpURLConnection) request); } }; if (accessToken != null) { consumer.setTokenWithSecret(accessToken, accessTokenSecret); } provider = new CommonsHttpOAuthProvider( "https://api.twitter.com/oauth/request_token", "https://api.twitter.com/oauth/access_token", "https://api.twitter.com/oauth/authorize"); } @Override public HttpURLConnection post2_connect(String uri, Map<String, String> vars) throws IOException, OAuthException { HttpURLConnection connection = (HttpURLConnection) new URL(uri) .openConnection(); connection.setRequestMethod("POST"); connection.setDoOutput(true); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setReadTimeout(timeout); connection.setConnectTimeout(timeout); final String payload = post2_getPayload(vars); // needed for OAuthConsumer.collectBodyParameters() not to get upset HttpURLConnectionRequestAdapter wrapped = new HttpURLConnectionRequestAdapter( connection) { @Override public InputStream getMessagePayload() throws IOException { // SHould we use ByteArrayInputStream instead? With what // encoding? return new StringBufferInputStream(payload); } }; // safetyCheck(); consumer.sign(wrapped); // add the payload OutputStream os = connection.getOutputStream(); os.write(payload.getBytes()); close(os); // check connection & process the envelope processError(connection); processHeaders(connection); return connection; } @Override protected void setAuthentication(URLConnection connection, String name, String password) { // safetyCheck(); try { // sign the request consumer.sign(connection); } catch (OAuthException e) { throw new TwitterException(e); } } /** * Set the authorisation code (aka the verifier). * * @param verifier * a pin code which Twitter gives the user (with the oob method), * or which you get from the callback response as the parameter * "oauth_verifier". * @throws RuntimeException * throws an exception if the verifier is invalid */ public void setAuthorizationCode(String verifier) throws TwitterException { assert accessToken == null : "This JTwitter already has an access token and is ready for use."; try { provider.retrieveAccessToken(consumer, verifier); accessToken = consumer.getToken(); accessTokenSecret = consumer.getTokenSecret(); } catch (Exception e) { if (e.getMessage().contains("401")) { throw new TwitterException.E401(e.getMessage()); } throw new TwitterException(e); } } public void setFoursquareProvider() { setProvider(FOURSQUARE_PROVIDER); } /** * Unlike the base class {@link URLConnectionHttpClient}, this does not set * name by default. But you can set it for nicer error messages. * * @param name */ public void setName(String name) { this.name = name; } /** * Set to Twitter settings by default. This method lets you override that. * * @param provider */ public void setProvider(CommonsHttpOAuthProvider provider) { this.provider = provider; } }