• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4
5/**
6 * @fileoverview
7 * The background script of auth extension that bridges the communications
8 * between main and injected script.
9 * Here are the communications along a SAML sign-in flow:
10 * 1. Main script sends an 'onAuthStarted' signal to indicate the authentication
11 *    flow is started and SAML pages might be loaded from now on;
12 * 2. After the 'onAuthTstarted' signal, injected script starts to scraping
13 *    all password fields on normal page (i.e. http or https) and sends page
14 *    load signal as well as the passwords to the background script here;
15 */
16
17/**
18 * BackgroundBridge holds the main script's state and the scraped passwords
19 * from the injected script to help the two collaborate.
20 */
21function BackgroundBridge() {
22}
23
24BackgroundBridge.prototype = {
25  // Gaia URL base that is set from main auth script.
26  gaiaUrl_: null,
27
28  // Whether auth flow has started. It is used as a signal of whether the
29  // injected script should scrape passwords.
30  authStarted_: false,
31
32  passwordStore_: {},
33
34  channelMain_: null,
35  channelInjected_: null,
36
37  run: function() {
38    chrome.runtime.onConnect.addListener(this.onConnect_.bind(this));
39
40    // Workarounds for loading SAML page in an iframe.
41    chrome.webRequest.onHeadersReceived.addListener(
42        function(details) {
43          if (!this.authStarted_)
44            return;
45
46          var headers = details.responseHeaders;
47          for (var i = 0; headers && i < headers.length; ++i) {
48            if (headers[i].name.toLowerCase() == 'x-frame-options') {
49              headers.splice(i, 1);
50              break;
51            }
52          }
53          return {responseHeaders: headers};
54        }.bind(this),
55        {urls: ['<all_urls>'], types: ['sub_frame']},
56        ['blocking', 'responseHeaders']);
57  },
58
59  onConnect_: function(port) {
60    if (port.name == 'authMain')
61      this.setupForAuthMain_(port);
62    else if (port.name == 'injected')
63      this.setupForInjected_(port);
64    else
65      console.error('Unexpected connection, port.name=' + port.name);
66  },
67
68  /**
69   * Sets up the communication channel with the main script.
70   */
71  setupForAuthMain_: function(port) {
72    this.channelMain_ = new Channel();
73    this.channelMain_.init(port);
74    this.channelMain_.registerMessage(
75        'setGaiaUrl', this.onSetGaiaUrl_.bind(this));
76    this.channelMain_.registerMessage(
77        'resetAuth', this.onResetAuth_.bind(this));
78    this.channelMain_.registerMessage(
79        'startAuth', this.onAuthStarted_.bind(this));
80    this.channelMain_.registerMessage(
81        'getScrapedPasswords',
82        this.onGetScrapedPasswords_.bind(this));
83  },
84
85  /**
86   * Sets up the communication channel with the injected script.
87   */
88  setupForInjected_: function(port) {
89    this.channelInjected_ = new Channel();
90    this.channelInjected_.init(port);
91    this.channelInjected_.registerMessage(
92        'updatePassword', this.onUpdatePassword_.bind(this));
93    this.channelInjected_.registerMessage(
94        'pageLoaded', this.onPageLoaded_.bind(this));
95  },
96
97  /**
98   * Handler for 'setGaiaUrl' signal sent from the main script.
99   */
100  onSetGaiaUrl_: function(msg) {
101    this.gaiaUrl_ = msg.gaiaUrl;
102
103    // Set request header to let Gaia know that saml support is on.
104    chrome.webRequest.onBeforeSendHeaders.addListener(
105        function(details) {
106          details.requestHeaders.push({
107            name: 'X-Cros-Auth-Ext-Support',
108            value: 'SAML'
109          });
110          return {requestHeaders: details.requestHeaders};
111        },
112        {urls: [this.gaiaUrl_ + '*'], types: ['sub_frame']},
113        ['blocking', 'requestHeaders']);
114  },
115
116  /**
117   * Handler for 'resetAuth' signal sent from the main script.
118   */
119  onResetAuth_: function() {
120    this.authStarted_ = false;
121    this.passwordStore_ = {};
122  },
123
124  /**
125   * Handler for 'authStarted' signal sent from the main script.
126   */
127  onAuthStarted_: function() {
128    this.authStarted_ = true;
129    this.passwordStore_ = {};
130  },
131
132  /**
133   * Handler for 'getScrapedPasswords' request sent from the main script.
134   * @return {Array.<string>} The array with de-duped scraped passwords.
135   */
136  onGetScrapedPasswords_: function() {
137    var passwords = {};
138    for (var property in this.passwordStore_) {
139      passwords[this.passwordStore_[property]] = true;
140    }
141    return Object.keys(passwords);
142  },
143
144  onUpdatePassword_: function(msg) {
145    if (!this.authStarted_)
146      return;
147
148    this.passwordStore_[msg.id] = msg.password;
149  },
150
151  onPageLoaded_: function(msg) {
152    this.channelMain_.send({name: 'onAuthPageLoaded', url: msg.url});
153  }
154};
155
156var backgroundBridge = new BackgroundBridge();
157backgroundBridge.run();
158