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

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: split extension framework changes into separate cl and fixed nits Created 6 years, 2 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 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. After initialization, call {@code load} to start
8 * the authentication flow. Two events may be raised after this point:
9 * 1) a 'ready' event when the authentication UI is ready to use.
10 * 2) a 'completed' event when the authentication is completed successfully. If
11 * the caller is interested in the user credentials, it may supply a success
12 * callback when calling {@code load}. The callback will be invoked with the
13 * available credential data before the 'completed' event is dispatched.
14 */
15
16 cr.define('cr.login', function() {
17 'use strict';
18
19 var IDP_ORIGIN = 'https://accounts.google.com/';
20 var IDP_PATH = 'ServiceLogin?skipvpage=true&sarp=1&rm=hide';
21 var CONTINUE_URL =
22 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik/success.html';
23 var SIGN_IN_HEADER = 'google-accounts-signin';
24 var EMBEDDED_FORM_HEADER = 'google-accounts-embedded';
25 var SAML_HEADER = 'google-accounts-saml';
26
27 /**
28 * The source URL parameter for the constrained signin flow.
29 */
30 var CONSTRAINED_FLOW_SOURCE = 'chrome';
31
32 /**
33 * Enum for the authorization mode, must match AuthMode defined in
34 * chrome/browser/ui/webui/inline_login_ui.cc.
35 * @enum {number}
36 */
37 var AuthMode = {
38 DEFAULT: 0,
39 OFFLINE: 1,
40 DESKTOP: 2
41 };
42
43 /**
44 * Enum for the authorization type.
45 * @enum {number}
46 */
47 var AuthFlow = {
48 DEFAULT: 0,
49 SAML: 1
50 };
51
52 /**
53 * Initializes the authenticator component.
54 * @param {webview|string} container The webview element or its ID to host IdP
55 * web pages.
56 * @constructor
57 * @extends {cr.EventTarget}
58 */
59 function Authenticator(container) {
60 this.frame_ = typeof container == 'string' ? $(container) : container;
61 assert(this.frame_);
Roger Tawa OOO till Jul 10th 2014/10/22 19:30:00 Nit: rename frame_ to webView_ ? Rename container
guohui 2014/10/23 19:12:01 Done.
62
63 this.email_ = null;
64 this.password_ = null;
65 this.sessionIndex_ = null;
66 this.chooseWhatToSync_ = 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 /**
76 * Invoked when authentication is completed successfully with credential
77 * data. A credential data object looks like this:
78 * <pre>
79 * {@code
80 * {
81 * email: 'xx@gmail.com',
82 * password: 'xxxx', // May not present
83 * authMode: 'x', // Authorization mode, default/offline/desktop.
84 * }
85 * }
86 * </pre>
87 */
88 this.successCallback_ = null;
89 }
90
91 Authenticator.prototype = Object.create(cr.EventTarget.prototype);
92
93 /**
94 * Loads the authenticator component with the given parameters.
95 * @param {AuthMode} authMode Authorization mode.
96 * @param {Object} data Parameters for the authorization flow.
97 * @param {function(Object)} successCallback A function to be called when
98 * the authentication is completed successfully. The callback is
99 * invoked with a credential object.
100 */
101 Authenticator.prototype.load = function(authMode, data, successCallback) {
102 this.idpOrigin_ = data.gaiaUrl || IDP_ORIGIN;
103 this.continueUrl_ = data.continueUrl || CONTINUE_URL;
104 this.continueUrlWithoutParams_ =
105 this.continueUrl_.substring(0, this.continueUrl_.indexOf('?')) ||
106 this.continueUrl_;
107 this.isConstrainedWindow_ = data.constrained == '1';
108
109 this.initialFrameUrl_ = this.constructInitialFrameUrl_(data);
110 this.reloadUrl_ = data.frameUrl || this.initialFrameUrl_;
111 this.successCallback_ = successCallback;
112 this.authFlow_ = AuthFlow.DEFAULT;
113
114 this.frame_.src = this.reloadUrl_;
115 this.frame_.addEventListener(
116 'newwindow', this.onNewWindow_.bind(this));
117 this.frame_.request.onCompleted.addListener(
118 this.onRequestCompleted_.bind(this),
119 {urls: ['*://*/*', this.continueUrlWithoutParams_ + '*'],
120 types: ['main_frame']},
121 ['responseHeaders']);
122 this.frame_.request.onHeadersReceived.addListener(
123 this.onHeadersReceived_.bind(this),
124 {urls: [this.idpOrigin_ + '*'], types: ['main_frame']},
125 ['responseHeaders']);
126 window.addEventListener(
127 'message', this.onMessage_.bind(this), false);
128 };
129
130 /**
131 * Reloads the authenticator component.
132 */
133 Authenticator.prototype.reload = function() {
134 this.frame_.src = this.reloadUrl_;
135 this.authFlow_ = AuthFlow.DEFAULT;
136 };
137
138 Authenticator.prototype.constructInitialFrameUrl_ = function(data) {
139 var url = this.idpOrigin_ + (data.gaiaPath || IDP_PATH);
140
141 url = appendParam(url, 'continue', this.continueUrl_);
142 url = appendParam(url, 'service', data.service);
143 if (data.hl)
144 url = appendParam(url, 'hl', data.hl);
145 if (data.email)
146 url = appendParam(url, 'Email', data.email);
147 if (this.isConstrainedWindow_)
148 url = appendParam(url, 'source', CONSTRAINED_FLOW_SOURCE);
149 return url;
150 };
151
152 /**
153 * Invoked when a main frame request in the webview has completed.
154 * @private
155 */
156 Authenticator.prototype.onRequestCompleted_ = function(details) {
157 var currentUrl = details.url;
158 if (currentUrl.lastIndexOf(this.continueUrlWithoutParams_, 0) == 0) {
159 this.onAuthCompleted_();
160 return;
161 }
162
163 if (this.isConstrainedWindow_) {
164 var isEmbeddedPage = false;
165 if (this.idpOrigin_ && currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
166 var headers = details.responseHeaders;
167 for (var i = 0; headers && i < headers.length; ++i) {
168 if (headers[i].name.toLowerCase() == EMBEDDED_FORM_HEADER) {
169 isEmbeddedPage = true;
170 break;
171 }
172 }
173 }
174 if (!isEmbeddedPage) {
175 chrome.send('switchToFullTab', [currentUrl]);
xiyuan 2014/10/22 17:20:53 This assumes webui page. Maybe we should have a ca
guohui 2014/10/23 19:12:01 Done.
176 return;
177 }
178 }
179
180 if (currentUrl.lastIndexOf(this.idpOrigin_) == 0) {
181 this.frame_.contentWindow.postMessage('', this.frame_.src);
xiyuan 2014/10/22 17:20:53 nit: Maybe using an empty object {} instead of ''?
guohui 2014/10/23 19:12:01 Done.
182 }
183
184 if (!this.loaded_) {
185 this.loaded_ = true;
186 cr.dispatchSimpleEvent(this, 'ready');
187 }
188 };
189
190 /**
191 * Invoked when headers are received in the main frame of the webview. It
192 * 1) reads the authenticated user info from a signin header,
193 * 2) signals the start of a saml flow upon receiving a saml header.
194 * @return {!Object} Modified request headers.
195 * @private
196 */
197 Authenticator.prototype.onHeadersReceived_ = function(details) {
198 var headers = details.responseHeaders;
199 for (var i = 0; headers && i < headers.length; ++i) {
200 var header = headers[i];
201 var headerName = header.name.toLowerCase();
202 if (headerName == SIGN_IN_HEADER) {
203 var headerValues = header.value.toLowerCase().split(',');
204 var signinDetails = {};
205 headerValues.forEach(function(e) {
206 var pair = e.split('=');
207 signinDetails[pair[0].trim()] = pair[1].trim();
208 });
209 // Removes "" around.
210 var email = signinDetails['email'].slice(1, -1);
211 if (this.email_ != email) {
212 this.email_ = email;
213 // Clears the scraped password if the email has changed.
214 this.password_ = null;
215 }
216 this.sessionIndex_ = signinDetails['sessionindex'];
217 } else if (headerName == SAML_HEADER) {
218 this.authFlow_ = AuthFlow.SAML;
219 }
220 }
221 };
222
223 /**
224 * Invoked when an HTML5 message is received.
225 * @param {object} e Payload of the received HTML5 message.
226 * @private
227 */
228 Authenticator.prototype.onMessage_ = function(e) {
229 if (e.origin != this.idpOrigin_) {
230 return;
231 }
232
233 var msg = e.data;
234
235 if (msg.method == 'attemptLogin') {
236 this.email_ = msg.email;
237 this.password_ = msg.password;
238 this.chooseWhatToSync_ = msg.chooseWhatToSync;
239 }
240 };
241
242 /**
243 * Invoked to process authentication completion.
244 * @private
245 */
246 Authenticator.prototype.onAuthCompleted_ = function() {
247 var skipForNow = false;
248 if (this.frame_.src.indexOf('ntp=1') >= 0) {
249 skipForNow = true;
250 }
251
252 if (!this.email_ && !skipForNow) {
253 this.frame_.src = this.initialFrameUrl_;
254 return;
255 }
256
257 if (this.successCallback_) {
258 this.successCallback_({email: this.email_,
259 password: this.password_,
260 usingSAML: this.authFlow_ == AuthFlow.SAML,
261 chooseWhatToSync: this.chooseWhatToSync_,
262 skipForNow: skipForNow,
263 sessionIndex: this.sessionIndex_ || ''});
264 }
265 cr.dispatchSimpleEvent(this, 'completed');
266 };
267
268 /**
269 * Invoked when the webview attempts to open a new window.
270 * @private
271 */
272 Authenticator.prototype.onNewWindow_ = function(e) {
273 window.open(e.targetUrl, '_blank');
xiyuan 2014/10/22 17:20:53 I wonder if we should let the embedder to decide w
guohui 2014/10/23 19:12:01 Done.
274 e.window.discard();
275 };
276
277 Authenticator.AuthFlow = AuthFlow;
278 Authenticator.AuthMode = AuthMode;
279
280 return {
281 // TODO(guohui, xiyuan): Rename GaiaAuthHost to Authenticator once the old
282 // iframe-based flow is deprecated.
283 GaiaAuthHost: Authenticator
284 };
285 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698