mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 705767 - Validate credentials before completing setup. r=rnewman
This commit is contained in:
parent
01a5c16cc5
commit
104dbfc7b6
File diff suppressed because one or more lines are too long
@ -78,4 +78,7 @@
|
||||
<!ENTITY sync.notification.oneaccount.label 'Only one &syncBrand.fullName.label; account is supported.'>
|
||||
|
||||
<!-- Incorrect settings and changing credentials. -->
|
||||
<!ENTITY sync.invalidcreds.label 'Incorrect account name or password.'>
|
||||
<!ENTITY sync.invalidserver.label 'Please enter a valid server URL'>
|
||||
<!ENTITY sync.verifying.label 'Verifying…'>
|
||||
<!ENTITY sync.new.recoverykey.status.incorrect 'Recovery Key incorrect. Please try again.'>
|
||||
|
@ -43,6 +43,10 @@
|
||||
<EditText android:id="@+id/keyInput"
|
||||
style="@style/SyncEditItem"
|
||||
android:hint="@string/sync_input_key" />
|
||||
|
||||
<TextView android:id="@+id/cred_error"
|
||||
style="@style/SyncTextError"
|
||||
android:text="@string/sync_invalidcreds_label" />
|
||||
|
||||
<CheckBox android:id="@+id/checkbox_server"
|
||||
android:layout_width="wrap_content"
|
||||
@ -56,6 +60,10 @@
|
||||
android:visibility="gone"
|
||||
android:hint="@string/sync_input_server" />
|
||||
|
||||
<TextView android:id="@+id/server_error"
|
||||
style="@style/SyncTextError"
|
||||
android:text="@string/sync_invalidserver_label" />
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
|
||||
@ -65,6 +73,7 @@
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<Button
|
||||
android:id="@+id/accountCancelButton"
|
||||
style="@style/SyncButton"
|
||||
android:onClick="cancelClickHandler"
|
||||
android:text="@string/sync_button_cancel" />
|
||||
|
@ -99,8 +99,7 @@
|
||||
style="@style/SyncBottom"
|
||||
android:orientation="horizontal" >
|
||||
<Button
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/SyncButton"
|
||||
android:onClick="cancelClickHandler"
|
||||
android:text="@string/sync_button_cancel" />
|
||||
</LinearLayout>
|
||||
|
@ -27,22 +27,17 @@
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<Button
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/SyncButton"
|
||||
android:onClick="tryAgainClickHandler"
|
||||
android:text="@string/sync_button_tryagain" />
|
||||
|
||||
<Button
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/SyncButton"
|
||||
android:onClick="manualClickHandler"
|
||||
android:text="@string/sync_button_manual" />
|
||||
|
||||
<Button
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
style="@style/SyncButton"
|
||||
android:onClick="cancelClickHandler"
|
||||
android:text="@string/sync_button_cancel" />
|
||||
</LinearLayout>
|
||||
|
@ -66,7 +66,7 @@
|
||||
android:visibility="invisible" >
|
||||
|
||||
<TextView
|
||||
style="@style/SyncTextItem"
|
||||
style="@style/SyncTextError"
|
||||
android:layout_margin="10dp"
|
||||
android:text="@string/sync_pair_tryagain"
|
||||
android:textSize="10dp" />
|
||||
|
@ -28,7 +28,12 @@
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:textSize">15dp</item>
|
||||
</style>
|
||||
<style name="SyncLinkItem" parent="SyncTextItem">
|
||||
<style name="SyncTextError" parent="@style/SyncTextItem">
|
||||
<item name="android:layout_margin">10dp</item>
|
||||
<item name="android:textSize">15dp</item>
|
||||
<item name="android:visibility">gone</item>
|
||||
</style>
|
||||
<style name="SyncLinkItem" parent="@style/SyncTextItem">
|
||||
<item name="android:clickable">true</item>
|
||||
<item name="android:textColor">#ACC4D5</item>
|
||||
</style>
|
||||
@ -67,6 +72,7 @@
|
||||
<style name="SyncBottom">
|
||||
<item name="android:layout_width">fill_parent</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:layout_gravity">center</item>
|
||||
<item name="android:gravity">center</item>
|
||||
<item name="android:layout_alignParentBottom">true</item>
|
||||
<item name="android:background">@android:drawable/bottom_bar</item>
|
||||
|
@ -12,6 +12,7 @@ import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.jpake.JPakeClient;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import org.mozilla.gecko.sync.net.SyncResourceDelegate;
|
||||
import org.mozilla.gecko.sync.setup.auth.AccountAuthenticator;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
@ -85,7 +86,7 @@ public class DeleteChannel {
|
||||
}
|
||||
};
|
||||
|
||||
JPakeClient.runOnThread(new Runnable() {
|
||||
AccountAuthenticator.runOnThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
httpResource.delete();
|
||||
|
@ -26,6 +26,17 @@ public class Constants {
|
||||
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT |
|
||||
Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||
|
||||
// Constants for Account Authentication.
|
||||
public static final String AUTH_NODE_DEFAULT = "https://auth.services.mozilla.com/";
|
||||
public static final String AUTH_NODE_PATHNAME = "user/";
|
||||
public static final String AUTH_NODE_VERSION = "1.0/";
|
||||
public static final String AUTH_NODE_SUFFIX = "node/weave";
|
||||
public static final String AUTH_SERVER_VERSION = "1.1/";
|
||||
public static final String AUTH_SERVER_SUFFIX = "info/collections/";
|
||||
|
||||
// Account Authentication Errors.
|
||||
public static final String AUTH_ERROR_NOUSER = "auth.error.badcredentials";
|
||||
|
||||
// Links for J-PAKE setup help pages.
|
||||
public static final String LINK_FIND_CODE = "https://support.mozilla.org/kb/find-code-to-add-device-to-firefox-sync";
|
||||
public static final String LINK_FIND_ADD_DEVICE = "https://support.mozilla.org/kb/add-a-device-to-firefox-sync";
|
||||
|
@ -12,19 +12,22 @@ import org.mozilla.gecko.sync.setup.Constants;
|
||||
import org.mozilla.gecko.sync.setup.InvalidSyncKeyException;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
|
||||
import org.mozilla.gecko.sync.setup.auth.AccountAuthenticator;
|
||||
import org.mozilla.gecko.sync.setup.auth.AuthenticationResult;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountAuthenticatorActivity;
|
||||
import android.accounts.AccountManager;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.widget.Button;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.CompoundButton;
|
||||
@ -33,14 +36,14 @@ import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class AccountActivity extends AccountAuthenticatorActivity {
|
||||
private final static String LOG_TAG = "AccountActivity";
|
||||
private final static String LOG_TAG = "AccountActivity";
|
||||
|
||||
private AccountManager mAccountManager;
|
||||
private Context mContext;
|
||||
private String username;
|
||||
private String password;
|
||||
private String key;
|
||||
private String server;
|
||||
private String server = Constants.AUTH_NODE_DEFAULT;
|
||||
|
||||
// UI elements.
|
||||
private EditText serverInput;
|
||||
@ -49,6 +52,10 @@ public class AccountActivity extends AccountAuthenticatorActivity {
|
||||
private EditText synckeyInput;
|
||||
private CheckBox serverCheckbox;
|
||||
private Button connectButton;
|
||||
private Button cancelButton;
|
||||
private ProgressDialog progressDialog;
|
||||
|
||||
private AccountAuthenticator accountAuthenticator;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
@ -56,7 +63,7 @@ public class AccountActivity extends AccountAuthenticatorActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.sync_account);
|
||||
mContext = getApplicationContext();
|
||||
Log.d(LOG_TAG, "AccountManager.get(" + mContext + ")");
|
||||
Logger.debug(LOG_TAG, "AccountManager.get(" + mContext + ")");
|
||||
mAccountManager = AccountManager.get(mContext);
|
||||
|
||||
// Set "screen on" flag.
|
||||
@ -78,18 +85,20 @@ public class AccountActivity extends AccountAuthenticatorActivity {
|
||||
serverInput.addTextChangedListener(inputValidator);
|
||||
|
||||
connectButton = (Button) findViewById(R.id.accountConnectButton);
|
||||
cancelButton = (Button) findViewById(R.id.accountCancelButton);
|
||||
serverCheckbox = (CheckBox) findViewById(R.id.checkbox_server);
|
||||
|
||||
serverCheckbox.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
Log.i(LOG_TAG, "Toggling checkbox: " + isChecked);
|
||||
// Hack for pre-3.0 Android: can enter text into disabled EditText.
|
||||
Logger.info(LOG_TAG, "Toggling checkbox: " + isChecked);
|
||||
if (!isChecked) { // Clear server input.
|
||||
serverInput.setVisibility(View.GONE);
|
||||
findViewById(R.id.server_error).setVisibility(View.GONE);
|
||||
serverInput.setText("");
|
||||
} else {
|
||||
serverInput.setVisibility(View.VISIBLE);
|
||||
serverInput.setEnabled(true);
|
||||
}
|
||||
// Activate connectButton if necessary.
|
||||
activateView(connectButton, validateInputs());
|
||||
@ -101,12 +110,32 @@ public class AccountActivity extends AccountAuthenticatorActivity {
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
clearCredentials();
|
||||
usernameInput.requestFocus();
|
||||
cancelButton.setOnClickListener(new OnClickListener() {
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cancelClickHandler(v);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public void cancelClickHandler(View target) {
|
||||
finish();
|
||||
}
|
||||
|
||||
public void cancelConnectHandler(View target) {
|
||||
if (accountAuthenticator != null) {
|
||||
accountAuthenticator.isCanceled = true;
|
||||
accountAuthenticator = null;
|
||||
}
|
||||
displayVerifying(false);
|
||||
activateView(connectButton, true);
|
||||
clearCredentials();
|
||||
usernameInput.requestFocus();
|
||||
}
|
||||
|
||||
private void clearCredentials() {
|
||||
// Only clear password. Re-typing the sync key or email is annoying.
|
||||
passwordInput.setText("");
|
||||
@ -116,13 +145,11 @@ public class AccountActivity extends AccountAuthenticatorActivity {
|
||||
* accessed by Fennec and Sync Service.
|
||||
*/
|
||||
public void connectClickHandler(View target) {
|
||||
Log.d(LOG_TAG, "connectClickHandler for view " + target);
|
||||
enableCredEntry(false);
|
||||
Logger.debug(LOG_TAG, "connectClickHandler for view " + target);
|
||||
// Validate sync key format.
|
||||
try {
|
||||
key = ActivityUtils.validateSyncKey(synckeyInput.getText().toString());
|
||||
} catch (InvalidSyncKeyException e) {
|
||||
enableCredEntry(true);
|
||||
// Toast: invalid sync key format.
|
||||
Toast toast = Toast.makeText(mContext, R.string.sync_new_recoverykey_status_incorrect, Toast.LENGTH_LONG);
|
||||
toast.show();
|
||||
@ -130,26 +157,42 @@ public class AccountActivity extends AccountAuthenticatorActivity {
|
||||
}
|
||||
username = usernameInput.getText().toString().toLowerCase(Locale.US);
|
||||
password = passwordInput.getText().toString();
|
||||
key = synckeyInput.getText().toString();
|
||||
server = Constants.AUTH_NODE_DEFAULT;
|
||||
|
||||
if (serverCheckbox.isChecked()) {
|
||||
server = serverInput.getText().toString();
|
||||
String userServer = serverInput.getText().toString();
|
||||
if (userServer != null) {
|
||||
userServer = userServer.trim();
|
||||
if (userServer.length() != 0) {
|
||||
if (!userServer.startsWith("https://") &&
|
||||
!userServer.startsWith("http://")) {
|
||||
// Assume HTTPS if not specified.
|
||||
userServer = "https://" + userServer;
|
||||
serverInput.setText(userServer);
|
||||
}
|
||||
server = userServer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO : Authenticate with Sync Service, once implemented, with
|
||||
// onAuthSuccess as callback
|
||||
clearErrors();
|
||||
displayVerifying(true);
|
||||
cancelButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cancelConnectHandler(v);
|
||||
// Set cancel click handler to leave account setup.
|
||||
cancelButton.setOnClickListener(new OnClickListener() {
|
||||
public void onClick(View v) {
|
||||
cancelClickHandler(v);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
authCallback();
|
||||
}
|
||||
|
||||
/* Helper UI functions */
|
||||
private void enableCredEntry(boolean toEnable) {
|
||||
usernameInput.setEnabled(toEnable);
|
||||
passwordInput.setEnabled(toEnable);
|
||||
synckeyInput.setEnabled(toEnable);
|
||||
if (!toEnable) {
|
||||
serverInput.setEnabled(toEnable);
|
||||
} else {
|
||||
serverInput.setEnabled(serverCheckbox.isChecked());
|
||||
}
|
||||
accountAuthenticator = new AccountAuthenticator(this);
|
||||
accountAuthenticator.authenticate(server, username, password);
|
||||
}
|
||||
|
||||
private TextWatcher makeInputValidator() {
|
||||
@ -185,13 +228,18 @@ public class AccountActivity extends AccountAuthenticatorActivity {
|
||||
/*
|
||||
* Callback that handles auth based on success/failure
|
||||
*/
|
||||
private void authCallback() {
|
||||
// Create and add account to AccountManager
|
||||
// TODO: only allow one account to be added?
|
||||
final SyncAccountParameters syncAccount = new SyncAccountParameters(mContext, mAccountManager,
|
||||
username, key, password, server);
|
||||
public void authCallback(final AuthenticationResult result) {
|
||||
displayVerifying(false);
|
||||
if (result != AuthenticationResult.SUCCESS) {
|
||||
Logger.debug(LOG_TAG, "displayFailure()");
|
||||
displayFailure(result);
|
||||
return;
|
||||
}
|
||||
// Successful authentication. Create and add account to AccountManager.
|
||||
final SyncAccountParameters syncAccount = new SyncAccountParameters(
|
||||
mContext, mAccountManager, username, key, password, server);
|
||||
final Account account = SyncAccounts.createSyncAccount(syncAccount);
|
||||
final boolean result = (account != null);
|
||||
final boolean accountResult = (account != null);
|
||||
|
||||
final Intent intent = new Intent(); // The intent to return.
|
||||
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, syncAccount.username);
|
||||
@ -199,39 +247,77 @@ public class AccountActivity extends AccountAuthenticatorActivity {
|
||||
intent.putExtra(AccountManager.KEY_AUTHTOKEN, Constants.ACCOUNTTYPE_SYNC);
|
||||
setAccountAuthenticatorResult(intent.getExtras());
|
||||
|
||||
if (!result) {
|
||||
if (!accountResult) {
|
||||
// Failed to add account!
|
||||
setResult(RESULT_CANCELED, intent);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
authFailure();
|
||||
// Use default error.
|
||||
// TODO: Display more accurate error (Account failed to be created).
|
||||
Logger.debug(LOG_TAG, "displayFailure()");
|
||||
displayFailure(result);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Currently, we do not actually authenticate username/pass against
|
||||
// Moz sync server.
|
||||
clearErrors();
|
||||
if (intent != null) {
|
||||
setAccountAuthenticatorResult(intent.getExtras());
|
||||
setResult(RESULT_OK, intent);
|
||||
|
||||
// Successfully added account.
|
||||
setResult(RESULT_OK, intent);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
authSuccess();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void displayVerifying(final boolean isVerifying) {
|
||||
if (isVerifying) {
|
||||
progressDialog = ProgressDialog.show(AccountActivity.this, "", getString(R.string.sync_verifying_label), true);
|
||||
} else {
|
||||
progressDialog.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void displayFailure(final AuthenticationResult result) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
authSuccess();
|
||||
switch (result) {
|
||||
case FAILURE_USERNAME:
|
||||
// No such username. Don't leak whether the username exists.
|
||||
case FAILURE_PASSWORD:
|
||||
findViewById(R.id.cred_error).setVisibility(View.VISIBLE);
|
||||
usernameInput.requestFocus();
|
||||
break;
|
||||
case FAILURE_SERVER:
|
||||
findViewById(R.id.server_error).setVisibility(View.VISIBLE);
|
||||
serverInput.requestFocus();
|
||||
break;
|
||||
case FAILURE_OTHER:
|
||||
default:
|
||||
// Display default error screen.
|
||||
Logger.debug(LOG_TAG, "displaying default failure.");
|
||||
Intent intent = new Intent(mContext, SetupFailureActivity.class);
|
||||
intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
private void authFailure() {
|
||||
enableCredEntry(true);
|
||||
Intent intent = new Intent(mContext, SetupFailureActivity.class);
|
||||
intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
private void authSuccess() {
|
||||
/**
|
||||
* Feedback to user of account setup success.
|
||||
*/
|
||||
public void authSuccess() {
|
||||
// Display feedback of successful account setup.
|
||||
Intent intent = new Intent(mContext, SetupSuccessActivity.class);
|
||||
intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION);
|
||||
startActivity(intent);
|
||||
@ -242,4 +328,14 @@ public class AccountActivity extends AccountAuthenticatorActivity {
|
||||
view.setEnabled(toActivate);
|
||||
view.setClickable(toActivate);
|
||||
}
|
||||
|
||||
private void clearErrors() {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
findViewById(R.id.cred_error).setVisibility(View.GONE);
|
||||
findViewById(R.id.server_error).setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ public class SetupFailureActivity extends Activity {
|
||||
|
||||
public void cancelClickHandler(View target) {
|
||||
setResult(RESULT_CANCELED);
|
||||
moveTaskToBack(true);
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
108
mobile/android/base/sync/setup/auth/AccountAuthenticator.java
Normal file
108
mobile/android/base/sync/setup/auth/AccountAuthenticator.java
Normal file
@ -0,0 +1,108 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.setup.auth;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.Queue;
|
||||
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.ThreadPool;
|
||||
import org.mozilla.gecko.sync.crypto.KeyBundle;
|
||||
import org.mozilla.gecko.sync.setup.activities.AccountActivity;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class AccountAuthenticator {
|
||||
private final String LOG_TAG = "AccountAuthenticator";
|
||||
|
||||
private AccountActivity activityCallback;
|
||||
private Queue<AuthenticatorStage> stages;
|
||||
|
||||
// Values for authentication.
|
||||
public String password;
|
||||
public String username;
|
||||
|
||||
public String authServer;
|
||||
public String nodeServer;
|
||||
|
||||
public boolean isSuccess = false;
|
||||
public boolean isCanceled = false;
|
||||
|
||||
public AccountAuthenticator(AccountActivity activity) {
|
||||
activityCallback = activity;
|
||||
prepareStages();
|
||||
}
|
||||
|
||||
private void prepareStages() {
|
||||
stages = new LinkedList<AuthenticatorStage>();
|
||||
stages.add(new EnsureUserExistenceStage());
|
||||
stages.add(new FetchUserNodeStage());
|
||||
stages.add(new AuthenticateAccountStage());
|
||||
}
|
||||
|
||||
public void authenticate(String server, String account, String password) {
|
||||
// Set authentication values.
|
||||
if (!server.endsWith("/")) {
|
||||
server += "/";
|
||||
}
|
||||
nodeServer = server;
|
||||
this.password = password;
|
||||
|
||||
// Calculate and save username hash.
|
||||
try {
|
||||
username = KeyBundle.usernameFromAccount(account);
|
||||
} catch (Exception e) {
|
||||
abort(AuthenticationResult.FAILURE_OTHER, e);
|
||||
return;
|
||||
}
|
||||
Logger.debug(LOG_TAG, "username:" + username);
|
||||
|
||||
Log.d(LOG_TAG, "running first stage.");
|
||||
// Start first stage of authentication.
|
||||
runNextStage();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run next stage of authentication.
|
||||
*/
|
||||
public void runNextStage() {
|
||||
if (isCanceled) {
|
||||
return;
|
||||
}
|
||||
if (stages.size() == 0) {
|
||||
Logger.debug(LOG_TAG, "Authentication completed.");
|
||||
activityCallback.authCallback(isSuccess ? AuthenticationResult.SUCCESS : AuthenticationResult.FAILURE_PASSWORD);
|
||||
return;
|
||||
}
|
||||
AuthenticatorStage nextStage = stages.remove();
|
||||
try {
|
||||
nextStage.execute(this);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Unhandled exception in stage " + nextStage);
|
||||
abort(AuthenticationResult.FAILURE_OTHER, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort authentication.
|
||||
*
|
||||
* @param e
|
||||
* Exception causing abort.
|
||||
* @param reason
|
||||
* Reason for abort.
|
||||
*/
|
||||
public void abort(AuthenticationResult result, Exception e) {
|
||||
if (isCanceled) {
|
||||
return;
|
||||
}
|
||||
Logger.warn(LOG_TAG, "Authentication failed.", e);
|
||||
activityCallback.authCallback(result);
|
||||
}
|
||||
|
||||
/* Helper functions */
|
||||
public static void runOnThread(Runnable run) {
|
||||
ThreadPool.run(run);
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.setup.auth;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.mozilla.apache.commons.codec.binary.Base64;
|
||||
import org.mozilla.gecko.sync.GlobalConstants;
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import org.mozilla.gecko.sync.net.SyncResourceDelegate;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
import ch.boye.httpclientandroidlib.client.methods.HttpRequestBase;
|
||||
import ch.boye.httpclientandroidlib.impl.client.DefaultHttpClient;
|
||||
import ch.boye.httpclientandroidlib.message.BasicHeader;
|
||||
|
||||
public class AuthenticateAccountStage implements AuthenticatorStage {
|
||||
private final String LOG_TAG = "AuthAccountStage";
|
||||
private HttpRequestBase httpRequest = null;
|
||||
|
||||
public interface AuthenticateAccountStageDelegate {
|
||||
public void handleSuccess(boolean isSuccess);
|
||||
public void handleFailure(HttpResponse response);
|
||||
public void handleError(Exception e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final AccountAuthenticator aa) throws URISyntaxException, UnsupportedEncodingException {
|
||||
AuthenticateAccountStageDelegate callbackDelegate = new AuthenticateAccountStageDelegate() {
|
||||
|
||||
@Override
|
||||
public void handleSuccess(boolean isSuccess) {
|
||||
aa.isSuccess = isSuccess;
|
||||
aa.runNextStage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(HttpResponse response) {
|
||||
Logger.debug(LOG_TAG, "handleFailure");
|
||||
aa.abort(AuthenticationResult.FAILURE_OTHER, new Exception(response.getStatusLine().getStatusCode() + " error."));
|
||||
if (response.getEntity() == null) {
|
||||
// No cleanup necessary.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"));
|
||||
Logger.warn(LOG_TAG, "content: " + reader.readLine());
|
||||
BaseResource.consumeReader(reader);
|
||||
} catch (IllegalStateException e) {
|
||||
Logger.debug(LOG_TAG, "Error reading content.", e);
|
||||
} catch (RuntimeException e) {
|
||||
Logger.debug(LOG_TAG, "Unexpected exception.", e);
|
||||
if (httpRequest != null) {
|
||||
httpRequest.abort();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Logger.debug(LOG_TAG, "Error reading content.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
Logger.debug(LOG_TAG, "handleError", e);
|
||||
aa.abort(AuthenticationResult.FAILURE_OTHER, e);
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate BasicAuth hash of username/password.
|
||||
String authHeader = makeAuthHeader(aa.username, aa.password);
|
||||
String authRequestUrl = makeAuthRequestUrl(aa.authServer, aa.username);
|
||||
Logger.trace(LOG_TAG, "Making auth request to: " + authRequestUrl);
|
||||
authenticateAccount(callbackDelegate, authRequestUrl, authHeader);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an authentication request to the server and passes appropriate response back to callback.
|
||||
* @param callbackDelegate
|
||||
* Delegate to deal with HTTP response.
|
||||
* @param authRequestUrl
|
||||
* @param authHeader
|
||||
* @throws URISyntaxException
|
||||
*/
|
||||
// Made public for testing.
|
||||
public void authenticateAccount(final AuthenticateAccountStageDelegate callbackDelegate, final String authRequestUrl, final String authHeader) throws URISyntaxException {
|
||||
final BaseResource httpResource = new BaseResource(authRequestUrl);
|
||||
httpResource.delegate = new SyncResourceDelegate(httpResource) {
|
||||
|
||||
@Override
|
||||
public void addHeaders(HttpRequestBase request, DefaultHttpClient client) {
|
||||
// Make reference to request, to abort if necessary.
|
||||
httpRequest = request;
|
||||
client.log.enableDebug(true);
|
||||
request.setHeader(new BasicHeader("User-Agent", GlobalConstants.USER_AGENT));
|
||||
// Host header is not set for some reason, so do it explicitly.
|
||||
try {
|
||||
URI authServerUri = new URI(authRequestUrl);
|
||||
request.setHeader(new BasicHeader("Host", authServerUri.getHost()));
|
||||
} catch (URISyntaxException e) {
|
||||
Logger.error(LOG_TAG, "Malformed uri, will be caught elsewhere.", e);
|
||||
}
|
||||
request.setHeader(new BasicHeader("Authorization", authHeader));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHttpResponse(HttpResponse response) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
try {
|
||||
switch (statusCode) {
|
||||
case 200:
|
||||
callbackDelegate.handleSuccess(true);
|
||||
break;
|
||||
case 401:
|
||||
callbackDelegate.handleSuccess(false);
|
||||
break;
|
||||
default:
|
||||
callbackDelegate.handleFailure(response);
|
||||
}
|
||||
} finally {
|
||||
BaseResource.consumeEntity(response.getEntity());
|
||||
Logger.info(LOG_TAG, "Released entity.");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHttpProtocolException(ClientProtocolException e) {
|
||||
Logger.error(LOG_TAG, "Client protocol error.", e);
|
||||
callbackDelegate.handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHttpIOException(IOException e) {
|
||||
Logger.error(LOG_TAG, "I/O exception.");
|
||||
callbackDelegate.handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportException(GeneralSecurityException e) {
|
||||
Logger.error(LOG_TAG, "Transport exception.");
|
||||
callbackDelegate.handleError(e);
|
||||
}
|
||||
};
|
||||
|
||||
AccountAuthenticator.runOnThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
httpResource.get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String makeAuthHeader(String usernameHash, String password) {
|
||||
try {
|
||||
return "Basic " + Base64.encodeBase64String((usernameHash + ":" + password).getBytes("UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
Logger.debug(LOG_TAG, "Unsupported encoding: UTF-8.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String makeAuthRequestUrl(String authServer, String usernameHash) {
|
||||
return authServer + Constants.AUTH_SERVER_VERSION + usernameHash + "/" + Constants.AUTH_SERVER_SUFFIX;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.setup.auth;
|
||||
|
||||
public enum AuthenticationResult {
|
||||
SUCCESS, FAILURE_USERNAME, FAILURE_PASSWORD, FAILURE_SERVER, FAILURE_OTHER
|
||||
}
|
12
mobile/android/base/sync/setup/auth/AuthenticatorStage.java
Normal file
12
mobile/android/base/sync/setup/auth/AuthenticatorStage.java
Normal file
@ -0,0 +1,12 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.setup.auth;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URISyntaxException;
|
||||
|
||||
public interface AuthenticatorStage {
|
||||
public void execute(AccountAuthenticator aa) throws URISyntaxException, UnsupportedEncodingException;
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.setup.auth;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import org.mozilla.gecko.sync.net.SyncResourceDelegate;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
|
||||
import android.util.Log;
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
|
||||
public class EnsureUserExistenceStage implements AuthenticatorStage {
|
||||
private final String LOG_TAG = "EnsureUserExistence";
|
||||
|
||||
public interface EnsureUserExistenceStageDelegate {
|
||||
public void handleSuccess();
|
||||
public void handleFailure(AuthenticationResult result);
|
||||
public void handleError(Exception e);
|
||||
}
|
||||
@Override
|
||||
public void execute(final AccountAuthenticator aa) throws URISyntaxException,
|
||||
UnsupportedEncodingException {
|
||||
final EnsureUserExistenceStageDelegate callbackDelegate = new EnsureUserExistenceStageDelegate() {
|
||||
|
||||
@Override
|
||||
public void handleSuccess() {
|
||||
// User exists; now determine auth node.
|
||||
Log.d(LOG_TAG, "handleSuccess()");
|
||||
aa.runNextStage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(AuthenticationResult result) {
|
||||
aa.abort(result, new Exception("Failure in EnsureUser"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
Logger.info(LOG_TAG, "Error checking for user existence.");
|
||||
aa.abort(AuthenticationResult.FAILURE_SERVER, e);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
String userRequestUrl = aa.nodeServer + Constants.AUTH_NODE_PATHNAME + Constants.AUTH_NODE_VERSION + aa.username;
|
||||
final BaseResource httpResource = new BaseResource(userRequestUrl);
|
||||
httpResource.delegate = new SyncResourceDelegate(httpResource) {
|
||||
|
||||
@Override
|
||||
public void handleHttpResponse(HttpResponse response) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
switch(statusCode) {
|
||||
case 200:
|
||||
try {
|
||||
InputStream content = response.getEntity().getContent();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(content, "UTF-8"), 1024);
|
||||
String inUse = reader.readLine();
|
||||
BaseResource.consumeReader(reader);
|
||||
reader.close();
|
||||
// Removed Logger.debug inUse, because stalling.
|
||||
if (inUse.equals("1")) { // Username exists.
|
||||
callbackDelegate.handleSuccess();
|
||||
} else { // User does not exist.
|
||||
Logger.info(LOG_TAG, "No such user.");
|
||||
callbackDelegate.handleFailure(AuthenticationResult.FAILURE_USERNAME);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Failure in content parsing.", e);
|
||||
callbackDelegate.handleFailure(AuthenticationResult.FAILURE_OTHER);
|
||||
}
|
||||
break;
|
||||
default: // No other response is acceptable.
|
||||
callbackDelegate.handleFailure(AuthenticationResult.FAILURE_OTHER);
|
||||
}
|
||||
Logger.debug(LOG_TAG, "Consuming entity.");
|
||||
BaseResource.consumeEntity(response.getEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHttpProtocolException(ClientProtocolException e) {
|
||||
callbackDelegate.handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHttpIOException(IOException e) {
|
||||
callbackDelegate.handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportException(GeneralSecurityException e) {
|
||||
callbackDelegate.handleError(e);
|
||||
}
|
||||
|
||||
};
|
||||
// Make request.
|
||||
AccountAuthenticator.runOnThread(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
httpResource.get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
126
mobile/android/base/sync/setup/auth/FetchUserNodeStage.java
Normal file
126
mobile/android/base/sync/setup/auth/FetchUserNodeStage.java
Normal file
@ -0,0 +1,126 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.mozilla.gecko.sync.setup.auth;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.net.BaseResource;
|
||||
import org.mozilla.gecko.sync.net.SyncResourceDelegate;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
|
||||
import ch.boye.httpclientandroidlib.HttpResponse;
|
||||
import ch.boye.httpclientandroidlib.client.ClientProtocolException;
|
||||
|
||||
public class FetchUserNodeStage implements AuthenticatorStage {
|
||||
private final String LOG_TAG = "FetchUserNodeStage";
|
||||
|
||||
public interface FetchNodeStageDelegate {
|
||||
public void handleSuccess(String url);
|
||||
public void handleFailure(HttpResponse response);
|
||||
public void handleError(Exception e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(final AccountAuthenticator aa) throws URISyntaxException {
|
||||
|
||||
FetchNodeStageDelegate callbackDelegate = new FetchNodeStageDelegate() {
|
||||
|
||||
@Override
|
||||
public void handleSuccess(String server) {
|
||||
if (server == null) { // No separate auth node; use server url.
|
||||
Logger.debug(LOG_TAG, "Using server as auth node.");
|
||||
aa.authServer = aa.nodeServer;
|
||||
aa.runNextStage();
|
||||
return;
|
||||
}
|
||||
if (!server.endsWith("/")) {
|
||||
server += "/";
|
||||
}
|
||||
aa.authServer = server;
|
||||
aa.runNextStage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleFailure(HttpResponse response) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
Logger.debug(LOG_TAG, "Failed to fetch user node, with status " + statusCode);
|
||||
aa.abort(AuthenticationResult.FAILURE_OTHER, new Exception("HTTP " + statusCode + " error."));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleError(Exception e) {
|
||||
Logger.debug(LOG_TAG, "Error in fetching node.");
|
||||
aa.abort(AuthenticationResult.FAILURE_OTHER, e);
|
||||
}
|
||||
};
|
||||
String nodeRequestUrl = aa.nodeServer + Constants.AUTH_NODE_PATHNAME + Constants.AUTH_NODE_VERSION + aa.username + "/" + Constants.AUTH_NODE_SUFFIX;
|
||||
Logger.debug(LOG_TAG, "nodeUrl: " + nodeRequestUrl);
|
||||
final BaseResource httpResource = makeFetchNodeRequest(callbackDelegate, nodeRequestUrl);
|
||||
// Make request on separate thread.
|
||||
AccountAuthenticator.runOnThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
httpResource.get();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private BaseResource makeFetchNodeRequest(final FetchNodeStageDelegate callbackDelegate, String fetchNodeUrl) throws URISyntaxException {
|
||||
// Fetch node containing user.
|
||||
final BaseResource httpResource = new BaseResource(fetchNodeUrl);
|
||||
httpResource.delegate = new SyncResourceDelegate(httpResource) {
|
||||
|
||||
@Override
|
||||
public void handleHttpResponse(HttpResponse response) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
switch(statusCode) {
|
||||
case 200:
|
||||
try {
|
||||
InputStream content = response.getEntity().getContent();
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(content, "UTF-8"), 1024);
|
||||
String server = reader.readLine();
|
||||
callbackDelegate.handleSuccess(server);
|
||||
BaseResource.consumeReader(reader);
|
||||
reader.close();
|
||||
} catch (IllegalStateException e) {
|
||||
callbackDelegate.handleError(e);
|
||||
} catch (IOException e) {
|
||||
callbackDelegate.handleError(e);
|
||||
}
|
||||
break;
|
||||
case 404: // Does not support auth nodes, use server instead.
|
||||
callbackDelegate.handleSuccess(null);
|
||||
break;
|
||||
default:
|
||||
// No other acceptable states.
|
||||
callbackDelegate.handleFailure(response);
|
||||
}
|
||||
BaseResource.consumeEntity(response.getEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHttpProtocolException(ClientProtocolException e) {
|
||||
callbackDelegate.handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHttpIOException(IOException e) {
|
||||
callbackDelegate.handleError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleTransportException(GeneralSecurityException e) {
|
||||
callbackDelegate.handleError(e);
|
||||
}
|
||||
};
|
||||
return httpResource;
|
||||
}
|
||||
}
|
@ -168,6 +168,12 @@ sync/setup/activities/ActivityUtils.java
|
||||
sync/setup/activities/SetupFailureActivity.java
|
||||
sync/setup/activities/SetupSuccessActivity.java
|
||||
sync/setup/activities/SetupSyncActivity.java
|
||||
sync/setup/auth/AccountAuthenticator.java
|
||||
sync/setup/auth/AuthenticateAccountStage.java
|
||||
sync/setup/auth/AuthenticationResult.java
|
||||
sync/setup/auth/AuthenticatorStage.java
|
||||
sync/setup/auth/EnsureUserExistenceStage.java
|
||||
sync/setup/auth/FetchUserNodeStage.java
|
||||
sync/setup/Constants.java
|
||||
sync/setup/InvalidSyncKeyException.java
|
||||
sync/setup/SyncAccounts.java
|
||||
|
@ -73,4 +73,7 @@
|
||||
<string name="sync_new_tab">&new_tab;</string>
|
||||
|
||||
<!-- Incorrect settings and changing credentials. -->
|
||||
<string name="sync.new.recoverykey.status.incorrect">&sync.new.recoverykey.status.incorrect;</string>
|
||||
<string name="sync_invalidcreds_label">&sync.invalidcreds.label;</string>
|
||||
<string name="sync_invalidserver_label">&sync.invalidserver.label;</string>
|
||||
<string name="sync_verifying_label">&sync.verifying.label;</string>
|
||||
<string name="sync_new_recoverykey_status_incorrect">&sync.new.recoverykey.status.incorrect;</string>
|
||||
|
Loading…
Reference in New Issue
Block a user