Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(444)

Side by Side Diff: chrome/browser/resources/gaia_auth/main.js

Issue 2686683002: Deprecate most of gaia_auth extension (Closed)
Patch Set: remove unused data member from GaiaAuthExtensionLoader Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 * Authenticator class wraps the communications between Gaia and its host.
7 */
8 function Authenticator() {
9 }
10
11 /**
12 * Gaia auth extension url origin.
13 * @type {string}
14 */
15 Authenticator.THIS_EXTENSION_ORIGIN =
16 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik';
17
18 /**
19 * The lowest version of the credentials passing API supported.
20 * @type {number}
21 */
22 Authenticator.MIN_API_VERSION_VERSION = 1;
23
24 /**
25 * The highest version of the credentials passing API supported.
26 * @type {number}
27 */
28 Authenticator.MAX_API_VERSION_VERSION = 1;
29
30 /**
31 * The key types supported by the credentials passing API.
32 * @type {Array} Array of strings.
33 */
34 Authenticator.API_KEY_TYPES = [
35 'KEY_TYPE_PASSWORD_PLAIN',
36 ];
37
38 /**
39 * Allowed origins of the hosting page.
40 * @type {Array<string>}
41 */
42 Authenticator.ALLOWED_PARENT_ORIGINS = [
43 'chrome://oobe',
44 'chrome://chrome-signin'
45 ];
46
47 /**
48 * Singleton getter of Authenticator.
49 * @return {Object} The singleton instance of Authenticator.
50 */
51 Authenticator.getInstance = function() {
52 if (!Authenticator.instance_) {
53 Authenticator.instance_ = new Authenticator();
54 }
55 return Authenticator.instance_;
56 };
57
58 Authenticator.prototype = {
59 email_: null,
60 gaiaId_: null,
61
62 // Depending on the key type chosen, this will contain the plain text password
63 // or a credential derived from it along with the information required to
64 // repeat the derivation, such as a salt. The information will be encoded so
65 // that it contains printable ASCII characters only. The exact encoding is TBD
66 // when support for key types other than plain text password is added.
67 passwordBytes_: null,
68
69 needPassword_: false,
70 chooseWhatToSync_: false,
71 skipForNow_: false,
72 sessionIndex_: null,
73 attemptToken_: null,
74
75 // Input params from extension initialization URL.
76 inputLang_: undefined,
77 intputEmail_: undefined,
78
79 isSAMLFlow_: false,
80 gaiaLoaded_: false,
81 supportChannel_: null,
82
83 useEafe_: false,
84 clientId_: '',
85
86 GAIA_URL: 'https://accounts.google.com/',
87 GAIA_PAGE_PATH: 'ServiceLogin?skipvpage=true&sarp=1&rm=hide',
88 SERVICE_ID: 'chromeoslogin',
89 CONTINUE_URL: Authenticator.THIS_EXTENSION_ORIGIN + '/success.html',
90 CONSTRAINED_FLOW_SOURCE: 'chrome',
91
92 initialize: function() {
93 var handleInitializeMessage = function(e) {
94 if (Authenticator.ALLOWED_PARENT_ORIGINS.indexOf(e.origin) == -1) {
95 console.error('Unexpected parent message, origin=' + e.origin);
96 return;
97 }
98 window.removeEventListener('message', handleInitializeMessage);
99
100 var params = e.data;
101 params.parentPage = e.origin;
102 this.initializeFromParent_(params);
103 this.onPageLoad_();
104 }.bind(this);
105
106 document.addEventListener('DOMContentLoaded', function() {
107 window.addEventListener('message', handleInitializeMessage);
108 window.parent.postMessage({'method': 'loginUIDOMContentLoaded'}, '*');
109 });
110 },
111
112 initializeFromParent_: function(params) {
113 this.parentPage_ = params.parentPage;
114 this.gaiaUrl_ = params.gaiaUrl || this.GAIA_URL;
115 this.gaiaPath_ = params.gaiaPath || this.GAIA_PAGE_PATH;
116 this.inputLang_ = params.hl;
117 this.inputEmail_ = params.email;
118 this.service_ = params.service || this.SERVICE_ID;
119 this.continueUrl_ = params.continueUrl || this.CONTINUE_URL;
120 this.desktopMode_ = params.desktopMode == '1';
121 this.isConstrainedWindow_ = params.constrained == '1';
122 this.useEafe_ = params.useEafe || false;
123 this.clientId_ = params.clientId || '';
124 this.initialFrameUrl_ = params.frameUrl || this.constructInitialFrameUrl_();
125 this.initialFrameUrlWithoutParams_ = stripParams(this.initialFrameUrl_);
126 this.needPassword_ = params.needPassword == '1';
127
128 // For CrOS 'ServiceLogin' we assume that Gaia is loaded if we recieved
129 // 'clearOldAttempts' message. For other scenarios Gaia doesn't send this
130 // message so we have to rely on 'load' event.
131 // TODO(dzhioev): Do not rely on 'load' event after b/16313327 is fixed.
132 this.assumeLoadedOnLoadEvent_ =
133 !this.gaiaPath_.startsWith('ServiceLogin') ||
134 this.service_ !== 'chromeoslogin' ||
135 this.useEafe_;
136 },
137
138 isGaiaMessage_: function(msg) {
139 // Not quite right, but good enough.
140 return this.gaiaUrl_.startsWith(msg.origin) ||
141 this.GAIA_URL.startsWith(msg.origin);
142 },
143
144 isParentMessage_: function(msg) {
145 return msg.origin == this.parentPage_;
146 },
147
148 constructInitialFrameUrl_: function() {
149 var url = this.gaiaUrl_ + this.gaiaPath_;
150
151 url = appendParam(url, 'service', this.service_);
152 // Easy bootstrap use auth_code message as success signal instead of
153 // continue URL.
154 if (!this.useEafe_)
155 url = appendParam(url, 'continue', this.continueUrl_);
156 if (this.inputLang_)
157 url = appendParam(url, 'hl', this.inputLang_);
158 if (this.inputEmail_)
159 url = appendParam(url, 'Email', this.inputEmail_);
160 if (this.isConstrainedWindow_)
161 url = appendParam(url, 'source', this.CONSTRAINED_FLOW_SOURCE);
162 return url;
163 },
164
165 onPageLoad_: function() {
166 window.addEventListener('message', this.onMessage.bind(this), false);
167 this.initSupportChannel_();
168
169 if (this.assumeLoadedOnLoadEvent_) {
170 var gaiaFrame = $('gaia-frame');
171 var handler = function() {
172 gaiaFrame.removeEventListener('load', handler);
173 if (!this.gaiaLoaded_) {
174 this.gaiaLoaded_ = true;
175 this.maybeInitialized_();
176
177 if (this.useEafe_ && this.clientId_) {
178 // Sends initial handshake message to EAFE. Note this fails with
179 // SSO redirect because |gaiaFrame| sits on a different origin.
180 gaiaFrame.contentWindow.postMessage({
181 clientId: this.clientId_
182 }, this.gaiaUrl_);
183 }
184 }
185 }.bind(this);
186 gaiaFrame.addEventListener('load', handler);
187 }
188 },
189
190 initSupportChannel_: function() {
191 var supportChannel = new Channel();
192 supportChannel.connect('authMain');
193
194 supportChannel.registerMessage('channelConnected', function() {
195 // Load the gaia frame after the background page indicates that it is
196 // ready, so that the webRequest handlers are all setup first.
197 var gaiaFrame = $('gaia-frame');
198 gaiaFrame.src = this.initialFrameUrl_;
199
200 if (this.supportChannel_) {
201 console.error('Support channel is already initialized.');
202 return;
203 }
204 this.supportChannel_ = supportChannel;
205
206 if (this.desktopMode_) {
207 this.supportChannel_.send({
208 name: 'initDesktopFlow',
209 gaiaUrl: this.gaiaUrl_,
210 continueUrl: stripParams(this.continueUrl_),
211 isConstrainedWindow: this.isConstrainedWindow_,
212 initialFrameUrlWithoutParams: this.initialFrameUrlWithoutParams_
213 });
214
215 this.supportChannel_.registerMessage(
216 'switchToFullTab', this.switchToFullTab_.bind(this));
217 }
218 this.supportChannel_.registerMessage(
219 'completeLogin', this.onCompleteLogin_.bind(this));
220 this.initSAML_();
221 this.supportChannel_.send({name: 'resetAuth'});
222 this.maybeInitialized_();
223 }.bind(this));
224
225 window.setTimeout(function() {
226 if (!this.supportChannel_) {
227 // Give up previous channel and bind its 'channelConnected' to a no-op.
228 supportChannel.registerMessage('channelConnected', function() {});
229
230 // Re-initialize the channel if it is not connected properly, e.g.
231 // connect may be called before background script started running.
232 this.initSupportChannel_();
233 }
234 }.bind(this), 200);
235 },
236
237 /**
238 * Called when one of the initialization stages has finished. If all the
239 * needed parts are initialized, notifies parent about successfull
240 * initialization.
241 */
242 maybeInitialized_: function() {
243 if (!this.gaiaLoaded_ || !this.supportChannel_)
244 return;
245 var msg = {
246 'method': 'loginUILoaded'
247 };
248 window.parent.postMessage(msg, this.parentPage_);
249 },
250
251 /**
252 * Invoked when the background script sends a message to indicate that the
253 * current content does not fit in a constrained window.
254 * @param {Object=} msg Extra info to send.
255 */
256 switchToFullTab_: function(msg) {
257 var parentMsg = {
258 'method': 'switchToFullTab',
259 'url': msg.url
260 };
261 window.parent.postMessage(parentMsg, this.parentPage_);
262 },
263
264 /**
265 * Invoked when the signin flow is complete.
266 * @param {Object=} opt_extraMsg Optional extra info to send.
267 */
268 completeLogin_: function(opt_extraMsg) {
269 var msg = {
270 'method': 'completeLogin',
271 'email': (opt_extraMsg && opt_extraMsg.email) || this.email_,
272 'password': this.passwordBytes_ ||
273 (opt_extraMsg && opt_extraMsg.password),
274 'usingSAML': this.isSAMLFlow_,
275 'chooseWhatToSync': this.chooseWhatToSync_ || false,
276 'skipForNow': (opt_extraMsg && opt_extraMsg.skipForNow) ||
277 this.skipForNow_,
278 'sessionIndex': (opt_extraMsg && opt_extraMsg.sessionIndex) ||
279 this.sessionIndex_,
280 'gaiaId': (opt_extraMsg && opt_extraMsg.gaiaId) || this.gaiaId_
281 };
282 window.parent.postMessage(msg, this.parentPage_);
283 this.supportChannel_.send({name: 'resetAuth'});
284 },
285
286 /**
287 * Invoked when support channel is connected.
288 */
289 initSAML_: function() {
290 this.isSAMLFlow_ = false;
291
292 this.supportChannel_.registerMessage(
293 'onAuthPageLoaded', this.onAuthPageLoaded_.bind(this));
294 this.supportChannel_.registerMessage(
295 'onInsecureContentBlocked', this.onInsecureContentBlocked_.bind(this));
296 this.supportChannel_.registerMessage(
297 'apiCall', this.onAPICall_.bind(this));
298 this.supportChannel_.send({
299 name: 'setGaiaUrl',
300 gaiaUrl: this.gaiaUrl_
301 });
302 if (!this.desktopMode_ && this.gaiaUrl_.startsWith('https://')) {
303 // Abort the login flow when content served over an unencrypted connection
304 // is detected on Chrome OS. This does not apply to tests that explicitly
305 // set a non-https GAIA URL and want to perform all authentication over
306 // http.
307 this.supportChannel_.send({
308 name: 'setBlockInsecureContent',
309 blockInsecureContent: true
310 });
311 }
312 },
313
314 /**
315 * Invoked when the background page sends 'onHostedPageLoaded' message.
316 * @param {!Object} msg Details sent with the message.
317 */
318 onAuthPageLoaded_: function(msg) {
319 if (msg.isSAMLPage && !this.isSAMLFlow_) {
320 // GAIA redirected to a SAML login page. The credentials provided to this
321 // page will determine what user gets logged in. The credentials obtained
322 // from the GAIA login form are no longer relevant and can be discarded.
323 this.isSAMLFlow_ = true;
324 this.email_ = null;
325 this.gaiaId_ = null;
326 this.passwordBytes_ = null;
327 }
328
329 window.parent.postMessage({
330 'method': 'authPageLoaded',
331 'isSAML': this.isSAMLFlow_,
332 'domain': extractDomain(msg.url)
333 }, this.parentPage_);
334 },
335
336 /**
337 * Invoked when the background page sends an 'onInsecureContentBlocked'
338 * message.
339 * @param {!Object} msg Details sent with the message.
340 */
341 onInsecureContentBlocked_: function(msg) {
342 window.parent.postMessage({
343 'method': 'insecureContentBlocked',
344 'url': stripParams(msg.url)
345 }, this.parentPage_);
346 },
347
348 /**
349 * Invoked when one of the credential passing API methods is called by a SAML
350 * provider.
351 * @param {!Object} msg Details of the API call.
352 */
353 onAPICall_: function(msg) {
354 var call = msg.call;
355 if (call.method == 'initialize') {
356 if (!Number.isInteger(call.requestedVersion) ||
357 call.requestedVersion < Authenticator.MIN_API_VERSION_VERSION) {
358 this.sendInitializationFailure_();
359 return;
360 }
361
362 this.apiVersion_ = Math.min(call.requestedVersion,
363 Authenticator.MAX_API_VERSION_VERSION);
364 this.initialized_ = true;
365 this.sendInitializationSuccess_();
366 return;
367 }
368
369 if (call.method == 'add') {
370 if (Authenticator.API_KEY_TYPES.indexOf(call.keyType) == -1) {
371 console.error('Authenticator.onAPICall_: unsupported key type');
372 return;
373 }
374 // Not setting |email_| and |gaiaId_| because this API call will
375 // eventually be followed by onCompleteLogin_() which does set it.
376 this.apiToken_ = call.token;
377 this.passwordBytes_ = call.passwordBytes;
378 } else if (call.method == 'confirm') {
379 if (call.token != this.apiToken_)
380 console.error('Authenticator.onAPICall_: token mismatch');
381 } else {
382 console.error('Authenticator.onAPICall_: unknown message');
383 }
384 },
385
386 onGotAuthCode_: function(authCode) {
387 window.parent.postMessage({
388 'method': 'completeAuthenticationAuthCodeOnly',
389 'authCode': authCode
390 }, this.parentPage_);
391 },
392
393 sendInitializationSuccess_: function() {
394 this.supportChannel_.send({name: 'apiResponse', response: {
395 result: 'initialized',
396 version: this.apiVersion_,
397 keyTypes: Authenticator.API_KEY_TYPES
398 }});
399 },
400
401 sendInitializationFailure_: function() {
402 this.supportChannel_.send({
403 name: 'apiResponse',
404 response: {result: 'initialization_failed'}
405 });
406 },
407
408 /**
409 * Callback invoked for 'completeLogin' message.
410 * @param {Object=} msg Message sent from background page.
411 */
412 onCompleteLogin_: function(msg) {
413 if (!msg.email || !msg.gaiaId || !msg.sessionIndex) {
414 // On desktop, if the skipForNow message field is set, send it to handler.
415 // This does not require the email, gaiaid or session to be valid.
416 if (this.desktopMode_ && msg.skipForNow) {
417 this.completeLogin_(msg);
418 } else {
419 console.error('Missing fields to complete login.');
420 window.parent.postMessage({method: 'missingGaiaInfo'},
421 this.parentPage_);
422 return;
423 }
424 }
425
426 // Skip SAML extra steps for desktop flow and non-SAML flow.
427 if (!this.isSAMLFlow_ || this.desktopMode_) {
428 this.completeLogin_(msg);
429 return;
430 }
431
432 this.email_ = msg.email;
433 this.gaiaId_ = msg.gaiaId;
434 // Password from |msg| is not used because ChromeOS SAML flow
435 // gets password by asking user to confirm.
436 this.skipForNow_ = msg.skipForNow;
437 this.sessionIndex_ = msg.sessionIndex;
438
439 if (this.passwordBytes_) {
440 // If the credentials passing API was used, login is complete.
441 window.parent.postMessage({method: 'samlApiUsed'}, this.parentPage_);
442 this.completeLogin_(msg);
443 } else if (!this.needPassword_) {
444 // If the credentials passing API was not used, the password was obtained
445 // by scraping. It must be verified before use. However, the host may not
446 // be interested in the password at all. In that case, verification is
447 // unnecessary and login is complete.
448 this.completeLogin_(msg);
449 } else {
450 this.supportChannel_.sendWithCallback(
451 {name: 'getScrapedPasswords'},
452 function(passwords) {
453 if (passwords.length == 0) {
454 window.parent.postMessage(
455 {method: 'noPassword', email: this.email_},
456 this.parentPage_);
457 } else {
458 window.parent.postMessage({method: 'confirmPassword',
459 email: this.email_,
460 passwordCount: passwords.length},
461 this.parentPage_);
462 }
463 }.bind(this));
464 }
465 },
466
467 onVerifyConfirmedPassword_: function(password) {
468 this.supportChannel_.sendWithCallback(
469 {name: 'getScrapedPasswords'},
470 function(passwords) {
471 for (var i = 0; i < passwords.length; ++i) {
472 if (passwords[i] == password) {
473 this.passwordBytes_ = passwords[i];
474 // SAML login is complete when the user has successfully
475 // confirmed the password.
476 if (this.passwordBytes_ !== null)
477 this.completeLogin_();
478 return;
479 }
480 }
481 window.parent.postMessage(
482 {method: 'confirmPassword', email: this.email_},
483 this.parentPage_);
484 }.bind(this));
485 },
486
487 onMessage: function(e) {
488 var msg = e.data;
489
490 if (this.useEafe_) {
491 if (msg == '!_{h:\'gaia-frame\'}' && this.isGaiaMessage_(e)) {
492 // Sends client ID again on the hello message to work around the SSO
493 // signin issue.
494 // TODO(xiyuan): Revisit this when EAFE is integrated or for webview.
495 $('gaia-frame').contentWindow.postMessage({
496 clientId: this.clientId_
497 }, this.gaiaUrl_);
498 } else if (typeof msg == 'object' &&
499 msg.type == 'authorizationCode' && this.isGaiaMessage_(e)) {
500 this.onGotAuthCode_(msg.authorizationCode);
501 } else {
502 console.error('Authenticator.onMessage: unknown message' +
503 ', msg=' + JSON.stringify(msg));
504 }
505
506 return;
507 }
508
509 if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) {
510 // At this point GAIA does not yet know the gaiaId, so its not set here.
511 this.email_ = msg.email;
512 this.passwordBytes_ = msg.password;
513 this.attemptToken_ = msg.attemptToken;
514 this.chooseWhatToSync_ = msg.chooseWhatToSync;
515 this.isSAMLFlow_ = false;
516 if (this.supportChannel_)
517 this.supportChannel_.send({name: 'startAuth'});
518 else
519 console.error('Support channel is not initialized.');
520 } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) {
521 if (!this.gaiaLoaded_) {
522 this.gaiaLoaded_ = true;
523 this.maybeInitialized_();
524 }
525 this.email_ = null;
526 this.gaiaId_ = null;
527 this.sessionIndex_ = false;
528 this.passwordBytes_ = null;
529 this.attemptToken_ = null;
530 this.isSAMLFlow_ = false;
531 this.skipForNow_ = false;
532 this.chooseWhatToSync_ = false;
533 if (this.supportChannel_) {
534 this.supportChannel_.send({name: 'resetAuth'});
535 // This message is for clearing saml properties in gaia_auth_host and
536 // oobe_screen_oauth_enrollment.
537 window.parent.postMessage({
538 'method': 'resetAuthFlow',
539 }, this.parentPage_);
540 }
541 } else if (msg.method == 'verifyConfirmedPassword' &&
542 this.isParentMessage_(e)) {
543 this.onVerifyConfirmedPassword_(msg.password);
544 } else if (msg.method == 'redirectToSignin' &&
545 this.isParentMessage_(e)) {
546 $('gaia-frame').src = this.constructInitialFrameUrl_();
547 } else {
548 console.error('Authenticator.onMessage: unknown message + origin!?');
549 }
550 }
551 };
552
553 Authenticator.getInstance().initialize();
OLDNEW
« no previous file with comments | « chrome/browser/resources/gaia_auth/main.html ('k') | chrome/browser/resources/gaia_auth/manifest.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698