Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(615)

Side by Side Diff: chrome/browser/resources/gaia_auth/saml_injected.js

Issue 2686683002: Deprecate most of gaia_auth extension (Closed)
Patch Set: remove unused data member from GaiaAuthExtensionLoader Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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 // A MutationObserver to watch for dynamic password field creation.
82 passwordFieldsObserver: null,
83
84 /**
85 * Initialize the scraper with given channel and docRoot. Note that the
86 * scanning for password fields happens inside the function and does not
87 * handle DOM tree changes after the call returns.
88 * @param {!Object} channel The channel to send back password.
89 * @param {!string} pageURL URL of the page.
90 * @param {!HTMLElement} docRoot The root element of the DOM tree that
91 * contains the password fields of interest.
92 */
93 init: function(channel, pageURL, docRoot) {
94 this.pageURL_ = pageURL;
95 this.channel_ = channel;
96
97 this.passwordFields_ = [];
98 this.passwordValues_ = [];
99
100 this.findAndTrackChildren(docRoot);
101
102 this.passwordFieldsObserver = new MutationObserver(function(mutations) {
103 mutations.forEach(function(mutation) {
104 Array.prototype.forEach.call(
105 mutation.addedNodes,
106 function(addedNode) {
107 if (addedNode.nodeType != Node.ELEMENT_NODE)
108 return;
109
110 if (addedNode.matches('input[type=password]')) {
111 this.trackPasswordField(addedNode);
112 } else {
113 this.findAndTrackChildren(addedNode);
114 }
115 }.bind(this));
116 }.bind(this));
117 }.bind(this));
118 this.passwordFieldsObserver.observe(docRoot,
119 {subtree: true, childList: true});
120 },
121
122 /**
123 * Find and track password fields that are descendants of the given element.
124 * @param {!HTMLElement} element The parent element to search from.
125 */
126 findAndTrackChildren: function(element) {
127 Array.prototype.forEach.call(
128 element.querySelectorAll('input[type=password]'), function(field) {
129 this.trackPasswordField(field);
130 }.bind(this));
131 },
132
133 /**
134 * Start tracking value changes of the given password field if it is
135 * not being tracked yet.
136 * @param {!HTMLInputElement} passworField The password field to track.
137 */
138 trackPasswordField: function(passwordField) {
139 var existing = this.passwordFields_.filter(function(element) {
140 return element === passwordField;
141 });
142 if (existing.length != 0)
143 return;
144
145 var index = this.passwordFields_.length;
146 var fieldId = passwordField.id || passwordField.name || '';
147 passwordField.addEventListener(
148 'input', this.onPasswordChanged_.bind(this, index, fieldId));
149 this.passwordFields_.push(passwordField);
150 this.passwordValues_.push(passwordField.value);
151 },
152
153 /**
154 * Check if the password field at |index| has changed. If so, sends back
155 * the updated value.
156 */
157 maybeSendUpdatedPassword: function(index, fieldId) {
158 var newValue = this.passwordFields_[index].value;
159 if (newValue == this.passwordValues_[index])
160 return;
161
162 this.passwordValues_[index] = newValue;
163
164 // Use an invalid char for URL as delimiter to concatenate page url,
165 // password field index and id to construct a unique ID for the password
166 // field.
167 var passwordId = this.pageURL_.split('#')[0].split('?')[0] +
168 '|' + index + '|' + fieldId;
169 this.channel_.send({
170 name: 'updatePassword',
171 id: passwordId,
172 password: newValue
173 });
174 },
175
176 /**
177 * Handles 'change' event in the scraped password fields.
178 * @param {number} index The index of the password fields in
179 * |passwordFields_|.
180 * @param {string} fieldId The id or name of the password field or blank.
181 */
182 onPasswordChanged_: function(index, fieldId) {
183 this.maybeSendUpdatedPassword(index, fieldId);
184 }
185 };
186
187 function onGetSAMLFlag(channel, isSAMLPage) {
188 if (!isSAMLPage)
189 return;
190 var pageURL = window.location.href;
191
192 channel.send({name: 'pageLoaded', url: pageURL});
193
194 var initPasswordScraper = function() {
195 var passwordScraper = new PasswordInputScraper();
196 passwordScraper.init(channel, pageURL, document.documentElement);
197 };
198
199 if (document.readyState == 'loading') {
200 window.addEventListener('readystatechange', function listener(event) {
201 if (document.readyState == 'loading')
202 return;
203 initPasswordScraper();
204 window.removeEventListener(event.type, listener, true);
205 }, true);
206 } else {
207 initPasswordScraper();
208 }
209 }
210
211 var channel = Channel.create();
212 channel.connect('injected');
213 channel.sendWithCallback({name: 'getSAMLFlag'},
214 onGetSAMLFlag.bind(undefined, channel));
215
216 var apiCallForwarder = new APICallForwarder();
217 apiCallForwarder.init(channel);
218 })();
OLDNEW
« no previous file with comments | « chrome/browser/resources/gaia_auth/offline.js ('k') | chrome/browser/resources/gaia_auth/util.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698