OLD | NEW |
---|---|
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 /** | 5 /** |
6 * @fileoverview An UI component to authenciate to Chrome. The component hosts | 6 * @fileoverview An UI component to authenciate to Chrome. The component hosts |
7 * IdP web pages in a webview. A client who is interested in monitoring | 7 * IdP web pages in a webview. A client who is interested in monitoring |
8 * authentication events should pass a listener object of type | 8 * authentication events should pass a listener object of type |
9 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization, | 9 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization, |
10 * call {@code load} to start the authentication flow. | 10 * call {@code load} to start the authentication flow. |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
42 */ | 42 */ |
43 var AuthFlow = { | 43 var AuthFlow = { |
44 DEFAULT: 0, | 44 DEFAULT: 0, |
45 SAML: 1 | 45 SAML: 1 |
46 }; | 46 }; |
47 | 47 |
48 /** | 48 /** |
49 * Initializes the authenticator component. | 49 * Initializes the authenticator component. |
50 * @param {webview|string} webview The webview element or its ID to host IdP | 50 * @param {webview|string} webview The webview element or its ID to host IdP |
51 * web pages. | 51 * web pages. |
52 * @param {Authenticator.Listener=} opt_listener An optional listener for | |
53 * authentication events. | |
54 * @constructor | 52 * @constructor |
55 * @extends {cr.EventTarget} | |
56 */ | 53 */ |
57 function Authenticator(webview, opt_listener) { | 54 function Authenticator(webview) { |
58 this.webview_ = typeof webview == 'string' ? $(webview) : webview; | 55 this.webview_ = typeof webview == 'string' ? $(webview) : webview; |
59 assert(this.webview_); | 56 assert(this.webview_); |
60 | 57 |
61 this.listener_ = opt_listener || null; | |
62 | |
63 this.email_ = null; | 58 this.email_ = null; |
64 this.password_ = null; | 59 this.password_ = null; |
65 this.gaiaId_ = null, | 60 this.gaiaId_ = null, |
66 this.sessionIndex_ = null; | 61 this.sessionIndex_ = null; |
67 this.chooseWhatToSync_ = false; | 62 this.chooseWhatToSync_ = false; |
68 this.skipForNow_ = false; | 63 this.skipForNow_ = false; |
69 this.authFlow_ = AuthFlow.DEFAULT; | 64 this.authFlow_ = AuthFlow.DEFAULT; |
70 this.loaded_ = false; | 65 this.loaded_ = false; |
71 this.idpOrigin_ = null; | 66 this.idpOrigin_ = null; |
72 this.continueUrl_ = null; | 67 this.continueUrl_ = null; |
73 this.continueUrlWithoutParams_ = null; | 68 this.continueUrlWithoutParams_ = null; |
74 this.initialFrameUrl_ = null; | 69 this.initialFrameUrl_ = null; |
75 this.reloadUrl_ = null; | 70 this.reloadUrl_ = null; |
71 this.trusted_ = true; | |
76 } | 72 } |
77 | 73 |
78 // TODO(guohui,xiyuan): no need to inherit EventTarget once we deprecate the | 74 // TODO(guohui,xiyuan): no need to inherit EventTarget once we deprecate the |
79 // old event-based signin flow. | 75 // old event-based signin flow. |
80 Authenticator.prototype = Object.create(cr.EventTarget.prototype); | 76 Authenticator.prototype = Object.create(cr.EventTarget.prototype); |
81 | 77 |
82 /** | 78 /** |
83 * An interface for receiving notifications upon authentication events. | |
84 * @interface | |
85 */ | |
86 Authenticator.Listener = function() {}; | |
87 | |
88 /** | |
89 * Invoked when authentication UI is ready. | |
90 */ | |
91 Authenticator.Listener.prototype.onReady = function(e) {}; | |
92 | |
93 /** | |
94 * Invoked when authentication is completed successfully with credential data. | |
95 * A credential data object looks like this: | |
96 * <pre> | |
97 * {@code | |
98 * { | |
99 * email: 'xx@gmail.com', | |
100 * password: 'xxxx', // May be null or empty. | |
101 * usingSAML: false, | |
102 * chooseWhatToSync: false, | |
103 * skipForNow: false, | |
104 * sessionIndex: '0' | |
105 * } | |
106 * } | |
107 * </pre> | |
108 * @param {Object} credentials A credential data object. | |
109 */ | |
110 Authenticator.Listener.prototype.onSuccess = function(credentials) {}; | |
111 | |
112 /** | |
113 * Invoked when the requested URL does not fit the container. | |
114 * @param {string} url Request URL. | |
115 */ | |
116 Authenticator.Listener.prototype.onResize = function(url) {}; | |
117 | |
118 /** | |
119 * Invoked when a new window event is fired. | |
120 * @param {Event} e Event object. | |
121 */ | |
122 Authenticator.Listener.prototype.onNewWindow = function(e) {}; | |
123 | |
124 /** | |
125 * Loads the authenticator component with the given parameters. | 79 * Loads the authenticator component with the given parameters. |
126 * @param {AuthMode} authMode Authorization mode. | 80 * @param {AuthMode} authMode Authorization mode. |
127 * @param {Object} data Parameters for the authorization flow. | 81 * @param {Object} data Parameters for the authorization flow. |
128 */ | 82 */ |
129 Authenticator.prototype.load = function(authMode, data) { | 83 Authenticator.prototype.load = function(authMode, data) { |
130 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN; | 84 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN; |
131 this.continueUrl_ = data.continueUrl || CONTINUE_URL; | 85 this.continueUrl_ = data.continueUrl || CONTINUE_URL; |
132 this.continueUrlWithoutParams_ = | 86 this.continueUrlWithoutParams_ = |
133 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) || | 87 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) || |
134 this.continueUrl_; | 88 this.continueUrl_; |
135 this.isConstrainedWindow_ = data.constrained == '1'; | 89 this.isConstrainedWindow_ = data.constrained == '1'; |
136 | 90 |
137 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data); | 91 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data); |
138 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_; | 92 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_; |
139 this.authFlow_ = AuthFlow.DEFAULT; | 93 this.authFlow_ = AuthFlow.DEFAULT; |
140 | 94 |
141 this.webview_.src = this.reloadUrl_; | 95 this.webview_.src = this.reloadUrl_; |
142 this.webview_.addEventListener( | 96 this.webview_.addEventListener( |
143 'newwindow', this.onNewWindow_.bind(this)); | 97 'newwindow', this.onNewWindow_.bind(this)); |
98 this.webview_.addEventListener( | |
99 'loadstop', this.onLoadStop_.bind(this)); | |
144 this.webview_.request.onCompleted.addListener( | 100 this.webview_.request.onCompleted.addListener( |
145 this.onRequestCompleted_.bind(this), | 101 this.onRequestCompleted_.bind(this), |
146 {urls: ['*://*/*', this.continueUrlWithoutParams_ + '*'], | 102 {urls: ['*://*/*', this.continueUrlWithoutParams_ + '*'], |
147 types: ['main_frame']}, | 103 types: ['main_frame']}, |
148 ['responseHeaders']); | 104 ['responseHeaders']); |
149 this.webview_.request.onHeadersReceived.addListener( | 105 this.webview_.request.onHeadersReceived.addListener( |
150 this.onHeadersReceived_.bind(this), | 106 this.onHeadersReceived_.bind(this), |
151 {urls: [this.idpOrigin_ + '*'], types: ['main_frame']}, | 107 {urls: [this.idpOrigin_ + '*'], types: ['main_frame']}, |
152 ['responseHeaders']); | 108 ['responseHeaders']); |
153 window.addEventListener( | 109 window.addEventListener( |
(...skipping 21 matching lines...) Expand all Loading... | |
175 url = appendParam(url, 'source', CONSTRAINED_FLOW_SOURCE); | 131 url = appendParam(url, 'source', CONSTRAINED_FLOW_SOURCE); |
176 return url; | 132 return url; |
177 }; | 133 }; |
178 | 134 |
179 /** | 135 /** |
180 * Invoked when a main frame request in the webview has completed. | 136 * Invoked when a main frame request in the webview has completed. |
181 * @private | 137 * @private |
182 */ | 138 */ |
183 Authenticator.prototype.onRequestCompleted_ = function(details) { | 139 Authenticator.prototype.onRequestCompleted_ = function(details) { |
184 var currentUrl = details.url; | 140 var currentUrl = details.url; |
141 if (currentUrl.indexOf('https') != 0) { | |
142 this.trusted_ = false; | |
noms (inactive)
2014/12/19 19:48:59
nit: no {}
Roger Tawa OOO till Jul 10th
2014/12/19 22:23:59
The convention in this file is to include { } in t
| |
143 } | |
144 | |
185 if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) { | 145 if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) { |
186 if (currentUrl.indexOf('ntp=1') >= 0) { | 146 if (currentUrl.indexOf('ntp=1') >= 0) { |
187 this.skipForNow_ = true; | 147 this.skipForNow_ = true; |
188 } | 148 } |
189 this.onAuthCompleted_(); | 149 this.onAuthCompleted_(); |
190 return; | 150 return; |
191 } | 151 } |
192 | 152 |
193 if (this.isConstrainedWindow_) { | 153 if (this.isConstrainedWindow_) { |
194 var isEmbeddedPage = false; | 154 var isEmbeddedPage = false; |
195 if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) { | 155 if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) { |
196 var headers = details.responseHeaders; | 156 var headers = details.responseHeaders; |
197 for (var i = 0; headers && i < headers.length; ++i) { | 157 for (var i = 0; headers && i < headers.length; ++i) { |
198 if (headers[i].name.toLowerCase() == EMBEDDED_FORM_HEADER) { | 158 if (headers[i].name.toLowerCase() == EMBEDDED_FORM_HEADER) { |
199 isEmbeddedPage = true; | 159 isEmbeddedPage = true; |
200 break; | 160 break; |
201 } | 161 } |
202 } | 162 } |
203 } | 163 } |
204 if (!isEmbeddedPage && this.listener_) { | 164 if (!isEmbeddedPage) { |
205 this.listener_.onResize(currentUrl); | 165 this.dispatchEvent(new CustomEvent('resize', {detail: currentUrl})); |
206 return; | 166 return; |
207 } | 167 } |
208 } | 168 } |
209 | 169 |
210 if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) { | 170 if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) { |
211 this.webview_.contentWindow.postMessage({}, currentUrl); | 171 this.webview_.contentWindow.postMessage({}, currentUrl); |
212 } | 172 } |
213 | |
214 if (!this.loaded_) { | |
215 this.loaded_ = true; | |
216 if (this.listener_) { | |
217 this.listener_.onReady(); | |
218 } | |
219 } | |
220 }; | 173 }; |
221 | 174 |
222 /** | 175 /** |
223 * Invoked when headers are received in the main frame of the webview. It | 176 * Invoked when headers are received in the main frame of the webview. It |
224 * 1) reads the authenticated user info from a signin header, | 177 * 1) reads the authenticated user info from a signin header, |
225 * 2) signals the start of a saml flow upon receiving a saml header. | 178 * 2) signals the start of a saml flow upon receiving a saml header. |
226 * @return {!Object} Modified request headers. | 179 * @return {!Object} Modified request headers. |
227 * @private | 180 * @private |
228 */ | 181 */ |
229 Authenticator.prototype.onHeadersReceived_ = function(details) { | 182 Authenticator.prototype.onHeadersReceived_ = function(details) { |
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
270 this.password_ = msg.password; | 223 this.password_ = msg.password; |
271 this.chooseWhatToSync_ = msg.chooseWhatToSync; | 224 this.chooseWhatToSync_ = msg.chooseWhatToSync; |
272 } | 225 } |
273 }; | 226 }; |
274 | 227 |
275 /** | 228 /** |
276 * Invoked to process authentication completion. | 229 * Invoked to process authentication completion. |
277 * @private | 230 * @private |
278 */ | 231 */ |
279 Authenticator.prototype.onAuthCompleted_ = function() { | 232 Authenticator.prototype.onAuthCompleted_ = function() { |
280 if (!this.listener_) { | |
281 return; | |
282 } | |
283 | |
284 if (!this.email_ && !this.skipForNow_) { | 233 if (!this.email_ && !this.skipForNow_) { |
285 this.webview_.src = this.initialFrameUrl_; | 234 this.webview_.src = this.initialFrameUrl_; |
286 return; | 235 return; |
287 } | 236 } |
288 | 237 |
289 this.listener_.onSuccess({email: this.email_, | 238 this.dispatchEvent( |
290 gaiaId: this.gaiaId_, | 239 new CustomEvent('authCompleted', |
291 password: this.password_, | 240 {detail: {email: this.email_, |
292 usingSAML: this.authFlow_ == AuthFlow.SAML, | 241 gaiaId: this.gaiaId_, |
293 chooseWhatToSync: this.chooseWhatToSync_, | 242 password: this.password_, |
294 skipForNow: this.skipForNow_, | 243 usingSAML: this.authFlow_ == AuthFlow.SAML, |
295 sessionIndex: this.sessionIndex_ || ''}); | 244 chooseWhatToSync: this.chooseWhatToSync_, |
245 skipForNow: this.skipForNow_, | |
246 sessionIndex: this.sessionIndex_ || '', | |
247 trusted: this.trusted_}})); | |
296 }; | 248 }; |
297 | 249 |
298 /** | 250 /** |
299 * Invoked when the webview attempts to open a new window. | 251 * Invoked when the webview attempts to open a new window. |
300 * @private | 252 * @private |
301 */ | 253 */ |
302 Authenticator.prototype.onNewWindow_ = function(e) { | 254 Authenticator.prototype.onNewWindow_ = function(e) { |
303 if (!this.listener_) { | 255 this.dispatchEvent(new CustomEvent('newWindow', {detail: e})); |
304 return; | 256 }; |
257 | |
258 /** | |
259 * Invoked when the webview finishes loading a page. | |
260 * @private | |
261 */ | |
262 Authenticator.prototype.onLoadStop_ = function(e) { | |
263 if (!this.loaded_) { | |
264 this.loaded_ = true; | |
265 this.dispatchEvent(new Event('ready')); | |
305 } | 266 } |
306 | |
307 this.listener_.onNewWindow(e); | |
308 }; | 267 }; |
309 | 268 |
310 Authenticator.AuthFlow = AuthFlow; | 269 Authenticator.AuthFlow = AuthFlow; |
311 Authenticator.AuthMode = AuthMode; | 270 Authenticator.AuthMode = AuthMode; |
312 | 271 |
313 return { | 272 return { |
314 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old | 273 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old |
315 // iframe-based flow is deprecated. | 274 // iframe-based flow is deprecated. |
316 GaiaAuthHost: Authenticator | 275 GaiaAuthHost: Authenticator |
317 }; | 276 }; |
318 }); | 277 }); |
OLD | NEW |