Make the authentication service remember selected usernames.
R=ppi@chromium.org, tonyg@chromium.org
Review URL: https://codereview.chromium.org/1163483004
diff --git a/mojo/services/authentication/public/interfaces/authentication.mojom b/mojo/services/authentication/public/interfaces/authentication.mojom
index a69e7de..0bd56d6 100644
--- a/mojo/services/authentication/public/interfaces/authentication.mojom
+++ b/mojo/services/authentication/public/interfaces/authentication.mojom
@@ -11,8 +11,10 @@
interface AuthenticationService {
// Requests a Google account to use. In case of success, error will be null.
// In case of error, username will be null and error will contain a
- // description of the error.
- SelectAccount() => (string? username, string? error);
+ // description of the error. If |return_last_selected| is true and the client
+ // application already selected an account, the same account will be returned
+ // without user intervention.
+ SelectAccount(bool return_last_selected) => (string? username, string? error);
// Requests an oauth2 token for the given Google account with the given
// scopes. In case of error, username will be null and error will contain a
diff --git a/services/authenticating_url_loader/authenticating_url_loader_factory_impl.cc b/services/authenticating_url_loader/authenticating_url_loader_factory_impl.cc
index 4eba375..795ce0c 100644
--- a/services/authenticating_url_loader/authenticating_url_loader_factory_impl.cc
+++ b/services/authenticating_url_loader/authenticating_url_loader_factory_impl.cc
@@ -53,8 +53,8 @@
return;
}
authentication_service_->SelectAccount(
- base::Bind(&AuthenticatingURLLoaderFactoryImpl::OnAccountSelected,
- base::Unretained(this), origin));
+ true, base::Bind(&AuthenticatingURLLoaderFactoryImpl::OnAccountSelected,
+ base::Unretained(this), origin));
}
pendings_retrieve_token_[origin].push_back(callback);
}
diff --git a/services/authentication/BUILD.gn b/services/authentication/BUILD.gn
index 72b61bc..88c72a6 100644
--- a/services/authentication/BUILD.gn
+++ b/services/authentication/BUILD.gn
@@ -2,6 +2,8 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
+import("//mojo/public/tools/bindings/mojom.gni")
+
if (is_android) {
import("//services/android/rules.gni")
@@ -14,6 +16,7 @@
mojo_main = "org.chromium.mojo.authentication.AuthenticationApp"
deps = [
+ ":interfaces_java",
"//mojo/public/interfaces/application:application_java",
"//mojo/public/java:application",
"//mojo/services/authentication/public/interfaces:interfaces_java",
@@ -21,4 +24,10 @@
"//third_party/android_tools:google_play_services_default_java",
]
}
+
+ mojom("interfaces") {
+ sources = [
+ "authentication_impl_db.mojom",
+ ]
+ }
}
diff --git a/services/authentication/authentication_impl_db.mojom b/services/authentication/authentication_impl_db.mojom
new file mode 100644
index 0000000..72b217d
--- /dev/null
+++ b/services/authentication/authentication_impl_db.mojom
@@ -0,0 +1,16 @@
+// 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.
+
+[JavaPackage="org.chromium.mojo.authentication"]
+module authentication;
+
+// Database for the authentication implementation.
+// This struct is used to persist state for the authentication service and is
+// not passed between services.
+struct Db {
+ // Version of the database.
+ uint32 version;
+ // Map from application to last selected account.
+ map<string, string> last_selected_accounts;
+};
diff --git a/services/authentication/src/org/chromium/mojo/authentication/AuthenticationServiceImpl.java b/services/authentication/src/org/chromium/mojo/authentication/AuthenticationServiceImpl.java
index 928a8cd..ed2eb74 100644
--- a/services/authentication/src/org/chromium/mojo/authentication/AuthenticationServiceImpl.java
+++ b/services/authentication/src/org/chromium/mojo/authentication/AuthenticationServiceImpl.java
@@ -16,21 +16,41 @@
import com.google.android.gms.common.AccountPicker;
import org.chromium.mojo.application.ShellHelper;
+import org.chromium.mojo.bindings.DeserializationException;
+import org.chromium.mojo.bindings.Message;
import org.chromium.mojo.bindings.SideEffectFreeCloseable;
import org.chromium.mojo.intent.IntentReceiver;
import org.chromium.mojo.intent.IntentReceiverManager;
import org.chromium.mojo.intent.IntentReceiverManager.RegisterActivityResultReceiverResponse;
import org.chromium.mojo.system.Core;
+import org.chromium.mojo.system.Handle;
import org.chromium.mojo.system.MojoException;
import org.chromium.mojom.mojo.Shell;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.util.ArrayList;
+import java.util.HashMap;
/**
* Implementation of AuthenticationService from services/authentication/authentication.mojom
*/
public class AuthenticationServiceImpl
extends SideEffectFreeCloseable implements AuthenticationService {
+ // The current version of the database. This must be incremented each time Db definition in
+ // authentication_impl_db.mojom is changed in a non backward-compatible way.
+ private static final int VERSION = 0;
+
+ // Type of google accounts.
+ private static final String GOOGLE_ACCOUNT_TYPE = "com.google";
+
+ // Type of the accounts that this service allows the user to pick.
+ private static final String[] ACCOUNT_TYPES = new String[] {GOOGLE_ACCOUNT_TYPE};
+
/**
* An callback that takes a serialized intent, add the intent the shell needs to send and start
* the container intent.
@@ -38,8 +58,7 @@
private final class RegisterActivityResultReceiverCallback
implements RegisterActivityResultReceiverResponse {
/**
- * The intent that the requesting application needs to be run by shell on its
-behalf.
+ * The intent that the requesting application needs to be run by shell on its behalf.
*/
private final Intent mIntent;
@@ -61,6 +80,8 @@
private final Activity mContext;
private final String mConsumerURL;
private final IntentReceiverManager mIntentReceiverManager;
+ private Db mDb = null;
+ private File mDbFile = null;
public AuthenticationServiceImpl(Context context, Core core, String consumerURL, Shell shell) {
mContext = (Activity) context;
@@ -136,11 +157,24 @@
}
/**
- * @see AuthenticationService#selectAccount(AuthenticationService.SelectAccountResponse)
+ * @see AuthenticationService#selectAccount(boolean,
+ * AuthenticationService.SelectAccountResponse)
*/
@Override
- public void selectAccount(final SelectAccountResponse callback) {
- String[] accountTypes = new String[] {"com.google"};
+ public void selectAccount(boolean returnLastSelected, final SelectAccountResponse callback) {
+ if (returnLastSelected) {
+ Db db = getDb();
+ String username = db.lastSelectedAccounts.get(mConsumerURL);
+ if (username != null) {
+ try {
+ GoogleAuthUtil.getAccountId(mContext, username);
+ callback.call(username, null);
+ return;
+ } catch (final GoogleAuthException | IOException e) {
+ }
+ }
+ }
+
String message = null;
if (mConsumerURL.equals("")) {
message = "Select an account to use with mojo shell";
@@ -148,7 +182,7 @@
message = "Select an account to use with application: " + mConsumerURL;
}
Intent accountPickerIntent = AccountPicker.newChooseAccountIntent(
- null, null, accountTypes, false, message, null, null, null);
+ null, null, ACCOUNT_TYPES, false, message, null, null, null);
mIntentReceiverManager.registerActivityResultReceiver(new IntentReceiver() {
SelectAccountResponse mPendingCallback = callback;
@@ -175,6 +209,7 @@
}
mPendingCallback.call(username, error);
mPendingCallback = null;
+ updateDb(username);
}
}, new RegisterActivityResultReceiverCallback(accountPickerIntent));
}
@@ -197,4 +232,64 @@
p.setDataPosition(0);
return Intent.CREATOR.createFromParcel(p);
}
+
+ private File getDbFile() {
+ if (mDbFile != null) {
+ return mDbFile;
+ }
+ File home = new File(System.getenv("HOME"));
+ File configDir = new File(home, ".mojo_authentication");
+ configDir.mkdirs();
+ mDbFile = new File(configDir, "db");
+ return mDbFile;
+ }
+
+ private Db getDb() {
+ if (mDb != null) {
+ return mDb;
+ }
+ File dbFile = getDbFile();
+ if (dbFile.exists()) {
+ try {
+ int size = (int) dbFile.length();
+ try (FileInputStream stream = new FileInputStream(dbFile);
+ FileChannel channel = stream.getChannel()) {
+ // Use mojo serialization to read the database.
+ Db db = Db.deserialize(new Message(
+ channel.map(MapMode.READ_ONLY, 0, size), new ArrayList<Handle>()));
+ if (db.version == VERSION) {
+ mDb = db;
+ return mDb;
+ }
+ } catch (DeserializationException e) {
+ }
+ dbFile.delete();
+ } catch (IOException e) {
+ }
+ }
+ mDb = new Db();
+ mDb.version = VERSION;
+ mDb.lastSelectedAccounts = new HashMap<>();
+ return mDb;
+ }
+
+ private void updateDb(String username) {
+ try {
+ Db db = getDb();
+ if (username == null) {
+ db.lastSelectedAccounts.remove(mConsumerURL);
+ } else {
+ db.lastSelectedAccounts.put(mConsumerURL, username);
+ }
+ // Use mojo serialization to persist the database.
+ Message m = db.serialize(null);
+ File dbFile = getDbFile();
+ dbFile.delete();
+ try (FileOutputStream stream = new FileOutputStream(dbFile);
+ FileChannel channel = stream.getChannel()) {
+ channel.write(m.getData());
+ }
+ } catch (IOException e) {
+ }
+ }
}