summaryrefslogtreecommitdiffstats
path: root/remoting
diff options
context:
space:
mode:
authorlambroslambrou <lambroslambrou@chromium.org>2015-03-18 12:17:34 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-18 19:18:24 +0000
commitd09f63e285e28e90743c16332b06bc89d17de1a5 (patch)
tree583e6b101b48f2c1bb413032874a83953c6f554f /remoting
parente20ec7cdb825c84c54392f7fa4dca9d2a1c05158 (diff)
downloadchromium_src-d09f63e285e28e90743c16332b06bc89d17de1a5.zip
chromium_src-d09f63e285e28e90743c16332b06bc89d17de1a5.tar.gz
chromium_src-d09f63e285e28e90743c16332b06bc89d17de1a5.tar.bz2
Pull authentication UX code out of JniInterface
UX code does not belong in the JniInterface layer. This is a first step towards refactoring the JNI layer away from static methods, into a separate object whose lifetime is controlled by Java, which can be mocked for testing. Review URL: https://codereview.chromium.org/981783003 Cr-Commit-Position: refs/heads/master@{#321187}
Diffstat (limited to 'remoting')
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/Chromoting.java44
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/SessionAuthenticator.java168
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/SessionConnector.java9
-rw-r--r--remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java119
4 files changed, 207 insertions, 133 deletions
diff --git a/remoting/android/java/src/org/chromium/chromoting/Chromoting.java b/remoting/android/java/src/org/chromium/chromoting/Chromoting.java
index 8fdd14d..15a8b15 100644
--- a/remoting/android/java/src/org/chromium/chromoting/Chromoting.java
+++ b/remoting/android/java/src/org/chromium/chromoting/Chromoting.java
@@ -90,8 +90,11 @@ public class Chromoting extends ActionBarActivity implements JniInterface.Connec
/** Dialog for reporting connection progress. */
private ProgressDialog mProgressIndicator;
- /** Object for fetching OAuth2 access tokens from third party authorization servers. */
- private ThirdPartyTokenFetcher mTokenFetcher;
+ /**
+ * Helper used by SessionConnection for session authentication. Receives onNewIntent()
+ * notifications to handle third-party authentication.
+ */
+ private SessionAuthenticator mAuthenticator;
/**
* This is set when receiving an authentication error from the HostListLoader. If that occurs,
@@ -214,10 +217,9 @@ public class Chromoting extends ActionBarActivity implements JniInterface.Connec
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
- if (mTokenFetcher != null) {
- if (mTokenFetcher.handleTokenFetched(intent)) {
- mTokenFetcher = null;
- }
+
+ if (mAuthenticator != null) {
+ mAuthenticator.onNewIntent(intent);
}
}
@@ -323,13 +325,11 @@ public class Chromoting extends ActionBarActivity implements JniInterface.Connec
@Override
public void onCancel(DialogInterface dialog) {
JniInterface.disconnectFromHost();
- mTokenFetcher = null;
}
});
SessionConnector connector = new SessionConnector(this, this, mHostListLoader);
- assert mTokenFetcher == null;
- mTokenFetcher = createTokenFetcher(host);
- connector.connectToHost(mAccount.name, mToken, host);
+ mAuthenticator = new SessionAuthenticator(this, host);
+ connector.connectToHost(mAccount.name, mToken, host, mAuthenticator);
}
private void refreshHostList() {
@@ -510,28 +510,4 @@ public class Chromoting extends ActionBarActivity implements JniInterface.Connec
mProgressIndicator = null;
}
}
-
- private ThirdPartyTokenFetcher createTokenFetcher(HostInfo host) {
- ThirdPartyTokenFetcher.Callback callback = new ThirdPartyTokenFetcher.Callback() {
- @Override
- public void onTokenFetched(String code, String accessToken) {
- // The native client sends the OAuth authorization code to the host as the token so
- // that the host can obtain the shared secret from the third party authorization
- // server.
- String token = code;
-
- // The native client uses the OAuth access token as the shared secret to
- // authenticate itself with the host using spake.
- String sharedSecret = accessToken;
-
- JniInterface.onThirdPartyTokenFetched(token, sharedSecret);
- }
- };
- return new ThirdPartyTokenFetcher(this, host.getTokenUrlPatterns(), callback);
- }
-
- public void fetchThirdPartyToken(String tokenUrl, String clientId, String scope) {
- assert mTokenFetcher != null;
- mTokenFetcher.fetchToken(tokenUrl, clientId, scope);
- }
}
diff --git a/remoting/android/java/src/org/chromium/chromoting/SessionAuthenticator.java b/remoting/android/java/src/org/chromium/chromoting/SessionAuthenticator.java
new file mode 100644
index 0000000..a18c2f4
--- /dev/null
+++ b/remoting/android/java/src/org/chromium/chromoting/SessionAuthenticator.java
@@ -0,0 +1,168 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.chromoting;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Build;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.CheckBox;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.chromium.chromoting.jni.JniInterface;
+
+/**
+ * This class performs the user-interaction needed to authenticate the session connection. This
+ * includes showing the PIN prompt and requesting tokens for third-party authentication.
+ */
+public class SessionAuthenticator {
+ /**
+ * Application context used for getting user preferences, displaying UI, and fetching
+ * third-party tokens.
+ */
+ private Chromoting mApplicationContext;
+
+ /** Provides the tokenUrlPatterns for this host during fetchThirdPartyTokens(). */
+ private HostInfo mHost;
+
+ /** Object for fetching OAuth2 access tokens from third party authorization servers. */
+ private ThirdPartyTokenFetcher mTokenFetcher;
+
+ public SessionAuthenticator(Chromoting context, HostInfo host) {
+ mApplicationContext = context;
+ mHost = host;
+ }
+
+ public void displayAuthenticationPrompt(boolean pairingSupported) {
+ AlertDialog.Builder pinPrompt = new AlertDialog.Builder(mApplicationContext);
+ pinPrompt.setTitle(mApplicationContext.getString(R.string.title_authenticate));
+ pinPrompt.setMessage(mApplicationContext.getString(R.string.pin_message_android));
+ pinPrompt.setIcon(android.R.drawable.ic_lock_lock);
+
+ final View pinEntry =
+ mApplicationContext.getLayoutInflater().inflate(R.layout.pin_dialog, null);
+ pinPrompt.setView(pinEntry);
+
+ final TextView pinTextView = (TextView) pinEntry.findViewById(R.id.pin_dialog_text);
+ final CheckBox pinCheckBox = (CheckBox) pinEntry.findViewById(R.id.pin_dialog_check);
+
+ if (!pairingSupported) {
+ pinCheckBox.setChecked(false);
+ pinCheckBox.setVisibility(View.GONE);
+ }
+
+ pinPrompt.setPositiveButton(
+ R.string.connect_button, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (JniInterface.isConnected()) {
+ JniInterface.handleAuthenticationResponse(
+ String.valueOf(pinTextView.getText()),
+ pinCheckBox.isChecked(), Build.MODEL);
+ } else {
+ String message =
+ mApplicationContext.getString(R.string.error_network_error);
+ Toast.makeText(mApplicationContext, message, Toast.LENGTH_LONG).show();
+ }
+ }
+ });
+
+ pinPrompt.setNegativeButton(
+ R.string.cancel, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ JniInterface.disconnectFromHost();
+ }
+ });
+
+ final AlertDialog pinDialog = pinPrompt.create();
+
+ pinTextView.setOnEditorActionListener(
+ new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ // The user pressed enter on the keypad (equivalent to the connect button).
+ pinDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick();
+ pinDialog.dismiss();
+ return true;
+ }
+ });
+
+ pinDialog.setOnCancelListener(
+ new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ // The user backed out of the dialog (equivalent to the cancel button).
+ pinDialog.getButton(AlertDialog.BUTTON_NEGATIVE).performClick();
+ }
+ });
+
+ pinDialog.show();
+ }
+
+ /** Saves newly-received pairing credentials to permanent storage. */
+ public void commitPairingCredentials(String host, String id, String secret) {
+ // Empty |id| indicates that pairing needs to be removed.
+ if (id.isEmpty()) {
+ mApplicationContext.getPreferences(Activity.MODE_PRIVATE).edit()
+ .remove(host + "_id")
+ .remove(host + "_secret")
+ .apply();
+ } else {
+ mApplicationContext.getPreferences(Activity.MODE_PRIVATE).edit()
+ .putString(host + "_id", id)
+ .putString(host + "_secret", secret)
+ .apply();
+ }
+ }
+
+ public void fetchThirdPartyToken(String tokenUrl, String clientId, String scope) {
+ assert mTokenFetcher == null;
+
+ ThirdPartyTokenFetcher.Callback callback = new ThirdPartyTokenFetcher.Callback() {
+ @Override
+ public void onTokenFetched(String code, String accessToken) {
+ // The native client sends the OAuth authorization code to the host as the token so
+ // that the host can obtain the shared secret from the third party authorization
+ // server.
+ String token = code;
+
+ // The native client uses the OAuth access token as the shared secret to
+ // authenticate itself with the host using spake.
+ String sharedSecret = accessToken;
+
+ JniInterface.onThirdPartyTokenFetched(token, sharedSecret);
+ }
+ };
+ mTokenFetcher = new ThirdPartyTokenFetcher(mApplicationContext, mHost.getTokenUrlPatterns(),
+ callback);
+ mTokenFetcher.fetchToken(tokenUrl, clientId, scope);
+ }
+
+ public void onNewIntent(Intent intent) {
+ if (mTokenFetcher != null) {
+ if (mTokenFetcher.handleTokenFetched(intent)) {
+ mTokenFetcher = null;
+ }
+ }
+ }
+
+ /** Returns the pairing ID for the given host, or an empty string if not set. */
+ public String getPairingId(String hostId) {
+ SharedPreferences prefs = mApplicationContext.getPreferences(Activity.MODE_PRIVATE);
+ return prefs.getString(hostId + "_id", "");
+ }
+
+ /** Returns the pairing secret for the given host, or an empty string if not set. */
+ public String getPairingSecret(String hostId) {
+ SharedPreferences prefs = mApplicationContext.getPreferences(Activity.MODE_PRIVATE);
+ return prefs.getString(hostId + "_secret", "");
+ }
+}
diff --git a/remoting/android/java/src/org/chromium/chromoting/SessionConnector.java b/remoting/android/java/src/org/chromium/chromoting/SessionConnector.java
index 20fad25..cb57a21 100644
--- a/remoting/android/java/src/org/chromium/chromoting/SessionConnector.java
+++ b/remoting/android/java/src/org/chromium/chromoting/SessionConnector.java
@@ -15,6 +15,7 @@ public class SessionConnector implements JniInterface.ConnectionListener,
private JniInterface.ConnectionListener mConnectionCallback;
private HostListLoader.Callback mHostListCallback;
private HostListLoader mHostListLoader;
+ private SessionAuthenticator mAuthenticator;
private String mAccountName;
private String mAuthToken;
@@ -43,11 +44,13 @@ public class SessionConnector implements JniInterface.ConnectionListener,
}
/** Initiates a connection to the host. */
- public void connectToHost(String accountName, String authToken, HostInfo host) {
+ public void connectToHost(String accountName, String authToken, HostInfo host,
+ SessionAuthenticator authenticator) {
mAccountName = accountName;
mAuthToken = authToken;
mHostId = host.id;
mHostJabberId = host.jabberId;
+ mAuthenticator = authenticator;
if (hostIncomplete(host)) {
// These keys might not be present in a newly-registered host, so treat this as a
@@ -57,7 +60,7 @@ public class SessionConnector implements JniInterface.ConnectionListener,
}
JniInterface.connectToHost(accountName, authToken, host.jabberId, host.id, host.publicKey,
- this);
+ this, mAuthenticator);
}
private static boolean hostIncomplete(HostInfo host) {
@@ -108,7 +111,7 @@ public class SessionConnector implements JniInterface.ConnectionListener,
// Reconnect to the host, but use the original callback directly, instead of this
// wrapper object, so the host list is not loaded again.
JniInterface.connectToHost(mAccountName, mAuthToken, foundHost.jabberId,
- foundHost.id, foundHost.publicKey, mConnectionCallback);
+ foundHost.id, foundHost.publicKey, mConnectionCallback, mAuthenticator);
}
}
diff --git a/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java b/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java
index a4f2fc5..7057179 100644
--- a/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java
+++ b/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java
@@ -4,27 +4,17 @@
package org.chromium.chromoting.jni;
-import android.app.Activity;
-import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
-import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Point;
-import android.os.Build;
import android.os.Looper;
import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.TextView;
-import android.widget.Toast;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.chromoting.CapabilityManager;
-import org.chromium.chromoting.Chromoting;
import org.chromium.chromoting.R;
+import org.chromium.chromoting.SessionAuthenticator;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@@ -41,8 +31,8 @@ public class JniInterface {
/** Whether the library has been loaded. Accessed on the UI thread. */
private static boolean sLoaded = false;
- /** The application context. Accessed on the UI thread. */
- private static Activity sContext = null;
+ /** Used for authentication-related UX during connection. Accessed on the UI thread. */
+ private static SessionAuthenticator sAuthenticator;
/** Interface used for connection state notifications. */
public interface ConnectionListener {
@@ -152,9 +142,7 @@ public class JniInterface {
* context, but not reload the library. This is useful e.g. when the activity is closed and the
* user later wants to return to the application. Called on the UI thread.
*/
- public static void loadLibrary(Activity context) {
- sContext = context;
-
+ public static void loadLibrary(Context context) {
if (sLoaded) return;
System.loadLibrary("remoting_client_jni");
@@ -173,15 +161,21 @@ public class JniInterface {
public static native String nativeGetClientId();
public static native String nativeGetClientSecret();
+ /** Returns whether the client is connected. */
+ public static boolean isConnected() {
+ return sConnected;
+ }
+
/** Attempts to form a connection to the user-selected host. Called on the UI thread. */
public static void connectToHost(String username, String authToken,
- String hostJid, String hostId, String hostPubkey, ConnectionListener listener) {
+ String hostJid, String hostId, String hostPubkey, ConnectionListener listener,
+ SessionAuthenticator authenticator) {
disconnectFromHost();
sConnectionListener = listener;
- SharedPreferences prefs = sContext.getPreferences(Activity.MODE_PRIVATE);
+ sAuthenticator = authenticator;
nativeConnect(username, authToken, hostJid, hostId, hostPubkey,
- prefs.getString(hostId + "_id", ""), prefs.getString(hostId + "_secret", ""),
+ sAuthenticator.getPairingId(hostId), sAuthenticator.getPairingSecret(hostId),
sCapabilityManager.getLocalCapabilities());
sConnected = true;
}
@@ -238,69 +232,7 @@ public class JniInterface {
/** Prompts the user to enter a PIN. Called on the UI thread. */
@CalledByNative
private static void displayAuthenticationPrompt(boolean pairingSupported) {
- AlertDialog.Builder pinPrompt = new AlertDialog.Builder(sContext);
- pinPrompt.setTitle(sContext.getString(R.string.title_authenticate));
- pinPrompt.setMessage(sContext.getString(R.string.pin_message_android));
- pinPrompt.setIcon(android.R.drawable.ic_lock_lock);
-
- final View pinEntry = sContext.getLayoutInflater().inflate(R.layout.pin_dialog, null);
- pinPrompt.setView(pinEntry);
-
- final TextView pinTextView = (TextView) pinEntry.findViewById(R.id.pin_dialog_text);
- final CheckBox pinCheckBox = (CheckBox) pinEntry.findViewById(R.id.pin_dialog_check);
-
- if (!pairingSupported) {
- pinCheckBox.setChecked(false);
- pinCheckBox.setVisibility(View.GONE);
- }
-
- pinPrompt.setPositiveButton(
- R.string.connect_button, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Log.i("jniiface", "User provided a PIN code");
- if (sConnected) {
- nativeAuthenticationResponse(String.valueOf(pinTextView.getText()),
- pinCheckBox.isChecked(), Build.MODEL);
- } else {
- String message = sContext.getString(R.string.error_network_error);
- Toast.makeText(sContext, message, Toast.LENGTH_LONG).show();
- }
- }
- });
-
- pinPrompt.setNegativeButton(
- R.string.cancel, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- Log.i("jniiface", "User canceled pin entry prompt");
- disconnectFromHost();
- }
- });
-
- final AlertDialog pinDialog = pinPrompt.create();
-
- pinTextView.setOnEditorActionListener(
- new TextView.OnEditorActionListener() {
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- // The user pressed enter on the keypad (equivalent to the connect button).
- pinDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick();
- pinDialog.dismiss();
- return true;
- }
- });
-
- pinDialog.setOnCancelListener(
- new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- // The user backed out of the dialog (equivalent to the cancel button).
- pinDialog.getButton(AlertDialog.BUTTON_NEGATIVE).performClick();
- }
- });
-
- pinDialog.show();
+ sAuthenticator.displayAuthenticationPrompt(pairingSupported);
}
/**
@@ -310,24 +242,20 @@ public class JniInterface {
* @param deviceName The device name to appear in the pairing registry. Only used if createPair
* is true.
*/
+ public static void handleAuthenticationResponse(String pin, boolean createPair,
+ String deviceName) {
+ assert sConnected;
+ nativeAuthenticationResponse(pin, createPair, deviceName);
+ }
+
+ /** Native implementation of handleAuthenticationResponse(). */
private static native void nativeAuthenticationResponse(String pin, boolean createPair,
String deviceName);
/** Saves newly-received pairing credentials to permanent storage. Called on the UI thread. */
@CalledByNative
private static void commitPairingCredentials(String host, String id, String secret) {
- // Empty |id| indicates that pairing needs to be removed.
- if (id.isEmpty()) {
- sContext.getPreferences(Activity.MODE_PRIVATE).edit()
- .remove(host + "_id")
- .remove(host + "_secret")
- .apply();
- } else {
- sContext.getPreferences(Activity.MODE_PRIVATE).edit()
- .putString(host + "_id", id)
- .putString(host + "_secret", secret)
- .apply();
- }
+ sAuthenticator.commitPairingCredentials(host, id, secret);
}
/**
@@ -498,8 +426,7 @@ public class JniInterface {
/** Pops up a third party login page to fetch the token required for authentication. */
@CalledByNative
public static void fetchThirdPartyToken(String tokenUrl, String clientId, String scope) {
- Chromoting app = (Chromoting) sContext;
- app.fetchThirdPartyToken(tokenUrl, clientId, scope);
+ sAuthenticator.fetchThirdPartyToken(tokenUrl, clientId, scope);
}
/**