OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview | 6 * @fileoverview |
7 * A background script of the auth extension that bridges the communication | 7 * A background script of the auth extension that bridges the communication |
8 * between the main and injected scripts. | 8 * between the main and injected scripts. |
9 * | 9 * |
10 * Here is an overview of the communication flow when SAML is being used: | 10 * Here is an overview of the communication flow when SAML is being used: |
(...skipping 14 matching lines...) Expand all Loading... |
25 | 25 |
26 /** | 26 /** |
27 * BackgroundBridge allows the main script and the injected script to | 27 * BackgroundBridge allows the main script and the injected script to |
28 * collaborate. It forwards credentials API calls to the main script and | 28 * collaborate. It forwards credentials API calls to the main script and |
29 * maintains a list of scraped passwords. | 29 * maintains a list of scraped passwords. |
30 */ | 30 */ |
31 function BackgroundBridge() { | 31 function BackgroundBridge() { |
32 } | 32 } |
33 | 33 |
34 BackgroundBridge.prototype = { | 34 BackgroundBridge.prototype = { |
| 35 // Continue URL that is set from main auth script. |
| 36 continueUrl_: null, |
| 37 |
| 38 // Whether the extension is loaded in a constrained window. |
| 39 // Set from main auth script. |
| 40 isConstrainedWindow_: null, |
| 41 |
| 42 // Email of the newly authenticated user based on the gaia response header |
| 43 // 'google-accounts-signin'. |
| 44 email_: null, |
| 45 |
| 46 // Session index of the newly authenticated user based on the gaia response |
| 47 // header 'google-accounts-signin'. |
| 48 sessionIndex_: null, |
| 49 |
35 // Gaia URL base that is set from main auth script. | 50 // Gaia URL base that is set from main auth script. |
36 gaiaUrl_: null, | 51 gaiaUrl_: null, |
37 | 52 |
38 // Whether auth flow has started. It is used as a signal of whether the | 53 // Whether auth flow has started. It is used as a signal of whether the |
39 // injected script should scrape passwords. | 54 // injected script should scrape passwords. |
40 authStarted_: false, | 55 authStarted_: false, |
41 | 56 |
42 passwordStore_: {}, | 57 passwordStore_: {}, |
43 | 58 |
44 channelMain_: null, | 59 channelMain_: {}, |
45 channelInjected_: null, | 60 channelInjected_: {}, |
46 | 61 |
47 run: function() { | 62 run: function() { |
48 chrome.runtime.onConnect.addListener(this.onConnect_.bind(this)); | 63 chrome.runtime.onConnect.addListener(this.onConnect_.bind(this)); |
49 | 64 |
50 // Workarounds for loading SAML page in an iframe. | 65 // Workarounds for loading SAML page in an iframe. |
51 chrome.webRequest.onHeadersReceived.addListener( | 66 chrome.webRequest.onHeadersReceived.addListener( |
52 function(details) { | 67 function(details) { |
53 if (!this.authStarted_) | 68 if (!this.authStarted_) |
54 return; | 69 return; |
55 | 70 |
(...skipping 16 matching lines...) Expand all Loading... |
72 else if (port.name == 'injected') | 87 else if (port.name == 'injected') |
73 this.setupForInjected_(port); | 88 this.setupForInjected_(port); |
74 else | 89 else |
75 console.error('Unexpected connection, port.name=' + port.name); | 90 console.error('Unexpected connection, port.name=' + port.name); |
76 }, | 91 }, |
77 | 92 |
78 /** | 93 /** |
79 * Sets up the communication channel with the main script. | 94 * Sets up the communication channel with the main script. |
80 */ | 95 */ |
81 setupForAuthMain_: function(port) { | 96 setupForAuthMain_: function(port) { |
82 this.channelMain_ = new Channel(); | 97 var currentChannel = new Channel(); |
83 this.channelMain_.init(port); | 98 currentChannel.init(port); |
84 this.channelMain_.registerMessage( | 99 |
| 100 // Registers for desktop related messages. |
| 101 currentChannel.registerMessage( |
| 102 'initDesktopFlow', this.onInitDesktopFlow_.bind(this)); |
| 103 |
| 104 // Registers for SAML related messages. |
| 105 currentChannel.registerMessage( |
85 'setGaiaUrl', this.onSetGaiaUrl_.bind(this)); | 106 'setGaiaUrl', this.onSetGaiaUrl_.bind(this)); |
86 this.channelMain_.registerMessage( | 107 currentChannel.registerMessage( |
87 'resetAuth', this.onResetAuth_.bind(this)); | 108 'resetAuth', this.onResetAuth_.bind(this)); |
88 this.channelMain_.registerMessage( | 109 currentChannel.registerMessage( |
89 'startAuth', this.onAuthStarted_.bind(this)); | 110 'startAuth', this.onAuthStarted_.bind(this)); |
90 this.channelMain_.registerMessage( | 111 currentChannel.registerMessage( |
91 'getScrapedPasswords', | 112 'getScrapedPasswords', |
92 this.onGetScrapedPasswords_.bind(this)); | 113 this.onGetScrapedPasswords_.bind(this)); |
| 114 |
| 115 currentChannel.send({ |
| 116 'name': 'channelConnected' |
| 117 }); |
| 118 this.channelMain_[this.getTabIdFromPort_(port)] = currentChannel; |
93 }, | 119 }, |
94 | 120 |
95 /** | 121 /** |
96 * Sets up the communication channel with the injected script. | 122 * Sets up the communication channel with the injected script. |
97 */ | 123 */ |
98 setupForInjected_: function(port) { | 124 setupForInjected_: function(port) { |
99 this.channelInjected_ = new Channel(); | 125 var currentChannel = new Channel(); |
100 this.channelInjected_.init(port); | 126 currentChannel.init(port); |
101 this.channelInjected_.registerMessage( | 127 |
102 'apiCall', this.onAPICall_.bind(this)); | 128 var tabId = this.getTabIdFromPort_(port); |
103 this.channelInjected_.registerMessage( | 129 currentChannel.registerMessage( |
| 130 'apiCall', this.onAPICall_.bind(this, tabId)); |
| 131 currentChannel.registerMessage( |
104 'updatePassword', this.onUpdatePassword_.bind(this)); | 132 'updatePassword', this.onUpdatePassword_.bind(this)); |
105 this.channelInjected_.registerMessage( | 133 currentChannel.registerMessage( |
106 'pageLoaded', this.onPageLoaded_.bind(this)); | 134 'pageLoaded', this.onPageLoaded_.bind(this, tabId)); |
| 135 |
| 136 this.channelInjected_[this.getTabIdFromPort_(port)] = currentChannel; |
| 137 }, |
| 138 |
| 139 getTabIdFromPort_: function(port) { |
| 140 return port.sender.tab ? port.sender.tab.id : -1; |
107 }, | 141 }, |
108 | 142 |
109 /** | 143 /** |
| 144 * Handler for 'initDesktopFlow' signal sent from the main script. |
| 145 * Only called in desktop mode. |
| 146 */ |
| 147 onInitDesktopFlow_: function(msg) { |
| 148 this.gaiaUrl_ = msg.gaiaUrl; |
| 149 this.continueUrl_ = msg.continueUrl; |
| 150 this.isConstrainedWindow_ = msg.isConstrainedWindow; |
| 151 |
| 152 var urls = []; |
| 153 var filter = {urls: urls, types: ['sub_frame']}; |
| 154 var optExtraInfoSpec = []; |
| 155 if (msg.isConstrainedWindow) { |
| 156 urls.push('<all_urls>'); |
| 157 optExtraInfoSpec.push('responseHeaders'); |
| 158 } else { |
| 159 urls.push(this.continueUrl_ + '*'); |
| 160 } |
| 161 |
| 162 chrome.webRequest.onCompleted.addListener( |
| 163 this.onRequestCompletedInDesktopMode_.bind(this), |
| 164 filter, optExtraInfoSpec); |
| 165 chrome.webRequest.onHeadersReceived.addListener( |
| 166 this.onHeadersReceivedInDesktopMode_.bind(this), |
| 167 {urls: [this.gaiaUrl_ + '*'], types: ['sub_frame']}, |
| 168 ['responseHeaders']); |
| 169 }, |
| 170 |
| 171 /** |
| 172 * Event listener for webRequest.onCompleted in desktop mode. |
| 173 */ |
| 174 onRequestCompletedInDesktopMode_: function(details) { |
| 175 var msg = null; |
| 176 if (details.url.lastIndexOf(this.continueUrl_, 0) == 0) { |
| 177 var skipForNow = false; |
| 178 if (details.url.indexOf('ntp=1') >= 0) { |
| 179 skipForNow = true; |
| 180 } |
| 181 msg = { |
| 182 'name': 'completeLogin', |
| 183 'email': this.email_, |
| 184 'sessionIndex': this.sessionIndex_, |
| 185 'skipForNow': skipForNow |
| 186 }; |
| 187 } else if (this.isConstrainedWindow_) { |
| 188 var headers = details.responseHeaders; |
| 189 for (var i = 0; headers && i < headers.length; ++i) { |
| 190 if (headers[i].name.toLowerCase() == 'google-accounts-embedded') { |
| 191 return; |
| 192 } |
| 193 } |
| 194 msg = { |
| 195 'name': 'switchToFullTab', |
| 196 'url': details.url |
| 197 }; |
| 198 } |
| 199 |
| 200 if (msg != null) |
| 201 this.channelMain_[details.tabId].send(msg); |
| 202 }, |
| 203 |
| 204 /** |
| 205 * Event listener for webRequest.onHeadersReceived in desktop mode. |
| 206 */ |
| 207 onHeadersReceivedInDesktopMode_: function(details) { |
| 208 var headers = details.responseHeaders; |
| 209 for (var i = 0; headers && i < headers.length; ++i) { |
| 210 if (headers[i].name.toLowerCase() == 'google-accounts-signin') { |
| 211 var headerValues = headers[i].value.toLowerCase().split(','); |
| 212 var signinDetails = {}; |
| 213 headerValues.forEach(function(e) { |
| 214 var pair = e.split('='); |
| 215 signinDetails[pair[0].trim()] = pair[1].trim(); |
| 216 }); |
| 217 this.email_ = signinDetails['email'].slice(1, -1); // Remove "" around. |
| 218 this.sessionIndex_ = signinDetails['sessionindex']; |
| 219 return; |
| 220 } |
| 221 } |
| 222 }, |
| 223 |
| 224 /** |
110 * Handler for 'setGaiaUrl' signal sent from the main script. | 225 * Handler for 'setGaiaUrl' signal sent from the main script. |
111 */ | 226 */ |
112 onSetGaiaUrl_: function(msg) { | 227 onSetGaiaUrl_: function(msg) { |
113 this.gaiaUrl_ = msg.gaiaUrl; | 228 this.gaiaUrl_ = msg.gaiaUrl; |
114 | 229 |
115 // Set request header to let Gaia know that saml support is on. | 230 // Set request header to let Gaia know that saml support is on. |
116 chrome.webRequest.onBeforeSendHeaders.addListener( | 231 chrome.webRequest.onBeforeSendHeaders.addListener( |
117 function(details) { | 232 function(details) { |
118 details.requestHeaders.push({ | 233 details.requestHeaders.push({ |
119 name: 'X-Cros-Auth-Ext-Support', | 234 name: 'X-Cros-Auth-Ext-Support', |
(...skipping 26 matching lines...) Expand all Loading... |
146 * @return {Array.<string>} The array with de-duped scraped passwords. | 261 * @return {Array.<string>} The array with de-duped scraped passwords. |
147 */ | 262 */ |
148 onGetScrapedPasswords_: function() { | 263 onGetScrapedPasswords_: function() { |
149 var passwords = {}; | 264 var passwords = {}; |
150 for (var property in this.passwordStore_) { | 265 for (var property in this.passwordStore_) { |
151 passwords[this.passwordStore_[property]] = true; | 266 passwords[this.passwordStore_[property]] = true; |
152 } | 267 } |
153 return Object.keys(passwords); | 268 return Object.keys(passwords); |
154 }, | 269 }, |
155 | 270 |
156 onAPICall_: function(msg) { | 271 onAPICall_: function(tabId, msg) { |
157 this.channelMain_.send(msg); | 272 if (tabId in this.channelMain_) { |
| 273 this.channelMain_[tabId].send(msg); |
| 274 } |
158 }, | 275 }, |
159 | 276 |
160 onUpdatePassword_: function(msg) { | 277 onUpdatePassword_: function(msg) { |
161 if (!this.authStarted_) | 278 if (!this.authStarted_) |
162 return; | 279 return; |
163 | 280 |
164 this.passwordStore_[msg.id] = msg.password; | 281 this.passwordStore_[msg.id] = msg.password; |
165 }, | 282 }, |
166 | 283 |
167 onPageLoaded_: function(msg) { | 284 onPageLoaded_: function(tabId, msg) { |
168 this.channelMain_.send({name: 'onAuthPageLoaded', url: msg.url}); | 285 if (tabId in this.channelMain_) { |
| 286 this.channelMain_[tabId].send({name: 'onAuthPageLoaded', url: msg.url}); |
| 287 } |
169 } | 288 } |
170 }; | 289 }; |
171 | 290 |
172 var backgroundBridge = new BackgroundBridge(); | 291 var backgroundBridge = new BackgroundBridge(); |
173 backgroundBridge.run(); | 292 backgroundBridge.run(); |
OLD | NEW |