package com.github.andlyticsproject.console.v2;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.Builder;
import android.util.Log;
import com.github.andlyticsproject.AndlyticsApp;
import com.github.andlyticsproject.R;
import com.github.andlyticsproject.console.AppAccessBlockedException;
import com.github.andlyticsproject.console.AuthenticationException;
import com.github.andlyticsproject.console.DevConsoleException;
import com.github.andlyticsproject.model.DeveloperConsoleAccount;
import com.github.andlyticsproject.util.FileUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.http.cookie.Cookie;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class BaseAuthenticator implements DevConsoleAuthenticator {
private static final String TAG = BaseAuthenticator.class.getSimpleName();
protected static final Pattern STARTUP_DATA_PATTERN = Pattern
.compile("startupData = (\\{.+?\\});");
protected String accountName;
protected BaseAuthenticator(String accountName) {
this.accountName = accountName;
}
protected String findXsrfToken(JSONObject startupData) {
try {
return new JSONObject(startupData.getString("XsrfToken")).getString("1");
} catch (JSONException e) {
throw new DevConsoleException(e);
}
}
protected DeveloperConsoleAccount[] findDeveloperAccounts(JSONObject startupData) {
List<DeveloperConsoleAccount> devAccounts = new ArrayList<DeveloperConsoleAccount>();
try {
JSONObject devConsoleAccountsObj = new JSONObject(
startupData.getString("DeveloperConsoleAccounts"));
JSONArray devConsoleAccountsArr = devConsoleAccountsObj.getJSONArray("1");
for (int i = 0; i < devConsoleAccountsArr.length(); i++) {
JSONObject accountObj = devConsoleAccountsArr.getJSONObject(i);
String developerId = accountObj.getString("1");
String developerName = StringEscapeUtils.unescapeJava(accountObj.getString("2"));
// Cannot access apps if e.g. a developer agreement needs to be accepted
// XXX seems to be always false? Disable check for now
// boolean canAccessApps = accountObj.getBoolean("3");
boolean canAccessApps = true;
devAccounts.add(new DeveloperConsoleAccount(developerId, developerName, canAccessApps));
}
return devAccounts.isEmpty() ? null : devAccounts
.toArray(new DeveloperConsoleAccount[devAccounts.size()]);
} catch (JSONException e) {
throw new DevConsoleException(e);
}
}
protected List<String> findWhitelistedFeatures(JSONObject startupData) {
List<String> result = new ArrayList<String>();
try {
JSONArray featuresArr = new JSONObject(startupData.getString("WhitelistedFeatures"))
.getJSONArray("1");
for (int i = 0; i < featuresArr.length(); i++) {
result.add(featuresArr.getString(i));
}
return Collections.unmodifiableList(result);
} catch (JSONException e) {
throw new DevConsoleException(e);
}
}
public JSONObject getStartupData(String responseStr) {
try {
Matcher m = STARTUP_DATA_PATTERN.matcher(responseStr);
if (m.find()) {
String startupDataStr = m.group(1);
return new JSONObject(startupDataStr);
}
return null;
} catch (JSONException e) {
throw new DevConsoleException(e);
}
}
protected String findPreferredCurrency(JSONObject startupData) {
// fallback
String result = "USD";
try {
JSONObject userDetails = new JSONObject(startupData.getString("UserDetails"));
if (userDetails.has("2")) {
result = userDetails.getString("2");
}
return result;
} catch (JSONException e) {
throw new DevConsoleException(e);
}
}
public String getAccountName() {
return accountName;
}
protected void debugAuthFailure(String responseStr, String webloginUrl) {
FileUtils.writeToAndlyticsDir("console-response.html", responseStr);
openAuthUrlInBrowser(webloginUrl);
}
protected void openAuthUrlInBrowser(String webloginUrl) {
if (webloginUrl == null) {
Log.d(TAG, "Null webloginUrl?");
return;
}
Log.d(TAG, "Opening login URL in browser: " + webloginUrl);
Intent viewInBrowser = new Intent(Intent.ACTION_VIEW);
viewInBrowser.setData(Uri.parse(webloginUrl));
// Always show the notification
// When this occurs, it can often occur in batches, e.g. if a the user also clicks to view
// comments which results in multiple dev consoles opening in their browser without an
// explanation. This is even worse if they have multiple accounts and/or are currently
// signed in via a different account
Context ctx = AndlyticsApp.getInstance();
Builder builder = new NotificationCompat.Builder(ctx);
builder.setSmallIcon(R.drawable.statusbar_andlytics);
builder.setContentTitle(ctx.getResources().getString(R.string.auth_error, accountName));
builder.setContentText(ctx.getResources().getString(R.string.auth_error_open_browser));
builder.setAutoCancel(true);
PendingIntent contentIntent = PendingIntent.getActivity(ctx, accountName.hashCode(),
viewInBrowser, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentIntent(contentIntent);
NotificationManager nm = (NotificationManager) ctx
.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(accountName.hashCode(), builder.build());
}
protected SessionCredentials createSessionCredentials(String accountName, String webloginUrl,
String responseStr, List<Cookie> cookies) {
JSONObject startupData = getStartupData(responseStr);
if (startupData == null) {
debugAuthFailure(responseStr, webloginUrl);
throw new AuthenticationException("Couldn't find StartupData JSON object.");
}
DeveloperConsoleAccount[] developerAccounts = findDeveloperAccounts(startupData);
if (developerAccounts == null) {
debugAuthFailure(responseStr, webloginUrl);
throw new AuthenticationException("Couldn't get developer account ID.");
}
boolean allowedToAccessAppsForSomeAccounts = false;
for (DeveloperConsoleAccount account : developerAccounts) {
if (account.getCanAccessApps()) {
allowedToAccessAppsForSomeAccounts = true;
} else {
// TODO Report this to the user properly, but don't spam them because they may
// never be able to resolve the problem e.g. the account owner needs to agree to new terms
Log.w(TAG, "Not allowed to fetch app info for " + account.getDeveloperId() + ". " +
"Log into the account via your browser to resolve the problem.");
}
}
if (!allowedToAccessAppsForSomeAccounts) {
throw new AppAccessBlockedException("Not allowed to fetch app info for any account.");
}
String xsrfToken = findXsrfToken(startupData);
if (xsrfToken == null) {
debugAuthFailure(responseStr, webloginUrl);
throw new AuthenticationException("Couldn't get XSRF token.");
}
List<String> whitelistedFeatures = findWhitelistedFeatures(startupData);
String preferredCurrency = findPreferredCurrency(startupData);
SessionCredentials result = new SessionCredentials(accountName, xsrfToken,
developerAccounts);
result.addCookies(cookies);
result.addWhitelistedFeatures(whitelistedFeatures);
result.setPreferredCurrency(preferredCurrency);
return result;
}
}