'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.
*/
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.
intputEmail_: undefined,
isSAMLFlow_: false,
- isSAMLEnabled_: false,
+ gaiaLoaded_: false,
supportChannel_: null,
GAIA_URL: 'https://accounts.google.com/',
this.initialFrameUrl_ = params.frameUrl || this.constructInitialFrameUrl_();
this.initialFrameUrlWithoutParams_ = stripParams(this.initialFrameUrl_);
+ // For CrOS 'ServiceLogin' we assume that Gaia is loaded if we recieved
+ // 'clearOldAttempts' message. For other scenarios Gaia doesn't send this
+ // message so we have to rely on 'load' event.
+ // TODO(dzhioev): Do not rely on 'load' event after b/16313327 is fixed.
+ this.assumeLoadedOnLoadEvent_ =
+ this.gaiaPath_.indexOf('ServiceLogin') !== 0 ||
+ this.service_ !== 'chromeoslogin';
+
document.addEventListener('DOMContentLoaded', this.onPageLoad_.bind(this));
- document.addEventListener('enableSAML', this.onEnableSAML_.bind(this));
},
isGaiaMessage_: function(msg) {
onPageLoad_: function() {
window.addEventListener('message', this.onMessage.bind(this), false);
+ this.initSupportChannel_();
var gaiaFrame = $('gaia-frame');
gaiaFrame.src = this.initialFrameUrl_;
- if (this.desktopMode_) {
+ if (this.assumeLoadedOnLoadEvent_) {
var handler = function() {
- this.onLoginUILoaded_();
gaiaFrame.removeEventListener('load', handler);
-
- this.initDesktopChannel_();
+ if (!this.gaiaLoaded_) {
+ this.gaiaLoaded_ = true;
+ this.maybeInitialized_();
+ }
}.bind(this);
gaiaFrame.addEventListener('load', handler);
}
},
- initDesktopChannel_: function() {
- this.supportChannel_ = new Channel();
- this.supportChannel_.connect('authMain');
-
- var channelConnected = false;
- this.supportChannel_.registerMessage('channelConnected', function() {
- channelConnected = true;
+ initSupportChannel_: function() {
+ var supportChannel = new Channel();
+ supportChannel.connect('authMain');
- 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));
+ supportChannel.registerMessage('channelConnected', function() {
+ if (this.supportChannel_) {
+ console.error('Support channel is already initialized.');
+ return;
+ }
+ this.supportChannel_ = supportChannel;
+
+ if (this.desktopMode_) {
+ 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.initSAML_();
+ this.maybeInitialized_();
}.bind(this));
window.setTimeout(function() {
- if (!channelConnected) {
+ if (!this.supportChannel_) {
// Re-initialize the channel if it is not connected properly, e.g.
// connect may be called before background script started running.
- this.initDesktopChannel_();
+ this.initSupportChannel_();
}
}.bind(this), 200);
},
/**
- * Invoked when the login UI is initialized or reset.
+ * Called when one of the initialization stages has finished. If all the
+ * needed parts are initialized, notifies parent about successfull
+ * initialization.
*/
- onLoginUILoaded_: function() {
+ maybeInitialized_: function() {
+ if (!this.gaiaLoaded_ || !this.supportChannel_)
+ return;
var msg = {
'method': 'loginUILoaded'
};
var msg = {
'method': 'completeLogin',
'email': (opt_extraMsg && opt_extraMsg.email) || this.email_,
- 'password': this.password_,
+ '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.isSAMLEnabled_)
- this.supportChannel_.send({name: 'resetAuth'});
+ this.supportChannel_.send({name: 'resetAuth'});
},
/**
- * Invoked when 'enableSAML' event is received to initialize SAML support.
+ * Invoked when support channel is connected.
*/
- onEnableSAML_: function() {
- this.isSAMLEnabled_ = true;
+ initSAML_: function() {
this.isSAMLFlow_ = false;
- if (!this.supportChannel_) {
- this.supportChannel_ = new Channel();
- this.supportChannel_.connect('authMain');
- }
-
this.supportChannel_.registerMessage(
'onAuthPageLoaded', this.onAuthPageLoaded_.bind(this));
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
+ });
+ }
},
/**
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 from are no longer relevant and can be discarded.
+ // from the GAIA login form are no longer relevant and can be discarded.
this.isSAMLFlow_ = true;
this.email_ = null;
- this.password_ = null;
+ this.passwordBytes_ = null;
}
window.parent.postMessage({
},
/**
+ * 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.password_ = call.password;
+ this.passwordBytes_ = call.passwordBytes;
} else if (call.method == 'confirm') {
if (call.token != this.apiToken_)
console.error('Authenticator.onAPICall_: token mismatch');
}
},
+ 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.isSAMLFlow_) {
this.completeLogin_();
return;
}
- var apiUsed = !!this.password_;
+ var apiUsed = !!this.passwordBytes_;
// Retrieve the e-mail address of the user who just authenticated from GAIA.
window.parent.postMessage({method: 'retrieveAuthenticatedUserEmail',
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.password_ !== null)
+ if (this.email_ !== null && this.passwordBytes_ !== null)
this.completeLogin_();
},
function(passwords) {
for (var i = 0; i < passwords.length; ++i) {
if (passwords[i] == password) {
- this.password_ = passwords[i];
+ this.passwordBytes_ = passwords[i];
this.maybeCompleteSAMLLogin_();
return;
}
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.chooseWhatToSync_ = msg.chooseWhatToSync;
this.isSAMLFlow_ = false;
- if (this.isSAMLEnabled_)
+ if (this.supportChannel_)
this.supportChannel_.send({name: 'startAuth'});
+ else
+ console.error('Support channel is not initialized.');
} else if (msg.method == 'clearOldAttempts' && this.isGaiaMessage_(e)) {
+ if (!this.gaiaLoaded_) {
+ this.gaiaLoaded_ = true;
+ this.maybeInitialized_();
+ }
this.email_ = null;
- this.password_ = null;
+ this.passwordBytes_ = null;
this.attemptToken_ = null;
this.isSAMLFlow_ = false;
- this.onLoginUILoaded_();
- if (this.isSAMLEnabled_)
+ if (this.supportChannel_)
this.supportChannel_.send({name: 'resetAuth'});
} else if (msg.method == 'setAuthenticatedUserEmail' &&
this.isParentMessage_(e)) {
} 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 if (msg.method == 'redirectToSignin' &&
this.isParentMessage_(e)) {
$('gaia-frame').src = this.constructInitialFrameUrl_();