Commit 5fdae3e4 authored by mkwst's avatar mkwst Committed by Commit bot

Credential Management: Support zeroclick credential in 'request()'.

If only a single credential is available, and zero-click behavior is
enabled, then we don't need to ask the user for permission to present
the credential in response to a website's call to `request()`.

This patch implements that logic, walking through the local credentials
for an origin in the password store, and returning zeroclick credentials
without prompting.

If no such credential is available, and the caller set 'zero_click_only',
then we also won't prompt the user, and will simply return an empty
credential.

BUG=400674

Review URL: https://codereview.chromium.org/879913004

Cr-Commit-Position: refs/heads/master@{#313885}
parent 19221b45
...@@ -36,6 +36,7 @@ class TestPasswordManagerClient ...@@ -36,6 +36,7 @@ class TestPasswordManagerClient
: did_prompt_user_to_save_(false), : did_prompt_user_to_save_(false),
did_prompt_user_to_choose_(false), did_prompt_user_to_choose_(false),
is_off_the_record_(false), is_off_the_record_(false),
is_zero_click_enabled_(true),
store_(store) {} store_(store) {}
~TestPasswordManagerClient() override {} ~TestPasswordManagerClient() override {}
...@@ -75,6 +76,7 @@ class TestPasswordManagerClient ...@@ -75,6 +76,7 @@ class TestPasswordManagerClient
} }
bool IsOffTheRecord() override { return is_off_the_record_; } bool IsOffTheRecord() override { return is_off_the_record_; }
bool IsZeroClickEnabled() override { return is_zero_click_enabled_; }
bool did_prompt_user_to_save() const { return did_prompt_user_to_save_; } bool did_prompt_user_to_save() const { return did_prompt_user_to_save_; }
bool did_prompt_user_to_choose() const { return did_prompt_user_to_choose_; } bool did_prompt_user_to_choose() const { return did_prompt_user_to_choose_; }
...@@ -87,10 +89,15 @@ class TestPasswordManagerClient ...@@ -87,10 +89,15 @@ class TestPasswordManagerClient
is_off_the_record_ = off_the_record; is_off_the_record_ = off_the_record;
} }
void set_zero_click_enabled(bool zero_click_enabled) {
is_zero_click_enabled_ = zero_click_enabled;
}
private: private:
bool did_prompt_user_to_save_; bool did_prompt_user_to_save_;
bool did_prompt_user_to_choose_; bool did_prompt_user_to_choose_;
bool is_off_the_record_; bool is_off_the_record_;
bool is_zero_click_enabled_;
password_manager::PasswordStore* store_; password_manager::PasswordStore* store_;
scoped_ptr<password_manager::PasswordFormManager> manager_; scoped_ptr<password_manager::PasswordFormManager> manager_;
}; };
...@@ -154,6 +161,13 @@ class CredentialManagerDispatcherTest ...@@ -154,6 +161,13 @@ class CredentialManagerDispatcherTest
form_.signon_realm = form_.origin.spec(); form_.signon_realm = form_.origin.spec();
form_.scheme = autofill::PasswordForm::SCHEME_HTML; form_.scheme = autofill::PasswordForm::SCHEME_HTML;
form2_.username_value = base::ASCIIToUTF16("Username 2");
form2_.display_name = base::ASCIIToUTF16("Display Name 2");
form2_.password_value = base::ASCIIToUTF16("Password 2");
form2_.origin = web_contents()->GetLastCommittedURL().GetOrigin();
form2_.signon_realm = form_.origin.spec();
form2_.scheme = autofill::PasswordForm::SCHEME_HTML;
cross_origin_form_.username_value = base::ASCIIToUTF16("Username"); cross_origin_form_.username_value = base::ASCIIToUTF16("Username");
cross_origin_form_.display_name = base::ASCIIToUTF16("Display Name"); cross_origin_form_.display_name = base::ASCIIToUTF16("Display Name");
cross_origin_form_.password_value = base::ASCIIToUTF16("Password"); cross_origin_form_.password_value = base::ASCIIToUTF16("Password");
...@@ -174,6 +188,7 @@ class CredentialManagerDispatcherTest ...@@ -174,6 +188,7 @@ class CredentialManagerDispatcherTest
protected: protected:
autofill::PasswordForm form_; autofill::PasswordForm form_;
autofill::PasswordForm form2_;
autofill::PasswordForm cross_origin_form_; autofill::PasswordForm cross_origin_form_;
scoped_refptr<TestPasswordStore> store_; scoped_refptr<TestPasswordStore> store_;
scoped_ptr<CredentialManagerDispatcher> dispatcher_; scoped_ptr<CredentialManagerDispatcher> dispatcher_;
...@@ -288,6 +303,7 @@ TEST_F(CredentialManagerDispatcherTest, ...@@ -288,6 +303,7 @@ TEST_F(CredentialManagerDispatcherTest,
TEST_F(CredentialManagerDispatcherTest, TEST_F(CredentialManagerDispatcherTest,
CredentialManagerOnRequestCredentialWithFullPasswordStore) { CredentialManagerOnRequestCredentialWithFullPasswordStore) {
client_->set_zero_click_enabled(false);
store_->AddLogin(form_); store_->AddLogin(form_);
std::vector<GURL> federations; std::vector<GURL> federations;
...@@ -302,8 +318,68 @@ TEST_F(CredentialManagerDispatcherTest, ...@@ -302,8 +318,68 @@ TEST_F(CredentialManagerDispatcherTest,
EXPECT_TRUE(client_->did_prompt_user_to_choose()); EXPECT_TRUE(client_->did_prompt_user_to_choose());
} }
TEST_F(
CredentialManagerDispatcherTest,
CredentialManagerOnRequestCredentialWithZeroClickOnlyEmptyPasswordStore) {
std::vector<GURL> federations;
dispatcher()->OnRequestCredential(kRequestId, true, federations);
RunAllPendingTasks();
const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID;
const IPC::Message* message =
process()->sink().GetFirstMessageMatching(kMsgID);
EXPECT_TRUE(message);
EXPECT_FALSE(client_->did_prompt_user_to_choose());
CredentialManagerMsg_SendCredential::Param send_param;
CredentialManagerMsg_SendCredential::Read(message, &send_param);
EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, get<1>(send_param).type);
}
TEST_F(CredentialManagerDispatcherTest,
CredentialManagerOnRequestCredentialWithZeroClickOnlyFullPasswordStore) {
store_->AddLogin(form_);
std::vector<GURL> federations;
dispatcher()->OnRequestCredential(kRequestId, true, federations);
RunAllPendingTasks();
const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID;
const IPC::Message* message =
process()->sink().GetFirstMessageMatching(kMsgID);
EXPECT_TRUE(message);
EXPECT_FALSE(client_->did_prompt_user_to_choose());
CredentialManagerMsg_SendCredential::Param send_param;
CredentialManagerMsg_SendCredential::Read(message, &send_param);
EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_LOCAL, get<1>(send_param).type);
}
TEST_F(CredentialManagerDispatcherTest,
CredentialManagerOnRequestCredentialWithZeroClickOnlyTwoPasswordStore) {
store_->AddLogin(form_);
store_->AddLogin(form2_);
std::vector<GURL> federations;
dispatcher()->OnRequestCredential(kRequestId, true, federations);
RunAllPendingTasks();
const uint32 kMsgID = CredentialManagerMsg_SendCredential::ID;
const IPC::Message* message =
process()->sink().GetFirstMessageMatching(kMsgID);
EXPECT_TRUE(message);
EXPECT_FALSE(client_->did_prompt_user_to_choose());
CredentialManagerMsg_SendCredential::Param send_param;
CredentialManagerMsg_SendCredential::Read(message, &send_param);
// With two items in the password store, we shouldn't get credentials back.
EXPECT_EQ(CredentialType::CREDENTIAL_TYPE_EMPTY, get<1>(send_param).type);
}
TEST_F(CredentialManagerDispatcherTest, TEST_F(CredentialManagerDispatcherTest,
CredentialManagerOnRequestCredentialWhileRequestPending) { CredentialManagerOnRequestCredentialWhileRequestPending) {
client_->set_zero_click_enabled(false);
store_->AddLogin(form_); store_->AddLogin(form_);
std::vector<GURL> federations; std::vector<GURL> federations;
......
...@@ -137,6 +137,7 @@ void CredentialManagerDispatcher::OnRequestCredential( ...@@ -137,6 +137,7 @@ void CredentialManagerDispatcher::OnRequestCredential(
request_id, zero_click_only, request_id, zero_click_only,
web_contents()->GetLastCommittedURL().GetOrigin(), federations)); web_contents()->GetLastCommittedURL().GetOrigin(), federations));
// This will result in a callback to ::OnGetPasswordStoreResults().
store->GetAutofillableLogins(this); store->GetAutofillableLogins(this);
} }
...@@ -150,19 +151,15 @@ void CredentialManagerDispatcher::OnGetPasswordStoreResults( ...@@ -150,19 +151,15 @@ void CredentialManagerDispatcher::OnGetPasswordStoreResults(
// We own the PasswordForm instances, so we're responsible for cleaning // We own the PasswordForm instances, so we're responsible for cleaning
// up the instances we don't add to |local_results| or |federated_results|. // up the instances we don't add to |local_results| or |federated_results|.
// We'll dump them into a ScopedVector and allow it to delete the
// PasswordForms upon destruction.
std::vector<autofill::PasswordForm*> local_results; std::vector<autofill::PasswordForm*> local_results;
std::vector<autofill::PasswordForm*> federated_results; std::vector<autofill::PasswordForm*> federated_results;
ScopedVector<autofill::PasswordForm> discarded_results;
for (autofill::PasswordForm* form : results) { for (autofill::PasswordForm* form : results) {
// TODO(mkwst): Extend this filter to include federations.
if (form->origin == pending_request_->origin) if (form->origin == pending_request_->origin)
local_results.push_back(form); local_results.push_back(form);
else if (federations.count(form->origin.spec()) != 0) else if (federations.count(form->origin.spec()) != 0)
federated_results.push_back(form); federated_results.push_back(form);
else else
discarded_results.push_back(form); delete form;
} }
if ((local_results.empty() && federated_results.empty()) || if ((local_results.empty() && federated_results.empty()) ||
...@@ -172,11 +169,26 @@ void CredentialManagerDispatcher::OnGetPasswordStoreResults( ...@@ -172,11 +169,26 @@ void CredentialManagerDispatcher::OnGetPasswordStoreResults(
return; return;
} }
if (!client_->PromptUserToChooseCredentials( if (local_results.size() == 1 && IsZeroClickAllowed()) {
local_results, // TODO(mkwst): Use the `one_time_disable_zero_click` flag on the result
federated_results, // to prevent auto-sign-in, once that flag is implemented.
CredentialInfo info(*local_results[0],
local_results[0]->federation_url.is_empty()
? CredentialType::CREDENTIAL_TYPE_LOCAL
: CredentialType::CREDENTIAL_TYPE_FEDERATED);
STLDeleteElements(&local_results);
STLDeleteElements(&federated_results);
SendCredential(pending_request_->id, info);
return;
}
if (pending_request_->zero_click_only ||
!client_->PromptUserToChooseCredentials(
local_results, federated_results,
base::Bind(&CredentialManagerDispatcher::SendCredential, base::Bind(&CredentialManagerDispatcher::SendCredential,
base::Unretained(this), pending_request_->id))) { base::Unretained(this), pending_request_->id))) {
STLDeleteElements(&local_results);
STLDeleteElements(&federated_results);
SendCredential(pending_request_->id, CredentialInfo()); SendCredential(pending_request_->id, CredentialInfo());
} }
} }
...@@ -191,7 +203,7 @@ bool CredentialManagerDispatcher::IsSavingEnabledForCurrentPage() const { ...@@ -191,7 +203,7 @@ bool CredentialManagerDispatcher::IsSavingEnabledForCurrentPage() const {
} }
bool CredentialManagerDispatcher::IsZeroClickAllowed() const { bool CredentialManagerDispatcher::IsZeroClickAllowed() const {
return !client_->IsOffTheRecord(); return !client_->IsOffTheRecord() && client_->IsZeroClickEnabled();
} }
base::WeakPtr<PasswordManagerDriver> CredentialManagerDispatcher::GetDriver() { base::WeakPtr<PasswordManagerDriver> CredentialManagerDispatcher::GetDriver() {
......
...@@ -71,6 +71,10 @@ bool PasswordManagerClient::IsOffTheRecord() { ...@@ -71,6 +71,10 @@ bool PasswordManagerClient::IsOffTheRecord() {
return false; return false;
} }
bool PasswordManagerClient::IsZeroClickEnabled() {
return true;
}
PasswordManager* PasswordManagerClient::GetPasswordManager() { PasswordManager* PasswordManagerClient::GetPasswordManager() {
return nullptr; return nullptr;
} }
......
...@@ -163,6 +163,9 @@ class PasswordManagerClient { ...@@ -163,6 +163,9 @@ class PasswordManagerClient {
// Returns the main frame URL. // Returns the main frame URL.
virtual const GURL& GetMainFrameURL(); virtual const GURL& GetMainFrameURL();
// Whether or not "zero-click" sign-in is enabled.
virtual bool IsZeroClickEnabled();
private: private:
DISALLOW_COPY_AND_ASSIGN(PasswordManagerClient); DISALLOW_COPY_AND_ASSIGN(PasswordManagerClient);
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment