OLD | NEW |
| (Empty) |
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 An UI component to host gaia auth extension in an iframe. | |
7 * After the component binds with an iframe, call its {@code load} to start the | |
8 * authentication flow. There are two events would be raised after this point: | |
9 * a 'ready' event when the authentication UI is ready to use and a 'completed' | |
10 * event when the authentication is completed successfully. If caller is | |
11 * interested in the user credentials, they may supply a success callback with | |
12 * {@code load} call. The callback will be invoked when the authentication is | |
13 * completed successfully and with the available credential data. | |
14 */ | |
15 | |
16 cr.define('cr.login', function() { | |
17 'use strict'; | |
18 | |
19 /** | |
20 * Base URL of gaia auth extension. | |
21 * @const | |
22 */ | |
23 var AUTH_URL_BASE = 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik'; | |
24 | |
25 /** | |
26 * Auth URL to use for online flow. | |
27 * @const | |
28 */ | |
29 var AUTH_URL = AUTH_URL_BASE + '/main.html'; | |
30 | |
31 /** | |
32 * Auth URL to use for offline flow. | |
33 * @const | |
34 */ | |
35 var OFFLINE_AUTH_URL = AUTH_URL_BASE + '/offline.html'; | |
36 | |
37 /** | |
38 * Origin of the gaia sign in page. | |
39 * @const | |
40 */ | |
41 var GAIA_ORIGIN = 'https://accounts.google.com'; | |
42 | |
43 /** | |
44 * Supported params of auth extension. For a complete list, check out the | |
45 * auth extension's main.js. | |
46 * @type {!Array<string>} | |
47 * @const | |
48 */ | |
49 var SUPPORTED_PARAMS = [ | |
50 'gaiaUrl', // Gaia url to use; | |
51 'gaiaPath', // Gaia path to use without a leading slash; | |
52 'hl', // Language code for the user interface; | |
53 'email', // Pre-fill the email field in Gaia UI; | |
54 'service', // Name of Gaia service; | |
55 'continueUrl', // Continue url to use; | |
56 'frameUrl', // Initial frame URL to use. If empty defaults to gaiaUrl. | |
57 'useEafe', // Whether to use EAFE. | |
58 'clientId', // Chrome's client id. | |
59 'constrained' // Whether the extension is loaded in a constrained window; | |
60 ]; | |
61 | |
62 /** | |
63 * Supported localized strings. For a complete list, check out the auth | |
64 * extension's offline.js | |
65 * @type {!Array<string>} | |
66 * @const | |
67 */ | |
68 var LOCALIZED_STRING_PARAMS = [ | |
69 'stringSignIn', | |
70 'stringEmail', | |
71 'stringPassword', | |
72 'stringEmptyEmail', | |
73 'stringEmptyPassword', | |
74 'stringError' | |
75 ]; | |
76 | |
77 /** | |
78 * Enum for the authorization mode, must match AuthMode defined in | |
79 * chrome/browser/ui/webui/inline_login_ui.cc. | |
80 * @enum {number} | |
81 */ | |
82 var AuthMode = { | |
83 DEFAULT: 0, | |
84 OFFLINE: 1, | |
85 DESKTOP: 2 | |
86 }; | |
87 | |
88 /** | |
89 * Enum for the auth flow. | |
90 * @enum {number} | |
91 */ | |
92 var AuthFlow = { | |
93 GAIA: 0, | |
94 SAML: 1 | |
95 }; | |
96 | |
97 /** | |
98 * Creates a new gaia auth extension host. | |
99 * @param {HTMLIFrameElement|string} container The iframe element or its id | |
100 * to host the auth extension. | |
101 * @constructor | |
102 * @extends {cr.EventTarget} | |
103 */ | |
104 function GaiaAuthHost(container) { | |
105 this.frame_ = typeof container == 'string' ? $(container) : container; | |
106 assert(this.frame_); | |
107 window.addEventListener('message', | |
108 this.onMessage_.bind(this), false); | |
109 } | |
110 | |
111 GaiaAuthHost.prototype = { | |
112 __proto__: cr.EventTarget.prototype, | |
113 | |
114 /** | |
115 * Auth extension params | |
116 * @type {Object} | |
117 */ | |
118 authParams_: {}, | |
119 | |
120 /** | |
121 * An url to use with {@code reload}. | |
122 * @type {?string} | |
123 * @private | |
124 */ | |
125 reloadUrl_: null, | |
126 | |
127 /** | |
128 * Invoked when authentication is completed successfully with credential | |
129 * data. A credential data object looks like this: | |
130 * <pre> | |
131 * {@code | |
132 * { | |
133 * email: 'xx@gmail.com', | |
134 * password: 'xxxx', // May not present | |
135 * authCode: 'x/xx', // May not present | |
136 * authMode: 'x', // Authorization mode, default/offline/desktop. | |
137 * } | |
138 * } | |
139 * </pre> | |
140 * @type {function(Object)} | |
141 * @private | |
142 */ | |
143 successCallback_: null, | |
144 | |
145 /** | |
146 * Invoked when the auth flow needs a user to confirm their passwords. This | |
147 * could happen when there are more than one passwords scraped during SAML | |
148 * flow. The embedder of GaiaAuthHost should show an UI to collect a | |
149 * password from user then call GaiaAuthHost.verifyConfirmedPassword to | |
150 * verify. If the password is good, the auth flow continues with success | |
151 * path. Otherwise, confirmPasswordCallback_ is invoked again. | |
152 * @type {function()} | |
153 */ | |
154 confirmPasswordCallback_: null, | |
155 | |
156 /** | |
157 * Similar to confirmPasswordCallback_ but is used when there is no | |
158 * password scraped after a success authentication. The authenticated user | |
159 * account is passed to the callback. The embedder should take over the | |
160 * flow and decide what to do next. | |
161 * @type {function(string)} | |
162 */ | |
163 noPasswordCallback_: null, | |
164 | |
165 /** | |
166 * Invoked when the authentication flow had to be aborted because content | |
167 * served over an unencrypted connection was detected. | |
168 */ | |
169 insecureContentBlockedCallback_: null, | |
170 | |
171 /** | |
172 * Invoked to display an error message to the user when a GAIA error occurs | |
173 * during authentication. | |
174 * @type {function()} | |
175 */ | |
176 missingGaiaInfoCallback_: null, | |
177 | |
178 /** | |
179 * Invoked to record that the credentials passing API was used. | |
180 * @type {function()} | |
181 */ | |
182 samlApiUsedCallback_: null, | |
183 | |
184 /** | |
185 * The iframe container. | |
186 * @type {HTMLIFrameElement} | |
187 */ | |
188 get frame() { | |
189 return this.frame_; | |
190 }, | |
191 | |
192 /** | |
193 * Sets confirmPasswordCallback_. | |
194 * @type {function()} | |
195 */ | |
196 set confirmPasswordCallback(callback) { | |
197 this.confirmPasswordCallback_ = callback; | |
198 }, | |
199 | |
200 /** | |
201 * Sets noPasswordCallback_. | |
202 * @type {function()} | |
203 */ | |
204 set noPasswordCallback(callback) { | |
205 this.noPasswordCallback_ = callback; | |
206 }, | |
207 | |
208 /** | |
209 * Sets insecureContentBlockedCallback_. | |
210 * @type {function(string)} | |
211 */ | |
212 set insecureContentBlockedCallback(callback) { | |
213 this.insecureContentBlockedCallback_ = callback; | |
214 }, | |
215 | |
216 /** | |
217 * Sets missingGaiaInfoCallback_. | |
218 * @type {function()} | |
219 */ | |
220 set missingGaiaInfoCallback(callback) { | |
221 this.missingGaiaInfoCallback_ = callback; | |
222 }, | |
223 | |
224 /** | |
225 * Sets samlApiUsedCallback_. | |
226 * @type {function()} | |
227 */ | |
228 set samlApiUsedCallback(callback) { | |
229 this.samlApiUsedCallback_ = callback; | |
230 }, | |
231 | |
232 /** | |
233 * Loads the auth extension. | |
234 * @param {AuthMode} authMode Authorization mode. | |
235 * @param {Object} data Parameters for the auth extension. See the auth | |
236 * extension's main.js for all supported params and their defaults. | |
237 * @param {function(Object)} successCallback A function to be called when | |
238 * the authentication is completed successfully. The callback is | |
239 * invoked with a credential object. | |
240 */ | |
241 load: function(authMode, data, successCallback) { | |
242 var params = {}; | |
243 | |
244 var populateParams = function(nameList, values) { | |
245 if (!values) | |
246 return; | |
247 | |
248 for (var i in nameList) { | |
249 var name = nameList[i]; | |
250 if (values[name]) | |
251 params[name] = values[name]; | |
252 } | |
253 }; | |
254 | |
255 populateParams(SUPPORTED_PARAMS, data); | |
256 populateParams(LOCALIZED_STRING_PARAMS, data.localizedStrings); | |
257 params['needPassword'] = true; | |
258 | |
259 var url; | |
260 switch (authMode) { | |
261 case AuthMode.OFFLINE: | |
262 url = OFFLINE_AUTH_URL; | |
263 break; | |
264 case AuthMode.DESKTOP: | |
265 url = AUTH_URL; | |
266 params['desktopMode'] = true; | |
267 break; | |
268 default: | |
269 url = AUTH_URL; | |
270 } | |
271 | |
272 this.authParams_ = params; | |
273 this.reloadUrl_ = url; | |
274 this.successCallback_ = successCallback; | |
275 | |
276 this.reload(); | |
277 }, | |
278 | |
279 /** | |
280 * Reloads the auth extension. | |
281 */ | |
282 reload: function() { | |
283 this.frame_.src = this.reloadUrl_; | |
284 this.authFlow = AuthFlow.GAIA; | |
285 }, | |
286 | |
287 /** | |
288 * Verifies the supplied password by sending it to the auth extension, | |
289 * which will then check if it matches the scraped passwords. | |
290 * @param {string} password The confirmed password that needs verification. | |
291 */ | |
292 verifyConfirmedPassword: function(password) { | |
293 var msg = { | |
294 method: 'verifyConfirmedPassword', | |
295 password: password | |
296 }; | |
297 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE); | |
298 }, | |
299 | |
300 /** | |
301 * Invoked to process authentication success. | |
302 * @param {Object} credentials Credential object to pass to success | |
303 * callback. | |
304 * @private | |
305 */ | |
306 onAuthSuccess_: function(credentials) { | |
307 if (this.successCallback_) | |
308 this.successCallback_(credentials); | |
309 cr.dispatchSimpleEvent(this, 'completed'); | |
310 }, | |
311 | |
312 /** | |
313 * Checks if message comes from the loaded authentication extension. | |
314 * @param {Object} e Payload of the received HTML5 message. | |
315 * @type {boolean} | |
316 */ | |
317 isAuthExtMessage_: function(e) { | |
318 return this.frame_.src && | |
319 this.frame_.src.startsWith(e.origin) && | |
320 e.source == this.frame_.contentWindow; | |
321 }, | |
322 | |
323 /** | |
324 * Event handler that is invoked when HTML5 message is received. | |
325 * @param {object} e Payload of the received HTML5 message. | |
326 */ | |
327 onMessage_: function(e) { | |
328 var msg = e.data; | |
329 | |
330 if (!this.isAuthExtMessage_(e)) | |
331 return; | |
332 | |
333 if (msg.method == 'loginUIDOMContentLoaded') { | |
334 this.frame_.contentWindow.postMessage(this.authParams_, AUTH_URL_BASE); | |
335 return; | |
336 } | |
337 | |
338 if (msg.method == 'loginUILoaded') { | |
339 cr.dispatchSimpleEvent(this, 'ready'); | |
340 return; | |
341 } | |
342 | |
343 if (/^complete(Login|Authentication)$|^offlineLogin$/.test(msg.method)) { | |
344 if (!msg.email && !this.email_ && !msg.skipForNow) { | |
345 var msg = {method: 'redirectToSignin'}; | |
346 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE); | |
347 return; | |
348 } | |
349 this.onAuthSuccess_({email: msg.email, | |
350 password: msg.password, | |
351 gaiaId: msg.gaiaId, | |
352 useOffline: msg.method == 'offlineLogin', | |
353 usingSAML: msg.usingSAML || false, | |
354 chooseWhatToSync: msg.chooseWhatToSync, | |
355 skipForNow: msg.skipForNow || false, | |
356 sessionIndex: msg.sessionIndex || ''}); | |
357 return; | |
358 } | |
359 | |
360 if (msg.method == 'completeAuthenticationAuthCodeOnly') { | |
361 if (!msg.authCode) { | |
362 console.error( | |
363 'GaiaAuthHost: completeAuthentication without auth code.'); | |
364 var msg = {method: 'redirectToSignin'}; | |
365 this.frame_.contentWindow.postMessage(msg, AUTH_URL_BASE); | |
366 return; | |
367 } | |
368 this.onAuthSuccess_({authCodeOnly: true, authCode: msg.authCode}); | |
369 return; | |
370 } | |
371 | |
372 if (msg.method == 'confirmPassword') { | |
373 if (this.confirmPasswordCallback_) | |
374 this.confirmPasswordCallback_(msg.email, msg.passwordCount); | |
375 else | |
376 console.error('GaiaAuthHost: Invalid confirmPasswordCallback_.'); | |
377 return; | |
378 } | |
379 | |
380 if (msg.method == 'noPassword') { | |
381 if (this.noPasswordCallback_) | |
382 this.noPasswordCallback_(msg.email); | |
383 else | |
384 console.error('GaiaAuthHost: Invalid noPasswordCallback_.'); | |
385 return; | |
386 } | |
387 | |
388 if (msg.method == 'authPageLoaded') { | |
389 this.authDomain = msg.domain; | |
390 this.authFlow = msg.isSAML ? AuthFlow.SAML : AuthFlow.GAIA; | |
391 return; | |
392 } | |
393 | |
394 if (msg.method == 'resetAuthFlow') { | |
395 this.authFlow = AuthFlow.GAIA; | |
396 return; | |
397 } | |
398 | |
399 if (msg.method == 'insecureContentBlocked') { | |
400 if (this.insecureContentBlockedCallback_) { | |
401 this.insecureContentBlockedCallback_(msg.url); | |
402 } else { | |
403 console.error( | |
404 'GaiaAuthHost: Invalid insecureContentBlockedCallback_.'); | |
405 } | |
406 return; | |
407 } | |
408 | |
409 if (msg.method == 'switchToFullTab') { | |
410 chrome.send('switchToFullTab', [msg.url]); | |
411 return; | |
412 } | |
413 | |
414 if (msg.method == 'missingGaiaInfo') { | |
415 if (this.missingGaiaInfoCallback_) { | |
416 this.missingGaiaInfoCallback_(); | |
417 } else { | |
418 console.error('GaiaAuthHost: Invalid missingGaiaInfoCallback_.'); | |
419 } | |
420 return; | |
421 } | |
422 | |
423 if (msg.method == 'samlApiUsed') { | |
424 if (this.samlApiUsedCallback_) { | |
425 this.samlApiUsedCallback_(); | |
426 } else { | |
427 console.error('GaiaAuthHost: Invalid samlApiUsedCallback_.'); | |
428 } | |
429 return; | |
430 } | |
431 | |
432 console.error('Unknown message method=' + msg.method); | |
433 } | |
434 }; | |
435 | |
436 /** | |
437 * The domain name of the current auth page. | |
438 * @type {string} | |
439 */ | |
440 cr.defineProperty(GaiaAuthHost, 'authDomain'); | |
441 | |
442 /** | |
443 * The current auth flow of the hosted gaia_auth extension. | |
444 * @type {AuthFlow} | |
445 */ | |
446 cr.defineProperty(GaiaAuthHost, 'authFlow'); | |
447 | |
448 GaiaAuthHost.SUPPORTED_PARAMS = SUPPORTED_PARAMS; | |
449 GaiaAuthHost.LOCALIZED_STRING_PARAMS = LOCALIZED_STRING_PARAMS; | |
450 GaiaAuthHost.AuthMode = AuthMode; | |
451 GaiaAuthHost.AuthFlow = AuthFlow; | |
452 | |
453 return { | |
454 GaiaAuthHost: GaiaAuthHost | |
455 }; | |
456 }); | |
OLD | NEW |