// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. /** * Authenticator class wraps the communications between Gaia and its host. */ function Authenticator() { } /** * Gaia auth extension url origin. * @type {string} */ Authenticator.THIS_EXTENSION_ORIGIN = 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik'; /** * Singleton getter of Authenticator. * @return {Object} The singleton instance of Authenticator. */ Authenticator.getInstance = function() { if (!Authenticator.instance_) { Authenticator.instance_ = new Authenticator(); } return Authenticator.instance_; }; Authenticator.prototype = { email_: null, password_: null, attemptToken_: null, // Input params from extension initialization URL. inputLang_: undefined, intputEmail_: undefined, samlPageLoaded_: false, samlSupportChannel_: null, GAIA_URL: 'https://accounts.google.com/', GAIA_PAGE_PATH: 'ServiceLogin?skipvpage=true&sarp=1&rm=hide', PARENT_PAGE: 'chrome://oobe/', SERVICE_ID: 'chromeoslogin', CONTINUE_URL: Authenticator.THIS_EXTENSION_ORIGIN + '/success.html', initialize: function() { var params = getUrlSearchParams(location.search); this.parentPage_ = params.parentPage || this.PARENT_PAGE; this.gaiaUrl_ = params.gaiaUrl || this.GAIA_URL; this.gaiaPath_ = params.gaiaPath || this.GAIA_PAGE_PATH; this.inputLang_ = params.hl; this.inputEmail_ = params.email; this.service_ = params.service || this.SERVICE_ID; this.continueUrl_ = params.continueUrl || this.CONTINUE_URL; this.continueUrlWithoutParams_ = stripParams(this.continueUrl_); this.inlineMode_ = params.inlineMode == '1'; this.constrained_ = params.constrained == '1'; this.partitionId_ = params.partitionId || ''; this.initialFrameUrl_ = params.frameUrl || this.constructInitialFrameUrl_(); this.initialFrameUrlWithoutParams_ = stripParams(this.initialFrameUrl_); this.loaded_ = false; document.addEventListener('DOMContentLoaded', this.onPageLoad.bind(this)); document.addEventListener('enableSAML', this.onEnableSAML_.bind(this)); }, isGaiaMessage_: function(msg) { // Not quite right, but good enough. return this.gaiaUrl_.indexOf(msg.origin) == 0 || this.GAIA_URL.indexOf(msg.origin) == 0; }, isInternalMessage_: function(msg) { return msg.origin == Authenticator.THIS_EXTENSION_ORIGIN; }, isParentMessage_: function(msg) { return msg.origin == this.parentPage_; }, constructInitialFrameUrl_: function() { var url = this.gaiaUrl_ + this.gaiaPath_; url = appendParam(url, 'service', this.service_); url = appendParam(url, 'continue', this.continueUrl_); if (this.inputLang_) url = appendParam(url, 'hl', this.inputLang_); if (this.inputEmail_) url = appendParam(url, 'Email', this.inputEmail_); return url; }, /** Callback when all loads in the gaia webview is complete. */ onWebviewLoadstop_: function(gaiaFrame) { // Report the current state to the parent which will then update the // browser history so that later it could respond properly to back/forward. var msg = { 'method': 'reportState', 'src': gaiaFrame.src }; window.parent.postMessage(msg, this.parentPage_); if (gaiaFrame.src.lastIndexOf( this.continueUrlWithoutParams_, 0) == 0) { // Detect when login is finished by the load stop event of the continue // URL. Cannot reuse the login complete flow in success.html, because // webview does not support extension pages yet. gaiaFrame.hidden = true; msg = {'method': 'completeLogin'}; window.parent.postMessage(msg, this.parentPage_); return; } if (gaiaFrame.src.lastIndexOf(this.gaiaUrl_, 0) == 0) { gaiaFrame.executeScript({file: 'inline_injected.js'}, function() { // Send an initial message to gaia so that it has an JavaScript // reference to the embedder. gaiaFrame.contentWindow.postMessage('', gaiaFrame.src); }); } this.loaded_ || this.onLoginUILoaded(); }, /** * Callback when the gaia webview attempts to open a new window. */ onWebviewNewWindow_: function(gaiaFrame, e) { window.open(e.targetUrl, '_blank'); e.window.discard(); }, onWebviewRequestCompleted_: function(details) { if (details.url.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) { return; } var headers = details.responseHeaders; for (var i = 0; headers && i < headers.length; ++i) { if (headers[i].name.toLowerCase() == 'google-accounts-embedded') { return; } } var msg = { 'method': 'switchToFullTab', 'url': details.url }; window.parent.postMessage(msg, this.parentPage_); }, loadFrame_: function() { var gaiaFrame = $('gaia-frame'); gaiaFrame.partition = this.partitionId_; gaiaFrame.src = this.initialFrameUrl_; if (this.inlineMode_) { gaiaFrame.addEventListener( 'loadstop', this.onWebviewLoadstop_.bind(this, gaiaFrame)); gaiaFrame.addEventListener( 'newwindow', this.onWebviewNewWindow_.bind(this, gaiaFrame)); } if (this.constrained_) { gaiaFrame.request.onCompleted.addListener( this.onWebviewRequestCompleted_.bind(this), {urls: ['<all_urls>'], types: ['main_frame']}, ['responseHeaders']); } }, completeLogin: function(username, password) { var msg = { 'method': 'completeLogin', 'email': username, 'password': password }; window.parent.postMessage(msg, this.parentPage_); if (this.samlSupportChannel_) this.samlSupportChannel_.send({name: 'resetAuth'}); }, onPageLoad: function(e) { window.addEventListener('message', this.onMessage.bind(this), false); this.loadFrame_(); }, /** * Invoked when 'enableSAML' event is received to initialize SAML support. */ onEnableSAML_: function() { this.samlPageLoaded_ = false; this.samlSupportChannel_ = new Channel(); this.samlSupportChannel_.connect('authMain'); this.samlSupportChannel_.registerMessage( 'onAuthPageLoaded', this.onAuthPageLoaded_.bind(this)); this.samlSupportChannel_.send({ name: 'setGaiaUrl', gaiaUrl: this.gaiaUrl_ }); }, /** * Invoked when the background page sends 'onHostedPageLoaded' message. * @param {!Object} msg Details sent with the message. */ onAuthPageLoaded_: function(msg) { this.samlPageLoaded_ = msg.url.indexOf(this.gaiaUrl_) != 0; window.parent.postMessage({ 'method': 'authPageLoaded', 'isSAML': this.samlPageLoaded_ }, this.parentPage_); }, onLoginUILoaded: function() { var msg = { 'method': 'loginUILoaded' }; window.parent.postMessage(msg, this.parentPage_); if (this.inlineMode_) { $('gaia-frame').focus(); } this.loaded_ = true; }, onConfirmLogin_: function() { if (!this.samlPageLoaded_) { this.completeLogin(this.email_, this.password_); return; } this.samlSupportChannel_.sendWithCallback( {name: 'getScrapedPasswords'}, function(passwords) { if (passwords.length == 0) { window.parent.postMessage( {method: 'noPassword', email: this.email_}, this.parentPage_); } else { window.parent.postMessage( {method: 'confirmPassword', email: this.email_}, this.parentPage_); } }.bind(this)); }, onVerifyConfirmedPassword_: function(password) { this.samlSupportChannel_.sendWithCallback( {name: 'getScrapedPasswords'}, function(passwords) { for (var i = 0; i < passwords.length; ++i) { if (passwords[i] == password) { this.completeLogin(this.email_, passwords[i]); return; } } window.parent.postMessage( {method: 'confirmPassword', email: this.email_}, this.parentPage_); }.bind(this)); }, onMessage: function(e) { var msg = e.data; if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) { this.email_ = msg.email; this.password_ = msg.password; this.attemptToken_ = msg.attemptToken; this.samlPageLoaded_ = false; if (this.samlSupportChannel_) this.samlSupportChannel_.send({name: 'startAuth'}); } else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) { this.email_ = null; this.password_ = null; this.attemptToken_ = null; this.samlPageLoaded_ = false; this.onLoginUILoaded(); if (this.samlSupportChannel_) this.samlSupportChannel_.send({name: 'resetAuth'}); } else if (msg.method == 'confirmLogin' && this.isInternalMessage_(e)) { if (this.attemptToken_ == msg.attemptToken) this.onConfirmLogin_(); else console.error('Authenticator.onMessage: unexpected attemptToken!?'); } else if (msg.method == 'verifyConfirmedPassword' && this.isParentMessage_(e)) { this.onVerifyConfirmedPassword_(msg.password); } else if (msg.method == 'navigate' && this.isParentMessage_(e)) { $('gaia-frame').src = msg.src; } else { console.error('Authenticator.onMessage: unknown message + origin!?'); } } }; Authenticator.getInstance().initialize();