summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/gaia_auth/main.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/resources/gaia_auth/main.js')
-rw-r--r--chromium/chrome/browser/resources/gaia_auth/main.js393
1 files changed, 266 insertions, 127 deletions
diff --git a/chromium/chrome/browser/resources/gaia_auth/main.js b/chromium/chrome/browser/resources/gaia_auth/main.js
index 7641a848546..41f5116fff6 100644
--- a/chromium/chrome/browser/resources/gaia_auth/main.js
+++ b/chromium/chrome/browser/resources/gaia_auth/main.js
@@ -16,6 +16,26 @@ Authenticator.THIS_EXTENSION_ORIGIN =
'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik';
/**
+ * The lowest version of the credentials passing API supported.
+ * @type {number}
+ */
+Authenticator.MIN_API_VERSION_VERSION = 1;
+
+/**
+ * The highest version of the credentials passing API supported.
+ * @type {number}
+ */
+Authenticator.MAX_API_VERSION_VERSION = 1;
+
+/**
+ * The key types supported by the credentials passing API.
+ * @type {Array} Array of strings.
+ */
+Authenticator.API_KEY_TYPES = [
+ 'KEY_TYPE_PASSWORD_PLAIN',
+];
+
+/**
* Singleton getter of Authenticator.
* @return {Object} The singleton instance of Authenticator.
*/
@@ -28,21 +48,30 @@ Authenticator.getInstance = function() {
Authenticator.prototype = {
email_: null,
- password_: null,
+
+ // Depending on the key type chosen, this will contain the plain text password
+ // or a credential derived from it along with the information required to
+ // repeat the derivation, such as a salt. The information will be encoded so
+ // that it contains printable ASCII characters only. The exact encoding is TBD
+ // when support for key types other than plain text password is added.
+ passwordBytes_: null,
+
attemptToken_: null,
// Input params from extension initialization URL.
inputLang_: undefined,
intputEmail_: undefined,
- samlPageLoaded_: false,
- samlSupportChannel_: null,
+ isSAMLFlow_: false,
+ isSAMLEnabled_: false,
+ supportChannel_: 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',
+ CONSTRAINED_FLOW_SOURCE: 'chrome',
initialize: function() {
var params = getUrlSearchParams(location.search);
@@ -53,16 +82,17 @@ Authenticator.prototype = {
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.desktopMode_ = params.desktopMode == '1';
+ this.isConstrainedWindow_ = params.constrained == '1';
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));
+ document.addEventListener('DOMContentLoaded', this.onPageLoad_.bind(this));
+ if (!this.desktopMode_) {
+ // SAML is always enabled in desktop mode, thus no need to listen for
+ // enableSAML event.
+ document.addEventListener('enableSAML', this.onEnableSAML_.bind(this));
+ }
},
isGaiaMessage_: function(msg) {
@@ -88,116 +118,135 @@ Authenticator.prototype = {
url = appendParam(url, 'hl', this.inputLang_);
if (this.inputEmail_)
url = appendParam(url, 'Email', this.inputEmail_);
-
+ if (this.isConstrainedWindow_)
+ url = appendParam(url, 'source', this.CONSTRAINED_FLOW_SOURCE);
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_);
+ onPageLoad_: function() {
+ window.addEventListener('message', this.onMessage.bind(this), false);
- 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;
- }
+ var gaiaFrame = $('gaia-frame');
+ gaiaFrame.src = this.initialFrameUrl_;
- 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);
- });
- }
+ if (this.desktopMode_) {
+ var handler = function() {
+ this.onLoginUILoaded_();
+ gaiaFrame.removeEventListener('load', handler);
- this.loaded_ || this.onLoginUILoaded();
+ this.initDesktopChannel_();
+ }.bind(this);
+ gaiaFrame.addEventListener('load', handler);
+ }
},
- /**
- * Callback when the gaia webview attempts to open a new window.
- */
- onWebviewNewWindow_: function(gaiaFrame, e) {
- window.open(e.targetUrl, '_blank');
- e.window.discard();
- },
+ initDesktopChannel_: function() {
+ this.supportChannel_ = new Channel();
+ this.supportChannel_.connect('authMain');
- onWebviewRequestCompleted_: function(details) {
- if (details.url.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) {
- return;
- }
+ var channelConnected = false;
+ this.supportChannel_.registerMessage('channelConnected', function() {
+ channelConnected = true;
- var headers = details.responseHeaders;
- for (var i = 0; headers && i < headers.length; ++i) {
- if (headers[i].name.toLowerCase() == 'google-accounts-embedded') {
- return;
+ this.supportChannel_.send({
+ name: 'initDesktopFlow',
+ gaiaUrl: this.gaiaUrl_,
+ continueUrl: stripParams(this.continueUrl_),
+ isConstrainedWindow: this.isConstrainedWindow_
+ });
+ this.supportChannel_.registerMessage(
+ 'switchToFullTab', this.switchToFullTab_.bind(this));
+ this.supportChannel_.registerMessage(
+ 'completeLogin', this.completeLogin_.bind(this));
+
+ this.onEnableSAML_();
+ }.bind(this));
+
+ window.setTimeout(function() {
+ if (!channelConnected) {
+ // Re-initialize the channel if it is not connected properly, e.g.
+ // connect may be called before background script started running.
+ this.initDesktopChannel_();
}
- }
+ }.bind(this), 200);
+ },
+
+ /**
+ * Invoked when the login UI is initialized or reset.
+ */
+ onLoginUILoaded_: function() {
var msg = {
- 'method': 'switchToFullTab',
- 'url': details.url
+ 'method': 'loginUILoaded'
};
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']);
- }
+ /**
+ * Invoked when the background script sends a message to indicate that the
+ * current content does not fit in a constrained window.
+ * @param {Object=} opt_extraMsg Optional extra info to send.
+ */
+ switchToFullTab_: function(msg) {
+ var parentMsg = {
+ 'method': 'switchToFullTab',
+ 'url': msg.url
+ };
+ window.parent.postMessage(parentMsg, this.parentPage_);
},
- completeLogin: function(username, password) {
+ /**
+ * Invoked when the signin flow is complete.
+ * @param {Object=} opt_extraMsg Optional extra info to send.
+ */
+ completeLogin_: function(opt_extraMsg) {
var msg = {
'method': 'completeLogin',
- 'email': username,
- 'password': password
+ 'email': (opt_extraMsg && opt_extraMsg.email) || this.email_,
+ 'password': (opt_extraMsg && opt_extraMsg.password) ||
+ this.passwordBytes_,
+ 'usingSAML': this.isSAMLFlow_,
+ 'chooseWhatToSync': this.chooseWhatToSync_ || false,
+ 'skipForNow': opt_extraMsg && opt_extraMsg.skipForNow,
+ 'sessionIndex': opt_extraMsg && opt_extraMsg.sessionIndex
};
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_();
+ if (this.isSAMLEnabled_)
+ this.supportChannel_.send({name: 'resetAuth'});
},
/**
- * Invoked when 'enableSAML' event is received to initialize SAML support.
+ * Invoked when 'enableSAML' event is received to initialize SAML support on
+ * Chrome OS, or when initDesktopChannel_ is called on desktop.
*/
onEnableSAML_: function() {
- this.samlPageLoaded_ = false;
+ this.isSAMLEnabled_ = true;
+ this.isSAMLFlow_ = false;
- this.samlSupportChannel_ = new Channel();
- this.samlSupportChannel_.connect('authMain');
- this.samlSupportChannel_.registerMessage(
+ if (!this.supportChannel_) {
+ this.supportChannel_ = new Channel();
+ this.supportChannel_.connect('authMain');
+ }
+
+ this.supportChannel_.registerMessage(
'onAuthPageLoaded', this.onAuthPageLoaded_.bind(this));
- this.samlSupportChannel_.send({
+ this.supportChannel_.registerMessage(
+ 'onInsecureContentBlocked', this.onInsecureContentBlocked_.bind(this));
+ this.supportChannel_.registerMessage(
+ 'apiCall', this.onAPICall_.bind(this));
+ this.supportChannel_.send({
name: 'setGaiaUrl',
gaiaUrl: this.gaiaUrl_
});
+ if (!this.desktopMode_ && this.gaiaUrl_.indexOf('https://') == 0) {
+ // Abort the login flow when content served over an unencrypted connection
+ // is detected on Chrome OS. This does not apply to tests that explicitly
+ // set a non-https GAIA URL and want to perform all authentication over
+ // http.
+ this.supportChannel_.send({
+ name: 'setBlockInsecureContent',
+ blockInsecureContent: true
+ });
+ }
},
/**
@@ -205,52 +254,135 @@ Authenticator.prototype = {
* @param {!Object} msg Details sent with the message.
*/
onAuthPageLoaded_: function(msg) {
- this.samlPageLoaded_ = msg.url.indexOf(this.gaiaUrl_) != 0;
+ var isSAMLPage = msg.url.indexOf(this.gaiaUrl_) != 0;
+
+ if (isSAMLPage && !this.isSAMLFlow_) {
+ // GAIA redirected to a SAML login page. The credentials provided to this
+ // page will determine what user gets logged in. The credentials obtained
+ // from the GAIA login form are no longer relevant and can be discarded.
+ this.isSAMLFlow_ = true;
+ this.email_ = null;
+ this.passwordBytes_ = null;
+ }
+
window.parent.postMessage({
'method': 'authPageLoaded',
- 'isSAML': this.samlPageLoaded_
+ 'isSAML': this.isSAMLFlow_,
+ 'domain': extractDomain(msg.url)
}, this.parentPage_);
},
- onLoginUILoaded: function() {
- var msg = {
- 'method': 'loginUILoaded'
- };
- window.parent.postMessage(msg, this.parentPage_);
- if (this.inlineMode_) {
- $('gaia-frame').focus();
+ /**
+ * Invoked when the background page sends an 'onInsecureContentBlocked'
+ * message.
+ * @param {!Object} msg Details sent with the message.
+ */
+ onInsecureContentBlocked_: function(msg) {
+ window.parent.postMessage({
+ 'method': 'insecureContentBlocked',
+ 'url': stripParams(msg.url)
+ }, this.parentPage_);
+ },
+
+ /**
+ * Invoked when one of the credential passing API methods is called by a SAML
+ * provider.
+ * @param {!Object} msg Details of the API call.
+ */
+ onAPICall_: function(msg) {
+ var call = msg.call;
+ if (call.method == 'initialize') {
+ if (!Number.isInteger(call.requestedVersion) ||
+ call.requestedVersion < Authenticator.MIN_API_VERSION_VERSION) {
+ this.sendInitializationFailure_();
+ return;
+ }
+
+ this.apiVersion_ = Math.min(call.requestedVersion,
+ Authenticator.MAX_API_VERSION_VERSION);
+ this.initialized_ = true;
+ this.sendInitializationSuccess_();
+ return;
+ }
+
+ if (call.method == 'add') {
+ if (Authenticator.API_KEY_TYPES.indexOf(call.keyType) == -1) {
+ console.error('Authenticator.onAPICall_: unsupported key type');
+ return;
+ }
+ this.apiToken_ = call.token;
+ this.email_ = call.user;
+ this.passwordBytes_ = call.passwordBytes;
+ } else if (call.method == 'confirm') {
+ if (call.token != this.apiToken_)
+ console.error('Authenticator.onAPICall_: token mismatch');
+ } else {
+ console.error('Authenticator.onAPICall_: unknown message');
}
- this.loaded_ = true;
+ },
+
+ sendInitializationSuccess_: function() {
+ this.supportChannel_.send({name: 'apiResponse', response: {
+ result: 'initialized',
+ version: this.apiVersion_,
+ keyTypes: Authenticator.API_KEY_TYPES
+ }});
+ },
+
+ sendInitializationFailure_: function() {
+ this.supportChannel_.send({
+ name: 'apiResponse',
+ response: {result: 'initialization_failed'}
+ });
},
onConfirmLogin_: function() {
- if (!this.samlPageLoaded_) {
- this.completeLogin(this.email_, this.password_);
+ if (!this.isSAMLFlow_) {
+ this.completeLogin_();
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));
+ var apiUsed = !!this.passwordBytes_;
+
+ // Retrieve the e-mail address of the user who just authenticated from GAIA.
+ window.parent.postMessage({method: 'retrieveAuthenticatedUserEmail',
+ attemptToken: this.attemptToken_,
+ apiUsed: apiUsed},
+ this.parentPage_);
+
+ if (!apiUsed) {
+ this.supportChannel_.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_,
+ passwordCount: passwords.length},
+ this.parentPage_);
+ }
+ }.bind(this));
+ }
+ },
+
+ maybeCompleteSAMLLogin_: function() {
+ // SAML login is complete when the user's e-mail address has been retrieved
+ // from GAIA and the user has successfully confirmed the password.
+ if (this.email_ !== null && this.passwordBytes_ !== null)
+ this.completeLogin_();
},
onVerifyConfirmedPassword_: function(password) {
- this.samlSupportChannel_.sendWithCallback(
+ this.supportChannel_.sendWithCallback(
{name: 'getScrapedPasswords'},
function(passwords) {
for (var i = 0; i < passwords.length; ++i) {
if (passwords[i] == password) {
- this.completeLogin(this.email_, passwords[i]);
+ this.passwordBytes_ = passwords[i];
+ this.maybeCompleteSAMLLogin_();
return;
}
}
@@ -264,19 +396,26 @@ Authenticator.prototype = {
var msg = e.data;
if (msg.method == 'attemptLogin' && this.isGaiaMessage_(e)) {
this.email_ = msg.email;
- this.password_ = msg.password;
+ this.passwordBytes_ = msg.password;
this.attemptToken_ = msg.attemptToken;
- this.samlPageLoaded_ = false;
- if (this.samlSupportChannel_)
- this.samlSupportChannel_.send({name: 'startAuth'});
+ this.chooseWhatToSync_ = msg.chooseWhatToSync;
+ this.isSAMLFlow_ = false;
+ if (this.isSAMLEnabled_)
+ this.supportChannel_.send({name: 'startAuth'});
} else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) {
this.email_ = null;
- this.password_ = null;
+ this.passwordBytes_ = null;
this.attemptToken_ = null;
- this.samlPageLoaded_ = false;
- this.onLoginUILoaded();
- if (this.samlSupportChannel_)
- this.samlSupportChannel_.send({name: 'resetAuth'});
+ this.isSAMLFlow_ = false;
+ this.onLoginUILoaded_();
+ if (this.isSAMLEnabled_)
+ this.supportChannel_.send({name: 'resetAuth'});
+ } else if (msg.method == 'setAuthenticatedUserEmail' &&
+ this.isParentMessage_(e)) {
+ if (this.attemptToken_ == msg.attemptToken) {
+ this.email_ = msg.email;
+ this.maybeCompleteSAMLLogin_();
+ }
} else if (msg.method == 'confirmLogin' && this.isInternalMessage_(e)) {
if (this.attemptToken_ == msg.attemptToken)
this.onConfirmLogin_();
@@ -285,11 +424,11 @@ Authenticator.prototype = {
} else if (msg.method == 'verifyConfirmedPassword' &&
this.isParentMessage_(e)) {
this.onVerifyConfirmedPassword_(msg.password);
- } else if (msg.method == 'navigate' &&
+ } else if (msg.method == 'redirectToSignin' &&
this.isParentMessage_(e)) {
- $('gaia-frame').src = msg.src;
+ $('gaia-frame').src = this.constructInitialFrameUrl_();
} else {
- console.error('Authenticator.onMessage: unknown message + origin!?');
+ console.error('Authenticator.onMessage: unknown message + origin!?');
}
}
};