diff --git a/android/build.gradle b/android/build.gradle index f34fd5b1b..68a5d71a7 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,7 +1,7 @@ // each of the version numbers must be 0-99 def versionMajor = 1 -def versionMinor = 7 // minor feature releases -def versionPatch = 12 // This should be bumped for hot fixes +def versionMinor = 8 // minor feature releases +def versionPatch = 1 // This should be bumped for hot fixes // Double check the versioning for (versionPart in [versionPatch, versionMinor, versionMajor]) { @@ -120,29 +120,23 @@ android { // standard unlabelled tiles. buildConfigField "boolean", "LABEL_MAP_TILES", "false" - buildConfigField "String", "FXA_APP_KEY", "\"86cd25bed2c63936\"" + // buildConfigField "String", "FXA_APP_KEY", "\"86cd25bed2c63936\"" + // buildConfigField "String", "FXA_OAUTH2_SERVER", "\"https://oauth-stable.dev.lcip.org\"" + // buildConfigField "String", "FXA_PROFILE_SERVER", "\"https://stable.dev.lcip.org/profile\"" + // buildConfigField "String", "FXA_APP_CALLBACK", "\"http://leaderboard-dev.jaredkerim.com\"" - buildConfigField "String", "FXA_OAUTH2_SERVER", "\"https://oauth-stable.dev.lcip.org/v1\"" - buildConfigField "String", "FXA_PROFILE_SERVER", "\"https://stable.dev.lcip.org/profile/v1\"" - - buildConfigField "String", "FXA_APP_CALLBACK", "\"http://leaderboard-dev.jaredkerim.com\"" - buildConfigField "String", "LB_SUBMIT_URL", "\"http://leaderboard-dev.jaredkerim.com/api/v1/contributions/\"" + // This is needed to generate the submission URL and the + // profile URL to view user stats + buildConfigField "String", "LB_BASE_URL", "\"http://cloudvm.jaredkerim.com\"" + // This should go away and hang off the LB_BASE_URL + buildConfigField "String", "LB_SUBMIT_URL", "\"http://cloudvm.jaredkerim.com/api/v1/contributions/\"" } release { signingConfig signingConfigs.release - // TODO: this key will need to be reprovisioned for - // production releases - buildConfigField "String", "FXA_APP_KEY", "\"d0f6d2ed3c5fcc3b\"" - - buildConfigField "String", "FXA_OAUTH2_SERVER", "\"https://oauth.accounts.firefox.com/v1\"" - buildConfigField "String", "FXA_PROFILE_SERVER", "\"https://profile.accounts.firefox.com/v1\"" - // buildConfigField "String", "FXA_SIGNIN_URL", "\"https://accounts.firefox.com/signin\"" - - // TODO: update this callback for production - buildConfigField "String", "FXA_APP_CALLBACK", "\"http://ec2-52-1-93-147.compute-1.amazonaws.com/fxa/callback\"" + buildConfigField "String", "LB_SUBMIT_URL", "\"http://leaderboard-dev.jaredkerim.com/api/v1/contributions/\"" } @@ -255,8 +249,8 @@ dependencies { compile "org.apache.james:apache-mime4j:0.6" // Firefox Accounts library code - compile 'org.mozilla.accounts.fxa:fxa:0.9.17' - testCompile 'org.mozilla.accounts.fxa:fxa:0.9.17' + compile 'org.mozilla.accounts.fxa:fxa:0.10.2' + testCompile 'org.mozilla.accounts.fxa:fxa:0.10.2' // org.apache.http.httpclient compile 'org.apache.httpcomponents:httpclient-android:4.3.5.1' diff --git a/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/FetchFxaConfiguration.java b/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/FetchFxaConfiguration.java new file mode 100644 index 000000000..24d113def --- /dev/null +++ b/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/FetchFxaConfiguration.java @@ -0,0 +1,115 @@ +package org.mozilla.mozstumbler.client.subactivities; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.AsyncTask; +import android.support.v4.content.LocalBroadcastManager; +import android.text.TextUtils; +import android.util.Log; + +import org.json.JSONException; +import org.json.JSONObject; +import org.mozilla.accounts.fxa.FxAGlobals; +import org.mozilla.accounts.fxa.LoggerUtil; +import org.mozilla.accounts.fxa.net.HTTPResponse; +import org.mozilla.accounts.fxa.net.HttpUtil; + +import java.util.HashMap; +import java.util.Map; + +import static org.mozilla.accounts.fxa.Intents.ACCESS_TOKEN_REFRESH; +import static org.mozilla.accounts.fxa.Intents.ACCESS_TOKEN_REFRESH_FAILURE; + +/** + * Created by victorng on 2015-12-31. + * + * + * This class jsut calls GET on a URL, passes in a refresh token, an existing access token + * and the server processes + * + */ +public class FetchFxaConfiguration extends AsyncTask { + + // Most applications should use a refreshed access token on application startup. + // This will minimize the lifetime of any access token. + + public static final String FXA_CONFIG_LOAD = "org.mozilla.accounts.fxa.config.load"; + public static final String FXA_CONFIG_LOAD_FAILURE = "org.mozilla.accounts.fxa.config.load.failure"; + + + private static final String LOG_TAG = LoggerUtil.makeLogTag(FetchFxaConfiguration.class); + private final String configuration_endpoint; + private final Context mContext; + + public FetchFxaConfiguration(Context ctx, String cfg_url) { + mContext = ctx; + this.configuration_endpoint = cfg_url; + } + + public String getFxaConfigEndpoint() { + return configuration_endpoint; + } + + public AsyncTask execute() { + return super.execute(); + } + + + /* + This task requires no arguments. + */ + @Override + protected JSONObject doInBackground(Void... params) { + if (params.length != 0) { + Log.i(LOG_TAG, "Invalid number of arguments."); + return null; + } + + HttpUtil httpUtil = new HttpUtil(System.getProperty("http.agent") + " " + + FxAGlobals.appName + "/" + FxAGlobals.appVersionName); + + Map headers = new HashMap(); + headers.put("Content-Type", "application/json"); + + HTTPResponse resp = httpUtil.get(getFxaConfigEndpoint(), headers); + + if (resp.isSuccessCode2XX()) { + try { + return new JSONObject(resp.body()); + } catch (JSONException e) { + Log.e(LOG_TAG, "Error marshalling the FxA configuration JSON blob."); + return null; + } + } else { + Log.w(LOG_TAG, "FxA Configuration HTTP Status: " + resp.httpStatusCode()); + return null; + } + } + + + + @Override + protected void onPostExecute(JSONObject result) { + if (result == null) { + Intent intent = new Intent(FXA_CONFIG_LOAD_FAILURE); + LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent); + } else { + Intent intent = new Intent(FXA_CONFIG_LOAD); + intent.putExtra("json", result.toString()); + LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent); + } + } + + public static void registerFxaIntents(Context ctx, BroadcastReceiver callbackReceiver) { + IntentFilter intentFilter = new IntentFilter(); + + intentFilter.addAction(FetchFxaConfiguration.FXA_CONFIG_LOAD); + intentFilter.addAction(FetchFxaConfiguration.FXA_CONFIG_LOAD_FAILURE); + + LocalBroadcastManager.getInstance(ctx) + .registerReceiver(callbackReceiver, intentFilter); + + } +} diff --git a/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/LeaderboardActivity.java b/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/LeaderboardActivity.java index 42afe42f0..1401381e8 100644 --- a/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/LeaderboardActivity.java +++ b/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/LeaderboardActivity.java @@ -14,11 +14,12 @@ import android.webkit.WebViewClient; import android.widget.Toast; +import org.mozilla.mozstumbler.BuildConfig; import org.mozilla.mozstumbler.R; import org.mozilla.mozstumbler.client.ClientPrefs; public class LeaderboardActivity extends ActionBarActivity { - private static final String LEADERBOARD_URL = "https://location.services.mozilla.com/leaders"; + private static final String LEADERBOARD_URL = BuildConfig.LB_BASE_URL; private WebView mWebView; private boolean mHasError; @@ -67,15 +68,14 @@ public void onPageFinished(WebView webview, String url) { setProgress(0); ClientPrefs prefs = ClientPrefs.getInstance(getApplicationContext()); - String nick = prefs.getNickname(); - String url = LEADERBOARD_URL; - if (nick != null) { - url += "#" + nick; - } else { - // TODO Get server side to add this anchor - // https://github.com/mozilla/ichnaea/issues/327 - url += "#leaderboard_start"; - } + String url = LEADERBOARD_URL + "/?profile=" + getPrefs().getLeaderboardUID(); + mWebView.loadUrl(url); } + + + private ClientPrefs getPrefs() { + return ClientPrefs.getInstance(this); + } + } diff --git a/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/PreferencesScreen.java b/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/PreferencesScreen.java index 8b90a71a0..f45818b0a 100644 --- a/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/PreferencesScreen.java +++ b/android/src/main/java/org/mozilla/mozstumbler/client/subactivities/PreferencesScreen.java @@ -5,6 +5,7 @@ package org.mozilla.mozstumbler.client.subactivities; import android.app.AlertDialog; +import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -22,6 +23,7 @@ import android.webkit.CookieSyncManager; import android.widget.Toast; +import org.json.JSONException; import org.json.JSONObject; import org.mozilla.accounts.fxa.FxAGlobals; import org.mozilla.accounts.fxa.IFxACallbacks; @@ -32,7 +34,6 @@ import org.mozilla.accounts.fxa.tasks.RetrieveProfileTask; import org.mozilla.accounts.fxa.tasks.SetDisplayNameTask; import org.mozilla.accounts.fxa.tasks.VerifyOAuthTask; -import org.mozilla.mozstumbler.BuildConfig; import org.mozilla.mozstumbler.R; import org.mozilla.mozstumbler.client.ClientPrefs; import org.mozilla.mozstumbler.client.MainApp; @@ -57,9 +58,66 @@ public class PreferencesScreen extends PreferenceActivity implements IFxACallbac private ListPreference mMapTileDetail; private Preference mFxaLoginPreference; + static String FXA_APP_CALLBACK; + static String FXA_PROFILE_SERVER; + static String FXA_OAUTH2_SERVER; + static String FXA_APP_KEY; + static String SCOPES; + + + private final BroadcastReceiver callbackReceiver = new BroadcastReceiver() { + + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(FetchFxaConfiguration.FXA_CONFIG_LOAD)) { + processFxaConfigLoad(intent, true); + } else if (intent.getAction().equals(FetchFxaConfiguration.FXA_CONFIG_LOAD_FAILURE)) { + processFxaConfigLoad(intent, false); + } else { + Log.w(LOG_TAG, "Unexpected intent: " + intent.toString()); + } + } + }; + + + private void processFxaConfigLoad(Intent intent, boolean success) { + if (!success) { + Log.e(LOG_TAG, "No fxa configuration is available"); + return; + } + + String configBlob = intent.getStringExtra("json"); + if (TextUtils.isEmpty(configBlob)) { + Log.w(LOG_TAG, "No fxa config blob was found in the intent"); + return; + } + + Log.i(LOG_TAG, "Obtained configuration JSON: " + configBlob); + + try { + JSONObject configJSON = new JSONObject(configBlob); + + // These secrets are provisioned from the FxA dashboard + FXA_APP_KEY = configJSON.getString("client_id"); + FXA_PROFILE_SERVER = configJSON.getString("profile_uri"); + FXA_OAUTH2_SERVER = configJSON.getString("oauth_uri"); + SCOPES = configJSON.getString("scopes"); + FXA_APP_CALLBACK = configJSON.getString("redirect_uri"); + + // TODO: enable the FxA login TextPreference + Log.i(LOG_TAG, "Fxa Config ["+FXA_APP_KEY+"]["+FXA_PROFILE_SERVER+"]["+FXA_OAUTH2_SERVER+"]["+SCOPES+"]["+FXA_APP_CALLBACK+"]"); + + } catch (JSONException e) { + Log.e(LOG_TAG, "Can't parse JSON config blob", e); + return; + } + + + } + private void fetchFxaProfile(String bearerToken) { RetrieveProfileTask task = new RetrieveProfileTask(getApplicationContext(), - BuildConfig.FXA_PROFILE_SERVER); + FXA_PROFILE_SERVER); task.execute(bearerToken); } @@ -97,8 +155,17 @@ protected void onCreate(Bundle savedInstanceState) { verifyBearerToken(); String app_name = getResources().getString(R.string.app_name); + FxAGlobals fxa = new FxAGlobals(); - fxa.startIntentListening((Context)this, (IFxACallbacks) this, app_name); + fxa.startIntentListening((Context) this, (IFxACallbacks) this, app_name); + + + // Register callbacks so that we can load the FxA configuration JSON blob + FetchFxaConfiguration.registerFxaIntents(this.getApplicationContext(), callbackReceiver); + + FetchFxaConfiguration fxaConfigTask = new FetchFxaConfiguration(this.getApplicationContext(), + "http://cloudvm.jaredkerim.com/api/v1/fxa/config/"); + fxaConfigTask.execute(); } @@ -109,7 +176,7 @@ private void verifyBearerToken() { String bearerToken = getPrefs().getBearerToken(); if (!TextUtils.isEmpty(bearerToken)) { VerifyOAuthTask task = new VerifyOAuthTask(getApplicationContext(), - BuildConfig.FXA_OAUTH2_SERVER); + FXA_OAUTH2_SERVER); task.execute(bearerToken); } } @@ -216,7 +283,7 @@ public boolean onPreferenceClick(Preference preference) { public void onClick(DialogInterface arg0, int arg1) { String bearerToken = getPrefs().getBearerToken(); DestroyOAuthTask task = new DestroyOAuthTask(getApplicationContext(), - BuildConfig.FXA_OAUTH2_SERVER); + FXA_OAUTH2_SERVER); task.execute(bearerToken); } }); @@ -229,30 +296,18 @@ public void onClick(DialogInterface arg0, int arg1) { return true; } - // These secrets are provisioned from the FxA dashboard - String FXA_APP_KEY = BuildConfig.FXA_APP_KEY; // And finally the callback endpoint on our web application // Example server endpoint code is available under the `sample_endpoint` subdirectory. - String FXA_APP_CALLBACK = BuildConfig.FXA_APP_CALLBACK; - CookieSyncManager cookies = CookieSyncManager.createInstance(PreferencesScreen.this); CookieManager.getInstance().removeAllCookie(); CookieManager.getInstance().removeSessionCookie(); cookies.sync(); - // Only untrusted scopes can go here for now. - // If you add an scope that is not on that list, the login screen will hang instead - // of going to the final redirect. No user visible error occurs. This is terrible. - // https://github.com/mozilla/fxa-content-server/issues/2508 - String[] scopes = new String[] {"profile:email", - "profile:display_name", - "profile:display_name:write"}; - new OAuthDialog(PreferencesScreen.this, - BuildConfig.FXA_OAUTH2_SERVER, + FXA_OAUTH2_SERVER, FXA_APP_CALLBACK, - scopes, + SCOPES, FXA_APP_KEY).show(); return true; } @@ -333,6 +388,7 @@ private void clearFxaLoginState() { getPrefs().setEmail(""); getPrefs().setNickname(""); setFxALoginTitle("", ""); + getPrefs().setLeaderboardUID(""); setNicknamePreferenceTitle(""); Toast.makeText(getApplicationContext(), getApplicationContext().getString(R.string.fxa_is_logged_out), @@ -365,15 +421,37 @@ private void setNicknamePreferenceTitle(String displayName) { // FxA Callbacks + @Override - public void processReceiveBearerToken(String bearerToken) { + public void processWebSvcAuthResponse(JSONObject authJSON) { + String leaderboard_uid = authJSON.optString("leaderboard_uid", ""); + + JSONObject fxa_auth_data = authJSON.optJSONObject("fxa_auth_data"); + + if (fxa_auth_data == null) { + clearFxaLoginState(); + return; + } + + String bearerToken = null; + try { + bearerToken = fxa_auth_data.getString("access_token"); + } catch (JSONException e) { + Log.e(LOG_TAG, "Expected to find an access token in the FxA auth data block."); + clearFxaLoginState(); + return; + } + getPrefs().setBearerToken(bearerToken); fetchFxaProfile(bearerToken); - } - @Override - public void processRawResponse(JSONObject jsonObject) { + if (!TextUtils.isEmpty(leaderboard_uid)) { + getPrefs().setLeaderboardUID(leaderboard_uid); + Log.i(LOG_TAG, "Saved leaderboard UID: " + leaderboard_uid); + } else { + Log.i(LOG_TAG, "Didn't save a leaderboard UID"); + } } @Override @@ -400,6 +478,7 @@ public void processProfileRead(JSONObject jsonObject) { String displayName = profileJson.getDisplayName(); String email = profileJson.getEmail(); + if (!TextUtils.isEmpty(email)) { Prefs.getInstance(this).setEmail(email); setFxALoginTitle(getPrefs().getBearerToken(), getPrefs().getEmail()); @@ -417,7 +496,7 @@ public void processProfileRead(JSONObject jsonObject) { private void setFxANickname(String displayName) { SetDisplayNameTask task = new SetDisplayNameTask(getApplicationContext(), - BuildConfig.FXA_PROFILE_SERVER); + FXA_PROFILE_SERVER); task.execute(getPrefs().getBearerToken(), displayName); } @@ -440,4 +519,9 @@ public void processOauthDestroy() { public void processOauthVerify() { } + + @Override + public void processRefreshToken(JSONObject jObj) { + + } } diff --git a/libraries/stumbler/src/main/java/org/mozilla/mozstumbler/service/Prefs.java b/libraries/stumbler/src/main/java/org/mozilla/mozstumbler/service/Prefs.java index 7c58582dc..f87cc6042 100644 --- a/libraries/stumbler/src/main/java/org/mozilla/mozstumbler/service/Prefs.java +++ b/libraries/stumbler/src/main/java/org/mozilla/mozstumbler/service/Prefs.java @@ -47,6 +47,7 @@ public class Prefs { private static final String USE_OFFLINE_GEO = "use_offline_geo"; private static final String USE_HIGH_POWER = "use_high_power"; public static final String FXA_LOGIN_PREF = "fxaLogin"; + public static final String UID_PREF = "leaderboard_uid"; protected static Prefs sInstance; private final SharedPreferences mSharedPrefs; @@ -162,6 +163,20 @@ public synchronized void setNickname(String nick) { } } + public synchronized String getLeaderboardUID() { + String uid = getStringPref(UID_PREF); + if (!TextUtils.isEmpty(uid)) { + return uid; + } + return null; + } + + public synchronized void setLeaderboardUID(String uid) { + if (!TextUtils.isEmpty(uid)) { + setStringPref(UID_PREF, uid); + } + } + public synchronized String getEmail() { String email = getStringPref(EMAIL_PREF); if (email != null) {