package com.github.andlyticsproject.console.v2; import android.accounts.Account; import android.accounts.AccountManager; import android.app.Activity; import android.app.Dialog; import android.content.Intent; import android.net.Uri; import android.util.Log; import com.github.andlyticsproject.AndlyticsApp; import com.github.andlyticsproject.console.AuthenticationException; import com.github.andlyticsproject.console.NetworkException; import com.google.android.gms.auth.GoogleAuthException; import com.google.android.gms.auth.GoogleAuthUtil; import com.google.android.gms.auth.GooglePlayServicesAvailabilityException; import com.google.android.gms.auth.UserRecoverableAuthException; import com.google.android.gms.auth.UserRecoverableNotifiedException; import com.google.android.gms.common.GooglePlayServicesUtil; import org.apache.http.HttpEntity; import org.apache.http.HttpHost; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; 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.protocol.BasicHttpContext; import org.apache.http.protocol.ExecutionContext; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import java.io.IOException; import java.util.ArrayList; import java.util.List; public class OauthAccountManagerAuthenticator extends BaseAuthenticator { private static final String TAG = OauthAccountManagerAuthenticator.class.getSimpleName(); private static final String DEVELOPER_CONSOLE_URL = "https://play.google.com/apps/publish/"; private static final String OAUTH_LOGIN_SCOPE = "oauth2:https://www.google.com/accounts/OAuthLogin"; private static final String OAUTH_LOGIN_URL = "https://accounts.google.com/OAuthLogin?source=ChromiumBrowser&issueuberauth=1"; private static final String MERGE_SESSION_URL = "https://accounts.google.com/MergeSession"; private static final int REQUEST_AUTHENTICATE = 42; private static final boolean DEBUG = false; private AccountManager accountManager; // includes one-time token private String webloginUrl; private DefaultHttpClient httpClient; public OauthAccountManagerAuthenticator(String accountName, DefaultHttpClient httpClient) { super(accountName); this.accountManager = AccountManager.get(AndlyticsApp.getInstance()); this.httpClient = httpClient; } @Override public SessionCredentials authenticate(Activity activity, boolean invalidate) throws AuthenticationException { return authenticateInternal(activity, invalidate); } @Override public SessionCredentials authenticateSilently(boolean invalidate) throws AuthenticationException { return authenticateInternal(null, invalidate); } private SessionCredentials authenticateInternal(Activity activity, boolean invalidate) throws AuthenticationException { try { Account[] accounts = accountManager.getAccountsByType("com.google"); Account account = null; for (Account acc : accounts) { if (acc.name.equals(accountName)) { account = acc; break; } } if (account == null) { throw new AuthenticationException(String.format("Account %s not found on device?", accountName)); } if (invalidate && webloginUrl != null) { // probably not needed, since what we are getting is a very // short-lived token accountManager.invalidateAuthToken(account.type, webloginUrl); } String oauthLoginToken = null; if (activity == null) { // background try { oauthLoginToken = GoogleAuthUtil.getTokenWithNotification( AndlyticsApp.getInstance(), accountName, OAUTH_LOGIN_SCOPE, null); } catch (UserRecoverableNotifiedException userNotifiedException) { throw new AuthenticationException( "Additional authentication requried, see notifications."); } catch (GoogleAuthException authEx) { // This is likely unrecoverable. Log.e(TAG, "Unrecoverable authentication exception: " + authEx.getMessage(), authEx); throw new AuthenticationException("Authentication error: " + authEx.getMessage()); } catch (IOException ioEx) { Log.w(TAG, "transient error encountered: " + ioEx.getMessage()); throw new NetworkException(ioEx.getMessage()); } } else { try { oauthLoginToken = GoogleAuthUtil.getToken(activity, accountName, OAUTH_LOGIN_SCOPE); } catch (GooglePlayServicesAvailabilityException playEx) { Dialog dialog = GooglePlayServicesUtil.getErrorDialog( playEx.getConnectionStatusCode(), activity, REQUEST_AUTHENTICATE); dialog.show(); return null; } catch (UserRecoverableAuthException recoverableException) { Intent recoveryIntent = recoverableException.getIntent(); activity.startActivityForResult(recoveryIntent, REQUEST_AUTHENTICATE); } catch (GoogleAuthException authEx) { // This is likely unrecoverable. Log.e(TAG, "Unrecoverable authentication exception: " + authEx.getMessage(), authEx); throw new AuthenticationException("Authentication error: " + authEx.getMessage()); } catch (IOException ioEx) { Log.w(TAG, "transient error encountered: " + ioEx.getMessage()); throw new NetworkException(ioEx.getMessage()); } } if (oauthLoginToken == null) { throw new AuthenticationException( "Unexpected authentication error: weblogin URL = null"); } if (DEBUG) { Log.d(TAG, "OAuth Login token: " + webloginUrl); } HttpGet getOauthUberToken = new HttpGet(OAUTH_LOGIN_URL); getOauthUberToken.addHeader("Authorization", "OAuth " + oauthLoginToken); HttpResponse response = httpClient.execute(getOauthUberToken); int status = response.getStatusLine().getStatusCode(); if (status == HttpStatus.SC_UNAUTHORIZED) { throw new AuthenticationException("Cannot get uber token: " + response.getStatusLine()); } String uberToken = EntityUtils.toString(response.getEntity(), "UTF-8"); if (DEBUG) { Log.d(TAG, "uber token: " + uberToken); } if (uberToken == null || "".equals(uberToken) || uberToken.contains("Error")) { throw new AuthenticationException("Cannot get uber token. Got: " + uberToken); } HttpPost getConsole = new HttpPost(MERGE_SESSION_URL); List<NameValuePair> pairs = new ArrayList<NameValuePair>(); pairs.add(new BasicNameValuePair("uberauth", uberToken)); pairs.add(new BasicNameValuePair("continue", DEVELOPER_CONSOLE_URL)); pairs.add(new BasicNameValuePair("source", "ChromiumBrowser")); // for debugging? webloginUrl = Uri.parse(MERGE_SESSION_URL).buildUpon() .appendQueryParameter("source", "ChromiumBrowser") .appendQueryParameter("uberauth", uberToken) .appendQueryParameter("continue", DEVELOPER_CONSOLE_URL).build().toString(); if (DEBUG) { Log.d(TAG, "MergeSession URL: " + webloginUrl); } UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(pairs, "UTF-8"); getConsole.setEntity(formEntity); HttpContext context = new BasicHttpContext(); response = httpClient.execute(getConsole, context); status = response.getStatusLine().getStatusCode(); if (status == HttpStatus.SC_UNAUTHORIZED) { throw new AuthenticationException("Authentication token expired: " + response.getStatusLine()); } if (status != HttpStatus.SC_OK) { throw new AuthenticationException("Authentication error: " + response.getStatusLine()); } String currentUrl = getCurrentUrl(context); if (DEBUG) { Log.d(TAG, "redirect URL" + currentUrl); } HttpEntity entity = response.getEntity(); if (entity == null) { throw new AuthenticationException("Authentication error: null result?"); } String responseStr = EntityUtils.toString(entity, "UTF-8"); if (DEBUG) { Log.d(TAG, "Response: " + responseStr); } if (!currentUrl.contains("play.google.com")) { debugAuthFailure(responseStr, currentUrl); throw new AuthenticationException( "Couldn't connect to developer console. Additional authentication may be required."); } return createSessionCredentials(accountName, webloginUrl, responseStr, httpClient .getCookieStore().getCookies()); } catch (IOException e) { throw new NetworkException(e); } } private static String getCurrentUrl(HttpContext context) { HttpUriRequest currentReq = (HttpUriRequest) context .getAttribute(ExecutionContext.HTTP_REQUEST); HttpHost currentHost = (HttpHost) context .getAttribute(ExecutionContext.HTTP_TARGET_HOST); String currentUrl = (currentReq.getURI().isAbsolute()) ? currentReq.getURI().toString() : (currentHost.toURI() + currentReq.getURI()); return currentUrl; } }