!import
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
8 *
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
13 *
14 * The Original Code is mozilla.org code.
15 *
16 * The Initial Developer of the Original Code is Mozilla Corporation.
17 * Portions created by the Initial Developer are Copyright (C) 2007
18 * the Initial Developer. All Rights Reserved.
19 *
20 * Contributor(s):
21 * Justin Dolske <dolske@mozilla.com> (original author)
22 * Ehsan Akhgari <ehsan.akhgari@gmail.com>
23 *
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
35 *
36 * ***** END LICENSE BLOCK ***** */
37
38
39 const Cc = Components.classes;
40 const Ci = Components.interfaces;
41 const Cr = Components.results;
42
43 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
44
45 /*
46 * LoginManagerPromptFactory
47 *
48 * Implements nsIPromptFactory
49 *
50 * Invoked by NS_NewAuthPrompter2()
51 * [embedding/components/windowwatcher/src/nsPrompt.cpp]
52 */
LoginManagerPromptFactory
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
53 function LoginManagerPromptFactory() {}
54
55 LoginManagerPromptFactory.prototype = {
56
57 classDescription : "LoginManagerPromptFactory",
58 contractID : "@mozilla.org/passwordmanager/authpromptfactory;1",
59 classID : Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"),
60 QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory]),
61
getPrompt
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
62 getPrompt : function (aWindow, aIID) {
63 var prompt = new LoginManagerPrompter().QueryInterface(aIID);
64 prompt.init(aWindow);
65 return prompt;
66 }
67 }; // end of LoginManagerPromptFactory implementation
68
69
70
71
72 /* ==================== LoginManagerPrompter ==================== */
73
74
75
76
77 /*
78 * LoginManagerPrompter
79 *
80 * Implements interfaces for prompting the user to enter/save/change auth info.
81 *
82 * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox.
83 *
84 * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication
85 * (eg HTTP Authenticate, FTP login).
86 *
87 * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins
88 * found in HTML forms.
89 */
LoginManagerPrompter
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
90 function LoginManagerPrompter() {}
91
92 LoginManagerPrompter.prototype = {
93
94 classDescription : "LoginManagerPrompter",
95 contractID : "@mozilla.org/login-manager/prompter;1",
96 classID : Components.ID("{8aa66d77-1bbb-45a6-991e-b8f47751c291}"),
97 QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPrompt,
98 Ci.nsIAuthPrompt2,
99 Ci.nsILoginManagerPrompter]),
100
101 _window : null,
102 _debug : false, // mirrors signon.debug
103
104 __pwmgr : null, // Password Manager service
get__pwmgr
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
105 get _pwmgr() {
106 if (!this.__pwmgr)
107 this.__pwmgr = Cc["@mozilla.org/login-manager;1"].
108 getService(Ci.nsILoginManager);
109 return this.__pwmgr;
110 },
111
112 __logService : null, // Console logging service, used for debugging.
get__logService
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
113 get _logService() {
114 if (!this.__logService)
115 this.__logService = Cc["@mozilla.org/consoleservice;1"].
116 getService(Ci.nsIConsoleService);
117 return this.__logService;
118 },
119
120 __promptService : null, // Prompt service for user interaction
get__promptService
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
121 get _promptService() {
122 if (!this.__promptService)
123 this.__promptService =
124 Cc["@mozilla.org/embedcomp/prompt-service;1"].
125 getService(Ci.nsIPromptService2);
126 return this.__promptService;
127 },
128
129
130 __strBundle : null, // String bundle for L10N
get__strBundle
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
131 get _strBundle() {
132 if (!this.__strBundle) {
133 var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
134 getService(Ci.nsIStringBundleService);
135 this.__strBundle = bunService.createBundle(
136 "chrome://passwordmgr/locale/passwordmgr.properties");
137 if (!this.__strBundle)
138 throw "String bundle for Login Manager not present!";
139 }
140
141 return this.__strBundle;
142 },
143
144
145 __brandBundle : null, // String bundle for L10N
get__brandBundle
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
146 get _brandBundle() {
147 if (!this.__brandBundle) {
148 var bunService = Cc["@mozilla.org/intl/stringbundle;1"].
149 getService(Ci.nsIStringBundleService);
150 this.__brandBundle = bunService.createBundle(
151 "chrome://branding/locale/brand.properties");
152 if (!this.__brandBundle)
153 throw "Branding string bundle not present!";
154 }
155
156 return this.__brandBundle;
157 },
158
159
160 __ioService: null, // IO service for string -> nsIURI conversion
get__ioService
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
161 get _ioService() {
162 if (!this.__ioService)
163 this.__ioService = Cc["@mozilla.org/network/io-service;1"].
164 getService(Ci.nsIIOService);
165 return this.__ioService;
166 },
167
168
169 /*
170 * log
171 *
172 * Internal function for logging debug messages to the Error Console window.
173 */
log
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
174 log : function (message) {
175 if (!this._debug)
176 return;
177
178 dump("Pwmgr Prompter: " + message + "\n");
179 this._logService.logStringMessage("Pwmgr Prompter: " + message);
180 },
181
182
183
184
185 /* ---------- nsIAuthPrompt prompts ---------- */
186
187
188 /*
189 * prompt
190 *
191 * Wrapper around the prompt service prompt. Saving random fields here
192 * doesn't really make sense and therefore isn't implemented.
193 */
prompt
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
194 prompt : function (aDialogTitle, aText, aPasswordRealm,
195 aSavePassword, aDefaultText, aResult) {
196 if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER)
197 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
198
199 this.log("===== prompt() called =====");
200
201 if (aDefaultText) {
202 aResult.value = aDefaultText;
203 }
204
205 return this._promptService.prompt(this._window,
206 aDialogTitle, aText, aResult, null, {});
207 },
208
209
210 /*
211 * promptUsernameAndPassword
212 *
213 * Looks up a username and password in the database. Will prompt the user
214 * with a dialog, even if a username and password are found.
215 */
promptUsernameAndPassword
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
216 promptUsernameAndPassword : function (aDialogTitle, aText, aPasswordRealm,
217 aSavePassword, aUsername, aPassword) {
218 this.log("===== promptUsernameAndPassword() called =====");
219
220 if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
221 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
222
223 var selectedLogin = null;
224 var checkBox = { value : false };
225 var checkBoxLabel = null;
226 var [hostname, realm, unused] = this._getRealmInfo(aPasswordRealm);
227
228 // If hostname is null, we can't save this login.
229 if (hostname) {
230 var canRememberLogin = (aSavePassword ==
231 Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
232 this._pwmgr.getLoginSavingEnabled(hostname);
233
234 // if checkBoxLabel is null, the checkbox won't be shown at all.
235 if (canRememberLogin)
236 checkBoxLabel = this._getLocalizedString("rememberPassword");
237
238 // Look for existing logins.
239 var foundLogins = this._pwmgr.findLogins({}, hostname, null,
240 realm);
241
242 // XXX Like the original code, we can't deal with multiple
243 // account selection. (bug 227632)
244 if (foundLogins.length > 0) {
245 selectedLogin = foundLogins[0];
246
247 // If the caller provided a username, try to use it. If they
248 // provided only a password, this will try to find a password-only
249 // login (or return null if none exists).
250 if (aUsername.value)
251 selectedLogin = this._repickSelectedLogin(foundLogins,
252 aUsername.value);
253
254 if (selectedLogin) {
255 checkBox.value = true;
256 aUsername.value = selectedLogin.username;
257 // If the caller provided a password, prefer it.
258 if (!aPassword.value)
259 aPassword.value = selectedLogin.password;
260 }
261 }
262 }
263
264 var ok = this._promptService.promptUsernameAndPassword(this._window,
265 aDialogTitle, aText, aUsername, aPassword,
266 checkBoxLabel, checkBox);
267
268 if (!ok || !checkBox.value || !hostname)
269 return ok;
270
271 var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
272 createInstance(Ci.nsILoginInfo);
273 newLogin.init(hostname, null, realm, aUsername.value, aPassword.value,
274 "", "");
275
276 // XXX We can't prompt with multiple logins yet (bug 227632), so
277 // the entered login might correspond to an existing login
278 // other than the one we originally selected.
279 selectedLogin = this._repickSelectedLogin(foundLogins, aUsername.value);
280
281 // If we didn't find an existing login, or if the username
282 // changed, save as a new login.
283 if (!selectedLogin) {
284 // add as new
285 this.log("New login seen for " + realm);
286 this._pwmgr.addLogin(newLogin);
287 } else if (aPassword.value != selectedLogin.password) {
288 // update password
289 this.log("Updating password for " + realm);
290 this._pwmgr.modifyLogin(selectedLogin, newLogin);
291 } else {
292 this.log("Login unchanged, no further action needed.");
293 }
294
295 return ok;
296 },
297
298
299 /*
300 * promptPassword
301 *
302 * If a password is found in the database for the password realm, it is
303 * returned straight away without displaying a dialog.
304 *
305 * If a password is not found in the database, the user will be prompted
306 * with a dialog with a text field and ok/cancel buttons. If the user
307 * allows it, then the password will be saved in the database.
308 */
promptPassword
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
309 promptPassword : function (aDialogTitle, aText, aPasswordRealm,
310 aSavePassword, aPassword) {
311 this.log("===== promptPassword called() =====");
312
313 if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION)
314 throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
315
316 var checkBox = { value : false };
317 var checkBoxLabel = null;
318 var [hostname, realm, username] = this._getRealmInfo(aPasswordRealm);
319
320 // If hostname is null, we can't save this login.
321 if (hostname) {
322 var canRememberLogin = (aSavePassword ==
323 Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) &&
324 this._pwmgr.getLoginSavingEnabled(hostname);
325
326 // if checkBoxLabel is null, the checkbox won't be shown at all.
327 if (canRememberLogin)
328 checkBoxLabel = this._getLocalizedString("rememberPassword");
329
330 if (!aPassword.value) {
331 // Look for existing logins.
332 var foundLogins = this._pwmgr.findLogins({}, hostname, null,
333 realm);
334
335 // XXX Like the original code, we can't deal with multiple
336 // account selection (bug 227632). We can deal with finding the
337 // account based on the supplied username - but in this case we'll
338 // just return the first match.
339 for (var i = 0; i < foundLogins.length; ++i) {
340 if (foundLogins[i].username == username) {
341 aPassword.value = foundLogins[i].password;
342 // wallet returned straight away, so this mimics that code
343 return true;
344 }
345 }
346 }
347 }
348
349 var ok = this._promptService.promptPassword(this._window, aDialogTitle,
350 aText, aPassword,
351 checkBoxLabel, checkBox);
352
353 if (ok && checkBox.value && hostname) {
354 var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
355 createInstance(Ci.nsILoginInfo);
356 newLogin.init(hostname, null, realm, username,
357 aPassword.value, "", "");
358
359 this.log("New login seen for " + realm);
360
361 this._pwmgr.addLogin(newLogin);
362 }
363
364 return ok;
365 },
366
367 /* ---------- nsIAuthPrompt helpers ---------- */
368
369
370 /**
371 * Given aRealmString, such as "http://user@example.com/foo", returns an
372 * array of:
373 * - the formatted hostname
374 * - the realm (hostname + path)
375 * - the username, if present
376 *
377 * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S]
378 * channels, e.g. "example.com:80 (httprealm)", null is returned for all
379 * arguments to let callers know the login can't be saved because we don't
380 * know whether it's http or https.
381 */
_getRealmInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
382 _getRealmInfo : function (aRealmString) {
383 var httpRealm = /^.+ \(.+\)$/;
384 if (httpRealm.test(aRealmString))
385 return [null, null, null];
386
387 var uri = this._ioService.newURI(aRealmString, null, null);
388 var pathname = "";
389
390 if (uri.path != "/")
391 pathname = uri.path;
392
393 var formattedHostname = this._getFormattedHostname(uri);
394
395 return [formattedHostname, formattedHostname + pathname, uri.username];
396 },
397
398 /* ---------- nsIAuthPrompt2 prompts ---------- */
399
400
401
402
403 /*
404 * promptAuth
405 *
406 * Implementation of nsIAuthPrompt2.
407 *
408 * nsIChannel aChannel
409 * int aLevel
410 * nsIAuthInformation aAuthInfo
411 */
promptAuth
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
412 promptAuth : function (aChannel, aLevel, aAuthInfo) {
413 var selectedLogin = null;
414 var checkbox = { value : false };
415 var checkboxLabel = null;
416 var epicfail = false;
417
418 try {
419
420 this.log("===== promptAuth called =====");
421
422 // If the user submits a login but it fails, we need to remove the
423 // notification bar that was displayed. Conveniently, the user will
424 // be prompted for authentication again, which brings us here.
425 var notifyBox = this._getNotifyBox();
426 if (notifyBox)
427 this._removeSaveLoginNotification(notifyBox);
428
429 var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);
430
431
432 // Looks for existing logins to prefill the prompt with.
433 var foundLogins = this._pwmgr.findLogins({},
434 hostname, null, httpRealm);
435 this.log("found " + foundLogins.length + " matching logins.");
436
437 // XXX Can't select from multiple accounts yet. (bug 227632)
438 if (foundLogins.length > 0) {
439 selectedLogin = foundLogins[0];
440 this._SetAuthInfo(aAuthInfo, selectedLogin.username,
441 selectedLogin.password);
442 checkbox.value = true;
443 }
444
445 var canRememberLogin = this._pwmgr.getLoginSavingEnabled(hostname);
446
447 // if checkboxLabel is null, the checkbox won't be shown at all.
448 if (canRememberLogin && !notifyBox)
449 checkboxLabel = this._getLocalizedString("rememberPassword");
450 } catch (e) {
451 // Ignore any errors and display the prompt anyway.
452 epicfail = true;
453 Components.utils.reportError("LoginManagerPrompter: " +
454 "Epic fail in promptAuth: " + e + "\n");
455 }
456
457 var ok = this._promptService.promptAuth(this._window, aChannel,
458 aLevel, aAuthInfo, checkboxLabel, checkbox);
459
460 // If there's a notification box, use it to allow the user to
461 // determine if the login should be saved. If there isn't a
462 // notification box, only save the login if the user set the
463 // checkbox to do so.
464 var rememberLogin = notifyBox ? canRememberLogin : checkbox.value;
465 if (!ok || !rememberLogin || epicfail)
466 return ok;
467
468 try {
469 var [username, password] = this._GetAuthInfo(aAuthInfo);
470
471 var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
472 createInstance(Ci.nsILoginInfo);
473 newLogin.init(hostname, null, httpRealm,
474 username, password, "", "");
475
476 // XXX We can't prompt with multiple logins yet (bug 227632), so
477 // the entered login might correspond to an existing login
478 // other than the one we originally selected.
479 selectedLogin = this._repickSelectedLogin(foundLogins, username);
480
481 // If we didn't find an existing login, or if the username
482 // changed, save as a new login.
483 if (!selectedLogin) {
484 // add as new
485 this.log("New login seen for " + username +
486 " @ " + hostname + " (" + httpRealm + ")");
487 if (notifyBox)
488 this._showSaveLoginNotification(notifyBox, newLogin);
489 else
490 this._pwmgr.addLogin(newLogin);
491
492 } else if (password != selectedLogin.password) {
493
494 this.log("Updating password for " + username +
495 " @ " + hostname + " (" + httpRealm + ")");
496 // update password
497 this._pwmgr.modifyLogin(selectedLogin, newLogin);
498
499 } else {
500 this.log("Login unchanged, no further action needed.");
501 }
502 } catch (e) {
503 Components.utils.reportError("LoginManagerPrompter: " +
504 "Fail2 in promptAuth: " + e + "\n");
505 }
506
507 return ok;
508 },
509
asyncPromptAuth
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
510 asyncPromptAuth : function () {
511 return NS_ERROR_NOT_IMPLEMENTED;
512 },
513
514
515
516
517 /* ---------- nsILoginManagerPrompter prompts ---------- */
518
519
520
521
522 /*
523 * init
524 *
525 */
init
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
526 init : function (aWindow) {
527 this._window = aWindow;
528
529 var prefBranch = Cc["@mozilla.org/preferences-service;1"].
530 getService(Ci.nsIPrefService).getBranch("signon.");
531 this._debug = prefBranch.getBoolPref("debug");
532 this.log("===== initialized =====");
533 },
534
535
536 /*
537 * promptToSavePassword
538 *
539 */
promptToSavePassword
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
540 promptToSavePassword : function (aLogin) {
541 var notifyBox = this._getNotifyBox();
542
543 if (notifyBox)
544 this._showSaveLoginNotification(notifyBox, aLogin);
545 else
546 this._showSaveLoginDialog(aLogin);
547 },
548
549
550 /*
551 * _showLoginNotification
552 *
553 * Displays a notification bar.
554 *
555 */
_showLoginNotification
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
556 _showLoginNotification : function (aNotifyBox, aName, aText, aButtons) {
557 var oldBar = aNotifyBox.getNotificationWithValue(aName);
558 const priority = aNotifyBox.PRIORITY_INFO_MEDIUM;
559
560 this.log("Adding new " + aName + " notification bar");
561 var newBar = aNotifyBox.appendNotification(
562 aText, aName,
563 "chrome://mozapps/skin/passwordmgr/key.png",
564 priority, aButtons);
565
566 // The page we're going to hasn't loaded yet, so we want to persist
567 // across the first location change.
568 newBar.persistence++;
569
570 // Sites like Gmail perform a funky redirect dance before you end up
571 // at the post-authentication page. I don't see a good way to
572 // heuristically determine when to ignore such location changes, so
573 // we'll try ignoring location changes based on a time interval.
574 newBar.timeout = Date.now() + 20000; // 20 seconds
575
576 if (oldBar) {
577 this.log("(...and removing old " + aName + " notification bar)");
578 aNotifyBox.removeNotification(oldBar);
579 }
580 },
581
582
583 /*
584 * _showSaveLoginNotification
585 *
586 * Displays a notification bar (rather than a popup), to allow the user to
587 * save the specified login. This allows the user to see the results of
588 * their login, and only save a login which they know worked.
589 *
590 */
_showSaveLoginNotification
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
591 _showSaveLoginNotification : function (aNotifyBox, aLogin) {
592
593 // Ugh. We can't use the strings from the popup window, because they
594 // have the access key marked in the string (eg "Mo&zilla"), along
595 // with some weird rules for handling access keys that do not occur
596 // in the string, for L10N. See commonDialog.js's setLabelForNode().
597 var neverButtonText =
598 this._getLocalizedString("notifyBarNeverForSiteButtonText");
599 var neverButtonAccessKey =
600 this._getLocalizedString("notifyBarNeverForSiteButtonAccessKey");
601 var rememberButtonText =
602 this._getLocalizedString("notifyBarRememberButtonText");
603 var rememberButtonAccessKey =
604 this._getLocalizedString("notifyBarRememberButtonAccessKey");
605 var notNowButtonText =
606 this._getLocalizedString("notifyBarNotNowButtonText");
607 var notNowButtonAccessKey =
608 this._getLocalizedString("notifyBarNotNowButtonAccessKey");
609
610 var brandShortName =
611 this._brandBundle.GetStringFromName("brandShortName");
612 var notificationText = this._getLocalizedString(
613 "savePasswordText", [brandShortName]);
614
615 // The callbacks in |buttons| have a closure to access the variables
616 // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
617 // without a getService() call.
618 var pwmgr = this._pwmgr;
619
620
621 var buttons = [
622 // "Remember" button
623 {
624 label: rememberButtonText,
625 accessKey: rememberButtonAccessKey,
626 popup: null,
callback
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
627 callback: function(aNotificationBar, aButton) {
628 pwmgr.addLogin(aLogin);
629 }
630 },
631
632 // "Never for this site" button
633 {
634 label: neverButtonText,
635 accessKey: neverButtonAccessKey,
636 popup: null,
callback
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
637 callback: function(aNotificationBar, aButton) {
638 pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
639 }
640 },
641
642 // "Not now" button
643 {
644 label: notNowButtonText,
645 accessKey: notNowButtonAccessKey,
646 popup: null,
callback
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
647 callback: function() { /* NOP */ }
648 }
649 ];
650
651 this._showLoginNotification(aNotifyBox, "password-save",
652 notificationText, buttons);
653 },
654
655
656 /*
657 * _removeSaveLoginNotification
658 *
659 */
_removeSaveLoginNotification
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
660 _removeSaveLoginNotification : function (aNotifyBox) {
661
662 var oldBar = aNotifyBox.getNotificationWithValue("password-save");
663
664 if (oldBar) {
665 this.log("Removing save-password notification bar.");
666 aNotifyBox.removeNotification(oldBar);
667 }
668 },
669
670
671 /*
672 * _showSaveLoginDialog
673 *
674 * Called when we detect a new login in a form submission,
675 * asks the user what to do.
676 *
677 */
_showSaveLoginDialog
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
678 _showSaveLoginDialog : function (aLogin) {
679 const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT +
680 (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
681 (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) +
682 (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2);
683
684 var brandShortName =
685 this._brandBundle.GetStringFromName("brandShortName");
686
687 var dialogText = this._getLocalizedString(
688 "savePasswordText", [brandShortName]);
689 var dialogTitle = this._getLocalizedString(
690 "savePasswordTitle");
691 var neverButtonText = this._getLocalizedString(
692 "neverForSiteButtonText");
693 var rememberButtonText = this._getLocalizedString(
694 "rememberButtonText");
695 var notNowButtonText = this._getLocalizedString(
696 "notNowButtonText");
697
698 this.log("Prompting user to save/ignore login");
699 var userChoice = this._promptService.confirmEx(this._window,
700 dialogTitle, dialogText,
701 buttonFlags, rememberButtonText,
702 notNowButtonText, neverButtonText,
703 null, {});
704 // Returns:
705 // 0 - Save the login
706 // 1 - Ignore the login this time
707 // 2 - Never save logins for this site
708 if (userChoice == 2) {
709 this.log("Disabling " + aLogin.hostname + " logins by request.");
710 this._pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
711 } else if (userChoice == 0) {
712 this.log("Saving login for " + aLogin.hostname);
713 this._pwmgr.addLogin(aLogin);
714 } else {
715 // userChoice == 1 --> just ignore the login.
716 this.log("Ignoring login.");
717 }
718 },
719
720
721 /*
722 * promptToChangePassword
723 *
724 * Called when we think we detect a password change for an existing
725 * login, when the form being submitted contains multiple password
726 * fields.
727 *
728 */
promptToChangePassword
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
729 promptToChangePassword : function (aOldLogin, aNewLogin) {
730 var notifyBox = this._getNotifyBox();
731
732 if (notifyBox)
733 this._showChangeLoginNotification(notifyBox, aOldLogin, aNewLogin);
734 else
735 this._showChangeLoginDialog(aOldLogin, aNewLogin);
736 },
737
738
739 /*
740 * _showChangeLoginNotification
741 *
742 * Shows the Change Password notification bar.
743 *
744 */
_showChangeLoginNotification
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
745 _showChangeLoginNotification : function (aNotifyBox, aOldLogin, aNewLogin) {
746 var notificationText;
747 if (aOldLogin.username)
748 notificationText = this._getLocalizedString(
749 "passwordChangeText",
750 [aOldLogin.username]);
751 else
752 notificationText = this._getLocalizedString(
753 "passwordChangeTextNoUser");
754
755 var changeButtonText =
756 this._getLocalizedString("notifyBarChangeButtonText");
757 var changeButtonAccessKey =
758 this._getLocalizedString("notifyBarChangeButtonAccessKey");
759 var dontChangeButtonText =
760 this._getLocalizedString("notifyBarDontChangeButtonText");
761 var dontChangeButtonAccessKey =
762 this._getLocalizedString("notifyBarDontChangeButtonAccessKey");
763
764 // The callbacks in |buttons| have a closure to access the variables
765 // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
766 // without a getService() call.
767 var pwmgr = this._pwmgr;
768
769 var buttons = [
770 // "Yes" button
771 {
772 label: changeButtonText,
773 accessKey: changeButtonAccessKey,
774 popup: null,
callback
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
775 callback: function(aNotificationBar, aButton) {
776 pwmgr.modifyLogin(aOldLogin, aNewLogin);
777 }
778 },
779
780 // "No" button
781 {
782 label: dontChangeButtonText,
783 accessKey: dontChangeButtonAccessKey,
784 popup: null,
callback
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
785 callback: function(aNotificationBar, aButton) {
786 // do nothing
787 }
788 }
789 ];
790
791 this._showLoginNotification(aNotifyBox, "password-change",
792 notificationText, buttons);
793 },
794
795
796 /*
797 * _showChangeLoginDialog
798 *
799 * Shows the Change Password dialog.
800 *
801 */
_showChangeLoginDialog
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
802 _showChangeLoginDialog : function (aOldLogin, aNewLogin) {
803 const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
804
805 var dialogText;
806 if (aOldLogin.username)
807 dialogText = this._getLocalizedString(
808 "passwordChangeText",
809 [aOldLogin.username]);
810 else
811 dialogText = this._getLocalizedString(
812 "passwordChangeTextNoUser");
813
814 var dialogTitle = this._getLocalizedString(
815 "passwordChangeTitle");
816
817 // returns 0 for yes, 1 for no.
818 var ok = !this._promptService.confirmEx(this._window,
819 dialogTitle, dialogText, buttonFlags,
820 null, null, null,
821 null, {});
822 if (ok) {
823 this.log("Updating password for user " + aOldLogin.username);
824 this._pwmgr.modifyLogin(aOldLogin, aNewLogin);
825 }
826 },
827
828
829 /*
830 * promptToChangePasswordWithUsernames
831 *
832 * Called when we detect a password change in a form submission, but we
833 * don't know which existing login (username) it's for. Asks the user
834 * to select a username and confirm the password change.
835 *
836 * Note: The caller doesn't know the username for aNewLogin, so this
837 * function fills in .username and .usernameField with the values
838 * from the login selected by the user.
839 *
840 * Note; XPCOM stupidity: |count| is just |logins.length|.
841 */
promptToChangePasswordWithUsernames
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
842 promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) {
843 const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
844
anon:845:35
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
845 var usernames = logins.map(function (l) l.username);
846 var dialogText = this._getLocalizedString("userSelectText");
847 var dialogTitle = this._getLocalizedString("passwordChangeTitle");
848 var selectedIndex = { value: null };
849
850 // If user selects ok, outparam.value is set to the index
851 // of the selected username.
852 var ok = this._promptService.select(this._window,
853 dialogTitle, dialogText,
854 usernames.length, usernames,
855 selectedIndex);
856 if (ok) {
857 // Now that we know which login to change the password for,
858 // update the missing username info in the aNewLogin.
859
860 var selectedLogin = logins[selectedIndex.value];
861
862 this.log("Updating password for user " + selectedLogin.username);
863
864 aNewLogin.username = selectedLogin.username;
865 aNewLogin.usernameField = selectedLogin.usernameField;
866
867 this._pwmgr.modifyLogin(selectedLogin, aNewLogin);
868 }
869 },
870
871
872
873
874 /* ---------- Internal Methods ---------- */
875
876
877
878
879 /*
880 * _getNotifyBox
881 *
882 * Returns the notification box to this prompter, or null if there isn't
883 * a notification box available.
884 */
_getNotifyBox
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
885 _getNotifyBox : function () {
886 try {
887 // Get topmost window, in case we're in a frame.
888 var notifyWindow = this._window.top
889
890 // Some sites pop up a temporary login window, when disappears
891 // upon submission of credentials. We want to put the notification
892 // bar in the opener window if this seems to be happening.
893 if (notifyWindow.opener) {
894 var webnav = notifyWindow
895 .QueryInterface(Ci.nsIInterfaceRequestor)
896 .getInterface(Ci.nsIWebNavigation);
897 var chromeWin = webnav
898 .QueryInterface(Ci.nsIDocShellTreeItem)
899 .rootTreeItem
900 .QueryInterface(Ci.nsIInterfaceRequestor)
901 .getInterface(Ci.nsIDOMWindow);
902 var chromeDoc = chromeWin.document.documentElement;
903
904 // Check to see if the current window was opened with chrome
905 // disabled, and if so use the opener window. But if the window
906 // has been used to visit other pages (ie, has a history),
907 // assume it'll stick around and *don't* use the opener.
908 if (chromeDoc.getAttribute("chromehidden") &&
909 webnav.sessionHistory.count == 1) {
910 this.log("Using opener window for notification bar.");
911 notifyWindow = notifyWindow.opener;
912 }
913 }
914
915
916 // Find the <browser> which contains notifyWindow, by looking
917 // through all the open windows and all the <browsers> in each.
918 var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
919 getService(Ci.nsIWindowMediator);
920 var enumerator = wm.getEnumerator("navigator:browser");
921 var tabbrowser = null;
922 var foundBrowser = null;
923
924 while (!foundBrowser && enumerator.hasMoreElements()) {
925 var win = enumerator.getNext();
926 tabbrowser = win.getBrowser();
927 foundBrowser = tabbrowser.getBrowserForDocument(
928 notifyWindow.document);
929 }
930
931 // Return the notificationBox associated with the browser.
932 if (foundBrowser)
933 return tabbrowser.getNotificationBox(foundBrowser)
934
935 } catch (e) {
936 // If any errors happen, just assume no notification box.
937 this.log("No notification box available: " + e)
938 }
939
940 return null;
941 },
942
943
944 /*
945 * _repickSelectedLogin
946 *
947 * The user might enter a login that isn't the one we prefilled, but
948 * is the same as some other existing login. So, pick a login with a
949 * matching username, or return null.
950 */
_repickSelectedLogin
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
951 _repickSelectedLogin : function (foundLogins, username) {
952 for (var i = 0; i < foundLogins.length; i++)
953 if (foundLogins[i].username == username)
954 return foundLogins[i];
955 return null;
956 },
957
958
959 /*
960 * _getLocalizedString
961 *
962 * Can be called as:
963 * _getLocalizedString("key1");
964 * _getLocalizedString("key2", ["arg1"]);
965 * _getLocalizedString("key3", ["arg1", "arg2"]);
966 * (etc)
967 *
968 * Returns the localized string for the specified key,
969 * formatted if required.
970 *
971 */
_getLocalizedString
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
972 _getLocalizedString : function (key, formatArgs) {
973 if (formatArgs)
974 return this._strBundle.formatStringFromName(
975 key, formatArgs, formatArgs.length);
976 else
977 return this._strBundle.GetStringFromName(key);
978 },
979
980
981 /*
982 * _getFormattedHostname
983 *
984 * The aURI parameter may either be a string uri, or an nsIURI instance.
985 *
986 * Returns the hostname to use in a nsILoginInfo object (for example,
987 * "http://example.com").
988 */
_getFormattedHostname
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
989 _getFormattedHostname : function (aURI) {
990 var uri;
991 if (aURI instanceof Ci.nsIURI) {
992 uri = aURI;
993 } else {
994 uri = this._ioService.newURI(aURI, null, null);
995 }
996 var scheme = uri.scheme;
997
998 var hostname = scheme + "://" + uri.host;
999
1000 // If the URI explicitly specified a port, only include it when
1001 // it's not the default. (We never want "http://foo.com:80")
1002 port = uri.port;
1003 if (port != -1) {
1004 var handler = this._ioService.getProtocolHandler(scheme);
1005 if (port != handler.defaultPort)
1006 hostname += ":" + port;
1007 }
1008
1009 return hostname;
1010 },
1011
1012 /*
1013 * _getAuthTarget
1014 *
1015 * Returns the hostname and realm for which authentication is being
1016 * requested, in the format expected to be used with nsILoginInfo.
1017 */
_getAuthTarget
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1018 _getAuthTarget : function (aChannel, aAuthInfo) {
1019 var hostname, realm;
1020
1021 // If our proxy is demanding authentication, don't use the
1022 // channel's actual destination.
1023 if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
1024 this.log("getAuthTarget is for proxy auth");
1025 if (!(aChannel instanceof Ci.nsIProxiedChannel))
1026 throw "proxy auth needs nsIProxiedChannel";
1027
1028 var info = aChannel.proxyInfo;
1029 if (!info)
1030 throw "proxy auth needs nsIProxyInfo";
1031
1032 // Proxies don't have a scheme, but we'll use "moz-proxy://"
1033 // so that it's more obvious what the login is for.
1034 var idnService = Cc["@mozilla.org/network/idn-service;1"].
1035 getService(Ci.nsIIDNService);
1036 hostname = "moz-proxy://" +
1037 idnService.convertUTF8toACE(info.host) +
1038 ":" + info.port;
1039 realm = aAuthInfo.realm;
1040 if (!realm)
1041 realm = hostname;
1042
1043 return [hostname, realm];
1044 }
1045
1046 hostname = this._getFormattedHostname(aChannel.URI);
1047
1048 // If a HTTP WWW-Authenticate header specified a realm, that value
1049 // will be available here. If it wasn't set or wasn't HTTP, we'll use
1050 // the formatted hostname instead.
1051 realm = aAuthInfo.realm;
1052 if (!realm)
1053 realm = hostname;
1054
1055 return [hostname, realm];
1056 },
1057
1058
1059 /**
1060 * Returns [username, password] as extracted from aAuthInfo (which
1061 * holds this info after having prompted the user).
1062 *
1063 * If the authentication was for a Windows domain, we'll prepend the
1064 * return username with the domain. (eg, "domain\user")
1065 */
_GetAuthInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1066 _GetAuthInfo : function (aAuthInfo) {
1067 var username, password;
1068
1069 var flags = aAuthInfo.flags;
1070 if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain)
1071 username = aAuthInfo.domain + "\\" + aAuthInfo.username;
1072 else
1073 username = aAuthInfo.username;
1074
1075 password = aAuthInfo.password;
1076
1077 return [username, password];
1078 },
1079
1080
1081 /**
1082 * Given a username (possibly in DOMAIN\user form) and password, parses the
1083 * domain out of the username if necessary and sets domain, username and
1084 * password on the auth information object.
1085 */
_SetAuthInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1086 _SetAuthInfo : function (aAuthInfo, username, password) {
1087 var flags = aAuthInfo.flags;
1088 if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
1089 // Domain is separated from username by a backslash
1090 var idx = username.indexOf("\\");
1091 if (idx == -1) {
1092 aAuthInfo.username = username;
1093 } else {
1094 aAuthInfo.domain = username.substring(0, idx);
1095 aAuthInfo.username = username.substring(idx+1);
1096 }
1097 } else {
1098 aAuthInfo.username = username;
1099 }
1100 aAuthInfo.password = password;
1101 }
1102
1103 }; // end of LoginManagerPrompter implementation
1104
1105
1106 var component = [LoginManagerPromptFactory, LoginManagerPrompter];
NSGetModule
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1107 function NSGetModule(compMgr, fileSpec) {
1108 return XPCOMUtils.generateModule(component);
1109 }