/* * Firetweet - Twitter client for Android * * Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.getlantern.firetweet.util; import android.text.TextUtils; import android.util.Xml; import org.apache.commons.lang3.ArrayUtils; import org.getlantern.firetweet.Constants; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import twitter4j.Twitter; import twitter4j.TwitterException; import twitter4j.auth.AccessToken; import twitter4j.auth.RequestToken; import twitter4j.conf.Configuration; import twitter4j.http.HeaderMap; import twitter4j.http.HttpClientWrapper; import twitter4j.http.HttpParameter; import twitter4j.http.HttpResponse; import static android.text.TextUtils.isEmpty; public class OAuthPasswordAuthenticator implements Constants { private static final String INPUT_AUTHENTICITY_TOKEN = "authenticity_token"; private static final String INPUT_REDIRECT_AFTER_LOGIN = "redirect_after_login"; private final Twitter twitter; private final HttpClientWrapper client; public OAuthPasswordAuthenticator(final Twitter twitter) { final Configuration conf = twitter.getConfiguration(); this.twitter = twitter; client = new HttpClientWrapper(conf); } public AccessToken getOAuthAccessToken(final String username, final String password) throws AuthenticationException { final RequestToken requestToken; try { requestToken = twitter.getOAuthRequestToken(OAUTH_CALLBACK_OOB); } catch (final TwitterException e) { if (e.isCausedByNetworkIssue()) throw new AuthenticationException(e); throw new AuthenticityTokenException(); } try { final String oauthToken = requestToken.getToken(); final String authorizationUrl = requestToken.getAuthorizationURL(); final HashMap<String, String> inputMap = new HashMap<>(); final HttpResponse authorizePage = client.get(authorizationUrl, authorizationUrl, null, null, null); final List<String> cookieHeaders = authorizePage.getResponseHeaders("Set-Cookie"); readInputFromHtml(authorizePage.asReader(), inputMap, INPUT_AUTHENTICITY_TOKEN, INPUT_REDIRECT_AFTER_LOGIN); final Configuration conf = twitter.getConfiguration(); final List<HttpParameter> params = new ArrayList<>(); params.add(new HttpParameter("oauth_token", oauthToken)); params.add(new HttpParameter(INPUT_AUTHENTICITY_TOKEN, inputMap.get(INPUT_AUTHENTICITY_TOKEN))); if (inputMap.containsKey(INPUT_REDIRECT_AFTER_LOGIN)) { params.add(new HttpParameter(INPUT_REDIRECT_AFTER_LOGIN, inputMap.get(INPUT_REDIRECT_AFTER_LOGIN))); } params.add(new HttpParameter("session[username_or_email]", username)); params.add(new HttpParameter("session[password]", password)); final HeaderMap requestHeaders = new HeaderMap(); requestHeaders.addHeader("Origin", "https://twitter.com"); requestHeaders.addHeader("Referer", "https://twitter.com/oauth/authorize?oauth_token=" + requestToken.getToken()); requestHeaders.put("Cookie", cookieHeaders); final String oAuthAuthorizationUrl = conf.getOAuthAuthorizationURL(); final String oauthPin = readOAuthPINFromHtml(client.post(oAuthAuthorizationUrl, oAuthAuthorizationUrl, params.toArray(new HttpParameter[params.size()]), requestHeaders).asReader()); if (isEmpty(oauthPin)) throw new WrongUserPassException(); return twitter.getOAuthAccessToken(requestToken, oauthPin); } catch (final IOException | TwitterException | NullPointerException | XmlPullParserException e) { throw new AuthenticationException(e); } } public static void readInputFromHtml(final Reader in, Map<String, String> map, String... desiredNames) throws IOException, XmlPullParserException { final XmlPullParserFactory f = XmlPullParserFactory.newInstance(); final XmlPullParser parser = f.newPullParser(); parser.setFeature(Xml.FEATURE_RELAXED, true); parser.setInput(in); while (parser.next() != XmlPullParser.END_DOCUMENT) { final String tag = parser.getName(); switch (parser.getEventType()) { case XmlPullParser.START_TAG: { final String name = parser.getAttributeValue(null, "name"); if ("input".equalsIgnoreCase(tag) && ArrayUtils.contains(desiredNames, name)) { map.put(name, parser.getAttributeValue(null, "value")); } break; } } } } public static String readOAuthPINFromHtml(final Reader in) throws XmlPullParserException, IOException { boolean start_div = false, start_code = false; final XmlPullParserFactory f = XmlPullParserFactory.newInstance(); final XmlPullParser parser = f.newPullParser(); parser.setFeature(Xml.FEATURE_RELAXED, true); parser.setInput(in); while (parser.next() != XmlPullParser.END_DOCUMENT) { final String tag = parser.getName(); final int type = parser.getEventType(); if (type == XmlPullParser.START_TAG) { if ("div".equalsIgnoreCase(tag)) { start_div = "oauth_pin".equals(parser.getAttributeValue(null, "id")); } else if ("code".equalsIgnoreCase(tag)) { if (start_div) { start_code = true; } } } else if (type == XmlPullParser.END_TAG) { if ("div".equalsIgnoreCase(tag)) { start_div = false; } else if ("code".equalsIgnoreCase(tag)) { start_code = false; } } else if (type == XmlPullParser.TEXT) { final String text = parser.getText(); if (start_code && !TextUtils.isEmpty(text) && TextUtils.isDigitsOnly(text)) return text; } } return null; } public static class AuthenticationException extends Exception { private static final long serialVersionUID = -5629194721838256378L; AuthenticationException() { } AuthenticationException(final Exception cause) { super(cause); } AuthenticationException(final String message) { super(message); } } public static final class AuthenticityTokenException extends AuthenticationException { private static final long serialVersionUID = -1840298989316218380L; } public static final class WrongUserPassException extends AuthenticationException { private static final long serialVersionUID = -4880737459768513029L; } }