[go: up one dir, main page]

Merge "cros: Support dynamic password field for SAML."

> - Add MutationObserver to handle dynamically added password fields;
> - Remove unnecessary default text css;
> - Put channel.js before background.js to fix the unknown Channel error;
>
> BUG=447655
> TEST=SamlTest.ScrapedDynamic
>
> Review URL: https://codereview.chromium.org/844083003
>
> Cr-Commit-Position: refs/heads/master@{#311099}
> (cherry picked from commit da0943f77322c319a724c5f14cbeb0952ca98d79)

BUG=458220
TBR=xiyuan@chromium.org

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

Cr-Commit-Position: refs/branch-heads/2272@{#295}
Cr-Branched-From: 827a380cfdb31aa54c8d56e63ce2c3fd8c3ba4d4-refs/heads/master@{#310958}
diff --git a/chrome/browser/chromeos/login/saml/saml_browsertest.cc b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
index f28c1d7..3e09228 100644
--- a/chrome/browser/chromeos/login/saml/saml_browsertest.cc
+++ b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
@@ -509,11 +509,44 @@
   // Lands on confirm password screen.
   OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
 
-  // Enter an unknown password should go back to confirm password screen.
+  // Entering an unknown password should go back to the confirm password screen.
   SendConfirmPassword("wrong_password");
   OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
 
-  // Enter a known password should finish login and start session.
+  // Entering a known password should finish login and start session.
+  SendConfirmPassword("fake_password");
+  content::WindowedNotificationObserver(
+      chrome::NOTIFICATION_SESSION_STARTED,
+      content::NotificationService::AllSources()).Wait();
+}
+
+// Tests password scraping from a dynamically created password field.
+IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedDynamic) {
+  fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
+  StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
+
+  ExecuteJsInSigninFrame(
+    "(function() {"
+      "var newPassInput = document.createElement('input');"
+      "newPassInput.id = 'DynamicallyCreatedPassword';"
+      "newPassInput.type = 'password';"
+      "newPassInput.name = 'Password';"
+      "document.forms[0].appendChild(newPassInput);"
+    "})();");
+
+  // Fill-in the SAML IdP form and submit.
+  SetSignFormField("Email", "fake_user");
+  SetSignFormField("DynamicallyCreatedPassword", "fake_password");
+  ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
+
+  // Lands on confirm password screen.
+  OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
+
+  // Entering an unknown password should go back to the confirm password screen.
+  SendConfirmPassword("wrong_password");
+  OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
+
+  // Entering a known password should finish login and start session.
   SendConfirmPassword("fake_password");
   content::WindowedNotificationObserver(
       chrome::NOTIFICATION_SESSION_STARTED,
diff --git a/chrome/browser/resources/gaia_auth/main.html b/chrome/browser/resources/gaia_auth/main.html
index 421364d..20d079ea 100644
--- a/chrome/browser/resources/gaia_auth/main.html
+++ b/chrome/browser/resources/gaia_auth/main.html
@@ -1,7 +1,6 @@
 <!doctype html>
 <html>
 <head>
-  <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
   <link rel="stylesheet" href="main.css">
   <meta charset="utf-8">
   <script src="channel.js"></script>
diff --git a/chrome/browser/resources/gaia_auth/manifest.json b/chrome/browser/resources/gaia_auth/manifest.json
index 7295c01..2281ee14 100644
--- a/chrome/browser/resources/gaia_auth/manifest.json
+++ b/chrome/browser/resources/gaia_auth/manifest.json
@@ -5,7 +5,7 @@
   "version": "0.0.1",
   "manifest_version": 2,
   "background" : {
-    "scripts": ["background.js", "channel.js"]
+    "scripts": ["channel.js", "background.js"]
   },
   "content_scripts": [
     {
diff --git a/chrome/browser/resources/gaia_auth/manifest_keyboard.json b/chrome/browser/resources/gaia_auth/manifest_keyboard.json
index 68d59a1..63d07a19 100644
--- a/chrome/browser/resources/gaia_auth/manifest_keyboard.json
+++ b/chrome/browser/resources/gaia_auth/manifest_keyboard.json
@@ -5,7 +5,7 @@
   "version": "0.0.1",
   "manifest_version": 2,
   "background" : {
-    "scripts": ["background.js", "channel.js"]
+    "scripts": ["channel.js", "background.js"]
   },
   "content_scripts": [
     {
diff --git a/chrome/browser/resources/gaia_auth/offline.html b/chrome/browser/resources/gaia_auth/offline.html
index 43ed5794..a9afb7c 100644
--- a/chrome/browser/resources/gaia_auth/offline.html
+++ b/chrome/browser/resources/gaia_auth/offline.html
@@ -2,7 +2,6 @@
 <html>
 <head>
   <meta charset="utf-8">
-  <link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
   <link rel="stylesheet" href="offline.css">
   <script src="util.js"></script>
   <script src="offline.js"></script>
diff --git a/chrome/browser/resources/gaia_auth/saml_injected.js b/chrome/browser/resources/gaia_auth/saml_injected.js
index fd08bd5..fd57600 100644
--- a/chrome/browser/resources/gaia_auth/saml_injected.js
+++ b/chrome/browser/resources/gaia_auth/saml_injected.js
@@ -78,6 +78,9 @@
     // An array to hold cached password values.
     passwordValues_: null,
 
+    // A MutationObserver to watch for dynamic password field creation.
+    passwordFieldsObserver: null,
+
     /**
      * Initialize the scraper with given channel and docRoot. Note that the
      * scanning for password fields happens inside the function and does not
@@ -91,15 +94,59 @@
       this.pageURL_ = pageURL;
       this.channel_ = channel;
 
-      this.passwordFields_ = docRoot.querySelectorAll('input[type=password]');
+      this.passwordFields_ = [];
       this.passwordValues_ = [];
 
-      for (var i = 0; i < this.passwordFields_.length; ++i) {
-        this.passwordFields_[i].addEventListener(
-            'input', this.onPasswordChanged_.bind(this, i));
+      this.findAndTrackChildren(docRoot);
 
-        this.passwordValues_[i] = this.passwordFields_[i].value;
-      }
+      this.passwordFieldsObserver = new MutationObserver(function(mutations) {
+        mutations.forEach(function(mutation) {
+          Array.prototype.forEach.call(
+            mutation.addedNodes,
+            function(addedNode) {
+              if (addedNode.nodeType != Node.ELEMENT_NODE)
+                return;
+
+              if (addedNode.matches('input[type=password]')) {
+                this.trackPasswordField(addedNode);
+              } else {
+                this.findAndTrackChildren(addedNode);
+              }
+            }.bind(this));
+        }.bind(this));
+      }.bind(this));
+      this.passwordFieldsObserver.observe(docRoot,
+                                          {subtree: true, childList: true});
+    },
+
+    /**
+     * Find and track password fields that are descendants of the given element.
+     * @param {!HTMLElement} element The parent element to search from.
+     */
+    findAndTrackChildren: function(element) {
+      Array.prototype.forEach.call(
+          element.querySelectorAll('input[type=password]'), function(field) {
+            this.trackPasswordField(field);
+          }.bind(this));
+    },
+
+    /**
+     * Start tracking value changes of the given password field if it is
+     * not being tracked yet.
+     * @param {!HTMLInputElement} passworField The password field to track.
+     */
+    trackPasswordField: function(passwordField) {
+      var existing = this.passwordFields_.filter(function(element) {
+        return element === passwordField;
+      });
+      if (existing.length != 0)
+        return;
+
+      var index = this.passwordFields_.length;
+      passwordField.addEventListener(
+          'input', this.onPasswordChanged_.bind(this, index));
+      this.passwordFields_.push(passwordField);
+      this.passwordValues_.push(passwordField.value);
     },
 
     /**
@@ -143,13 +190,25 @@
     var apiCallForwarder = new APICallForwarder();
     apiCallForwarder.init(channel);
 
-    var passwordScraper = new PasswordInputScraper();
-    passwordScraper.init(channel, pageURL, document.documentElement);
+    var initPasswordScraper = function() {
+      var passwordScraper = new PasswordInputScraper();
+      passwordScraper.init(channel, pageURL, document.documentElement);
+    };
+
+    if (document.readyState == 'loading') {
+      window.addEventListener('readystatechange', function listener(event) {
+        if (document.readyState == 'loading')
+          return;
+        initPasswordScraper();
+        window.removeEventListener(event.type, listener, true);
+      }, true);
+    } else {
+      initPasswordScraper();
+    }
   }
 
   var channel = new Channel();
   channel.connect('injected');
   channel.sendWithCallback({name: 'getSAMLFlag'},
                            onGetSAMLFlag.bind(undefined, channel));
-
 })();
diff --git a/chrome/browser/resources/gaia_auth/success.html b/chrome/browser/resources/gaia_auth/success.html
index 240e531..bdbf41a6 100644
--- a/chrome/browser/resources/gaia_auth/success.html
+++ b/chrome/browser/resources/gaia_auth/success.html
@@ -1,7 +1,6 @@
 <!doctype html>
 <head>
 <meta charset="utf-8">
-<link rel="stylesheet" href="chrome://resources/css/text_defaults.css">
 </head>
 <html>
 <body></body>