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

Side by Side Diff: chrome/browser/resources/gaia_auth_host/authenticator.js

Issue 646983008: Implement signin using webview (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: add a listener interface and remove events Created 6 years, 1 month 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 2014 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 An UI component to authenciate to Chrome. The component hosts
7 * IdP web pages in a webview. A client who is interested in monitoring
8 * authentication events should pass a listener object of type
9 * cr.login.GaiaAuthHost.Listener as defined in this file. After initialization,
10 * call {@code load} to start the authentication flow.
11 */
12 cr.define('cr.login', function() {
13 'use strict';
14
15 var IDP_ORIGIN = 'https://accounts.google.com/';
16 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide';
17 var CONTINUE_URL =
18 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html';
19 var SIGN_IN_HEADER = 'google-accounts-signin';
20 var EMBEDDED_FORM_HEADER = 'google-accounts-embedded';
21 var SAML_HEADER = 'google-accounts-saml';
22
23 /**
24 * The source URL parameter for the constrained signin flow.
25 */
26 var CONSTRAINED_FLOW_SOURCE = 'chrome';
27
28 /**
29 * Enum for the authorization mode, must match AuthMode defined in
30 * chrome/browser/ui/webui/inline_login_ui.cc.
31 * @enum {number}
32 */
33 var AuthMode = {
34 DEFAULT: 0,
35 OFFLINE: 1,
36 DESKTOP: 2
37 };
38
39 /**
40 * Enum for the authorization type.
41 * @enum {number}
42 */
43 var AuthFlow = {
44 DEFAULT: 0,
45 SAML: 1
46 };
47
48 /**
49 * Initializes the authenticator component.
50 * @param {webview|string} webview The webview element or its ID to host IdP
51 * web pages.
52 * @param {Authenticator.Listener=} listener An optional listener for events.
xiyuan 2014/10/23 19:32:03 nit: listener -> opt_listner
guohui 2014/10/23 20:13:34 Done.
53 * @constructor
54 * @extends {cr.EventTarget}
55 */
56 function Authenticator(webview, opt_listener) {
57 this.webview_ = typeof webview == 'string' ? $(webview) : webview;
58 assert(this.webview_);
59
60 this.listener_ = opt_listener || null;
61
62 this.email_ = null;
63 this.password_ = null;
64 this.sessionIndex_ = null;
65 this.chooseWhatToSync_ = false;
66 this.skipForNow_ = false;
67 this.authFlow_ = AuthFlow.DEFAULT;
68 this.loaded_ = false;
69 this.idpOrigin_ = null;
70 this.continueUrl_ = null;
71 this.continueUrlWithoutParams_ = null;
72 this.initialFrameUrl_ = null;
73 this.reloadUrl_ = null;
74 }
75 Authenticator.prototype = Object.create(cr.EventTarget.prototype);
xiyuan 2014/10/23 19:32:03 If we don't dispatch event on this class, it does
guohui 2014/10/23 20:13:34 Unfortunately, to be compatible with the old event
76
77 /**
78 * An interface for receiving notifications upon authentication events.
79 * @interface
80 */
81 Authenticator.Listener = function() {};
82
83 /**
84 * Invoked when authentication UI is ready.
85 */
86 Authenticator.Listener.prototype.onReady = function(e) {};
87
88 /**
89 * Invoked when authentication is completed successfully with credential data.
90 * A credential data object looks like this:
91 * <pre>
92 * {@code
93 * {
94 * email: 'xx@gmail.com',
95 * password: 'xxxx', // May be null or empty.
96 * usingSAML: false,
97 * chooseWhatToSync: false,
98 * skipForNow: false,
99 * sessionIndex: '0'
100 * }
101 * }
102 * </pre>
103 * @param {Object} credentials A credential data object.
104 */
105 Authenticator.Listener.prototype.onSuccess = function(credentials) {};
106
107 /**
108 * Invoked when the requested URL does not fit the container.
109 * @param {string} url Request URL.
110 */
111 Authenticator.Listener.prototype.onResize = function(url) {};
112
113 /**
114 * Invoked when a new window event is fired.
115 * @param {Event} e Event object.
116 */
117 Authenticator.Listener.prototype.onNewWindow = function(e) {};
118
119 /**
120 * Loads the authenticator component with the given parameters.
121 * @param {AuthMode} authMode Authorization mode.
122 * @param {Object} data Parameters for the authorization flow.
123 */
124 Authenticator.prototype.load = function(authMode, data) {
125 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN;
126 this.continueUrl_ = data.continueUrl || CONTINUE_URL;
127 this.continueUrlWithoutParams_ =
128 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) ||
129 this.continueUrl_;
130 this.isConstrainedWindow_ = data.constrained == '1';
131
132 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data);
133 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_;
134 this.authFlow_ = AuthFlow.DEFAULT;
135
136 this.webview_.src = this.reloadUrl_;
137 this.webview_.addEventListener(
138 'newwindow', this.onNewWindow_.bind(this));
139 this.webview_.request.onCompleted.addListener(
140 this.onRequestCompleted_.bind(this),
141 {urls: ['*://*/*', this.continueUrlWithoutParams_ + '*'],
142 types: ['main_frame']},
143 ['responseHeaders']);
144 this.webview_.request.onHeadersReceived.addListener(
145 this.onHeadersReceived_.bind(this),
146 {urls: [this.idpOrigin_ + '*'], types: ['main_frame']},
147 ['responseHeaders']);
148 window.addEventListener(
149 'message', this.onMessage_.bind(this), false);
150 };
151
152 /**
153 * Reloads the authenticator component.
154 */
155 Authenticator.prototype.reload = function() {
156 this.webview_.src = this.reloadUrl_;
157 this.authFlow_ = AuthFlow.DEFAULT;
158 };
159
160 Authenticator.prototype.constructInitialFrameUrl_ = function(data) {
161 var url = this.idpOrigin_ + (data.gaiaPath || IDP_PATH);
162
163 url = appendParam(url, 'continue', this.continueUrl_);
164 url = appendParam(url, 'service', data.service);
165 if (data.hl)
166 url = appendParam(url, 'hl', data.hl);
167 if (data.email)
168 url = appendParam(url, 'Email', data.email);
169 if (this.isConstrainedWindow_)
170 url = appendParam(url, 'source', CONSTRAINED_FLOW_SOURCE);
171 return url;
172 };
173
174 /**
175 * Invoked when a main frame request in the webview has completed.
176 * @private
177 */
178 Authenticator.prototype.onRequestCompleted_ = function(details) {
179 var currentUrl = details.url;
180 if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) {
181 if (currentUrl.indexOf('ntp=1') >= 0) {
182 this.skipForNow_ = true;
183 }
184 this.onAuthCompleted_();
185 return;
186 }
187
188 if (this.isConstrainedWindow_) {
189 var isEmbeddedPage = false;
190 if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
191 var headers = details.responseHeaders;
192 for (var i = 0; headers && i < headers.length; ++i) {
193 if (headers[i].name.toLowerCase() == EMBEDDED_FORM_HEADER) {
194 isEmbeddedPage = true;
195 break;
196 }
197 }
198 }
199 if (!isEmbeddedPage && this.listener_) {
200 this.listener_.onResize(currentUrl);
201 return;
202 }
203 }
204
205 if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
206 this.webview_.contentWindow.postMessage({}, currentUrl);
207 }
208
209 if (!this.loaded_) {
210 this.loaded_ = true;
211 if (this.listener_) this.listener_.onReady();
xiyuan 2014/10/23 19:32:03 nit: Put "this.listener_.onReady()" on a separate
guohui 2014/10/23 20:13:34 Done.
212 }
213 };
214
215 /**
216 * Invoked when headers are received in the main frame of the webview. It
217 * 1) reads the authenticated user info from a signin header,
218 * 2) signals the start of a saml flow upon receiving a saml header.
219 * @return {!Object} Modified request headers.
220 * @private
221 */
222 Authenticator.prototype.onHeadersReceived_ = function(details) {
223 var headers = details.responseHeaders;
224 for (var i = 0; headers && i < headers.length; ++i) {
225 var header = headers[i];
226 var headerName = header.name.toLowerCase();
227 if (headerName == SIGN_IN_HEADER) {
228 var headerValues = header.value.toLowerCase().split(',');
229 var signinDetails = {};
230 headerValues.forEach(function(e) {
231 var pair = e.split('=');
232 signinDetails[pair[0].trim()] = pair[1].trim();
233 });
234 // Removes "" around.
235 var email = signinDetails['email'].slice(1, -1);
236 if (this.email_ != email) {
237 this.email_ = email;
238 // Clears the scraped password if the email has changed.
239 this.password_ = null;
240 }
241 this.sessionIndex_ = signinDetails['sessionindex'];
242 } else if (headerName == SAML_HEADER) {
243 this.authFlow_ = AuthFlow.SAML;
244 }
245 }
246 };
247
248 /**
249 * Invoked when an HTML5 message is received.
250 * @param {object} e Payload of the received HTML5 message.
251 * @private
252 */
253 Authenticator.prototype.onMessage_ = function(e) {
254 if (e.origin != this.idpOrigin_) {
255 return;
256 }
257
258 var msg = e.data;
259
260 if (msg.method == 'attemptLogin') {
261 this.email_ = msg.email;
262 this.password_ = msg.password;
263 this.chooseWhatToSync_ = msg.chooseWhatToSync;
264 }
265 };
266
267 /**
268 * Invoked to process authentication completion.
269 * @private
270 */
271 Authenticator.prototype.onAuthCompleted_ = function() {
272 if (!this.listener_) return;
273
274 if (!this.email_ && !this.skipForNow_) {
275 this.webview_.src = this.initialFrameUrl_;
276 return;
277 }
278
279 this.listener_.onSuccess({email: this.email_,
280 password: this.password_,
281 usingSAML: this.authFlow_ == AuthFlow.SAML,
282 chooseWhatToSync: this.chooseWhatToSync_,
283 skipForNow: this.skipForNow_,
284 sessionIndex: this.sessionIndex_ || ''});
285 };
286
287 /**
288 * Invoked when the webview attempts to open a new window.
289 * @private
290 */
291 Authenticator.prototype.onNewWindow_ = function(e) {
292 if (!this.listener_) return;
293
294 this.listener_.onNewWindow(e);
295 };
296
297 Authenticator.AuthFlow = AuthFlow;
298 Authenticator.AuthMode = AuthMode;
299
300 return {
301 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old
302 // iframe-based flow is deprecated.
303 GaiaAuthHost: Authenticator
304 };
305 });
OLDNEW
« no previous file with comments | « chrome/browser/chrome_content_browser_client.cc ('k') | chrome/browser/resources/inline_login/inline_login.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698