• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview
7 * Script to be injected into SAML provider pages that do not support the
8 * auth service provider postMessage API. It serves two main purposes:
9 * 1. Signal hosting extension that an external page is loaded so that the
10 *    UI around it could be changed accordingly;
11 * 2. Scrape password and send it back to be used for encrypt user data and
12 *    use for offline login;
13 */
14
15(function() {
16  /**
17   * A class to scrape password from type=password input elements under a given
18   * docRoot and send them back via a Channel.
19   */
20  function PasswordInputScraper() {
21  }
22
23  PasswordInputScraper.prototype = {
24    // URL of the page.
25    pageURL_: null,
26
27    // Channel to send back changed password.
28    channel_: null,
29
30    // An array to hold password fields.
31    passwordFields_: null,
32
33    // An array to hold cached password values.
34    passwordValues_: null,
35
36    /**
37     * Initialize the scraper with given channel and docRoot. Note that the
38     * scanning for password fields happens inside the function and does not
39     * handle DOM tree changes after the call returns.
40     * @param {!Object} channel The channel to send back password.
41     * @param {!string} pageURL URL of the page.
42     * @param {!HTMLElement} docRoot The root element of the DOM tree that
43     *     contains the password fields of interest.
44     */
45    init: function(channel, pageURL, docRoot) {
46      this.pageURL_ = pageURL;
47      this.channel_ = channel;
48
49      this.passwordFields_ = docRoot.querySelectorAll('input[type=password]');
50      this.passwordValues_ = [];
51
52      for (var i = 0; i < this.passwordFields_.length; ++i) {
53        this.passwordFields_[i].addEventListener(
54            'change', this.onPasswordChanged_.bind(this, i));
55        // 'keydown' event is needed for the case that the form is submitted
56        // on enter key, in which case no 'change' event is dispatched.
57        this.passwordFields_[i].addEventListener(
58            'keydown', this.onPasswordKeyDown_.bind(this, i));
59
60        this.passwordValues_[i] = this.passwordFields_[i].value;
61      }
62    },
63
64    /**
65     * Check if the password field at |index| has changed. If so, sends back
66     * the updated value.
67     */
68    maybeSendUpdatedPassword: function(index) {
69      var newValue = this.passwordFields_[index].value;
70      if (newValue == this.passwordValues_[index])
71        return;
72
73      this.passwordValues_[index] = newValue;
74
75      // Use an invalid char for URL as delimiter to concatenate page url and
76      // password field index to construct a unique ID for the password field.
77      var passwordId = this.pageURL_ + '|' + index;
78      this.channel_.send({
79        name: 'updatePassword',
80        id: passwordId,
81        password: newValue
82      });
83    },
84
85    /**
86     * Handles 'change' event in the scraped password fields.
87     * @param {number} index The index of the password fields in
88     *     |passwordFields_|.
89     */
90    onPasswordChanged_: function(index) {
91      this.maybeSendUpdatedPassword(index);
92    },
93
94    /**
95     * Handles 'keydown' event to trigger password change detection and
96     * updates on enter key.
97     * @param {number} index The index of the password fields in
98     *     |passwordFields_|.
99     * @param {Event} e The keydown event.
100     */
101    onPasswordKeyDown_: function(index, e) {
102      if (e.keyIdentifier == 'Enter')
103        this.maybeSendUpdatedPassword(index);
104    }
105  };
106
107  /**
108   * Returns true if the script is injected into auth main page.
109   */
110  function isAuthMainPage() {
111    return window.location.href.indexOf(
112        'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/main.html') == 0;
113  }
114
115  /**
116   * Heuristic test whether the current page is a relevant SAML page.
117   * Current implementation checks if it is a http or https page and has
118   * some content in it.
119   * @return {boolean} Whether the current page looks like a SAML page.
120   */
121  function isSAMLPage() {
122    var url = window.location.href;
123    if (!url.match(/^(http|https):\/\//))
124      return false;
125
126    return document.body.scrollWidth > 50 && document.body.scrollHeight > 50;
127  }
128
129  if (isAuthMainPage()) {
130    // Use an event to signal the auth main to enable SAML support.
131    var e = document.createEvent('Event');
132    e.initEvent('enableSAML', false, false);
133    document.dispatchEvent(e);
134  } else {
135    var channel;
136    var passwordScraper;
137    if (isSAMLPage()) {
138      var pageURL = window.location.href;
139
140      channel = new Channel();
141      channel.connect('injected');
142      channel.send({name: 'pageLoaded', url: pageURL});
143
144      passwordScraper = new PasswordInputScraper();
145      passwordScraper.init(channel, pageURL, document.documentElement);
146    }
147  }
148})();
149