PyAuto hook to remove saved passwords, more info added to passwords. BUG=36179

Review URL: http://codereview.chromium.org/3054004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@53634 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc
index bf52686..5b3fe45 100644
--- a/chrome/browser/automation/automation_provider.cc
+++ b/chrome/browser/automation/automation_provider.cc
@@ -2536,35 +2536,87 @@
                                      new ProfileWriter(profile), first_run);
 }
 
+namespace {
+
+// Translates a dictionary password to a PasswordForm struct.
+webkit_glue::PasswordForm GetPasswordFormFromDict(
+    const DictionaryValue& password_dict) {
+
+  // If the time is specified, change time to the specified time.
+  base::Time time = base::Time::Now();
+  int it;
+  double dt;
+  if (password_dict.GetInteger(L"time", &it))
+    time = base::Time::FromTimeT(it);
+  else if (password_dict.GetReal(L"time", &dt))
+    time = base::Time::FromDoubleT(dt);
+
+  std::string signon_realm;
+  string16 username_value;
+  string16 password_value;
+  string16 origin_url_text;
+  string16 username_element;
+  string16 password_element;
+  string16 submit_element;
+  string16 action_target_text;
+  bool blacklist = false;
+  string16 old_password_element;
+  string16 old_password_value;
+
+  // We don't care if any of these fail - they are either optional or checked
+  // before this function is called.
+  password_dict.GetString(L"signon_realm", &signon_realm);
+  password_dict.GetStringAsUTF16(L"username_value", &username_value);
+  password_dict.GetStringAsUTF16(L"password_value", &password_value);
+  password_dict.GetStringAsUTF16(L"origin_url", &origin_url_text);
+  password_dict.GetStringAsUTF16(L"username_element", &username_element);
+  password_dict.GetStringAsUTF16(L"password_element", &password_element);
+  password_dict.GetStringAsUTF16(L"submit_element", &submit_element);
+  password_dict.GetStringAsUTF16(L"action_target", &action_target_text);
+  password_dict.GetBoolean(L"blacklist", &blacklist);
+
+  GURL origin_gurl(origin_url_text);
+  GURL action_target(action_target_text);
+
+  webkit_glue::PasswordForm password_form;
+  password_form.signon_realm = signon_realm;
+  password_form.username_value = username_value;
+  password_form.password_value = password_value;
+  password_form.origin = origin_gurl;
+  password_form.username_element = username_element;
+  password_form.password_element = password_element;
+  password_form.submit_element = submit_element;
+  password_form.action = action_target;
+  password_form.blacklisted_by_user = blacklist;
+  password_form.date_created = time;
+
+  return password_form;
+}
+
+} // namespace
+
 // See AddSavedPassword() in chrome/test/functional/pyauto.py for sample json
 // input.
 // Sample json output: { "password_added": true }
 void AutomationProvider::AddSavedPassword(Browser* browser,
                                           DictionaryValue* args,
                                           IPC::Message* reply_message) {
-  string16 username;
-  string16 password;
-  base::Time time = base::Time::Now();
   AutomationJSONReply reply(this, reply_message);
+  DictionaryValue* password_dict = NULL;
 
-  if (!args->GetStringAsUTF16(L"password", &password) ||
-      !args->GetStringAsUTF16(L"username", &username)) {
-    reply.SendError("Username and password must be strings.");
+  if (!args->GetDictionary(L"password", &password_dict)) {
+    reply.SendError("Password must be a dictionary.");
     return;
   }
 
-  // If the time is specified, change time to the specified time.
-  int it;
-  double dt;
-  if (args->GetInteger(L"time", &it))
-    time = base::Time::FromTimeT(it);
-  else if (args->GetReal(L"time", &dt))
-    time = base::Time::FromDoubleT(dt);
-
-  webkit_glue::PasswordForm new_password;
-  new_password.username_value = username;
-  new_password.password_value = password;
-  new_password.date_created = time;
+  // The signon realm is effectively the primary key and must be included.
+  // Check here before calling GetPasswordFormFromDict.
+  if (!password_dict->HasKey(L"signon_realm")) {
+    reply.SendError("Password must include signon_realm.");
+    return;
+  }
+  webkit_glue::PasswordForm new_password =
+      GetPasswordFormFromDict(*password_dict);
 
   Profile* profile = browser->profile();
   // Use IMPLICIT_ACCESS since new passwords aren't added off the record.
@@ -2585,6 +2637,38 @@
   reply.SendSuccess(return_value.get());
 }
 
+// See RemoveSavedPassword() in chrome/test/functional/pyauto.py for sample
+// json input.
+// Sample json output: {}
+void AutomationProvider::RemoveSavedPassword(Browser* browser,
+                                             DictionaryValue* args,
+                                             IPC::Message* reply_message) {
+  AutomationJSONReply reply(this, reply_message);
+  DictionaryValue* password_dict = NULL;
+
+  if (!args->GetDictionary(L"password", &password_dict)) {
+    reply.SendError("Password must be a dictionary.");
+    return;
+  }
+
+  // The signon realm is effectively the primary key and must be included.
+  // Check here before calling GetPasswordFormFromDict.
+  if (!password_dict->HasKey(L"signon_realm")) {
+    reply.SendError("Password must include signon_realm.");
+    return;
+  }
+  webkit_glue::PasswordForm to_remove =
+      GetPasswordFormFromDict(*password_dict);
+
+  Profile* profile = browser->profile();
+  // Use EXPLICIT_ACCESS since passwords can be removed off the record.
+  PasswordStore* password_store =
+      profile->GetPasswordStore(Profile::EXPLICIT_ACCESS);
+
+  password_store->RemoveLogin(to_remove);
+  reply.SendSuccess(NULL);
+}
+
 // Sample json input: { "command": "GetSavedPasswords" }
 // Refer to GetSavedPasswords() in chrome/test/pyautolib/pyauto.py for sample
 // json output.
@@ -2998,6 +3082,8 @@
   handler_map["ImportSettings"] = &AutomationProvider::ImportSettings;
 
   handler_map["AddSavedPassword"] = &AutomationProvider::AddSavedPassword;
+  handler_map["RemoveSavedPassword"] =
+      &AutomationProvider::RemoveSavedPassword;
   handler_map["GetSavedPasswords"] = &AutomationProvider::GetSavedPasswords;
 
   handler_map["ClearBrowsingData"] = &AutomationProvider::ClearBrowsingData;
diff --git a/chrome/browser/automation/automation_provider.h b/chrome/browser/automation/automation_provider.h
index 0bc30fcc..dbaeb1f 100644
--- a/chrome/browser/automation/automation_provider.h
+++ b/chrome/browser/automation/automation_provider.h
@@ -62,6 +62,10 @@
 class Point;
 }
 
+namespace webkit_glue {
+struct PasswordForm;
+}
+
 class AutomationProvider : public base::RefCounted<AutomationProvider>,
                            public IPC::Channel::Listener,
                            public IPC::Message::Sender {
@@ -467,12 +471,21 @@
                       DictionaryValue* args,
                       IPC::Message* reply_message);
 
-  // Add a new username-password combination to the saved passwords.
+  // Add a new entry to the password store based on the password information
+  // provided. This method can also be used to add a blacklisted site (which
+  // will never fill in the password).
   // Uses the JSON interface for input/output.
   void AddSavedPassword(Browser* browser,
                         DictionaryValue* args,
                         IPC::Message* reply_message);
 
+  // Removes the password matching the information provided. This method can
+  // also be used to remove a blacklisted site.
+  // Uses the JSON interface for input/output.
+  void RemoveSavedPassword(Browser* browser,
+                           DictionaryValue* args,
+                           IPC::Message* reply_message);
+
   // Return the saved username/password combinations.
   // Uses the JSON interface for input/output.
   void GetSavedPasswords(Browser* browser,
diff --git a/chrome/browser/automation/automation_provider_observers.cc b/chrome/browser/automation/automation_provider_observers.cc
index 6725ae1b..a9414be 100644
--- a/chrome/browser/automation/automation_provider_observers.cc
+++ b/chrome/browser/automation/automation_provider_observers.cc
@@ -1024,13 +1024,23 @@
           result.begin(); it != result.end(); ++it) {
     DictionaryValue* password_val = new DictionaryValue;
     webkit_glue::PasswordForm* password_form = *it;
-    password_val->SetStringFromUTF16(L"username",
+    password_val->SetStringFromUTF16(L"username_value",
                                      password_form->username_value);
-    password_val->SetStringFromUTF16(L"password",
+    password_val->SetStringFromUTF16(L"password_value",
                                      password_form->password_value);
+    password_val->SetString(L"signon_realm", password_form->signon_realm);
     password_val->SetReal(
         L"time", static_cast<double>(
             password_form->date_created.ToDoubleT()));
+    password_val->SetString(L"origin_url", password_form->origin.spec());
+    password_val->SetStringFromUTF16(L"username_element",
+                                     password_form->username_element);
+    password_val->SetStringFromUTF16(L"password_element",
+                                     password_form->password_element);
+    password_val->SetStringFromUTF16(L"submit_element",
+                                     password_form->submit_element);
+    password_val->SetString(L"action_target", password_form->action.spec());
+    password_val->SetBoolean(L"blacklist", password_form->blacklisted_by_user);
     passwords->Append(password_val);
   }
 
diff --git a/chrome/test/functional/passwords.py b/chrome/test/functional/passwords.py
index f0d7e9e..7d88eb9f7 100644
--- a/chrome/test/functional/passwords.py
+++ b/chrome/test/functional/passwords.py
@@ -3,8 +3,6 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
-import time
-
 import pyauto_functional  # Must be imported before pyauto
 import pyauto
 
@@ -31,29 +29,18 @@
 
   def testSavePassword(self):
     """Test saving a password and getting saved passwords."""
-    username1 = 'username1'
-    password1 = 'password1'
-    username2 = 'username2'
-    password2 = 'password2'
-
-    self.AddSavedPassword(username1, password1)
-    passwords = self.GetSavedPasswords()['passwords']
-    self.assertEqual(1, len(passwords))
-    pw = passwords[0]
-    self.assertEqual(username1, pw['username'])
-    self.assertEqual(password1, pw['password'])
-
-    now = time.time()
-    self.AddSavedPassword(username2, password2, now)
-    passwords = self.GetSavedPasswords()['passwords']
-    self.assertEqual(2, len(passwords))
-    pw1 = passwords[0]
-    pw2 = passwords[1]
-    self.assertEqual(username1, pw1['username'])
-    self.assertEqual(password1, pw1['password'])
-    self.assertEqual(username2, pw2['username'])
-    self.assertEqual(password2, pw2['password'])
-    self._AssertWithinOneSecond(now, pw2['time'])
+    password1 = { 'username_value': '[email protected]',
+      'password_value': 'test.password',
+      'signon_realm': 'https://www.example.com/',
+      'time': 1279650942.0,
+      'origin_url': 'https://www.example.com/login',
+      'username_element': 'username',
+      'password_element': 'password',
+      'submit_element': 'submit',
+      'action_target': 'https://www.example.com/login/',
+      'blacklist': False }
+    self.assertTrue(self.AddSavedPassword(password1))
+    self.assertEquals(self.GetSavedPasswords(), [password1])
 
 
 if __name__ == '__main__':
diff --git a/chrome/test/pyautolib/pyauto.py b/chrome/test/pyautolib/pyauto.py
index 716f14d8..28e39ed 100644
--- a/chrome/test/pyautolib/pyauto.py
+++ b/chrome/test/pyautolib/pyauto.py
@@ -890,42 +890,68 @@
     }
     return self._GetResultFromJSONRequest(cmd_dict)
 
-  def AddSavedPassword(self, username, password, time=None, window_index=0):
+  def AddSavedPassword(self, password_dict, window_index=0):
     """Adds the given username-password combination to the saved passwords.
 
     Args:
-      username: a string representing the username
-      password: a string representing the password
+      password_dict: a dictionary that represents a password. Example:
+      { 'username_value': '[email protected]',        # Required
+        'password_value': 'test.password',           # Required
+        'signon_realm': 'https://www.example.com/',  # Required
+        'time': 1279317810.0,                        # Can get from time.time()
+        'origin_url': 'https://www.example.com/login',
+        'username_element': 'username',              # The HTML element
+        'password_element': 'password',              # The HTML element
+        'submit_element': 'submit',                  # The HTML element
+        'action_target': 'https://www.example.com/login/',
+        'blacklist': False }
       window_index: window index, defaults to 0
 
+    *Blacklist notes* To blacklist a site, add a blacklist password with the
+    following dictionary items: origin_url, signon_realm, username_element,
+    password_element, action_target, and 'blacklist': True. Then all sites that
+    have password forms matching those are blacklisted.
+
     Returns:
-      The success or failure of adding the password. In incognito mode, adding
-      the password should fail. Example return:
-      { "password_added": True }
+      True if adding the password succeeded, false otherwise. In incognito
+      mode, adding the password should fail.
 
     Raises:
       JSONInterfaceError on error.
     """
     cmd_dict = {  # Prepare command for the json interface
       'command': 'AddSavedPassword',
-      'username': username,
-      'password': password,
-      'time': time
+      'password': password_dict
     }
-    return self._GetResultFromJSONRequest(cmd_dict, windex=window_index)
+    return self._GetResultFromJSONRequest(
+        cmd_dict, windex=window_index)['password_added']
+
+  def RemoveSavedPassword(self, password_dict):
+    """Removes the password matching the provided password dictionary.
+
+    Args:
+      password_dict: A dictionary that represents a password.
+                     For an example, see the dictionary in AddSavedPassword.
+    """
+    cmd_dict = {  # Prepare command for the json interface
+      'command': 'RemoveSavedPassword',
+      'password': password_dict
+    }
+    self._GetResultFromJSONRequest(cmd_dict, windex=window_index)
 
   def GetSavedPasswords(self):
     """Return the passwords currently saved.
 
     Returns:
-      A list of 2-item lists of username, password for all saved passwords.
-      Example:
-      { 'passwords': [['username1', 'password1'], ['username2', 'password2']] }
+      A list of dictionaries representing each password. For an example
+      dictionary see AddSavedPassword documentation. The overall structure will
+      be:
+      [ {password1 dictionary}, {password2 dictionary} ]
     """
     cmd_dict = {  # Prepare command for the json interface
       'command': 'GetSavedPasswords'
     }
-    return self._GetResultFromJSONRequest(cmd_dict)
+    return self._GetResultFromJSONRequest(cmd_dict)['passwords']
 
   def SetTheme(self, crx_file_path):
     """Installs the given theme synchronously.