Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / gaia_auth / saml_injected.js
blob62857b4c8849bdde2af98f1bd4a08426f8ea563d
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.
5 /**
6  * @fileoverview
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
12  *    offline login.
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.
15  */
17 (function() {
18   function APICallForwarder() {
19   }
21   /**
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
24    * |Channel|.
25    */
26   APICallForwarder.prototype = {
27     // Channel to which API calls are forwarded.
28     channel_: null,
30     /**
31      * Initialize the API call forwarder.
32      * @param {!Object} channel Channel to which API calls should be forwarded.
33      */
34     init: function(channel) {
35       this.channel_ = channel;
36       window.addEventListener('message', this.onMessage_.bind(this));
37     },
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') {
44         return;
45       }
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}}, '/');
51       } else {
52         // Forward all other calls.
53         this.channel_.send({name: 'apiCall', call: event.data.call});
54       }
55     }
56   };
58   /**
59    * A class to scrape password from type=password input elements under a given
60    * docRoot and send them back via a Channel.
61    */
62   function PasswordInputScraper() {
63   }
65   PasswordInputScraper.prototype = {
66     // URL of the page.
67     pageURL_: null,
69     // Channel to send back changed password.
70     channel_: null,
72     // An array to hold password fields.
73     passwordFields_: null,
75     // An array to hold cached password values.
76     passwordValues_: null,
78     /**
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.
86      */
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;
99       }
100     },
102     /**
103      * Check if the password field at |index| has changed. If so, sends back
104      * the updated value.
105      */
106     maybeSendUpdatedPassword: function(index) {
107       var newValue = this.passwordFields_[index].value;
108       if (newValue == this.passwordValues_[index])
109         return;
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;
116       this.channel_.send({
117         name: 'updatePassword',
118         id: passwordId,
119         password: newValue
120       });
121     },
123     /**
124      * Handles 'change' event in the scraped password fields.
125      * @param {number} index The index of the password fields in
126      *     |passwordFields_|.
127      */
128     onPasswordChanged_: function(index) {
129       this.maybeSendUpdatedPassword(index);
130     }
131   };
133   /**
134    * Returns true if the script is injected into auth main page.
135    */
136   function isAuthMainPage() {
137     return window.location.href.indexOf(
138         'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/main.html') == 0;
139   }
141   /**
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.
146    */
147   function isSAMLPage() {
148     var url = window.location.href;
149     if (!url.match(/^(http|https):\/\//))
150       return false;
152     return document.body.scrollWidth > 50 && document.body.scrollHeight > 50;
153   }
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);
160   } else {
161     var channel;
162     var passwordScraper;
163     if (isSAMLPage()) {
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);
175     }
176   }
177 })();