// 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: [''], 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.} 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();