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