// 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();