main.js revision 5d1f7b1de12d16ceb2c938c56701a3e8bfa558f7
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 */ 8function Authenticator() { 9} 10 11/** 12 * Gaia auth extension url origin. 13 * @type {string} 14 */ 15Authenticator.THIS_EXTENSION_ORIGIN = 16 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik'; 17 18/** 19 * Singleton getter of Authenticator. 20 * @return {Object} The singleton instance of Authenticator. 21 */ 22Authenticator.getInstance = function() { 23 if (!Authenticator.instance_) { 24 Authenticator.instance_ = new Authenticator(); 25 } 26 return Authenticator.instance_; 27}; 28 29Authenticator.prototype = { 30 email_: null, 31 password_: null, 32 attemptToken_: null, 33 34 // Input params from extension initialization URL. 35 inputLang_: undefined, 36 intputEmail_: undefined, 37 38 isSAMLFlow_: false, 39 isSAMLEnabled_: false, 40 supportChannel_: null, 41 42 GAIA_URL: 'https://accounts.google.com/', 43 GAIA_PAGE_PATH: 'ServiceLogin?skipvpage=true&sarp=1&rm=hide', 44 PARENT_PAGE: 'chrome://oobe/', 45 SERVICE_ID: 'chromeoslogin', 46 CONTINUE_URL: Authenticator.THIS_EXTENSION_ORIGIN + '/success.html', 47 CONSTRAINED_FLOW_SOURCE: 'chrome', 48 49 initialize: function() { 50 var params = getUrlSearchParams(location.search); 51 this.parentPage_ = params.parentPage || this.PARENT_PAGE; 52 this.gaiaUrl_ = params.gaiaUrl || this.GAIA_URL; 53 this.gaiaPath_ = params.gaiaPath || this.GAIA_PAGE_PATH; 54 this.inputLang_ = params.hl; 55 this.inputEmail_ = params.email; 56 this.service_ = params.service || this.SERVICE_ID; 57 this.continueUrl_ = params.continueUrl || this.CONTINUE_URL; 58 this.desktopMode_ = params.desktopMode == '1'; 59 this.isConstrainedWindow_ = params.constrained == '1'; 60 this.initialFrameUrl_ = params.frameUrl || this.constructInitialFrameUrl_(); 61 this.initialFrameUrlWithoutParams_ = stripParams(this.initialFrameUrl_); 62 63 if (this.desktopMode_) { 64 this.supportChannel_ = new Channel(); 65 this.supportChannel_.connect('authMain'); 66 67 this.supportChannel_.send({ 68 name: 'initDesktopFlow', 69 gaiaUrl: this.gaiaUrl_, 70 continueUrl: stripParams(this.continueUrl_), 71 isConstrainedWindow: this.isConstrainedWindow_ 72 }); 73 74 this.supportChannel_.registerMessage( 75 'switchToFullTab', this.switchToFullTab_.bind(this)); 76 this.supportChannel_.registerMessage( 77 'completeLogin', this.completeLogin_.bind(this)); 78 } 79 80 document.addEventListener('DOMContentLoaded', this.onPageLoad_.bind(this)); 81 document.addEventListener('enableSAML', this.onEnableSAML_.bind(this)); 82 }, 83 84 isGaiaMessage_: function(msg) { 85 // Not quite right, but good enough. 86 return this.gaiaUrl_.indexOf(msg.origin) == 0 || 87 this.GAIA_URL.indexOf(msg.origin) == 0; 88 }, 89 90 isInternalMessage_: function(msg) { 91 return msg.origin == Authenticator.THIS_EXTENSION_ORIGIN; 92 }, 93 94 isParentMessage_: function(msg) { 95 return msg.origin == this.parentPage_; 96 }, 97 98 constructInitialFrameUrl_: function() { 99 var url = this.gaiaUrl_ + this.gaiaPath_; 100 101 url = appendParam(url, 'service', this.service_); 102 url = appendParam(url, 'continue', this.continueUrl_); 103 if (this.inputLang_) 104 url = appendParam(url, 'hl', this.inputLang_); 105 if (this.inputEmail_) 106 url = appendParam(url, 'Email', this.inputEmail_); 107 if (this.isConstrainedWindow_) 108 url = appendParam(url, 'source', this.CONSTRAINED_FLOW_SOURCE); 109 return url; 110 }, 111 112 onPageLoad_: function() { 113 window.addEventListener('message', this.onMessage.bind(this), false); 114 this.loadFrame_(); 115 }, 116 117 loadFrame_: function() { 118 var gaiaFrame = $('gaia-frame'); 119 gaiaFrame.src = this.initialFrameUrl_; 120 if (this.desktopMode_) { 121 var handler = function() { 122 this.onLoginUILoaded_(); 123 gaiaFrame.removeEventListener('load', handler); 124 }.bind(this); 125 gaiaFrame.addEventListener('load', handler); 126 } 127 }, 128 129 /** 130 * Invoked when the login UI is initialized or reset. 131 */ 132 onLoginUILoaded_: function() { 133 var msg = { 134 'method': 'loginUILoaded' 135 }; 136 window.parent.postMessage(msg, this.parentPage_); 137 }, 138 139 /** 140 * Invoked when the background script sends a message to indicate that the 141 * current content does not fit in a constrained window. 142 * @param {Object=} opt_extraMsg Optional extra info to send. 143 */ 144 switchToFullTab_: function(msg) { 145 var parentMsg = { 146 'method': 'switchToFullTab', 147 'url': msg.url 148 }; 149 window.parent.postMessage(parentMsg, this.parentPage_); 150 }, 151 152 /** 153 * Invoked when the signin flow is complete. 154 * @param {Object=} opt_extraMsg Optional extra info to send. 155 */ 156 completeLogin_: function(opt_extraMsg) { 157 var msg = { 158 'method': 'completeLogin', 159 'email': (opt_extraMsg && opt_extraMsg.email) || this.email_, 160 'password': this.password_, 161 'usingSAML': this.isSAMLFlow_, 162 'chooseWhatToSync': this.chooseWhatToSync_ || false, 163 'skipForNow': opt_extraMsg && opt_extraMsg.skipForNow, 164 'sessionIndex': opt_extraMsg && opt_extraMsg.sessionIndex 165 }; 166 window.parent.postMessage(msg, this.parentPage_); 167 if (this.isSAMLEnabled_) 168 this.supportChannel_.send({name: 'resetAuth'}); 169 }, 170 171 /** 172 * Invoked when 'enableSAML' event is received to initialize SAML support. 173 */ 174 onEnableSAML_: function() { 175 this.isSAMLEnabled_ = true; 176 this.isSAMLFlow_ = false; 177 178 if (!this.supportChannel_) { 179 this.supportChannel_ = new Channel(); 180 this.supportChannel_.connect('authMain'); 181 } 182 183 this.supportChannel_.registerMessage( 184 'onAuthPageLoaded', this.onAuthPageLoaded_.bind(this)); 185 this.supportChannel_.registerMessage( 186 'apiCall', this.onAPICall_.bind(this)); 187 this.supportChannel_.send({ 188 name: 'setGaiaUrl', 189 gaiaUrl: this.gaiaUrl_ 190 }); 191 }, 192 193 /** 194 * Invoked when the background page sends 'onHostedPageLoaded' message. 195 * @param {!Object} msg Details sent with the message. 196 */ 197 onAuthPageLoaded_: function(msg) { 198 var isSAMLPage = msg.url.indexOf(this.gaiaUrl_) != 0; 199 200 if (isSAMLPage && !this.isSAMLFlow_) { 201 // GAIA redirected to a SAML login page. The credentials provided to this 202 // page will determine what user gets logged in. The credentials obtained 203 // from the GAIA login from are no longer relevant and can be discarded. 204 this.isSAMLFlow_ = true; 205 this.email_ = null; 206 this.password_ = null; 207 } 208 209 window.parent.postMessage({ 210 'method': 'authPageLoaded', 211 'isSAML': this.isSAMLFlow_, 212 'domain': extractDomain(msg.url) 213 }, this.parentPage_); 214 }, 215 216 /** 217 * Invoked when one of the credential passing API methods is called by a SAML 218 * provider. 219 * @param {!Object} msg Details of the API call. 220 */ 221 onAPICall_: function(msg) { 222 var call = msg.call; 223 if (call.method == 'add') { 224 this.apiToken_ = call.token; 225 this.email_ = call.user; 226 this.password_ = call.password; 227 } else if (call.method == 'confirm') { 228 if (call.token != this.apiToken_) 229 console.error('Authenticator.onAPICall_: token mismatch'); 230 } else { 231 console.error('Authenticator.onAPICall_: unknown message'); 232 } 233 }, 234 235 onConfirmLogin_: function() { 236 if (!this.isSAMLFlow_) { 237 this.completeLogin_(); 238 return; 239 } 240 241 var apiUsed = !!this.password_; 242 243 // Retrieve the e-mail address of the user who just authenticated from GAIA. 244 window.parent.postMessage({method: 'retrieveAuthenticatedUserEmail', 245 attemptToken: this.attemptToken_, 246 apiUsed: apiUsed}, 247 this.parentPage_); 248 249 if (!apiUsed) { 250 this.supportChannel_.sendWithCallback( 251 {name: 'getScrapedPasswords'}, 252 function(passwords) { 253 if (passwords.length == 0) { 254 window.parent.postMessage( 255 {method: 'noPassword', email: this.email_}, 256 this.parentPage_); 257 } else { 258 window.parent.postMessage({method: 'confirmPassword', 259 email: this.email_, 260 passwordCount: passwords.length}, 261 this.parentPage_); 262 } 263 }.bind(this)); 264 } 265 }, 266 267 maybeCompleteSAMLLogin_: function() { 268 // SAML login is complete when the user's e-mail address has been retrieved 269 // from GAIA and the user has successfully confirmed the password. 270 if (this.email_ !== null && this.password_ !== null) 271 this.completeLogin_(); 272 }, 273 274 onVerifyConfirmedPassword_: function(password) { 275 this.supportChannel_.sendWithCallback( 276 {name: 'getScrapedPasswords'}, 277 function(passwords) { 278 for (var i = 0; i < passwords.length; ++i) { 279 if (passwords[i] == password) { 280 this.password_ = passwords[i]; 281 this.maybeCompleteSAMLLogin_(); 282 return; 283 } 284 } 285 window.parent.postMessage( 286 {method: 'confirmPassword', email: this.email_}, 287 this.parentPage_); 288 }.bind(this)); 289 }, 290 291 onMessage: function(e) { 292 var msg = e.data; 293 if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) { 294 this.email_ = msg.email; 295 this.password_ = msg.password; 296 this.attemptToken_ = msg.attemptToken; 297 this.chooseWhatToSync_ = msg.chooseWhatToSync; 298 this.isSAMLFlow_ = false; 299 if (this.isSAMLEnabled_) 300 this.supportChannel_.send({name: 'startAuth'}); 301 } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) { 302 this.email_ = null; 303 this.password_ = null; 304 this.attemptToken_ = null; 305 this.isSAMLFlow_ = false; 306 this.onLoginUILoaded_(); 307 if (this.isSAMLEnabled_) 308 this.supportChannel_.send({name: 'resetAuth'}); 309 } else if (msg.method == 'setAuthenticatedUserEmail' && 310 this.isParentMessage_(e)) { 311 if (this.attemptToken_ == msg.attemptToken) { 312 this.email_ = msg.email; 313 this.maybeCompleteSAMLLogin_(); 314 } 315 } else if (msg.method == 'confirmLogin' && this.isInternalMessage_(e)) { 316 if (this.attemptToken_ == msg.attemptToken) 317 this.onConfirmLogin_(); 318 else 319 console.error('Authenticator.onMessage: unexpected attemptToken!?'); 320 } else if (msg.method == 'verifyConfirmedPassword' && 321 this.isParentMessage_(e)) { 322 this.onVerifyConfirmedPassword_(msg.password); 323 } else if (msg.method == 'navigate' && 324 this.isParentMessage_(e)) { 325 $('gaia-frame').src = msg.src; 326 } else if (msg.method == 'redirectToSignin' && 327 this.isParentMessage_(e)) { 328 $('gaia-frame').src = this.constructInitialFrameUrl_(); 329 } else { 330 console.error('Authenticator.onMessage: unknown message + origin!?'); 331 } 332 } 333}; 334 335Authenticator.getInstance().initialize(); 336