Javascript  |  158行  |  4.71 KB

// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/**
 * @fileoverview
 * The background script of auth extension that bridges the communications
 * between main and injected script.
 * Here are the communications along a SAML sign-in flow:
 * 1. Main script sends an 'onAuthStarted' signal to indicate the authentication
 *    flow is started and SAML pages might be loaded from now on;
 * 2. After the 'onAuthTstarted' signal, injected script starts to scraping
 *    all password fields on normal page (i.e. http or https) and sends page
 *    load signal as well as the passwords to the background script here;
 */

/**
 * BackgroundBridge holds the main script's state and the scraped passwords
 * from the injected script to help the two collaborate.
 */
function BackgroundBridge() {
}

BackgroundBridge.prototype = {
  // Gaia URL base that is set from main auth script.
  gaiaUrl_: null,

  // Whether auth flow has started. It is used as a signal of whether the
  // injected script should scrape passwords.
  authStarted_: false,

  passwordStore_: {},

  channelMain_: null,
  channelInjected_: null,

  run: function() {
    chrome.runtime.onConnect.addListener(this.onConnect_.bind(this));

    // Workarounds for loading SAML page in an iframe.
    chrome.webRequest.onHeadersReceived.addListener(
        function(details) {
          if (!this.authStarted_)
            return;

          var headers = details.responseHeaders;
          for (var i = 0; headers && i < headers.length; ++i) {
            if (headers[i].name.toLowerCase() == 'x-frame-options') {
              headers.splice(i, 1);
              break;
            }
          }
          return {responseHeaders: headers};
        }.bind(this),
        {urls: ['<all_urls>'], types: ['sub_frame']},
        ['blocking', 'responseHeaders']);
  },

  onConnect_: function(port) {
    if (port.name == 'authMain')
      this.setupForAuthMain_(port);
    else if (port.name == 'injected')
      this.setupForInjected_(port);
    else
      console.error('Unexpected connection, port.name=' + port.name);
  },

  /**
   * Sets up the communication channel with the main script.
   */
  setupForAuthMain_: function(port) {
    this.channelMain_ = new Channel();
    this.channelMain_.init(port);
    this.channelMain_.registerMessage(
        'setGaiaUrl', this.onSetGaiaUrl_.bind(this));
    this.channelMain_.registerMessage(
        'resetAuth', this.onResetAuth_.bind(this));
    this.channelMain_.registerMessage(
        'startAuth', this.onAuthStarted_.bind(this));
    this.channelMain_.registerMessage(
        'getScrapedPasswords',
        this.onGetScrapedPasswords_.bind(this));
  },

  /**
   * Sets up the communication channel with the injected script.
   */
  setupForInjected_: function(port) {
    this.channelInjected_ = new Channel();
    this.channelInjected_.init(port);
    this.channelInjected_.registerMessage(
        'updatePassword', this.onUpdatePassword_.bind(this));
    this.channelInjected_.registerMessage(
        'pageLoaded', this.onPageLoaded_.bind(this));
  },

  /**
   * Handler for 'setGaiaUrl' signal sent from the main script.
   */
  onSetGaiaUrl_: function(msg) {
    this.gaiaUrl_ = msg.gaiaUrl;

    // Set request header to let Gaia know that saml support is on.
    chrome.webRequest.onBeforeSendHeaders.addListener(
        function(details) {
          details.requestHeaders.push({
            name: 'X-Cros-Auth-Ext-Support',
            value: 'SAML'
          });
          return {requestHeaders: details.requestHeaders};
        },
        {urls: [this.gaiaUrl_ + '*'], types: ['sub_frame']},
        ['blocking', 'requestHeaders']);
  },

  /**
   * Handler for 'resetAuth' signal sent from the main script.
   */
  onResetAuth_: function() {
    this.authStarted_ = false;
    this.passwordStore_ = {};
  },

  /**
   * Handler for 'authStarted' signal sent from the main script.
   */
  onAuthStarted_: function() {
    this.authStarted_ = true;
    this.passwordStore_ = {};
  },

  /**
   * Handler for 'getScrapedPasswords' request sent from the main script.
   * @return {Array.<string>} The array with de-duped scraped passwords.
   */
  onGetScrapedPasswords_: function() {
    var passwords = {};
    for (var property in this.passwordStore_) {
      passwords[this.passwordStore_[property]] = true;
    }
    return Object.keys(passwords);
  },

  onUpdatePassword_: function(msg) {
    if (!this.authStarted_)
      return;

    this.passwordStore_[msg.id] = msg.password;
  },

  onPageLoaded_: function(msg) {
    this.channelMain_.send({name: 'onAuthPageLoaded', url: msg.url});
  }
};

var backgroundBridge = new BackgroundBridge();
backgroundBridge.run();