1 // Copyright 2013 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.
7 * Script to be injected into SAML provider pages, serving three main purposes:
8 * 1. Signal hosting extension that an external page is loaded so that the
9 * UI around it should be changed accordingly;
10 * 2. Provide an API via which the SAML provider can pass user credentials to
11 * Chrome OS, allowing the password to be used for encrypting user data and
13 * 3. Scrape password fields, making the password available to Chrome OS even if
14 * the SAML provider does not support the credential passing API.
18 function APICallForwarder() {
22 * The credential passing API is used by sending messages to the SAML page's
23 * |window| object. This class forwards the calls to a background script via a
26 APICallForwarder.prototype = {
27 // Channel to which API calls are forwarded.
31 * Initialize the API call forwarder.
32 * @param {!Object} channel Channel to which API calls should be forwarded.
34 init: function(channel) {
35 this.channel_ = channel;
36 window.addEventListener('message', this.onMessage_.bind(this));
39 onMessage_: function(event) {
40 if (event.source != window ||
41 typeof event.data != 'object' ||
42 !event.data.hasOwnProperty('type') ||
43 event.data.type != 'gaia_saml_api') {
46 if (event.data.call.method == 'initialize') {
47 // Respond to the |initialize| call directly.
48 event.source.postMessage({
49 type: 'gaia_saml_api_reply',
50 response: {result: 'initialized', version: 1}}, '/');
52 // Forward all other calls.
53 this.channel_.send({name: 'apiCall', call: event.data.call});
59 * A class to scrape password from type=password input elements under a given
60 * docRoot and send them back via a Channel.
62 function PasswordInputScraper() {
65 PasswordInputScraper.prototype = {
69 // Channel to send back changed password.
72 // An array to hold password fields.
73 passwordFields_: null,
75 // An array to hold cached password values.
76 passwordValues_: null,
79 * Initialize the scraper with given channel and docRoot. Note that the
80 * scanning for password fields happens inside the function and does not
81 * handle DOM tree changes after the call returns.
82 * @param {!Object} channel The channel to send back password.
83 * @param {!string} pageURL URL of the page.
84 * @param {!HTMLElement} docRoot The root element of the DOM tree that
85 * contains the password fields of interest.
87 init: function(channel, pageURL, docRoot) {
88 this.pageURL_ = pageURL;
89 this.channel_ = channel;
91 this.passwordFields_ = docRoot.querySelectorAll('input[type=password]');
92 this.passwordValues_ = [];
94 for (var i = 0; i < this.passwordFields_.length; ++i) {
95 this.passwordFields_[i].addEventListener(
96 'input', this.onPasswordChanged_.bind(this, i));
98 this.passwordValues_[i] = this.passwordFields_[i].value;
103 * Check if the password field at |index| has changed. If so, sends back
106 maybeSendUpdatedPassword: function(index) {
107 var newValue = this.passwordFields_[index].value;
108 if (newValue == this.passwordValues_[index])
111 this.passwordValues_[index] = newValue;
113 // Use an invalid char for URL as delimiter to concatenate page url and
114 // password field index to construct a unique ID for the password field.
115 var passwordId = this.pageURL_ + '|' + index;
117 name: 'updatePassword',
124 * Handles 'change' event in the scraped password fields.
125 * @param {number} index The index of the password fields in
128 onPasswordChanged_: function(index) {
129 this.maybeSendUpdatedPassword(index);
134 * Returns true if the script is injected into auth main page.
136 function isAuthMainPage() {
137 return window.location.href.indexOf(
138 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/main.html') == 0;
142 * Heuristic test whether the current page is a relevant SAML page.
143 * Current implementation checks if it is a http or https page and has
144 * some content in it.
145 * @return {boolean} Whether the current page looks like a SAML page.
147 function isSAMLPage() {
148 var url = window.location.href;
149 if (!url.match(/^(http|https):\/\//))
152 return document.body.scrollWidth > 50 && document.body.scrollHeight > 50;
155 if (isAuthMainPage()) {
156 // Use an event to signal the auth main to enable SAML support.
157 var e = document.createEvent('Event');
158 e.initEvent('enableSAML', false, false);
159 document.dispatchEvent(e);
164 var pageURL = window.location.href;
166 channel = new Channel();
167 channel.connect('injected');
168 channel.send({name: 'pageLoaded', url: pageURL});
170 apiCallForwarder = new APICallForwarder();
171 apiCallForwarder.init(channel);
173 passwordScraper = new PasswordInputScraper();
174 passwordScraper.init(channel, pageURL, document.documentElement);