OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2014 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 An UI component to authenciate to Chrome. The component hosts | |
7 * IdP web pages in a webview. A client who is interested in monitoring | |
8 * authentication events should pass a listener object of type | |
9 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization, | |
10 * call {@code load} to start the authentication flow. | |
11 */ | |
12 cr.define('cr.login', function() { | |
13 'use strict'; | |
14 | |
15 var IDP_ORIGIN = 'https://accounts.google.com/'; | |
16 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide'; | |
17 var CONTINUE_URL = | |
18 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html'; | |
19 var SIGN_IN_HEADER = 'google-accounts-signin'; | |
20 var EMBEDDED_FORM_HEADER = 'google-accounts-embedded'; | |
21 var SAML_HEADER = 'google-accounts-saml'; | |
22 | |
23 /** | |
24 * The source URL parameter for the constrained signin flow. | |
25 */ | |
26 var CONSTRAINED_FLOW_SOURCE = 'chrome'; | |
27 | |
28 /** | |
29 * Enum for the authorization mode, must match AuthMode defined in | |
30 * chrome/browser/ui/webui/inline_login_ui.cc. | |
31 * @enum {number} | |
32 */ | |
33 var AuthMode = { | |
34 DEFAULT: 0, | |
35 OFFLINE: 1, | |
36 DESKTOP: 2 | |
37 }; | |
38 | |
39 /** | |
40 * Enum for the authorization type. | |
41 * @enum {number} | |
42 */ | |
43 var AuthFlow = { | |
44 DEFAULT: 0, | |
45 SAML: 1 | |
46 }; | |
47 | |
48 /** | |
49 * Initializes the authenticator component. | |
50 * @param {webview|string} webview The webview element or its ID to host IdP | |
51 * web pages. | |
52 * @param {Authenticator.Listener=} listener An optional listener for events. | |
xiyuan
2014/10/23 19:32:03
nit: listener -> opt_listner
guohui
2014/10/23 20:13:34
Done.
| |
53 * @constructor | |
54 * @extends {cr.EventTarget} | |
55 */ | |
56 function Authenticator(webview, opt_listener) { | |
57 this.webview_ = typeof webview == 'string' ? $(webview) : webview; | |
58 assert(this.webview_); | |
59 | |
60 this.listener_ = opt_listener || null; | |
61 | |
62 this.email_ = null; | |
63 this.password_ = null; | |
64 this.sessionIndex_ = null; | |
65 this.chooseWhatToSync_ = false; | |
66 this.skipForNow_ = false; | |
67 this.authFlow_ = AuthFlow.DEFAULT; | |
68 this.loaded_ = false; | |
69 this.idpOrigin_ = null; | |
70 this.continueUrl_ = null; | |
71 this.continueUrlWithoutParams_ = null; | |
72 this.initialFrameUrl_ = null; | |
73 this.reloadUrl_ = null; | |
74 } | |
75 Authenticator.prototype = Object.create(cr.EventTarget.prototype); | |
xiyuan
2014/10/23 19:32:03
If we don't dispatch event on this class, it does
guohui
2014/10/23 20:13:34
Unfortunately, to be compatible with the old event
| |
76 | |
77 /** | |
78 * An interface for receiving notifications upon authentication events. | |
79 * @interface | |
80 */ | |
81 Authenticator.Listener = function() {}; | |
82 | |
83 /** | |
84 * Invoked when authentication UI is ready. | |
85 */ | |
86 Authenticator.Listener.prototype.onReady = function(e) {}; | |
87 | |
88 /** | |
89 * Invoked when authentication is completed successfully with credential data. | |
90 * A credential data object looks like this: | |
91 * <pre> | |
92 * {@code | |
93 * { | |
94 * email: 'xx@gmail.com', | |
95 * password: 'xxxx', // May be null or empty. | |
96 * usingSAML: false, | |
97 * chooseWhatToSync: false, | |
98 * skipForNow: false, | |
99 * sessionIndex: '0' | |
100 * } | |
101 * } | |
102 * </pre> | |
103 * @param {Object} credentials A credential data object. | |
104 */ | |
105 Authenticator.Listener.prototype.onSuccess = function(credentials) {}; | |
106 | |
107 /** | |
108 * Invoked when the requested URL does not fit the container. | |
109 * @param {string} url Request URL. | |
110 */ | |
111 Authenticator.Listener.prototype.onResize = function(url) {}; | |
112 | |
113 /** | |
114 * Invoked when a new window event is fired. | |
115 * @param {Event} e Event object. | |
116 */ | |
117 Authenticator.Listener.prototype.onNewWindow = function(e) {}; | |
118 | |
119 /** | |
120 * Loads the authenticator component with the given parameters. | |
121 * @param {AuthMode} authMode Authorization mode. | |
122 * @param {Object} data Parameters for the authorization flow. | |
123 */ | |
124 Authenticator.prototype.load = function(authMode, data) { | |
125 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN; | |
126 this.continueUrl_ = data.continueUrl || CONTINUE_URL; | |
127 this.continueUrlWithoutParams_ = | |
128 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) || | |
129 this.continueUrl_; | |
130 this.isConstrainedWindow_ = data.constrained == '1'; | |
131 | |
132 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data); | |
133 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_; | |
134 this.authFlow_ = AuthFlow.DEFAULT; | |
135 | |
136 this.webview_.src = this.reloadUrl_; | |
137 this.webview_.addEventListener( | |
138 'newwindow', this.onNewWindow_.bind(this)); | |
139 this.webview_.request.onCompleted.addListener( | |
140 this.onRequestCompleted_.bind(this), | |
141 {urls: ['*://*/*', this.continueUrlWithoutParams_ + '*'], | |
142 types: ['main_frame']}, | |
143 ['responseHeaders']); | |
144 this.webview_.request.onHeadersReceived.addListener( | |
145 this.onHeadersReceived_.bind(this), | |
146 {urls: [this.idpOrigin_ + '*'], types: ['main_frame']}, | |
147 ['responseHeaders']); | |
148 window.addEventListener( | |
149 'message', this.onMessage_.bind(this), false); | |
150 }; | |
151 | |
152 /** | |
153 * Reloads the authenticator component. | |
154 */ | |
155 Authenticator.prototype.reload = function() { | |
156 this.webview_.src = this.reloadUrl_; | |
157 this.authFlow_ = AuthFlow.DEFAULT; | |
158 }; | |
159 | |
160 Authenticator.prototype.constructInitialFrameUrl_ = function(data) { | |
161 var url = this.idpOrigin_ + (data.gaiaPath || IDP_PATH); | |
162 | |
163 url = appendParam(url, 'continue', this.continueUrl_); | |
164 url = appendParam(url, 'service', data.service); | |
165 if (data.hl) | |
166 url = appendParam(url, 'hl', data.hl); | |
167 if (data.email) | |
168 url = appendParam(url, 'Email', data.email); | |
169 if (this.isConstrainedWindow_) | |
170 url = appendParam(url, 'source', CONSTRAINED_FLOW_SOURCE); | |
171 return url; | |
172 }; | |
173 | |
174 /** | |
175 * Invoked when a main frame request in the webview has completed. | |
176 * @private | |
177 */ | |
178 Authenticator.prototype.onRequestCompleted_ = function(details) { | |
179 var currentUrl = details.url; | |
180 if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) { | |
181 if (currentUrl.indexOf('ntp=1') >= 0) { | |
182 this.skipForNow_ = true; | |
183 } | |
184 this.onAuthCompleted_(); | |
185 return; | |
186 } | |
187 | |
188 if (this.isConstrainedWindow_) { | |
189 var isEmbeddedPage = false; | |
190 if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) { | |
191 var headers = details.responseHeaders; | |
192 for (var i = 0; headers && i < headers.length; ++i) { | |
193 if (headers[i].name.toLowerCase() == EMBEDDED_FORM_HEADER) { | |
194 isEmbeddedPage = true; | |
195 break; | |
196 } | |
197 } | |
198 } | |
199 if (!isEmbeddedPage && this.listener_) { | |
200 this.listener_.onResize(currentUrl); | |
201 return; | |
202 } | |
203 } | |
204 | |
205 if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) { | |
206 this.webview_.contentWindow.postMessage({}, currentUrl); | |
207 } | |
208 | |
209 if (!this.loaded_) { | |
210 this.loaded_ = true; | |
211 if (this.listener_) this.listener_.onReady(); | |
xiyuan
2014/10/23 19:32:03
nit: Put "this.listener_.onReady()" on a separate
guohui
2014/10/23 20:13:34
Done.
| |
212 } | |
213 }; | |
214 | |
215 /** | |
216 * Invoked when headers are received in the main frame of the webview. It | |
217 * 1) reads the authenticated user info from a signin header, | |
218 * 2) signals the start of a saml flow upon receiving a saml header. | |
219 * @return {!Object} Modified request headers. | |
220 * @private | |
221 */ | |
222 Authenticator.prototype.onHeadersReceived_ = function(details) { | |
223 var headers = details.responseHeaders; | |
224 for (var i = 0; headers && i < headers.length; ++i) { | |
225 var header = headers[i]; | |
226 var headerName = header.name.toLowerCase(); | |
227 if (headerName == SIGN_IN_HEADER) { | |
228 var headerValues = header.value.toLowerCase().split(','); | |
229 var signinDetails = {}; | |
230 headerValues.forEach(function(e) { | |
231 var pair = e.split('='); | |
232 signinDetails[pair[0].trim()] = pair[1].trim(); | |
233 }); | |
234 // Removes "" around. | |
235 var email = signinDetails['email'].slice(1, -1); | |
236 if (this.email_ != email) { | |
237 this.email_ = email; | |
238 // Clears the scraped password if the email has changed. | |
239 this.password_ = null; | |
240 } | |
241 this.sessionIndex_ = signinDetails['sessionindex']; | |
242 } else if (headerName == SAML_HEADER) { | |
243 this.authFlow_ = AuthFlow.SAML; | |
244 } | |
245 } | |
246 }; | |
247 | |
248 /** | |
249 * Invoked when an HTML5 message is received. | |
250 * @param {object} e Payload of the received HTML5 message. | |
251 * @private | |
252 */ | |
253 Authenticator.prototype.onMessage_ = function(e) { | |
254 if (e.origin != this.idpOrigin_) { | |
255 return; | |
256 } | |
257 | |
258 var msg = e.data; | |
259 | |
260 if (msg.method == 'attemptLogin') { | |
261 this.email_ = msg.email; | |
262 this.password_ = msg.password; | |
263 this.chooseWhatToSync_ = msg.chooseWhatToSync; | |
264 } | |
265 }; | |
266 | |
267 /** | |
268 * Invoked to process authentication completion. | |
269 * @private | |
270 */ | |
271 Authenticator.prototype.onAuthCompleted_ = function() { | |
272 if (!this.listener_) return; | |
273 | |
274 if (!this.email_ && !this.skipForNow_) { | |
275 this.webview_.src = this.initialFrameUrl_; | |
276 return; | |
277 } | |
278 | |
279 this.listener_.onSuccess({email: this.email_, | |
280 password: this.password_, | |
281 usingSAML: this.authFlow_ == AuthFlow.SAML, | |
282 chooseWhatToSync: this.chooseWhatToSync_, | |
283 skipForNow: this.skipForNow_, | |
284 sessionIndex: this.sessionIndex_ || ''}); | |
285 }; | |
286 | |
287 /** | |
288 * Invoked when the webview attempts to open a new window. | |
289 * @private | |
290 */ | |
291 Authenticator.prototype.onNewWindow_ = function(e) { | |
292 if (!this.listener_) return; | |
293 | |
294 this.listener_.onNewWindow(e); | |
295 }; | |
296 | |
297 Authenticator.AuthFlow = AuthFlow; | |
298 Authenticator.AuthMode = AuthMode; | |
299 | |
300 return { | |
301 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old | |
302 // iframe-based flow is deprecated. | |
303 GaiaAuthHost: Authenticator | |
304 }; | |
305 }); | |
OLD | NEW |