1d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved.
2d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
3d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)// found in the LICENSE file.
4d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
5d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)/**
6d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * @fileoverview
75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Script to be injected into SAML provider pages, serving three main purposes:
8d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * 1. Signal hosting extension that an external page is loaded so that the
95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) *    UI around it should be changed accordingly;
105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * 2. Provide an API via which the SAML provider can pass user credentials to
115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) *    Chrome OS, allowing the password to be used for encrypting user data and
125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) *    offline login.
135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * 3. Scrape password fields, making the password available to Chrome OS even if
145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) *    the SAML provider does not support the credential passing API.
15d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) */
16d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
17d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)(function() {
185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  function APICallForwarder() {
195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  }
205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  /**
225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)   * The credential passing API is used by sending messages to the SAML page's
23010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)   * |window| object. This class forwards API calls from the SAML page to a
24010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)   * background script and API responses from the background script to the SAML
25010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)   * page. Communication with the background script occurs via a |Channel|.
265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)   */
275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  APICallForwarder.prototype = {
285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    // Channel to which API calls are forwarded.
295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    channel_: null,
305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    /**
325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)     * Initialize the API call forwarder.
335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)     * @param {!Object} channel Channel to which API calls should be forwarded.
345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)     */
355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    init: function(channel) {
365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      this.channel_ = channel;
37010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      this.channel_.registerMessage('apiResponse',
38010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)                                    this.onAPIResponse_.bind(this));
39010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      window.addEventListener('message', this.onMessage_.bind(this));
415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    },
425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    onMessage_: function(event) {
445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      if (event.source != window ||
455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          typeof event.data != 'object' ||
465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          !event.data.hasOwnProperty('type') ||
475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)          event.data.type != 'gaia_saml_api') {
485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)        return;
495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)      }
50010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      // Forward API calls to the background script.
51010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      this.channel_.send({name: 'apiCall', call: event.data.call});
52010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    },
53010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)
54010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)    onAPIResponse_: function(msg) {
55010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      // Forward API responses to the SAML page.
56010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)      window.postMessage({type: 'gaia_saml_api_reply', response: msg.response},
57010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles)                         '/');
585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)    }
595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)  };
605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)
61d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  /**
62d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)   * A class to scrape password from type=password input elements under a given
63d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)   * docRoot and send them back via a Channel.
64d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)   */
65d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  function PasswordInputScraper() {
66d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  }
67d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
68d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  PasswordInputScraper.prototype = {
69d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    // URL of the page.
70d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    pageURL_: null,
71d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
72d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    // Channel to send back changed password.
73d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    channel_: null,
74d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
75d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    // An array to hold password fields.
76d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    passwordFields_: null,
77d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
78d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    // An array to hold cached password values.
79d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    passwordValues_: null,
80d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
81d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    /**
82d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     * Initialize the scraper with given channel and docRoot. Note that the
83d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     * scanning for password fields happens inside the function and does not
84d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     * handle DOM tree changes after the call returns.
85d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     * @param {!Object} channel The channel to send back password.
86d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     * @param {!string} pageURL URL of the page.
87d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     * @param {!HTMLElement} docRoot The root element of the DOM tree that
88d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     *     contains the password fields of interest.
89d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     */
90d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    init: function(channel, pageURL, docRoot) {
91d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      this.pageURL_ = pageURL;
92d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      this.channel_ = channel;
93d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
94d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      this.passwordFields_ = docRoot.querySelectorAll('input[type=password]');
95d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      this.passwordValues_ = [];
96d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
97d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      for (var i = 0; i < this.passwordFields_.length; ++i) {
98d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        this.passwordFields_[i].addEventListener(
995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)            'input', this.onPasswordChanged_.bind(this, i));
100d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
101d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        this.passwordValues_[i] = this.passwordFields_[i].value;
102d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      }
103d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    },
104d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
105d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    /**
106d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     * Check if the password field at |index| has changed. If so, sends back
107d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     * the updated value.
108d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     */
109d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    maybeSendUpdatedPassword: function(index) {
110d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      var newValue = this.passwordFields_[index].value;
111d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      if (newValue == this.passwordValues_[index])
112d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        return;
113d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
114d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      this.passwordValues_[index] = newValue;
115d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
116d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      // Use an invalid char for URL as delimiter to concatenate page url and
117d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      // password field index to construct a unique ID for the password field.
118d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      var passwordId = this.pageURL_ + '|' + index;
119d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      this.channel_.send({
120d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        name: 'updatePassword',
121d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        id: passwordId,
122d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)        password: newValue
123d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      });
124d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    },
125d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
126d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    /**
127d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     * Handles 'change' event in the scraped password fields.
128d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     * @param {number} index The index of the password fields in
129d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     *     |passwordFields_|.
130d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)     */
131d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    onPasswordChanged_: function(index) {
132d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      this.maybeSendUpdatedPassword(index);
133d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    }
134d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  };
135d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
136d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  /**
137d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)   * Heuristic test whether the current page is a relevant SAML page.
138d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)   * Current implementation checks if it is a http or https page and has
139d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)   * some content in it.
140d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)   * @return {boolean} Whether the current page looks like a SAML page.
141d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)   */
142d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  function isSAMLPage() {
143d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    var url = window.location.href;
144d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    if (!url.match(/^(http|https):\/\//))
145d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)      return false;
146d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
147d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)    return document.body.scrollWidth > 50 && document.body.scrollHeight > 50;
148d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  }
149d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)
1505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  if (isSAMLPage()) {
1515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    var pageURL = window.location.href;
1525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    var channel = new Channel();
1545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    channel.connect('injected');
1555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    channel.send({name: 'pageLoaded', url: pageURL});
1565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    var apiCallForwarder = new APICallForwarder();
1585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    apiCallForwarder.init(channel);
1595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    var passwordScraper = new PasswordInputScraper();
1615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    passwordScraper.init(channel, pageURL, document.documentElement);
162d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)  }
163d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)})();
164