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

Side by Side Diff: chrome/browser/resources/gaia_auth_host/authenticator.js

Issue 1004753004: cros: Port SAML support to webview sign-in. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fix typo on api code path Created 5 years, 9 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
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">
6
5 /** 7 /**
6 * @fileoverview An UI component to authenciate to Chrome. The component hosts 8 * @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 9 * IdP web pages in a webview. A client who is interested in monitoring
8 * authentication events should pass a listener object of type 10 * authentication events should pass a listener object of type
9 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization, 11 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization,
10 * call {@code load} to start the authentication flow. 12 * call {@code load} to start the authentication flow.
11 */ 13 */
14
12 cr.define('cr.login', function() { 15 cr.define('cr.login', function() {
13 'use strict'; 16 'use strict';
14 17
15 // TODO(rogerta): should use gaia URL from GaiaUrls::gaia_url() instead 18 // TODO(rogerta): should use gaia URL from GaiaUrls::gaia_url() instead
16 // 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
17 // environments. 20 // environments.
18 var IDP_ORIGIN = 'https://accounts.google.com/'; 21 var IDP_ORIGIN = 'https://accounts.google.com/';
19 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide'; 22 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide';
20 var CONTINUE_URL = 23 var CONTINUE_URL =
21 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html'; 24 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html';
(...skipping 66 matching lines...) Expand 10 before | Expand all | Expand 10 after
88 this.authFlow_ = AuthFlow.DEFAULT; 91 this.authFlow_ = AuthFlow.DEFAULT;
89 this.loaded_ = false; 92 this.loaded_ = false;
90 this.idpOrigin_ = null; 93 this.idpOrigin_ = null;
91 this.continueUrl_ = null; 94 this.continueUrl_ = null;
92 this.continueUrlWithoutParams_ = null; 95 this.continueUrlWithoutParams_ = null;
93 this.initialFrameUrl_ = null; 96 this.initialFrameUrl_ = null;
94 this.reloadUrl_ = null; 97 this.reloadUrl_ = null;
95 this.trusted_ = true; 98 this.trusted_ = true;
96 this.oauth_code_ = null; 99 this.oauth_code_ = null;
97 100
101 this.samlHandler_ = new cr.login.SamlHandler(this.webview_);
102 this.confirmPasswordCallback = null;
103 this.noPasswordCallback = null;
104 this.insecureContentBlockedCallback = null;
105 this.samlApiUsedCallback = null;
106 this.needPassword = true;
107 this.samlHandler_.addEventListener(
108 'insecureContentBlocked',
109 this.onInsecureContentBlocked_.bind(this));
110
98 this.webview_.addEventListener('droplink', this.onDropLink_.bind(this)); 111 this.webview_.addEventListener('droplink', this.onDropLink_.bind(this));
99 this.webview_.addEventListener( 112 this.webview_.addEventListener(
100 'newwindow', this.onNewWindow_.bind(this)); 113 'newwindow', this.onNewWindow_.bind(this));
101 this.webview_.addEventListener( 114 this.webview_.addEventListener(
102 'contentload', this.onContentLoad_.bind(this)); 115 'contentload', this.onContentLoad_.bind(this));
103 this.webview_.addEventListener( 116 this.webview_.addEventListener(
104 'loadstop', this.onLoadStop_.bind(this)); 117 'loadstop', this.onLoadStop_.bind(this));
105 this.webview_.addEventListener( 118 this.webview_.addEventListener(
106 'loadcommit', this.onLoadCommit_.bind(this)); 119 'loadcommit', this.onLoadCommit_.bind(this));
107 this.webview_.request.onCompleted.addListener( 120 this.webview_.request.onCompleted.addListener(
108 this.onRequestCompleted_.bind(this), 121 this.onRequestCompleted_.bind(this),
109 {urls: ['<all_urls>'], types: ['main_frame']}, 122 {urls: ['<all_urls>'], types: ['main_frame']},
110 ['responseHeaders']); 123 ['responseHeaders']);
111 this.webview_.request.onHeadersReceived.addListener( 124 this.webview_.request.onHeadersReceived.addListener(
112 this.onHeadersReceived_.bind(this), 125 this.onHeadersReceived_.bind(this),
113 {urls: ['<all_urls>'], types: ['main_frame', 'xmlhttprequest']}, 126 {urls: ['<all_urls>'], types: ['main_frame', 'xmlhttprequest']},
114 ['responseHeaders']); 127 ['responseHeaders']);
115 window.addEventListener( 128 window.addEventListener(
116 'message', this.onMessageFromWebview_.bind(this), false); 129 'message', this.onMessageFromWebview_.bind(this), false);
117 window.addEventListener( 130 window.addEventListener(
118 'focus', this.onFocus_.bind(this), false); 131 'focus', this.onFocus_.bind(this), false);
119 window.addEventListener( 132 window.addEventListener(
120 'popstate', this.onPopState_.bind(this), false); 133 'popstate', this.onPopState_.bind(this), false);
121 } 134 }
122 135
123 // TODO(guohui,xiyuan): no need to inherit EventTarget once we deprecate the
124 // old event-based signin flow.
125 Authenticator.prototype = Object.create(cr.EventTarget.prototype); 136 Authenticator.prototype = Object.create(cr.EventTarget.prototype);
126 137
127 /** 138 /**
128 * Loads the authenticator component with the given parameters. 139 * Loads the authenticator component with the given parameters.
129 * @param {AuthMode} authMode Authorization mode. 140 * @param {AuthMode} authMode Authorization mode.
130 * @param {Object} data Parameters for the authorization flow. 141 * @param {Object} data Parameters for the authorization flow.
131 */ 142 */
132 Authenticator.prototype.load = function(authMode, data) { 143 Authenticator.prototype.load = function(authMode, data) {
133 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN; 144 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN;
134 this.continueUrl_ = data.continueUrl || CONTINUE_URL; 145 this.continueUrl_ = data.continueUrl || CONTINUE_URL;
135 this.continueUrlWithoutParams_ = 146 this.continueUrlWithoutParams_ =
136 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) || 147 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) ||
137 this.continueUrl_; 148 this.continueUrl_;
138 this.isConstrainedWindow_ = data.constrained == '1'; 149 this.isConstrainedWindow_ = data.constrained == '1';
139 this.isMinuteMaidChromeOS = data.isMinuteMaidChromeOS; 150 this.isMinuteMaidChromeOS = data.isMinuteMaidChromeOS;
140 151
141 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data); 152 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data);
142 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_; 153 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_;
143 this.authFlow_ = AuthFlow.DEFAULT; 154 this.authFlow_ = AuthFlow.DEFAULT;
155 this.samlHandler_.reset();
156 // Don't block insecure content for desktop flow because it lands on
157 // http. Otherwise, block insecure content as long as gaia is https.
158 this.samlHandler_.blockInsecureContent = authMode != AuthMode.DESKTOP &&
159 this.idpOrigin_.indexOf('https://') == 0;
144 160
145 this.webview_.src = this.reloadUrl_; 161 this.webview_.src = this.reloadUrl_;
146 162
147 this.loaded_ = false; 163 this.loaded_ = false;
148 }; 164 };
149 165
150 /** 166 /**
151 * Reloads the authenticator component. 167 * Reloads the authenticator component.
152 */ 168 */
153 Authenticator.prototype.reload = function() { 169 Authenticator.prototype.reload = function() {
154 this.webview_.src = this.reloadUrl_; 170 this.webview_.src = this.reloadUrl_;
155 this.authFlow_ = AuthFlow.DEFAULT; 171 this.authFlow_ = AuthFlow.DEFAULT;
172 this.samlHandler_.reset();
156 this.loaded_ = false; 173 this.loaded_ = false;
157 }; 174 };
158 175
159 Authenticator.prototype.constructInitialFrameUrl_ = function(data) { 176 Authenticator.prototype.constructInitialFrameUrl_ = function(data) {
160 var url = this.idpOrigin_ + (data.gaiaPath || IDP_PATH); 177 var url = this.idpOrigin_ + (data.gaiaPath || IDP_PATH);
161 178
162 if (this.isMinuteMaidChromeOS) { 179 if (this.isMinuteMaidChromeOS) {
163 if (data.chromeType) 180 if (data.chromeType)
164 url = appendParam(url, 'chrometype', data.chromeType); 181 url = appendParam(url, 'chrometype', data.chromeType);
165 if (data.clientId) 182 if (data.clientId)
(...skipping 17 matching lines...) Expand all
183 * Invoked when a main frame request in the webview has completed. 200 * Invoked when a main frame request in the webview has completed.
184 * @private 201 * @private
185 */ 202 */
186 Authenticator.prototype.onRequestCompleted_ = function(details) { 203 Authenticator.prototype.onRequestCompleted_ = function(details) {
187 var currentUrl = details.url; 204 var currentUrl = details.url;
188 205
189 if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) { 206 if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) {
190 if (currentUrl.indexOf('ntp=1') >= 0) 207 if (currentUrl.indexOf('ntp=1') >= 0)
191 this.skipForNow_ = true; 208 this.skipForNow_ = true;
192 209
193 this.onAuthCompleted_(); 210 this.maybeCompleteAuth_();
194 return; 211 return;
195 } 212 }
196 213
197 if (currentUrl.indexOf('https') != 0) 214 if (currentUrl.indexOf('https') != 0)
198 this.trusted_ = false; 215 this.trusted_ = false;
199 216
200 if (this.isConstrainedWindow_) { 217 if (this.isConstrainedWindow_) {
201 var isEmbeddedPage = false; 218 var isEmbeddedPage = false;
202 if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) { 219 if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
203 var headers = details.responseHeaders; 220 var headers = details.responseHeaders;
204 for (var i = 0; headers && i < headers.length; ++i) { 221 for (var i = 0; headers && i < headers.length; ++i) {
205 if (headers[i].name.toLowerCase() == EMBEDDED_FORM_HEADER) { 222 if (headers[i].name.toLowerCase() == EMBEDDED_FORM_HEADER) {
206 isEmbeddedPage = true; 223 isEmbeddedPage = true;
207 break; 224 break;
208 } 225 }
209 } 226 }
210 } 227 }
211 if (!isEmbeddedPage) { 228 if (!isEmbeddedPage) {
212 this.dispatchEvent(new CustomEvent('resize', {detail: currentUrl})); 229 this.dispatchEvent(new CustomEvent('resize', {detail: currentUrl}));
213 return; 230 return;
214 } 231 }
215 } 232 }
216 233
217 this.updateHistoryState_(currentUrl); 234 this.updateHistoryState_(currentUrl);
218
219 }; 235 };
220 236
221 /** 237 /**
222 * Manually updates the history. Invoked upon completion of a webview 238 * Manually updates the history. Invoked upon completion of a webview
223 * navigation. 239 * navigation.
224 * @param {string} url Request URL. 240 * @param {string} url Request URL.
225 * @private 241 * @private
226 */ 242 */
227 Authenticator.prototype.updateHistoryState_ = function(url) { 243 Authenticator.prototype.updateHistoryState_ = function(url) {
228 if (history.state && history.state.url != url) 244 if (history.state && history.state.url != url)
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after
299 * Invoked when an HTML5 message is received from the webview element. 315 * Invoked when an HTML5 message is received from the webview element.
300 * @param {object} e Payload of the received HTML5 message. 316 * @param {object} e Payload of the received HTML5 message.
301 * @private 317 * @private
302 */ 318 */
303 Authenticator.prototype.onMessageFromWebview_ = function(e) { 319 Authenticator.prototype.onMessageFromWebview_ = function(e) {
304 // The event origin does not have a trailing slash. 320 // The event origin does not have a trailing slash.
305 if (e.origin != this.idpOrigin_.substring(0, this.idpOrigin_.length - 1)) { 321 if (e.origin != this.idpOrigin_.substring(0, this.idpOrigin_.length - 1)) {
306 return; 322 return;
307 } 323 }
308 324
325 // Gaia messages must be an object with 'method' property.
326 if (typeof e.data != 'object' || !e.data.hasOwnProperty('method')) {
327 return;
328 }
329
309 var msg = e.data; 330 var msg = e.data;
310 if (msg.method == 'attemptLogin') { 331 if (msg.method == 'attemptLogin') {
311 this.email_ = msg.email; 332 this.email_ = msg.email;
312 this.password_ = msg.password; 333 this.password_ = msg.password;
313 this.chooseWhatToSync_ = msg.chooseWhatToSync; 334 this.chooseWhatToSync_ = msg.chooseWhatToSync;
314 } 335 }
315 }; 336 };
316 337
317 /** 338 /**
339 * Invoked by the hosting page to verify the Saml password.
340 */
341 Authenticator.prototype.verifyConfirmedPassword = function(password) {
342 if (!this.samlHandler_.verifyConfirmedPassword(password)) {
343 // Invoke confirm password callback asynchronously because the
344 // verification was based on messages and caller (GaiaSigninScreen)
345 // does not expect it to be called immediately.
346 // TODO(xiyuan): Change to synchronous call when iframe based code
347 // is removed.
348 var invokeConfirmPassword = (function() {
349 this.confirmPasswordCallback(this.samlHandler_.scrapedPasswordCount);
350 }).bind(this);
351 window.setTimeout(invokeConfirmPassword, 0);
352 return;
353 }
354
355 this.password_ = password;
356 this.onAuthCompleted_();
357 };
358
359 /**
360 * Check Saml flow and start password confirmation flow if needed. Otherwise,
361 * continue with auto completion.
362 * @private
363 */
364 Authenticator.prototype.maybeCompleteAuth_ = function() {
365 if (this.authFlow_ != AuthFlow.SAML) {
366 this.onAuthCompleted_();
367 return;
368 }
369
370 if (this.samlHandler_.samlApiUsed) {
371 if (this.samlApiUsedCallback) {
372 this.samlApiUsedCallback();
373 }
374 this.password_ = this.samlHandler_.apiPasswordBytes;
375 } else if (this.samlHandler_.scrapedPasswordCount == 0) {
376 if (this.noPasswordCallback) {
377 this.noPasswordCallback(this.email_);
378 } else {
379 console.error('Authenticator: No password scraped for SAML.');
380 return;
381 }
382 } else if (this.needPassword) {
383 if (this.confirmPasswordCallback) {
384 // Confirm scraped password. The flow follows in
385 // verifyConfirmedPassword.
386 this.confirmPasswordCallback(this.samlHandler_.scrapedPasswordCount);
387 return;
388 }
389 }
390
391 this.onAuthCompleted_();
392 };
393
394 /**
318 * Invoked to process authentication completion. 395 * Invoked to process authentication completion.
319 * @private 396 * @private
320 */ 397 */
321 Authenticator.prototype.onAuthCompleted_ = function() { 398 Authenticator.prototype.onAuthCompleted_ = function() {
322 if (!this.email_ && !this.skipForNow_) { 399 if (!this.email_ && !this.skipForNow_) {
323 this.webview_.src = this.initialFrameUrl_; 400 this.webview_.src = this.initialFrameUrl_;
324 return; 401 return;
325 } 402 }
326 403
327 this.dispatchEvent( 404 this.dispatchEvent(
328 new CustomEvent('authCompleted', 405 new CustomEvent('authCompleted',
329 // TODO(rsorokin): get rid of the stub values. 406 // TODO(rsorokin): get rid of the stub values.
330 {detail: {email: this.email_ || '', 407 {detail: {email: this.email_ || '',
331 gaiaId: this.gaiaId_ || '', 408 gaiaId: this.gaiaId_ || '',
332 password: this.password_ || '', 409 password: this.password_ || '',
333 authCode: this.oauth_code_, 410 authCode: this.oauth_code_,
334 usingSAML: this.authFlow_ == AuthFlow.SAML, 411 usingSAML: this.authFlow_ == AuthFlow.SAML,
335 chooseWhatToSync: this.chooseWhatToSync_, 412 chooseWhatToSync: this.chooseWhatToSync_,
336 skipForNow: this.skipForNow_, 413 skipForNow: this.skipForNow_,
337 sessionIndex: this.sessionIndex_ || '', 414 sessionIndex: this.sessionIndex_ || '',
338 trusted: this.trusted_}})); 415 trusted: this.trusted_}}));
339 }; 416 };
340 417
341 /** 418 /**
419 * Invoked when |samlHandler_| fires 'insecureContentBlocked' event.
420 * @private
421 */
422 Authenticator.prototype.onInsecureContentBlocked_ = function(e) {
423 if (this.insecureContentBlockedCallback) {
424 this.insecureContentBlockedCallback(e.details.url);
425 } else {
426 console.error('Authenticator: Insecure content blocked.');
427 }
428 };
429
430 /**
342 * Invoked when a link is dropped on the webview. 431 * Invoked when a link is dropped on the webview.
343 * @private 432 * @private
344 */ 433 */
345 Authenticator.prototype.onDropLink_ = function(e) { 434 Authenticator.prototype.onDropLink_ = function(e) {
346 this.dispatchEvent(new CustomEvent('dropLink', {detail: e.url})); 435 this.dispatchEvent(new CustomEvent('dropLink', {detail: e.url}));
347 }; 436 };
348 437
349 /** 438 /**
350 * Invoked when the webview attempts to open a new window. 439 * Invoked when the webview attempts to open a new window.
351 * @private 440 * @private
(...skipping 28 matching lines...) Expand all
380 // Focus webview after dispatching event when webview is already visible. 469 // Focus webview after dispatching event when webview is already visible.
381 this.webview_.focus(); 470 this.webview_.focus();
382 } 471 }
383 }; 472 };
384 473
385 /** 474 /**
386 * Invoked when the webview navigates withing the current document. 475 * Invoked when the webview navigates withing the current document.
387 * @private 476 * @private
388 */ 477 */
389 Authenticator.prototype.onLoadCommit_ = function(e) { 478 Authenticator.prototype.onLoadCommit_ = function(e) {
390 // TODO(rsorokin): Investigate whether this breaks SAML. 479 // TODO(rsorokin): Investigate whether this breaks SAML.
Dmitry Polukhin 2015/03/13 11:56:19 nit, it seems this todo can be removed now.
xiyuan 2015/03/17 22:00:15 Done.
391 if (this.oauth_code_) { 480 if (this.oauth_code_) {
392 this.skipForNow_ = true; 481 this.skipForNow_ = true;
393 this.onAuthCompleted_(); 482 this.maybeCompleteAuth_();
394 } 483 }
395 }; 484 };
396 485
397 Authenticator.AuthFlow = AuthFlow; 486 Authenticator.AuthFlow = AuthFlow;
398 Authenticator.AuthMode = AuthMode; 487 Authenticator.AuthMode = AuthMode;
399 Authenticator.SUPPORTED_PARAMS = SUPPORTED_PARAMS; 488 Authenticator.SUPPORTED_PARAMS = SUPPORTED_PARAMS;
400 489
401 return { 490 return {
402 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old 491 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old
403 // iframe-based flow is deprecated. 492 // iframe-based flow is deprecated.
404 GaiaAuthHost: Authenticator 493 GaiaAuthHost: Authenticator
405 }; 494 };
406 }); 495 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698