/*
* Copyright (c) 2010 Jan Berkel <jan.berkel@gmail.com>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.zegoggles.smssync.auth;
import android.content.Context;
import android.util.Log;
import com.zegoggles.smssync.R;
import oauth.signpost.commonshttp.CommonsHttpOAuthConsumer;
import oauth.signpost.commonshttp.CommonsHttpOAuthProvider;
import oauth.signpost.exception.OAuthException;
import oauth.signpost.http.HttpParameters;
import oauth.signpost.http.HttpRequest;
import oauth.signpost.signature.SignatureBaseString;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import static com.zegoggles.smssync.App.TAG;
import static java.net.HttpURLConnection.HTTP_OK;
import static oauth.signpost.OAuth.ENCODING;
import static oauth.signpost.OAuth.OAUTH_SIGNATURE;
import static oauth.signpost.OAuth.percentEncode;
@Deprecated
public class XOAuthConsumer extends CommonsHttpOAuthConsumer {
private static final String MAC_NAME = "HmacSHA1";
private static final String ANONYMOUS = "anonymous";
private static final String ERROR = "error";
// Scopes as defined in http://code.google.com/apis/accounts/docs/OAuth.html#prepScope
private static final String GMAIL_SCOPE = "https://mail.google.com/";
private static final String CONTACTS_SCOPE = "https://www.google.com/m8/feeds/";
private static final String DEFAULT_SCOPE = GMAIL_SCOPE + " " + CONTACTS_SCOPE;
// endpoints
private static final String CONTACTS_URL = "https://www.google.com/m8/feeds/contacts/default/thin?max-results=1";
private static final String REQUEST_TOKEN_URL = "https://www.google.com/accounts/OAuthGetRequestToken" +
"?scope=%s&xoauth_displayname=%s";
private static final String ACCESS_TOKEN_ENDPOINT_URL = "https://www.google.com/accounts/OAuthGetAccessToken";
private static final String AUTHORIZE_TOKEN_URL = "https://www.google.com/accounts/OAuthAuthorizeToken?btmpl=mobile";
private String mUsername;
public XOAuthConsumer(String username) {
super(ANONYMOUS, ANONYMOUS);
this.mUsername = username;
}
public XOAuthConsumer(String username, String token, String secret) {
this(username);
setTokenWithSecret(token, secret);
}
public String generateXOAuthString() {
return generateXOAuthString(mUsername);
}
public String generateXOAuthString(final String username) {
if (username == null) throw new IllegalArgumentException("username is null");
try {
final URI uri = new URI(String.format("https://mail.google.com/mail/b/%s/imap/", urlEncode(username)));
final HttpRequest request = wrap(new HttpGet(uri));
final HttpParameters requestParameters = new HttpParameters();
completeOAuthParameters(requestParameters);
StringBuilder sasl = new StringBuilder()
.append("GET ")
.append(uri.toString())
.append(" ");
requestParameters.put(OAUTH_SIGNATURE, generateSig(request, requestParameters), true);
Iterator<Map.Entry<String, SortedSet<String>>> it = requestParameters.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, SortedSet<String>> e = it.next();
sasl.append(e.getKey()).append("=\"").append(
e.getValue().iterator().next()
).append("\"");
if (it.hasNext()) {
sasl.append(",");
}
}
return base64(sasl.toString().getBytes(ENCODING));
} catch (URISyntaxException e) {
throw new IllegalArgumentException(e);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public CommonsHttpOAuthProvider getProvider(Context context) {
//System.setProperty("debug", "true");
return new CommonsHttpOAuthProvider(
requestTokenEndpointUrl(context),
ACCESS_TOKEN_ENDPOINT_URL,
AUTHORIZE_TOKEN_URL) {
{
setOAuth10a(true);
}
};
}
/**
* @param clientId the oauth2 client id
* @return an oauth2 refresh token
* @throws IOException
*/
public String migrateToken(String clientId) throws IOException {
HttpPost post = new HttpPost(OAuth2Client.TOKEN_URL);
List<NameValuePair> postParams = new ArrayList<NameValuePair>();
postParams.add(new BasicNameValuePair("grant_type", "urn:ietf:params:oauth:grant-type:migration:oauth1"));
postParams.add(new BasicNameValuePair("client_id", clientId));
try {
post.setEntity(new UrlEncodedFormEntity(postParams));
} catch (UnsupportedEncodingException e) {
return null;
}
try {
HttpUriRequest request = (HttpUriRequest) sign(post).unwrap();
final HttpClient httpClient = new DefaultHttpClient();
final HttpResponse response = httpClient.execute(request);
// After a migration request is validated, your application is issued a refresh token
// with down-scoped scopes. The response body is in JSON format, and it contains only an OAuth 2.0 refresh token
// (no access token).
if (response.getStatusLine().getStatusCode() == HTTP_OK) {
final HttpEntity responseString = response.getEntity();
JSONTokener tokener = new JSONTokener(EntityUtils.toString(responseString));
Object value = tokener.nextValue();
if (value instanceof JSONObject) {
return ((JSONObject)value).optString("refresh_token", null);
} else {
Log.w(TAG, "invalid response from server: " + responseString);
}
} else {
Log.w(TAG, "invalid response from server: " + response.getStatusLine());
}
} catch (OAuthException ignored) {
Log.w(TAG, ignored);
} catch (JSONException e) {
Log.w(TAG, e);
}
return null;
}
private String requestTokenEndpointUrl(Context context) {
return String.format(REQUEST_TOKEN_URL,
urlEncode(DEFAULT_SCOPE),
urlEncode(context.getString(R.string.app_name)));
}
public String getUsername() {
return mUsername;
}
public String loadUsernameFromContacts() {
this.mUsername = getUsernameFromContacts();
return this.mUsername;
}
// Retrieves the google email account address using the contacts API
protected String getUsernameFromContacts() {
final HttpClient httpClient = new DefaultHttpClient();
try {
HttpGet get = new HttpGet(sign(CONTACTS_URL));
return extractEmail(httpClient.execute(get));
} catch (OAuthException e) {
Log.e(TAG, ERROR, e);
return null;
} catch (SAXException e) {
Log.e(TAG, ERROR, e);
return null;
} catch (IOException e) {
Log.e(TAG, ERROR, e);
return null;
} catch (ParserConfigurationException e) {
Log.e(TAG, ERROR, e);
return null;
}
}
private String extractEmail(HttpResponse response) throws ParserConfigurationException, SAXException, IOException {
final XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
final FeedHandler feedHandler = new FeedHandler();
xmlReader.setContentHandler(feedHandler);
xmlReader.parse(new InputSource(response.getEntity().getContent()));
return feedHandler.getEmail();
}
private String urlEncode(String s) {
try {
return URLEncoder.encode(s, "utf-8");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private String generateSig(HttpRequest request, HttpParameters requestParameters) throws Exception {
String keyString = percentEncode(getConsumerSecret()) + '&' + percentEncode(getTokenSecret());
SecretKey key = new SecretKeySpec(keyString.getBytes(ENCODING), MAC_NAME);
Mac mac = Mac.getInstance(MAC_NAME);
mac.init(key);
String sbs = new SignatureBaseString(request, requestParameters).generate();
return base64(mac.doFinal(sbs.getBytes(ENCODING)));
}
private String base64(byte[] data) {
try {
return new String(Base64.encodeBase64(data), "UTF-8");
} catch (java.io.UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
private static class FeedHandler extends DefaultHandler {
private static final String EMAIL = "email";
private static final String AUTHOR = "author";
private final StringBuilder email = new StringBuilder();
private boolean inEmail;
private boolean inAuthor;
@Override
public void startElement(String uri, String localName, String qName, Attributes atts) {
inEmail = EMAIL.equals(qName);
if (AUTHOR.equals(qName)) {
inAuthor = true;
}
}
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
if (inAuthor && AUTHOR.equals(qName)) {
inAuthor = false;
}
}
@Override
public void characters(char[] c, int start, int length) {
if (inAuthor && inEmail) {
email.append(c, start, length);
}
}
@Override
public void error(SAXParseException e) throws SAXException {
Log.e(TAG, "error during parsing", e);
}
@Override public void warning(SAXParseException e) throws SAXException {
Log.w(TAG, "error during parsing", e);
}
public String getEmail() {
return email.toString().trim();
}
}
}