OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 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 <include src="saml_handler.js"> | 5 <include src="saml_handler.js"> |
6 | 6 |
7 /** | 7 /** |
8 * @fileoverview An UI component to authenciate to Chrome. The component hosts | 8 * @fileoverview An UI component to authenciate to Chrome. The component hosts |
9 * IdP web pages in a webview. A client who is interested in monitoring | 9 * IdP web pages in a webview. A client who is interested in monitoring |
10 * authentication events should pass a listener object of type | 10 * authentication events should pass a listener object of type |
11 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization, | 11 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization, |
12 * call {@code load} to start the authentication flow. | 12 * call {@code load} to start the authentication flow. |
13 */ | 13 */ |
14 | 14 |
15 cr.define('cr.login', function() { | 15 cr.define('cr.login', function() { |
16 'use strict'; | 16 'use strict'; |
17 | 17 |
18 // TODO(rogerta): should use gaia URL from GaiaUrls::gaia_url() instead | 18 // TODO(rogerta): should use gaia URL from GaiaUrls::gaia_url() instead |
19 // of hardcoding the prod URL here. As is, this does not work with staging | 19 // of hardcoding the prod URL here. As is, this does not work with staging |
20 // environments. | 20 // environments. |
21 var IDP_ORIGIN = 'https://accounts.google.com/'; | 21 var IDP_ORIGIN = 'https://accounts.google.com/'; |
22 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide'; | 22 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide'; |
23 var CONTINUE_URL = | 23 var CONTINUE_URL = |
24 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html'; | 24 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html'; |
25 var SIGN_IN_HEADER = 'google-accounts-signin'; | 25 var SIGN_IN_HEADER = 'google-accounts-signin'; |
26 var EMBEDDED_FORM_HEADER = 'google-accounts-embedded'; | 26 var EMBEDDED_FORM_HEADER = 'google-accounts-embedded'; |
27 var LOCATION_HEADER = 'location'; | 27 var LOCATION_HEADER = 'location'; |
28 var COOKIE_HEADER = 'cookie'; | |
28 var SET_COOKIE_HEADER = 'set-cookie'; | 29 var SET_COOKIE_HEADER = 'set-cookie'; |
29 var OAUTH_CODE_COOKIE = 'oauth_code'; | 30 var OAUTH_CODE_COOKIE = 'oauth_code'; |
31 var GAPS_COOKIE = 'GAPS'; | |
30 var SERVICE_ID = 'chromeoslogin'; | 32 var SERVICE_ID = 'chromeoslogin'; |
31 var EMBEDDED_SETUP_CHROMEOS_ENDPOINT = 'embedded/setup/chromeos'; | 33 var EMBEDDED_SETUP_CHROMEOS_ENDPOINT = 'embedded/setup/chromeos'; |
32 | 34 |
33 /** | 35 /** |
34 * The source URL parameter for the constrained signin flow. | 36 * The source URL parameter for the constrained signin flow. |
35 */ | 37 */ |
36 var CONSTRAINED_FLOW_SOURCE = 'chrome'; | 38 var CONSTRAINED_FLOW_SOURCE = 'chrome'; |
37 | 39 |
38 /** | 40 /** |
39 * Enum for the authorization mode, must match AuthMode defined in | 41 * Enum for the authorization mode, must match AuthMode defined in |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
80 // not called before dispatching |authCopleted|. | 82 // not called before dispatching |authCopleted|. |
81 // Default is |true|. | 83 // Default is |true|. |
82 'flow', // One of 'default', 'enterprise', or 'theftprotection'. | 84 'flow', // One of 'default', 'enterprise', or 'theftprotection'. |
83 'enterpriseDomain', // Domain in which hosting device is (or should be) | 85 'enterpriseDomain', // Domain in which hosting device is (or should be) |
84 // enrolled. | 86 // enrolled. |
85 'emailDomain', // Value used to prefill domain for email. | 87 'emailDomain', // Value used to prefill domain for email. |
86 'clientVersion', // Version of the Chrome build. | 88 'clientVersion', // Version of the Chrome build. |
87 'platformVersion', // Version of the OS build. | 89 'platformVersion', // Version of the OS build. |
88 'releaseChannel', // Installation channel. | 90 'releaseChannel', // Installation channel. |
89 'endpointGen', // Current endpoint generation. | 91 'endpointGen', // Current endpoint generation. |
92 'gapsCookie', // GAPS cookie | |
90 ]; | 93 ]; |
91 | 94 |
92 /** | 95 /** |
93 * Initializes the authenticator component. | 96 * Initializes the authenticator component. |
94 * @param {webview|string} webview The webview element or its ID to host IdP | 97 * @param {webview|string} webview The webview element or its ID to host IdP |
95 * web pages. | 98 * web pages. |
96 * @constructor | 99 * @constructor |
97 */ | 100 */ |
98 function Authenticator(webview) { | 101 function Authenticator(webview) { |
99 this.webview_ = typeof webview == 'string' ? $(webview) : webview; | 102 this.webview_ = typeof webview == 'string' ? $(webview) : webview; |
100 assert(this.webview_); | 103 assert(this.webview_); |
101 | 104 |
102 this.email_ = null; | 105 this.email_ = null; |
103 this.password_ = null; | 106 this.password_ = null; |
104 this.gaiaId_ = null, | 107 this.gaiaId_ = null, |
105 this.sessionIndex_ = null; | 108 this.sessionIndex_ = null; |
106 this.chooseWhatToSync_ = false; | 109 this.chooseWhatToSync_ = false; |
107 this.skipForNow_ = false; | 110 this.skipForNow_ = false; |
108 this.authFlow = AuthFlow.DEFAULT; | 111 this.authFlow = AuthFlow.DEFAULT; |
109 this.authDomain = ''; | 112 this.authDomain = ''; |
110 this.loaded_ = false; | 113 this.loaded_ = false; |
111 this.idpOrigin_ = null; | 114 this.idpOrigin_ = null; |
112 this.continueUrl_ = null; | 115 this.continueUrl_ = null; |
113 this.continueUrlWithoutParams_ = null; | 116 this.continueUrlWithoutParams_ = null; |
114 this.initialFrameUrl_ = null; | 117 this.initialFrameUrl_ = null; |
115 this.reloadUrl_ = null; | 118 this.reloadUrl_ = null; |
116 this.trusted_ = true; | 119 this.trusted_ = true; |
117 this.oauth_code_ = null; | 120 this.oauth_code_ = null; |
121 this.gaps_cookie_ = null; | |
xiyuan
2015/07/09 23:08:29
nit: gaps_cookie_ -> gapsCookie_
JS variable name
Alexander Alekseev
2015/07/09 23:22:40
Done.
| |
122 this.gaps_cookie_sent_ = false; | |
123 this.new_gaps_cookie_ = null; | |
118 | 124 |
119 this.useEafe_ = false; | 125 this.useEafe_ = false; |
120 this.clientId_ = null; | 126 this.clientId_ = null; |
121 | 127 |
122 this.samlHandler_ = new cr.login.SamlHandler(this.webview_); | 128 this.samlHandler_ = new cr.login.SamlHandler(this.webview_); |
123 this.confirmPasswordCallback = null; | 129 this.confirmPasswordCallback = null; |
124 this.noPasswordCallback = null; | 130 this.noPasswordCallback = null; |
125 this.insecureContentBlockedCallback = null; | 131 this.insecureContentBlockedCallback = null; |
126 this.samlApiUsedCallback = null; | 132 this.samlApiUsedCallback = null; |
127 this.missingGaiaInfoCallback = null; | 133 this.missingGaiaInfoCallback = null; |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
164 | 170 |
165 /** | 171 /** |
166 * Reinitializes authentication parameters so that a failed login attempt | 172 * Reinitializes authentication parameters so that a failed login attempt |
167 * would not result in an infinite loop. | 173 * would not result in an infinite loop. |
168 */ | 174 */ |
169 Authenticator.prototype.clearCredentials_ = function() { | 175 Authenticator.prototype.clearCredentials_ = function() { |
170 this.email_ = null; | 176 this.email_ = null; |
171 this.gaiaId_ = null; | 177 this.gaiaId_ = null; |
172 this.password_ = null; | 178 this.password_ = null; |
173 this.oauth_code_ = null; | 179 this.oauth_code_ = null; |
180 this.gaps_cookie_ = null; | |
181 this.gaps_cookie_sent_ = false; | |
182 this.new_gaps_cookie_ = null; | |
174 this.chooseWhatToSync_ = false; | 183 this.chooseWhatToSync_ = false; |
175 this.skipForNow_ = false; | 184 this.skipForNow_ = false; |
176 this.sessionIndex_ = null; | 185 this.sessionIndex_ = null; |
177 this.trusted_ = true; | 186 this.trusted_ = true; |
178 this.authFlow = AuthFlow.DEFAULT; | 187 this.authFlow = AuthFlow.DEFAULT; |
179 this.samlHandler_.reset(); | 188 this.samlHandler_.reset(); |
180 }; | 189 }; |
181 | 190 |
182 /** | 191 /** |
183 * Loads the authenticator component with the given parameters. | 192 * Loads the authenticator component with the given parameters. |
(...skipping 18 matching lines...) Expand all Loading... | |
202 // Don't block insecure content for desktop flow because it lands on | 211 // Don't block insecure content for desktop flow because it lands on |
203 // http. Otherwise, block insecure content as long as gaia is https. | 212 // http. Otherwise, block insecure content as long as gaia is https. |
204 this.samlHandler_.blockInsecureContent = authMode != AuthMode.DESKTOP && | 213 this.samlHandler_.blockInsecureContent = authMode != AuthMode.DESKTOP && |
205 this.idpOrigin_.indexOf('https://') == 0; | 214 this.idpOrigin_.indexOf('https://') == 0; |
206 this.needPassword = !('needPassword' in data) || data.needPassword; | 215 this.needPassword = !('needPassword' in data) || data.needPassword; |
207 | 216 |
208 if (this.isNewGaiaFlowChromeOS) { | 217 if (this.isNewGaiaFlowChromeOS) { |
209 this.webview_.contextMenus.onShow.addListener(function(e) { | 218 this.webview_.contextMenus.onShow.addListener(function(e) { |
210 e.preventDefault(); | 219 e.preventDefault(); |
211 }); | 220 }); |
221 | |
222 var filterPrefix = this.idpOrigin_ + EMBEDDED_SETUP_CHROMEOS_ENDPOINT; | |
223 if (!this.onBeforeSetHeadersSet_) { | |
224 this.onBeforeSetHeadersSet_ = true; | |
225 this.webview_.request.onBeforeSendHeaders.addListener( | |
xiyuan
2015/07/09 23:08:29
Why this code lives here instead of with other req
Alexander Alekseev
2015/07/09 23:22:40
Because it depends on load parameter (line 199):
xiyuan
2015/07/09 23:30:05
I see. Could you add a comment to document why thi
Alexander Alekseev
2015/07/09 23:43:22
Done.
| |
226 this.onBeforeSendHeaders_.bind(this), | |
227 {urls: [filterPrefix + '?*', filterPrefix + '/*']}, | |
228 ['requestHeaders', 'blocking']); | |
229 } | |
212 } | 230 } |
213 | 231 |
214 this.webview_.src = this.reloadUrl_; | 232 this.webview_.src = this.reloadUrl_; |
215 }; | 233 }; |
216 | 234 |
217 /** | 235 /** |
218 * Reloads the authenticator component. | 236 * Reloads the authenticator component. |
219 */ | 237 */ |
220 Authenticator.prototype.reload = function() { | 238 Authenticator.prototype.reload = function() { |
221 this.clearCredentials_(); | 239 this.clearCredentials_(); |
(...skipping 17 matching lines...) Expand all Loading... | |
239 if (data.enterpriseDomain) | 257 if (data.enterpriseDomain) |
240 url = appendParam(url, 'manageddomain', data.enterpriseDomain); | 258 url = appendParam(url, 'manageddomain', data.enterpriseDomain); |
241 if (data.clientVersion) | 259 if (data.clientVersion) |
242 url = appendParam(url, 'client_version', data.clientVersion); | 260 url = appendParam(url, 'client_version', data.clientVersion); |
243 if (data.platformVersion) | 261 if (data.platformVersion) |
244 url = appendParam(url, 'platform_version', data.platformVersion); | 262 url = appendParam(url, 'platform_version', data.platformVersion); |
245 if (data.releaseChannel) | 263 if (data.releaseChannel) |
246 url = appendParam(url, 'release_channel', data.releaseChannel); | 264 url = appendParam(url, 'release_channel', data.releaseChannel); |
247 if (data.endpointGen) | 265 if (data.endpointGen) |
248 url = appendParam(url, 'endpoint_gen', data.endpointGen); | 266 url = appendParam(url, 'endpoint_gen', data.endpointGen); |
267 this.gaps_cookie_ = data.gapsCookie; | |
268 this.gaps_cookie_sent_ = false; | |
269 this.new_gaps_cookie_ = null; | |
249 } else { | 270 } else { |
250 url = appendParam(url, 'continue', this.continueUrl_); | 271 url = appendParam(url, 'continue', this.continueUrl_); |
251 url = appendParam(url, 'service', data.service || SERVICE_ID); | 272 url = appendParam(url, 'service', data.service || SERVICE_ID); |
252 } | 273 } |
253 if (data.hl) | 274 if (data.hl) |
254 url = appendParam(url, 'hl', data.hl); | 275 url = appendParam(url, 'hl', data.hl); |
255 if (data.gaiaId) | 276 if (data.gaiaId) |
256 url = appendParam(url, 'user_id', data.gaiaId); | 277 url = appendParam(url, 'user_id', data.gaiaId); |
257 if (data.email) | 278 if (data.email) |
258 url = appendParam(url, 'Email', data.email); | 279 url = appendParam(url, 'Email', data.email); |
(...skipping 109 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
368 // URL will contain a source=3 field. | 389 // URL will contain a source=3 field. |
369 var location = decodeURIComponent(header.value); | 390 var location = decodeURIComponent(header.value); |
370 this.chooseWhatToSync_ = !!location.match(/(\?|&)source=3($|&)/); | 391 this.chooseWhatToSync_ = !!location.match(/(\?|&)source=3($|&)/); |
371 } else if ( | 392 } else if ( |
372 this.isNewGaiaFlowChromeOS && headerName == SET_COOKIE_HEADER) { | 393 this.isNewGaiaFlowChromeOS && headerName == SET_COOKIE_HEADER) { |
373 var headerValue = header.value; | 394 var headerValue = header.value; |
374 if (headerValue.indexOf(OAUTH_CODE_COOKIE + '=', 0) == 0) { | 395 if (headerValue.indexOf(OAUTH_CODE_COOKIE + '=', 0) == 0) { |
375 this.oauth_code_ = | 396 this.oauth_code_ = |
376 headerValue.substring(OAUTH_CODE_COOKIE.length + 1).split(';')[0]; | 397 headerValue.substring(OAUTH_CODE_COOKIE.length + 1).split(';')[0]; |
377 } | 398 } |
399 if (headerValue.indexOf(GAPS_COOKIE + '=', 0) == 0) { | |
400 this.new_gaps_cookie_ = | |
401 headerValue.substring(GAPS_COOKIE.length + 1).split(';')[0]; | |
402 } | |
378 } | 403 } |
379 } | 404 } |
380 }; | 405 }; |
381 | 406 |
382 /** | 407 /** |
408 * This method replaces cookie value in cookie header. | |
409 * @param@ {header_value} Original string value of Cookie header. | |
xiyuan
2015/07/09 23:08:29
Wrong JSDoc format here and below.
Should be some
Alexander Alekseev
2015/07/09 23:22:40
Done.
| |
410 * @param@ {cookie_name} Name of cookie to be replaced. | |
411 * @param@ {cookie_value} New cookie value. | |
412 * @return {string} New Cookie header value. | |
413 * @private | |
414 */ | |
415 Authenticator.prototype.updateCookieValue_ = function( | |
416 header_value, cookie_name, cookie_value) { | |
417 var cookies = header_value.split(/\s*;\s*/); | |
418 var found = false; | |
419 for (var i = 0; i < cookies.length; ++i) { | |
420 if (cookies[i].indexOf(cookie_name + '=', 0) == 0) { | |
421 found = true; | |
422 cookies[i] = cookie_name + '=' + cookie_value; | |
423 break; | |
424 } | |
425 } | |
426 if (!found) { | |
427 cookies.push(cookie_name + '=' + cookie_value); | |
428 } | |
429 return cookies.join('; '); | |
430 }; | |
431 | |
432 /** | |
433 * Handler for webView.request.onBeforeSendHeaders . | |
434 * @return {!Object} Modified request headers. | |
435 * @private | |
436 */ | |
437 Authenticator.prototype.onBeforeSendHeaders_ = function(details) { | |
438 if (this.isNewGaiaFlowChromeOS && this.gaps_cookie_ && | |
439 !this.gaps_cookie_sent_) { | |
440 var headers = details.requestHeaders; | |
441 var found = false; | |
442 var gapsCookie = this.gaps_cookie_; | |
443 | |
444 for (var i = 0, l = headers.length; i < l; ++i) { | |
445 if (headers[i].name == COOKIE_HEADER) { | |
446 headers[i].value = this.updateCookieValue_(headers[i].value, | |
447 GAPS_COOKIE, gapsCookie); | |
448 found = true; | |
449 break; | |
450 } | |
451 } | |
452 if (!found) { | |
453 details.requestHeaders.push( | |
454 {name: COOKIE_HEADER, value: GAPS_COOKIE + '=' + gapsCookie}); | |
455 } | |
456 this.gaps_cookie_sent_ = true; | |
457 } | |
458 return { | |
459 requestHeaders: details.requestHeaders | |
460 }; | |
461 }; | |
462 | |
463 /** | |
383 * Returns true if given HTML5 message is received from the webview element. | 464 * Returns true if given HTML5 message is received from the webview element. |
384 * @param {object} e Payload of the received HTML5 message. | 465 * @param {object} e Payload of the received HTML5 message. |
385 */ | 466 */ |
386 Authenticator.prototype.isGaiaMessage = function(e) { | 467 Authenticator.prototype.isGaiaMessage = function(e) { |
387 if (!this.isWebviewEvent_(e)) | 468 if (!this.isWebviewEvent_(e)) |
388 return false; | 469 return false; |
389 | 470 |
390 // The event origin does not have a trailing slash. | 471 // The event origin does not have a trailing slash. |
391 if (e.origin != this.idpOrigin_.substring(0, this.idpOrigin_.length - 1)) { | 472 if (e.origin != this.idpOrigin_.substring(0, this.idpOrigin_.length - 1)) { |
392 return false; | 473 return false; |
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
515 this.onAuthCompleted_(); | 596 this.onAuthCompleted_(); |
516 }; | 597 }; |
517 | 598 |
518 /** | 599 /** |
519 * Invoked to process authentication completion. | 600 * Invoked to process authentication completion. |
520 * @private | 601 * @private |
521 */ | 602 */ |
522 Authenticator.prototype.onAuthCompleted_ = function() { | 603 Authenticator.prototype.onAuthCompleted_ = function() { |
523 assert(this.skipForNow_ || | 604 assert(this.skipForNow_ || |
524 (this.email_ && this.gaiaId_ && this.sessionIndex_)); | 605 (this.email_ && this.gaiaId_ && this.sessionIndex_)); |
525 this.dispatchEvent( | 606 this.dispatchEvent(new CustomEvent( |
526 new CustomEvent('authCompleted', | 607 'authCompleted', |
527 // TODO(rsorokin): get rid of the stub values. | 608 // TODO(rsorokin): get rid of the stub values. |
528 { | 609 { |
529 detail: { | 610 detail: { |
530 email: this.email_ || '', | 611 email: this.email_ || '', |
531 gaiaId: this.gaiaId_ || '', | 612 gaiaId: this.gaiaId_ || '', |
532 password: this.password_ || '', | 613 password: this.password_ || '', |
533 authCode: this.oauth_code_, | 614 authCode: this.oauth_code_, |
534 usingSAML: this.authFlow == AuthFlow.SAML, | 615 usingSAML: this.authFlow == AuthFlow.SAML, |
535 chooseWhatToSync: this.chooseWhatToSync_, | 616 chooseWhatToSync: this.chooseWhatToSync_, |
536 skipForNow: this.skipForNow_, | 617 skipForNow: this.skipForNow_, |
537 sessionIndex: this.sessionIndex_ || '', | 618 sessionIndex: this.sessionIndex_ || '', |
538 trusted: this.trusted_ | 619 trusted: this.trusted_, |
539 } | 620 gapsCookie: this.new_gaps_cookie_ || this.gaps_cookie_ || '', |
540 })); | 621 } |
622 })); | |
541 this.clearCredentials_(); | 623 this.clearCredentials_(); |
542 }; | 624 }; |
543 | 625 |
544 /** | 626 /** |
545 * Invoked when |samlHandler_| fires 'insecureContentBlocked' event. | 627 * Invoked when |samlHandler_| fires 'insecureContentBlocked' event. |
546 * @private | 628 * @private |
547 */ | 629 */ |
548 Authenticator.prototype.onInsecureContentBlocked_ = function(e) { | 630 Authenticator.prototype.onInsecureContentBlocked_ = function(e) { |
549 if (this.insecureContentBlockedCallback) { | 631 if (this.insecureContentBlockedCallback) { |
550 this.insecureContentBlockedCallback(e.detail.url); | 632 this.insecureContentBlockedCallback(e.detail.url); |
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
682 Authenticator.AuthMode = AuthMode; | 764 Authenticator.AuthMode = AuthMode; |
683 Authenticator.SUPPORTED_PARAMS = SUPPORTED_PARAMS; | 765 Authenticator.SUPPORTED_PARAMS = SUPPORTED_PARAMS; |
684 | 766 |
685 return { | 767 return { |
686 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old | 768 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old |
687 // iframe-based flow is deprecated. | 769 // iframe-based flow is deprecated. |
688 GaiaAuthHost: Authenticator, | 770 GaiaAuthHost: Authenticator, |
689 Authenticator: Authenticator | 771 Authenticator: Authenticator |
690 }; | 772 }; |
691 }); | 773 }); |
OLD | NEW |