blob: d7fd1b8e37ee050ca6ce316bfc55e6035fcb09c6 [file] [log] [blame]
// Copyright 2013 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.
/**
* @fileoverview
* Script to be injected into SAML provider pages that do not support the
* auth service provider postMessage API. It serves two main purposes:
* 1. Signal hosting extension that an external page is loaded so that the
* UI around it could be changed accordingly;
* 2. Scrape password and send it back to be used for encrypt user data and
* use for offline login;
*/
(function() {
/**
* A class to scrape password from type=password input elements under a given
* docRoot and send them back via a Channel.
*/
function PasswordInputScraper() {
}
PasswordInputScraper.prototype = {
// URL of the page.
pageURL_: null,
// Channel to send back changed password.
channel_: null,
// An array to hold password fields.
passwordFields_: null,
// An array to hold cached password values.
passwordValues_: null,
/**
* Initialize the scraper with given channel and docRoot. Note that the
* scanning for password fields happens inside the function and does not
* handle DOM tree changes after the call returns.
* @param {!Object} channel The channel to send back password.
* @param {!string} pageURL URL of the page.
* @param {!HTMLElement} docRoot The root element of the DOM tree that
* contains the password fields of interest.
*/
init: function(channel, pageURL, docRoot) {
this.pageURL_ = pageURL;
this.channel_ = channel;
this.passwordFields_ = docRoot.querySelectorAll('input[type=password]');
this.passwordValues_ = [];
for (var i = 0; i < this.passwordFields_.length; ++i) {
this.passwordFields_[i].addEventListener(
'change', this.onPasswordChanged_.bind(this, i));
// 'keydown' event is needed for the case that the form is submitted
// on enter key, in which case no 'change' event is dispatched.
this.passwordFields_[i].addEventListener(
'keydown', this.onPasswordKeyDown_.bind(this, i));
this.passwordValues_[i] = this.passwordFields_[i].value;
}
},
/**
* Check if the password field at |index| has changed. If so, sends back
* the updated value.
*/
maybeSendUpdatedPassword: function(index) {
var newValue = this.passwordFields_[index].value;
if (newValue == this.passwordValues_[index])
return;
this.passwordValues_[index] = newValue;
// Use an invalid char for URL as delimiter to concatenate page url and
// password field index to construct a unique ID for the password field.
var passwordId = this.pageURL_ + '|' + index;
this.channel_.send({
name: 'updatePassword',
id: passwordId,
password: newValue
});
},
/**
* Handles 'change' event in the scraped password fields.
* @param {number} index The index of the password fields in
* |passwordFields_|.
*/
onPasswordChanged_: function(index) {
this.maybeSendUpdatedPassword(index);
},
/**
* Handles 'keydown' event to trigger password change detection and
* updates on enter key.
* @param {number} index The index of the password fields in
* |passwordFields_|.
* @param {Event} e The keydown event.
*/
onPasswordKeyDown_: function(index, e) {
if (e.keyIdentifier == 'Enter')
this.maybeSendUpdatedPassword(index);
}
};
/**
* Returns true if the script is injected into auth main page.
*/
function isAuthMainPage() {
return window.location.href.indexOf(
'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/main.html') == 0;
}
/**
* Heuristic test whether the current page is a relevant SAML page.
* Current implementation checks if it is a http or https page and has
* some content in it.
* @return {boolean} Whether the current page looks like a SAML page.
*/
function isSAMLPage() {
var url = window.location.href;
if (!url.match(/^(http|https):\/\//))
return false;
return document.body.scrollWidth > 50 && document.body.scrollHeight > 50;
}
if (isAuthMainPage()) {
// Use an event to signal the auth main to enable SAML support.
var e = document.createEvent('Event');
e.initEvent('enableSAML', false, false);
document.dispatchEvent(e);
} else {
var channel;
var passwordScraper;
if (isSAMLPage()) {
var pageURL = window.location.href;
channel = new Channel();
channel.connect('injected');
channel.send({name: 'pageLoaded', url: pageURL});
passwordScraper = new PasswordInputScraper();
passwordScraper.init(channel, pageURL, document.documentElement);
}
}
})();