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;
}
}