[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / resources / gaia_auth / main.js
blob41f5116fff64978fe281fe376a4ffc93902131c3
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.
5 /**
6 * Authenticator class wraps the communications between Gaia and its host.
7 */
8 function Authenticator() {
11 /**
12 * Gaia auth extension url origin.
13 * @type {string}
15 Authenticator.THIS_EXTENSION_ORIGIN =
16 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik';
18 /**
19 * The lowest version of the credentials passing API supported.
20 * @type {number}
22 Authenticator.MIN_API_VERSION_VERSION = 1;
24 /**
25 * The highest version of the credentials passing API supported.
26 * @type {number}
28 Authenticator.MAX_API_VERSION_VERSION = 1;
30 /**
31 * The key types supported by the credentials passing API.
32 * @type {Array} Array of strings.
34 Authenticator.API_KEY_TYPES = [
35 'KEY_TYPE_PASSWORD_PLAIN',
38 /**
39 * Singleton getter of Authenticator.
40 * @return {Object} The singleton instance of Authenticator.
42 Authenticator.getInstance = function() {
43 if (!Authenticator.instance_) {
44 Authenticator.instance_ = new Authenticator();
46 return Authenticator.instance_;
49 Authenticator.prototype = {
50 email_: null,
52 // Depending on the key type chosen, this will contain the plain text password
53 // or a credential derived from it along with the information required to
54 // repeat the derivation, such as a salt. The information will be encoded so
55 // that it contains printable ASCII characters only. The exact encoding is TBD
56 // when support for key types other than plain text password is added.
57 passwordBytes_: null,
59 attemptToken_: null,
61 // Input params from extension initialization URL.
62 inputLang_: undefined,
63 intputEmail_: undefined,
65 isSAMLFlow_: false,
66 isSAMLEnabled_: false,
67 supportChannel_: null,
69 GAIA_URL: 'https://accounts.google.com/',
70 GAIA_PAGE_PATH: 'ServiceLogin?skipvpage=true&sarp=1&rm=hide',
71 PARENT_PAGE: 'chrome://oobe/',
72 SERVICE_ID: 'chromeoslogin',
73 CONTINUE_URL: Authenticator.THIS_EXTENSION_ORIGIN + '/success.html',
74 CONSTRAINED_FLOW_SOURCE: 'chrome',
76 initialize: function() {
77 var params = getUrlSearchParams(location.search);
78 this.parentPage_ = params.parentPage || this.PARENT_PAGE;
79 this.gaiaUrl_ = params.gaiaUrl || this.GAIA_URL;
80 this.gaiaPath_ = params.gaiaPath || this.GAIA_PAGE_PATH;
81 this.inputLang_ = params.hl;
82 this.inputEmail_ = params.email;
83 this.service_ = params.service || this.SERVICE_ID;
84 this.continueUrl_ = params.continueUrl || this.CONTINUE_URL;
85 this.desktopMode_ = params.desktopMode == '1';
86 this.isConstrainedWindow_ = params.constrained == '1';
87 this.initialFrameUrl_ = params.frameUrl || this.constructInitialFrameUrl_();
88 this.initialFrameUrlWithoutParams_ = stripParams(this.initialFrameUrl_);
90 document.addEventListener('DOMContentLoaded', this.onPageLoad_.bind(this));
91 if (!this.desktopMode_) {
92 // SAML is always enabled in desktop mode, thus no need to listen for
93 // enableSAML event.
94 document.addEventListener('enableSAML', this.onEnableSAML_.bind(this));
98 isGaiaMessage_: function(msg) {
99 // Not quite right, but good enough.
100 return this.gaiaUrl_.indexOf(msg.origin) == 0 ||
101 this.GAIA_URL.indexOf(msg.origin) == 0;
104 isInternalMessage_: function(msg) {
105 return msg.origin == Authenticator.THIS_EXTENSION_ORIGIN;
108 isParentMessage_: function(msg) {
109 return msg.origin == this.parentPage_;
112 constructInitialFrameUrl_: function() {
113 var url = this.gaiaUrl_ + this.gaiaPath_;
115 url = appendParam(url, 'service', this.service_);
116 url = appendParam(url, 'continue', this.continueUrl_);
117 if (this.inputLang_)
118 url = appendParam(url, 'hl', this.inputLang_);
119 if (this.inputEmail_)
120 url = appendParam(url, 'Email', this.inputEmail_);
121 if (this.isConstrainedWindow_)
122 url = appendParam(url, 'source', this.CONSTRAINED_FLOW_SOURCE);
123 return url;
126 onPageLoad_: function() {
127 window.addEventListener('message', this.onMessage.bind(this), false);
129 var gaiaFrame = $('gaia-frame');
130 gaiaFrame.src = this.initialFrameUrl_;
132 if (this.desktopMode_) {
133 var handler = function() {
134 this.onLoginUILoaded_();
135 gaiaFrame.removeEventListener('load', handler);
137 this.initDesktopChannel_();
138 }.bind(this);
139 gaiaFrame.addEventListener('load', handler);
143 initDesktopChannel_: function() {
144 this.supportChannel_ = new Channel();
145 this.supportChannel_.connect('authMain');
147 var channelConnected = false;
148 this.supportChannel_.registerMessage('channelConnected', function() {
149 channelConnected = true;
151 this.supportChannel_.send({
152 name: 'initDesktopFlow',
153 gaiaUrl: this.gaiaUrl_,
154 continueUrl: stripParams(this.continueUrl_),
155 isConstrainedWindow: this.isConstrainedWindow_
157 this.supportChannel_.registerMessage(
158 'switchToFullTab', this.switchToFullTab_.bind(this));
159 this.supportChannel_.registerMessage(
160 'completeLogin', this.completeLogin_.bind(this));
162 this.onEnableSAML_();
163 }.bind(this));
165 window.setTimeout(function() {
166 if (!channelConnected) {
167 // Re-initialize the channel if it is not connected properly, e.g.
168 // connect may be called before background script started running.
169 this.initDesktopChannel_();
171 }.bind(this), 200);
175 * Invoked when the login UI is initialized or reset.
177 onLoginUILoaded_: function() {
178 var msg = {
179 'method': 'loginUILoaded'
181 window.parent.postMessage(msg, this.parentPage_);
185 * Invoked when the background script sends a message to indicate that the
186 * current content does not fit in a constrained window.
187 * @param {Object=} opt_extraMsg Optional extra info to send.
189 switchToFullTab_: function(msg) {
190 var parentMsg = {
191 'method': 'switchToFullTab',
192 'url': msg.url
194 window.parent.postMessage(parentMsg, this.parentPage_);
198 * Invoked when the signin flow is complete.
199 * @param {Object=} opt_extraMsg Optional extra info to send.
201 completeLogin_: function(opt_extraMsg) {
202 var msg = {
203 'method': 'completeLogin',
204 'email': (opt_extraMsg && opt_extraMsg.email) || this.email_,
205 'password': (opt_extraMsg && opt_extraMsg.password) ||
206 this.passwordBytes_,
207 'usingSAML': this.isSAMLFlow_,
208 'chooseWhatToSync': this.chooseWhatToSync_ || false,
209 'skipForNow': opt_extraMsg && opt_extraMsg.skipForNow,
210 'sessionIndex': opt_extraMsg && opt_extraMsg.sessionIndex
212 window.parent.postMessage(msg, this.parentPage_);
213 if (this.isSAMLEnabled_)
214 this.supportChannel_.send({name: 'resetAuth'});
218 * Invoked when 'enableSAML' event is received to initialize SAML support on
219 * Chrome OS, or when initDesktopChannel_ is called on desktop.
221 onEnableSAML_: function() {
222 this.isSAMLEnabled_ = true;
223 this.isSAMLFlow_ = false;
225 if (!this.supportChannel_) {
226 this.supportChannel_ = new Channel();
227 this.supportChannel_.connect('authMain');
230 this.supportChannel_.registerMessage(
231 'onAuthPageLoaded', this.onAuthPageLoaded_.bind(this));
232 this.supportChannel_.registerMessage(
233 'onInsecureContentBlocked', this.onInsecureContentBlocked_.bind(this));
234 this.supportChannel_.registerMessage(
235 'apiCall', this.onAPICall_.bind(this));
236 this.supportChannel_.send({
237 name: 'setGaiaUrl',
238 gaiaUrl: this.gaiaUrl_
240 if (!this.desktopMode_ && this.gaiaUrl_.indexOf('https://') == 0) {
241 // Abort the login flow when content served over an unencrypted connection
242 // is detected on Chrome OS. This does not apply to tests that explicitly
243 // set a non-https GAIA URL and want to perform all authentication over
244 // http.
245 this.supportChannel_.send({
246 name: 'setBlockInsecureContent',
247 blockInsecureContent: true
253 * Invoked when the background page sends 'onHostedPageLoaded' message.
254 * @param {!Object} msg Details sent with the message.
256 onAuthPageLoaded_: function(msg) {
257 var isSAMLPage = msg.url.indexOf(this.gaiaUrl_) != 0;
259 if (isSAMLPage && !this.isSAMLFlow_) {
260 // GAIA redirected to a SAML login page. The credentials provided to this
261 // page will determine what user gets logged in. The credentials obtained
262 // from the GAIA login form are no longer relevant and can be discarded.
263 this.isSAMLFlow_ = true;
264 this.email_ = null;
265 this.passwordBytes_ = null;
268 window.parent.postMessage({
269 'method': 'authPageLoaded',
270 'isSAML': this.isSAMLFlow_,
271 'domain': extractDomain(msg.url)
272 }, this.parentPage_);
276 * Invoked when the background page sends an 'onInsecureContentBlocked'
277 * message.
278 * @param {!Object} msg Details sent with the message.
280 onInsecureContentBlocked_: function(msg) {
281 window.parent.postMessage({
282 'method': 'insecureContentBlocked',
283 'url': stripParams(msg.url)
284 }, this.parentPage_);
288 * Invoked when one of the credential passing API methods is called by a SAML
289 * provider.
290 * @param {!Object} msg Details of the API call.
292 onAPICall_: function(msg) {
293 var call = msg.call;
294 if (call.method == 'initialize') {
295 if (!Number.isInteger(call.requestedVersion) ||
296 call.requestedVersion < Authenticator.MIN_API_VERSION_VERSION) {
297 this.sendInitializationFailure_();
298 return;
301 this.apiVersion_ = Math.min(call.requestedVersion,
302 Authenticator.MAX_API_VERSION_VERSION);
303 this.initialized_ = true;
304 this.sendInitializationSuccess_();
305 return;
308 if (call.method == 'add') {
309 if (Authenticator.API_KEY_TYPES.indexOf(call.keyType) == -1) {
310 console.error('Authenticator.onAPICall_: unsupported key type');
311 return;
313 this.apiToken_ = call.token;
314 this.email_ = call.user;
315 this.passwordBytes_ = call.passwordBytes;
316 } else if (call.method == 'confirm') {
317 if (call.token != this.apiToken_)
318 console.error('Authenticator.onAPICall_: token mismatch');
319 } else {
320 console.error('Authenticator.onAPICall_: unknown message');
324 sendInitializationSuccess_: function() {
325 this.supportChannel_.send({name: 'apiResponse', response: {
326 result: 'initialized',
327 version: this.apiVersion_,
328 keyTypes: Authenticator.API_KEY_TYPES
329 }});
332 sendInitializationFailure_: function() {
333 this.supportChannel_.send({
334 name: 'apiResponse',
335 response: {result: 'initialization_failed'}
339 onConfirmLogin_: function() {
340 if (!this.isSAMLFlow_) {
341 this.completeLogin_();
342 return;
345 var apiUsed = !!this.passwordBytes_;
347 // Retrieve the e-mail address of the user who just authenticated from GAIA.
348 window.parent.postMessage({method: 'retrieveAuthenticatedUserEmail',
349 attemptToken: this.attemptToken_,
350 apiUsed: apiUsed},
351 this.parentPage_);
353 if (!apiUsed) {
354 this.supportChannel_.sendWithCallback(
355 {name: 'getScrapedPasswords'},
356 function(passwords) {
357 if (passwords.length == 0) {
358 window.parent.postMessage(
359 {method: 'noPassword', email: this.email_},
360 this.parentPage_);
361 } else {
362 window.parent.postMessage({method: 'confirmPassword',
363 email: this.email_,
364 passwordCount: passwords.length},
365 this.parentPage_);
367 }.bind(this));
371 maybeCompleteSAMLLogin_: function() {
372 // SAML login is complete when the user's e-mail address has been retrieved
373 // from GAIA and the user has successfully confirmed the password.
374 if (this.email_ !== null && this.passwordBytes_ !== null)
375 this.completeLogin_();
378 onVerifyConfirmedPassword_: function(password) {
379 this.supportChannel_.sendWithCallback(
380 {name: 'getScrapedPasswords'},
381 function(passwords) {
382 for (var i = 0; i < passwords.length; ++i) {
383 if (passwords[i] == password) {
384 this.passwordBytes_ = passwords[i];
385 this.maybeCompleteSAMLLogin_();
386 return;
389 window.parent.postMessage(
390 {method: 'confirmPassword', email: this.email_},
391 this.parentPage_);
392 }.bind(this));
395 onMessage: function(e) {
396 var msg = e.data;
397 if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) {
398 this.email_ = msg.email;
399 this.passwordBytes_ = msg.password;
400 this.attemptToken_ = msg.attemptToken;
401 this.chooseWhatToSync_ = msg.chooseWhatToSync;
402 this.isSAMLFlow_ = false;
403 if (this.isSAMLEnabled_)
404 this.supportChannel_.send({name: 'startAuth'});
405 } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) {
406 this.email_ = null;
407 this.passwordBytes_ = null;
408 this.attemptToken_ = null;
409 this.isSAMLFlow_ = false;
410 this.onLoginUILoaded_();
411 if (this.isSAMLEnabled_)
412 this.supportChannel_.send({name: 'resetAuth'});
413 } else if (msg.method == 'setAuthenticatedUserEmail' &&
414 this.isParentMessage_(e)) {
415 if (this.attemptToken_ == msg.attemptToken) {
416 this.email_ = msg.email;
417 this.maybeCompleteSAMLLogin_();
419 } else if (msg.method == 'confirmLogin' && this.isInternalMessage_(e)) {
420 if (this.attemptToken_ == msg.attemptToken)
421 this.onConfirmLogin_();
422 else
423 console.error('Authenticator.onMessage: unexpected attemptToken!?');
424 } else if (msg.method == 'verifyConfirmedPassword' &&
425 this.isParentMessage_(e)) {
426 this.onVerifyConfirmedPassword_(msg.password);
427 } else if (msg.method == 'redirectToSignin' &&
428 this.isParentMessage_(e)) {
429 $('gaia-frame').src = this.constructInitialFrameUrl_();
430 } else {
431 console.error('Authenticator.onMessage: unknown message + origin!?');
436 Authenticator.getInstance().initialize();