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.
7 * A background script of the auth extension that bridges the communication
8 * between the main and injected scripts.
10 * Here is an overview of the communication flow when SAML is being used:
11 * 1. The main script sends the |startAuth| signal to this background script,
12 * indicating that the authentication flow has started and SAML pages may be
14 * 2. A script is injected into each SAML page. The injected script sends three
15 * main types of messages to this background script:
16 * a) A |pageLoaded| message is sent when the page has been loaded. This is
17 * forwarded to the main script as |onAuthPageLoaded|.
18 * b) If the SAML provider supports the credential passing API, the API calls
19 * are sent to this backgroudn script as |apiCall| messages. These
20 * messages are forwarded unmodified to the main script.
21 * c) The injected script scrapes passwords. They are sent to this background
22 * script in |updatePassword| messages. The main script can request a list
23 * of the scraped passwords by sending the |getScrapedPasswords| message.
27 * BackgroundBridge allows the main script and the injected script to
28 * collaborate. It forwards credentials API calls to the main script and
29 * maintains a list of scraped passwords.
31 function BackgroundBridge() {
34 BackgroundBridge.prototype = {
35 // Gaia URL base that is set from main auth script.
38 // Whether auth flow has started. It is used as a signal of whether the
39 // injected script should scrape passwords.
45 channelInjected_: null,
48 chrome.runtime.onConnect.addListener(this.onConnect_.bind(this));
50 // Workarounds for loading SAML page in an iframe.
51 chrome.webRequest.onHeadersReceived.addListener(
53 if (!this.authStarted_)
56 var headers = details.responseHeaders;
57 for (var i = 0; headers && i < headers.length; ++i) {
58 if (headers[i].name.toLowerCase() == 'x-frame-options') {
63 return {responseHeaders: headers};
65 {urls: ['<all_urls>'], types: ['sub_frame']},
66 ['blocking', 'responseHeaders']);
69 onConnect_: function(port) {
70 if (port.name == 'authMain')
71 this.setupForAuthMain_(port);
72 else if (port.name == 'injected')
73 this.setupForInjected_(port);
75 console.error('Unexpected connection, port.name=' + port.name);
79 * Sets up the communication channel with the main script.
81 setupForAuthMain_: function(port) {
82 this.channelMain_ = new Channel();
83 this.channelMain_.init(port);
84 this.channelMain_.registerMessage(
85 'setGaiaUrl', this.onSetGaiaUrl_.bind(this));
86 this.channelMain_.registerMessage(
87 'resetAuth', this.onResetAuth_.bind(this));
88 this.channelMain_.registerMessage(
89 'startAuth', this.onAuthStarted_.bind(this));
90 this.channelMain_.registerMessage(
91 'getScrapedPasswords',
92 this.onGetScrapedPasswords_.bind(this));
96 * Sets up the communication channel with the injected script.
98 setupForInjected_: function(port) {
99 this.channelInjected_ = new Channel();
100 this.channelInjected_.init(port);
101 this.channelInjected_.registerMessage(
102 'apiCall', this.onAPICall_.bind(this));
103 this.channelInjected_.registerMessage(
104 'updatePassword', this.onUpdatePassword_.bind(this));
105 this.channelInjected_.registerMessage(
106 'pageLoaded', this.onPageLoaded_.bind(this));
110 * Handler for 'setGaiaUrl' signal sent from the main script.
112 onSetGaiaUrl_: function(msg) {
113 this.gaiaUrl_ = msg.gaiaUrl;
115 // Set request header to let Gaia know that saml support is on.
116 chrome.webRequest.onBeforeSendHeaders.addListener(
118 details.requestHeaders.push({
119 name: 'X-Cros-Auth-Ext-Support',
122 return {requestHeaders: details.requestHeaders};
124 {urls: [this.gaiaUrl_ + '*'], types: ['sub_frame']},
125 ['blocking', 'requestHeaders']);
129 * Handler for 'resetAuth' signal sent from the main script.
131 onResetAuth_: function() {
132 this.authStarted_ = false;
133 this.passwordStore_ = {};
137 * Handler for 'authStarted' signal sent from the main script.
139 onAuthStarted_: function() {
140 this.authStarted_ = true;
141 this.passwordStore_ = {};
145 * Handler for 'getScrapedPasswords' request sent from the main script.
146 * @return {Array.<string>} The array with de-duped scraped passwords.
148 onGetScrapedPasswords_: function() {
150 for (var property in this.passwordStore_) {
151 passwords[this.passwordStore_[property]] = true;
153 return Object.keys(passwords);
156 onAPICall_: function(msg) {
157 this.channelMain_.send(msg);
160 onUpdatePassword_: function(msg) {
161 if (!this.authStarted_)
164 this.passwordStore_[msg.id] = msg.password;
167 onPageLoaded_: function(msg) {
168 this.channelMain_.send({name: 'onAuthPageLoaded', url: msg.url});
172 var backgroundBridge = new BackgroundBridge();
173 backgroundBridge.run();