1d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)// Copyright 2013 The Chromium Authors. All rights reserved. 2d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 3d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)// found in the LICENSE file. 4d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 5d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)/** 6d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * @fileoverview 75d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Script to be injected into SAML provider pages, serving three main purposes: 8d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * 1. Signal hosting extension that an external page is loaded so that the 95d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * UI around it should be changed accordingly; 105d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * 2. Provide an API via which the SAML provider can pass user credentials to 115d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Chrome OS, allowing the password to be used for encrypting user data and 125d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * offline login. 135d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * 3. Scrape password fields, making the password available to Chrome OS even if 145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * the SAML provider does not support the credential passing API. 15d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) */ 16d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 17d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)(function() { 185d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) function APICallForwarder() { 195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 215d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 225d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * The credential passing API is used by sending messages to the SAML page's 23010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * |window| object. This class forwards API calls from the SAML page to a 24010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * background script and API responses from the background script to the SAML 25010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) * page. Communication with the background script occurs via a |Channel|. 265d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 275d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) APICallForwarder.prototype = { 285d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // Channel to which API calls are forwarded. 295d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) channel_: null, 305d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 315d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) /** 325d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * Initialize the API call forwarder. 335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) * @param {!Object} channel Channel to which API calls should be forwarded. 345d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) */ 355d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) init: function(channel) { 365d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) this.channel_ = channel; 37010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) this.channel_.registerMessage('apiResponse', 38010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) this.onAPIResponse_.bind(this)); 39010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) 405d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) window.addEventListener('message', this.onMessage_.bind(this)); 415d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) }, 425d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) onMessage_: function(event) { 445d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (event.source != window || 455d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) typeof event.data != 'object' || 465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) !event.data.hasOwnProperty('type') || 475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) event.data.type != 'gaia_saml_api') { 485d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) return; 495d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 50010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) // Forward API calls to the background script. 51010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) this.channel_.send({name: 'apiCall', call: event.data.call}); 52010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) }, 53010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) 54010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) onAPIResponse_: function(msg) { 55010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) // Forward API responses to the SAML page. 56010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) window.postMessage({type: 'gaia_saml_api_reply', response: msg.response}, 57010d83a9304c5a91596085d917d248abff47903aTorne (Richard Coles) '/'); 585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) } 595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) }; 605d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 61d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) /** 62d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * A class to scrape password from type=password input elements under a given 63d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * docRoot and send them back via a Channel. 64d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) */ 65d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) function PasswordInputScraper() { 66d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) } 67d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 68d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) PasswordInputScraper.prototype = { 69d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) // URL of the page. 70d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) pageURL_: null, 71d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 72d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) // Channel to send back changed password. 73d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) channel_: null, 74d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 75d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) // An array to hold password fields. 76d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) passwordFields_: null, 77d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 78d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) // An array to hold cached password values. 79d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) passwordValues_: null, 80d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 81d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) /** 82d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * Initialize the scraper with given channel and docRoot. Note that the 83d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * scanning for password fields happens inside the function and does not 84d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * handle DOM tree changes after the call returns. 85d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * @param {!Object} channel The channel to send back password. 86d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * @param {!string} pageURL URL of the page. 87d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * @param {!HTMLElement} docRoot The root element of the DOM tree that 88d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * contains the password fields of interest. 89d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) */ 90d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) init: function(channel, pageURL, docRoot) { 91d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) this.pageURL_ = pageURL; 92d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) this.channel_ = channel; 93d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 94d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) this.passwordFields_ = docRoot.querySelectorAll('input[type=password]'); 95d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) this.passwordValues_ = []; 96d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 97d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) for (var i = 0; i < this.passwordFields_.length; ++i) { 98d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) this.passwordFields_[i].addEventListener( 995d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 'input', this.onPasswordChanged_.bind(this, i)); 100d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 101d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) this.passwordValues_[i] = this.passwordFields_[i].value; 102d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) } 103d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) }, 104d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 105d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) /** 106d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * Check if the password field at |index| has changed. If so, sends back 107d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * the updated value. 108d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) */ 109d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) maybeSendUpdatedPassword: function(index) { 110d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) var newValue = this.passwordFields_[index].value; 111d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) if (newValue == this.passwordValues_[index]) 112d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) return; 113d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 114d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) this.passwordValues_[index] = newValue; 115d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 116d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) // Use an invalid char for URL as delimiter to concatenate page url and 117d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) // password field index to construct a unique ID for the password field. 118d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) var passwordId = this.pageURL_ + '|' + index; 119d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) this.channel_.send({ 120d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) name: 'updatePassword', 121d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) id: passwordId, 122d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) password: newValue 123d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) }); 124d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) }, 125d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 126d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) /** 127d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * Handles 'change' event in the scraped password fields. 128d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * @param {number} index The index of the password fields in 129d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * |passwordFields_|. 130d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) */ 131d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) onPasswordChanged_: function(index) { 132d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) this.maybeSendUpdatedPassword(index); 133d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) } 134d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) }; 135d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 136d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) /** 137d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * Heuristic test whether the current page is a relevant SAML page. 138d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * Current implementation checks if it is a http or https page and has 139d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * some content in it. 140d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) * @return {boolean} Whether the current page looks like a SAML page. 141d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) */ 142d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) function isSAMLPage() { 143d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) var url = window.location.href; 144d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) if (!url.match(/^(http|https):\/\//)) 145d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) return false; 146d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 147d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) return document.body.scrollWidth > 50 && document.body.scrollHeight > 50; 148d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) } 149d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) 1505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) if (isSAMLPage()) { 1515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) var pageURL = window.location.href; 1525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) var channel = new Channel(); 1545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) channel.connect('injected'); 1555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) channel.send({name: 'pageLoaded', url: pageURL}); 1565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) var apiCallForwarder = new APICallForwarder(); 1585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) apiCallForwarder.init(channel); 1595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) 1605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) var passwordScraper = new PasswordInputScraper(); 1615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) passwordScraper.init(channel, pageURL, document.documentElement); 162d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles) } 163d0247b1b59f9c528cb6df88b4f2b9afaf80d181eTorne (Richard Coles)})(); 164