1 // Copyright (c) 2012 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.
6 * Authenticator class wraps the communications between Gaia and its host.
8 function Authenticator() {
12 * Gaia auth extension url origin.
15 Authenticator
.THIS_EXTENSION_ORIGIN
=
16 'chrome-extension://mfffpogegjflfpflabcdkioaeobkgjik';
19 * Singleton getter of Authenticator.
20 * @return {Object} The singleton instance of Authenticator.
22 Authenticator
.getInstance = function() {
23 if (!Authenticator
.instance_
) {
24 Authenticator
.instance_
= new Authenticator();
26 return Authenticator
.instance_
;
29 Authenticator
.prototype = {
34 // Input params from extension initialization URL.
35 inputLang_
: undefined,
36 intputEmail_
: undefined,
39 isSAMLEnabled_
: false,
40 supportChannel_
: null,
42 GAIA_URL
: 'https://accounts.google.com/',
43 GAIA_PAGE_PATH
: 'ServiceLogin?skipvpage=true&sarp=1&rm=hide',
44 PARENT_PAGE
: 'chrome://oobe/',
45 SERVICE_ID
: 'chromeoslogin',
46 CONTINUE_URL
: Authenticator
.THIS_EXTENSION_ORIGIN
+ '/success.html',
47 CONSTRAINED_FLOW_SOURCE
: 'chrome',
49 initialize: function() {
50 var params
= getUrlSearchParams(location
.search
);
51 this.parentPage_
= params
.parentPage
|| this.PARENT_PAGE
;
52 this.gaiaUrl_
= params
.gaiaUrl
|| this.GAIA_URL
;
53 this.gaiaPath_
= params
.gaiaPath
|| this.GAIA_PAGE_PATH
;
54 this.inputLang_
= params
.hl
;
55 this.inputEmail_
= params
.email
;
56 this.service_
= params
.service
|| this.SERVICE_ID
;
57 this.continueUrl_
= params
.continueUrl
|| this.CONTINUE_URL
;
58 this.desktopMode_
= params
.desktopMode
== '1';
59 this.isConstrainedWindow_
= params
.constrained
== '1';
60 this.initialFrameUrl_
= params
.frameUrl
|| this.constructInitialFrameUrl_();
61 this.initialFrameUrlWithoutParams_
= stripParams(this.initialFrameUrl_
);
63 if (this.desktopMode_
) {
64 this.supportChannel_
= new Channel();
65 this.supportChannel_
.connect('authMain');
67 this.supportChannel_
.send({
68 name
: 'initDesktopFlow',
69 gaiaUrl
: this.gaiaUrl_
,
70 continueUrl
: stripParams(this.continueUrl_
),
71 isConstrainedWindow
: this.isConstrainedWindow_
74 this.supportChannel_
.registerMessage(
75 'switchToFullTab', this.switchToFullTab_
.bind(this));
76 this.supportChannel_
.registerMessage(
77 'completeLogin', this.completeLogin_
.bind(this));
80 document
.addEventListener('DOMContentLoaded', this.onPageLoad_
.bind(this));
81 document
.addEventListener('enableSAML', this.onEnableSAML_
.bind(this));
84 isGaiaMessage_: function(msg
) {
85 // Not quite right, but good enough.
86 return this.gaiaUrl_
.indexOf(msg
.origin
) == 0 ||
87 this.GAIA_URL
.indexOf(msg
.origin
) == 0;
90 isInternalMessage_: function(msg
) {
91 return msg
.origin
== Authenticator
.THIS_EXTENSION_ORIGIN
;
94 isParentMessage_: function(msg
) {
95 return msg
.origin
== this.parentPage_
;
98 constructInitialFrameUrl_: function() {
99 var url
= this.gaiaUrl_
+ this.gaiaPath_
;
101 url
= appendParam(url
, 'service', this.service_
);
102 url
= appendParam(url
, 'continue', this.continueUrl_
);
104 url
= appendParam(url
, 'hl', this.inputLang_
);
105 if (this.inputEmail_
)
106 url
= appendParam(url
, 'Email', this.inputEmail_
);
107 if (this.isConstrainedWindow_
)
108 url
= appendParam(url
, 'source', this.CONSTRAINED_FLOW_SOURCE
);
112 onPageLoad_: function() {
113 window
.addEventListener('message', this.onMessage
.bind(this), false);
117 loadFrame_: function() {
118 var gaiaFrame
= $('gaia-frame');
119 gaiaFrame
.src
= this.initialFrameUrl_
;
120 if (this.desktopMode_
) {
121 var handler = function() {
122 this.onLoginUILoaded_();
123 gaiaFrame
.removeEventListener('load', handler
);
125 gaiaFrame
.addEventListener('load', handler
);
130 * Invoked when the login UI is initialized or reset.
132 onLoginUILoaded_: function() {
134 'method': 'loginUILoaded'
136 window
.parent
.postMessage(msg
, this.parentPage_
);
140 * Invoked when the background script sends a message to indicate that the
141 * current content does not fit in a constrained window.
142 * @param {Object=} opt_extraMsg Optional extra info to send.
144 switchToFullTab_: function(msg
) {
146 'method': 'switchToFullTab',
149 window
.parent
.postMessage(parentMsg
, this.parentPage_
);
153 * Invoked when the signin flow is complete.
154 * @param {Object=} opt_extraMsg Optional extra info to send.
156 completeLogin_: function(opt_extraMsg
) {
158 'method': 'completeLogin',
159 'email': (opt_extraMsg
&& opt_extraMsg
.email
) || this.email_
,
160 'password': this.password_
,
161 'usingSAML': this.isSAMLFlow_
,
162 'chooseWhatToSync': this.chooseWhatToSync_
|| false,
163 'skipForNow': opt_extraMsg
&& opt_extraMsg
.skipForNow
,
164 'sessionIndex': opt_extraMsg
&& opt_extraMsg
.sessionIndex
166 window
.parent
.postMessage(msg
, this.parentPage_
);
167 if (this.isSAMLEnabled_
)
168 this.supportChannel_
.send({name
: 'resetAuth'});
172 * Invoked when 'enableSAML' event is received to initialize SAML support.
174 onEnableSAML_: function() {
175 this.isSAMLEnabled_
= true;
176 this.isSAMLFlow_
= false;
178 if (!this.supportChannel_
) {
179 this.supportChannel_
= new Channel();
180 this.supportChannel_
.connect('authMain');
183 this.supportChannel_
.registerMessage(
184 'onAuthPageLoaded', this.onAuthPageLoaded_
.bind(this));
185 this.supportChannel_
.registerMessage(
186 'apiCall', this.onAPICall_
.bind(this));
187 this.supportChannel_
.send({
189 gaiaUrl
: this.gaiaUrl_
194 * Invoked when the background page sends 'onHostedPageLoaded' message.
195 * @param {!Object} msg Details sent with the message.
197 onAuthPageLoaded_: function(msg
) {
198 var isSAMLPage
= msg
.url
.indexOf(this.gaiaUrl_
) != 0;
200 if (isSAMLPage
&& !this.isSAMLFlow_
) {
201 // GAIA redirected to a SAML login page. The credentials provided to this
202 // page will determine what user gets logged in. The credentials obtained
203 // from the GAIA login from are no longer relevant and can be discarded.
204 this.isSAMLFlow_
= true;
206 this.password_
= null;
209 window
.parent
.postMessage({
210 'method': 'authPageLoaded',
211 'isSAML': this.isSAMLFlow_
,
212 'domain': extractDomain(msg
.url
)
213 }, this.parentPage_
);
217 * Invoked when one of the credential passing API methods is called by a SAML
219 * @param {!Object} msg Details of the API call.
221 onAPICall_: function(msg
) {
223 if (call
.method
== 'add') {
224 this.apiToken_
= call
.token
;
225 this.email_
= call
.user
;
226 this.password_
= call
.password
;
227 } else if (call
.method
== 'confirm') {
228 if (call
.token
!= this.apiToken_
)
229 console
.error('Authenticator.onAPICall_: token mismatch');
231 console
.error('Authenticator.onAPICall_: unknown message');
235 onConfirmLogin_: function() {
236 if (!this.isSAMLFlow_
) {
237 this.completeLogin_();
241 var apiUsed
= !!this.password_
;
243 // Retrieve the e-mail address of the user who just authenticated from GAIA.
244 window
.parent
.postMessage({method
: 'retrieveAuthenticatedUserEmail',
245 attemptToken
: this.attemptToken_
,
250 this.supportChannel_
.sendWithCallback(
251 {name
: 'getScrapedPasswords'},
252 function(passwords
) {
253 if (passwords
.length
== 0) {
254 window
.parent
.postMessage(
255 {method
: 'noPassword', email
: this.email_
},
258 window
.parent
.postMessage({method
: 'confirmPassword',
260 passwordCount
: passwords
.length
},
267 maybeCompleteSAMLLogin_: function() {
268 // SAML login is complete when the user's e-mail address has been retrieved
269 // from GAIA and the user has successfully confirmed the password.
270 if (this.email_
!== null && this.password_
!== null)
271 this.completeLogin_();
274 onVerifyConfirmedPassword_: function(password
) {
275 this.supportChannel_
.sendWithCallback(
276 {name
: 'getScrapedPasswords'},
277 function(passwords
) {
278 for (var i
= 0; i
< passwords
.length
; ++i
) {
279 if (passwords
[i
] == password
) {
280 this.password_
= passwords
[i
];
281 this.maybeCompleteSAMLLogin_();
285 window
.parent
.postMessage(
286 {method
: 'confirmPassword', email
: this.email_
},
291 onMessage: function(e
) {
293 if (msg
.method
== 'attemptLogin' && this.isGaiaMessage_(e
)) {
294 this.email_
= msg
.email
;
295 this.password_
= msg
.password
;
296 this.attemptToken_
= msg
.attemptToken
;
297 this.chooseWhatToSync_
= msg
.chooseWhatToSync
;
298 this.isSAMLFlow_
= false;
299 if (this.isSAMLEnabled_
)
300 this.supportChannel_
.send({name
: 'startAuth'});
301 } else if (msg
.method
== 'clearOldAttempts' && this.isGaiaMessage_(e
)) {
303 this.password_
= null;
304 this.attemptToken_
= null;
305 this.isSAMLFlow_
= false;
306 this.onLoginUILoaded_();
307 if (this.isSAMLEnabled_
)
308 this.supportChannel_
.send({name
: 'resetAuth'});
309 } else if (msg
.method
== 'setAuthenticatedUserEmail' &&
310 this.isParentMessage_(e
)) {
311 if (this.attemptToken_
== msg
.attemptToken
) {
312 this.email_
= msg
.email
;
313 this.maybeCompleteSAMLLogin_();
315 } else if (msg
.method
== 'confirmLogin' && this.isInternalMessage_(e
)) {
316 if (this.attemptToken_
== msg
.attemptToken
)
317 this.onConfirmLogin_();
319 console
.error('Authenticator.onMessage: unexpected attemptToken!?');
320 } else if (msg
.method
== 'verifyConfirmedPassword' &&
321 this.isParentMessage_(e
)) {
322 this.onVerifyConfirmedPassword_(msg
.password
);
323 } else if (msg
.method
== 'navigate' &&
324 this.isParentMessage_(e
)) {
325 $('gaia-frame').src
= msg
.src
;
326 } else if (msg
.method
== 'redirectToSignin' &&
327 this.isParentMessage_(e
)) {
328 $('gaia-frame').src
= this.constructInitialFrameUrl_();
330 console
.error('Authenticator.onMessage: unknown message + origin!?');
335 Authenticator
.getInstance().initialize();