Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / gaia_auth / saml_injected.js
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.
4
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  */
16
17 (function() {
18   function APICallForwarder() {
19   }
20
21   /**
22    * The credential passing API is used by sending messages to the SAML page's
23    * |window| object. This class forwards API calls from the SAML page to a
24    * background script and API responses from the background script to the SAML
25    * page. Communication with the background script occurs via a |Channel|.
26    */
27   APICallForwarder.prototype = {
28     // Channel to which API calls are forwarded.
29     channel_: null,
30
31     /**
32      * Initialize the API call forwarder.
33      * @param {!Object} channel Channel to which API calls should be forwarded.
34      */
35     init: function(channel) {
36       this.channel_ = channel;
37       this.channel_.registerMessage('apiResponse',
38                                     this.onAPIResponse_.bind(this));
39
40       window.addEventListener('message', this.onMessage_.bind(this));
41     },
42
43     onMessage_: function(event) {
44       if (event.source != window ||
45           typeof event.data != 'object' ||
46           !event.data.hasOwnProperty('type') ||
47           event.data.type != 'gaia_saml_api') {
48         return;
49       }
50       // Forward API calls to the background script.
51       this.channel_.send({name: 'apiCall', call: event.data.call});
52     },
53
54     onAPIResponse_: function(msg) {
55       // Forward API responses to the SAML page.
56       window.postMessage({type: 'gaia_saml_api_reply', response: msg.response},
57                          '/');
58     }
59   };
60
61   /**
62    * A class to scrape password from type=password input elements under a given
63    * docRoot and send them back via a Channel.
64    */
65   function PasswordInputScraper() {
66   }
67
68   PasswordInputScraper.prototype = {
69     // URL of the page.
70     pageURL_: null,
71
72     // Channel to send back changed password.
73     channel_: null,
74
75     // An array to hold password fields.
76     passwordFields_: null,
77
78     // An array to hold cached password values.
79     passwordValues_: null,
80
81     /**
82      * Initialize the scraper with given channel and docRoot. Note that the
83      * scanning for password fields happens inside the function and does not
84      * handle DOM tree changes after the call returns.
85      * @param {!Object} channel The channel to send back password.
86      * @param {!string} pageURL URL of the page.
87      * @param {!HTMLElement} docRoot The root element of the DOM tree that
88      *     contains the password fields of interest.
89      */
90     init: function(channel, pageURL, docRoot) {
91       this.pageURL_ = pageURL;
92       this.channel_ = channel;
93
94       this.passwordFields_ = docRoot.querySelectorAll('input[type=password]');
95       this.passwordValues_ = [];
96
97       for (var i = 0; i < this.passwordFields_.length; ++i) {
98         this.passwordFields_[i].addEventListener(
99             'input', this.onPasswordChanged_.bind(this, i));
100
101         this.passwordValues_[i] = this.passwordFields_[i].value;
102       }
103     },
104
105     /**
106      * Check if the password field at |index| has changed. If so, sends back
107      * the updated value.
108      */
109     maybeSendUpdatedPassword: function(index) {
110       var newValue = this.passwordFields_[index].value;
111       if (newValue == this.passwordValues_[index])
112         return;
113
114       this.passwordValues_[index] = newValue;
115
116       // Use an invalid char for URL as delimiter to concatenate page url and
117       // password field index to construct a unique ID for the password field.
118       var passwordId = this.pageURL_ + '|' + index;
119       this.channel_.send({
120         name: 'updatePassword',
121         id: passwordId,
122         password: newValue
123       });
124     },
125
126     /**
127      * Handles 'change' event in the scraped password fields.
128      * @param {number} index The index of the password fields in
129      *     |passwordFields_|.
130      */
131     onPasswordChanged_: function(index) {
132       this.maybeSendUpdatedPassword(index);
133     }
134   };
135
136   /**
137    * Returns true if the script is injected into auth main page.
138    */
139   function isAuthMainPage() {
140     return window.location.href.indexOf(
141         'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/main.html') == 0;
142   }
143
144   /**
145    * Heuristic test whether the current page is a relevant SAML page.
146    * Current implementation checks if it is a http or https page and has
147    * some content in it.
148    * @return {boolean} Whether the current page looks like a SAML page.
149    */
150   function isSAMLPage() {
151     var url = window.location.href;
152     if (!url.match(/^(http|https):\/\//))
153       return false;
154
155     return document.body.scrollWidth > 50 && document.body.scrollHeight > 50;
156   }
157
158   if (isAuthMainPage()) {
159     // Use an event to signal the auth main to enable SAML support.
160     var e = document.createEvent('Event');
161     e.initEvent('enableSAML', false, false);
162     document.dispatchEvent(e);
163   } else {
164     var channel;
165     var passwordScraper;
166     if (isSAMLPage()) {
167       var pageURL = window.location.href;
168
169       channel = new Channel();
170       channel.connect('injected');
171       channel.send({name: 'pageLoaded', url: pageURL});
172
173       apiCallForwarder = new APICallForwarder();
174       apiCallForwarder.init(channel);
175
176       passwordScraper = new PasswordInputScraper();
177       passwordScraper.init(channel, pageURL, document.documentElement);
178     }
179   }
180 })();