!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
436 // XXX Can't select from multiple accounts yet. (bug 227632)
437 if (foundLogins.length > 0) {
438 selectedLogin = foundLogins[0];
439 this._SetAuthInfo(aAuthInfo, selectedLogin.username,
440 selectedLogin.password);
441 checkbox.value = true;
442 }
443
444 var canRememberLogin = this._pwmgr.getLoginSavingEnabled(hostname);
445
446 // if checkboxLabel is null, the checkbox won't be shown at all.
447 if (canRememberLogin && !notifyBox)
448 checkboxLabel = this._getLocalizedString("rememberPassword");
449 } catch (e) {
450 // Ignore any errors and display the prompt anyway.
451 epicfail = true;
452 Components.utils.reportError("LoginManagerPrompter: " +
453 "Epic fail in promptAuth: " + e + "\n");
454 }
455
456 var ok = this._promptService.promptAuth(this._window, aChannel,
457 aLevel, aAuthInfo, checkboxLabel, checkbox);
458
459 // If there's a notification box, use it to allow the user to
460 // determine if the login should be saved. If there isn't a
461 // notification box, only save the login if the user set the
462 // checkbox to do so.
463 var rememberLogin = notifyBox ? canRememberLogin : checkbox.value;
464 if (!ok || !rememberLogin || epicfail)
465 return ok;
466
467 try {
468 var [username, password] = this._GetAuthInfo(aAuthInfo);
469
470 var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"].
471 createInstance(Ci.nsILoginInfo);
472 newLogin.init(hostname, null, httpRealm,
473 username, password, "", "");
474
475 // XXX We can't prompt with multiple logins yet (bug 227632), so
476 // the entered login might correspond to an existing login
477 // other than the one we originally selected.
478 selectedLogin = this._repickSelectedLogin(foundLogins, username);
479
480 // If we didn't find an existing login, or if the username
481 // changed, save as a new login.
482 if (!selectedLogin) {
483 // add as new
484 this.log("New login seen for " + username +
485 " @ " + hostname + " (" + httpRealm + ")");
486 if (notifyBox)
487 this._showSaveLoginNotification(notifyBox, newLogin);
488 else
489 this._pwmgr.addLogin(newLogin);
490
491 } else if (password != selectedLogin.password) {
492
493 this.log("Updating password for " + username +
494 " @ " + hostname + " (" + httpRealm + ")");
495 // update password
496 this._pwmgr.modifyLogin(selectedLogin, newLogin);
497
498 } else {
499 this.log("Login unchanged, no further action needed.");
500 }
501 } catch (e) {
502 Components.utils.reportError("LoginManagerPrompter: " +
503 "Fail2 in promptAuth: " + e + "\n");
504 }
505
506 return ok;
507 },
508
asyncPromptAuth
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
509 asyncPromptAuth : function () {
510 return NS_ERROR_NOT_IMPLEMENTED;
511 },
512
513
514
515
516 /* ---------- nsILoginManagerPrompter prompts ---------- */
517
518
519
520
521 /*
522 * init
523 *
524 */
init
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
525 init : function (aWindow) {
526 this._window = aWindow;
527
528 var prefBranch = Cc["@mozilla.org/preferences-service;1"].
529 getService(Ci.nsIPrefService).getBranch("signon.");
530 this._debug = prefBranch.getBoolPref("debug");
531 this.log("===== initialized =====");
532 },
533
534
535 /*
536 * promptToSavePassword
537 *
538 */
promptToSavePassword
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
539 promptToSavePassword : function (aLogin) {
540 var notifyBox = this._getNotifyBox();
541
542 if (notifyBox)
543 this._showSaveLoginNotification(notifyBox, aLogin);
544 else
545 this._showSaveLoginDialog(aLogin);
546 },
547
548
549 /*
550 * _showLoginNotification
551 *
552 * Displays a notification bar.
553 *
554 */
_showLoginNotification
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
555 _showLoginNotification : function (aNotifyBox, aName, aText, aButtons) {
556 var oldBar = aNotifyBox.getNotificationWithValue(aName);
557 const priority = aNotifyBox.PRIORITY_INFO_MEDIUM;
558
559 this.log("Adding new " + aName + " notification bar");
560 var newBar = aNotifyBox.appendNotification(
561 aText, aName,
562 "chrome://mozapps/skin/passwordmgr/key.png",
563 priority, aButtons);
564
565 // The page we're going to hasn't loaded yet, so we want to persist
566 // across the first location change.
567 newBar.persistence++;
568
569 // Sites like Gmail perform a funky redirect dance before you end up
570 // at the post-authentication page. I don't see a good way to
571 // heuristically determine when to ignore such location changes, so
572 // we'll try ignoring location changes based on a time interval.
573 newBar.timeout = Date.now() + 20000; // 20 seconds
574
575 if (oldBar) {
576 this.log("(...and removing old " + aName + " notification bar)");
577 aNotifyBox.removeNotification(oldBar);
578 }
579 },
580
581
582 /*
583 * _showSaveLoginNotification
584 *
585 * Displays a notification bar (rather than a popup), to allow the user to
586 * save the specified login. This allows the user to see the results of
587 * their login, and only save a login which they know worked.
588 *
589 */
_showSaveLoginNotification
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
590 _showSaveLoginNotification : function (aNotifyBox, aLogin) {
591
592 // Ugh. We can't use the strings from the popup window, because they
593 // have the access key marked in the string (eg "Mo&zilla"), along
594 // with some weird rules for handling access keys that do not occur
595 // in the string, for L10N. See commonDialog.js's setLabelForNode().
596 var neverButtonText =
597 this._getLocalizedString("notifyBarNeverForSiteButtonText");
598 var neverButtonAccessKey =
599 this._getLocalizedString("notifyBarNeverForSiteButtonAccessKey");
600 var rememberButtonText =
601 this._getLocalizedString("notifyBarRememberButtonText");
602 var rememberButtonAccessKey =
603 this._getLocalizedString("notifyBarRememberButtonAccessKey");
604 var notNowButtonText =
605 this._getLocalizedString("notifyBarNotNowButtonText");
606 var notNowButtonAccessKey =
607 this._getLocalizedString("notifyBarNotNowButtonAccessKey");
608
609 var brandShortName =
610 this._brandBundle.GetStringFromName("brandShortName");
611 var notificationText = this._getLocalizedString(
612 "savePasswordText", [brandShortName]);
613
614 // The callbacks in |buttons| have a closure to access the variables
615 // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
616 // without a getService() call.
617 var pwmgr = this._pwmgr;
618
619
620 var buttons = [
621 // "Remember" button
622 {
623 label: rememberButtonText,
624 accessKey: rememberButtonAccessKey,
625 popup: null,
callback
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
626 callback: function(aNotificationBar, aButton) {
627 pwmgr.addLogin(aLogin);
628 }
629 },
630
631 // "Never for this site" button
632 {
633 label: neverButtonText,
634 accessKey: neverButtonAccessKey,
635 popup: null,
callback
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
636 callback: function(aNotificationBar, aButton) {
637 pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
638 }
639 },
640
641 // "Not now" button
642 {
643 label: notNowButtonText,
644 accessKey: notNowButtonAccessKey,
645 popup: null,
callback
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
646 callback: function() { /* NOP */ }
647 }
648 ];
649
650 this._showLoginNotification(aNotifyBox, "password-save",
651 notificationText, buttons);
652 },
653
654
655 /*
656 * _removeSaveLoginNotification
657 *
658 */
_removeSaveLoginNotification
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
659 _removeSaveLoginNotification : function (aNotifyBox) {
660
661 var oldBar = aNotifyBox.getNotificationWithValue("password-save");
662
663 if (oldBar) {
664 this.log("Removing save-password notification bar.");
665 aNotifyBox.removeNotification(oldBar);
666 }
667 },
668
669
670 /*
671 * _showSaveLoginDialog
672 *
673 * Called when we detect a new login in a form submission,
674 * asks the user what to do.
675 *
676 */
_showSaveLoginDialog
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
677 _showSaveLoginDialog : function (aLogin) {
678 const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT +
679 (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) +
680 (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) +
681 (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2);
682
683 var brandShortName =
684 this._brandBundle.GetStringFromName("brandShortName");
685
686 var dialogText = this._getLocalizedString(
687 "savePasswordText", [brandShortName]);
688 var dialogTitle = this._getLocalizedString(
689 "savePasswordTitle");
690 var neverButtonText = this._getLocalizedString(
691 "neverForSiteButtonText");
692 var rememberButtonText = this._getLocalizedString(
693 "rememberButtonText");
694 var notNowButtonText = this._getLocalizedString(
695 "notNowButtonText");
696
697 this.log("Prompting user to save/ignore login");
698 var userChoice = this._promptService.confirmEx(this._window,
699 dialogTitle, dialogText,
700 buttonFlags, rememberButtonText,
701 notNowButtonText, neverButtonText,
702 null, {});
703 // Returns:
704 // 0 - Save the login
705 // 1 - Ignore the login this time
706 // 2 - Never save logins for this site
707 if (userChoice == 2) {
708 this.log("Disabling " + aLogin.hostname + " logins by request.");
709 this._pwmgr.setLoginSavingEnabled(aLogin.hostname, false);
710 } else if (userChoice == 0) {
711 this.log("Saving login for " + aLogin.hostname);
712 this._pwmgr.addLogin(aLogin);
713 } else {
714 // userChoice == 1 --> just ignore the login.
715 this.log("Ignoring login.");
716 }
717 },
718
719
720 /*
721 * promptToChangePassword
722 *
723 * Called when we think we detect a password change for an existing
724 * login, when the form being submitted contains multiple password
725 * fields.
726 *
727 */
promptToChangePassword
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
728 promptToChangePassword : function (aOldLogin, aNewLogin) {
729 var notifyBox = this._getNotifyBox();
730
731 if (notifyBox)
732 this._showChangeLoginNotification(notifyBox, aOldLogin, aNewLogin);
733 else
734 this._showChangeLoginDialog(aOldLogin, aNewLogin);
735 },
736
737
738 /*
739 * _showChangeLoginNotification
740 *
741 * Shows the Change Password notification bar.
742 *
743 */
_showChangeLoginNotification
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
744 _showChangeLoginNotification : function (aNotifyBox, aOldLogin, aNewLogin) {
745 var notificationText;
746 if (aOldLogin.username)
747 notificationText = this._getLocalizedString(
748 "passwordChangeText",
749 [aOldLogin.username]);
750 else
751 notificationText = this._getLocalizedString(
752 "passwordChangeTextNoUser");
753
754 var changeButtonText =
755 this._getLocalizedString("notifyBarChangeButtonText");
756 var changeButtonAccessKey =
757 this._getLocalizedString("notifyBarChangeButtonAccessKey");
758 var dontChangeButtonText =
759 this._getLocalizedString("notifyBarDontChangeButtonText");
760 var dontChangeButtonAccessKey =
761 this._getLocalizedString("notifyBarDontChangeButtonAccessKey");
762
763 // The callbacks in |buttons| have a closure to access the variables
764 // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
765 // without a getService() call.
766 var pwmgr = this._pwmgr;
767
768 var buttons = [
769 // "Yes" button
770 {
771 label: changeButtonText,
772 accessKey: changeButtonAccessKey,
773 popup: null,
callback
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
774 callback: function(aNotificationBar, aButton) {
775 pwmgr.modifyLogin(aOldLogin, aNewLogin);
776 }
777 },
778
779 // "No" button
780 {
781 label: dontChangeButtonText,
782 accessKey: dontChangeButtonAccessKey,
783 popup: null,
callback
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
784 callback: function(aNotificationBar, aButton) {
785 // do nothing
786 }
787 }
788 ];
789
790 this._showLoginNotification(aNotifyBox, "password-change",
791 notificationText, buttons);
792 },
793
794
795 /*
796 * _showChangeLoginDialog
797 *
798 * Shows the Change Password dialog.
799 *
800 */
_showChangeLoginDialog
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
801 _showChangeLoginDialog : function (aOldLogin, aNewLogin) {
802 const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
803
804 var dialogText;
805 if (aOldLogin.username)
806 dialogText = this._getLocalizedString(
807 "passwordChangeText",
808 [aOldLogin.username]);
809 else
810 dialogText = this._getLocalizedString(
811 "passwordChangeTextNoUser");
812
813 var dialogTitle = this._getLocalizedString(
814 "passwordChangeTitle");
815
816 // returns 0 for yes, 1 for no.
817 var ok = !this._promptService.confirmEx(this._window,
818 dialogTitle, dialogText, buttonFlags,
819 null, null, null,
820 null, {});
821 if (ok) {
822 this.log("Updating password for user " + aOldLogin.username);
823 this._pwmgr.modifyLogin(aOldLogin, aNewLogin);
824 }
825 },
826
827
828 /*
829 * promptToChangePasswordWithUsernames
830 *
831 * Called when we detect a password change in a form submission, but we
832 * don't know which existing login (username) it's for. Asks the user
833 * to select a username and confirm the password change.
834 *
835 * Note: The caller doesn't know the username for aNewLogin, so this
836 * function fills in .username and .usernameField with the values
837 * from the login selected by the user.
838 *
839 * Note; XPCOM stupidity: |count| is just |logins.length|.
840 */
promptToChangePasswordWithUsernames
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
841 promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) {
842 const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS;
843
anon:844:35
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
844 var usernames = logins.map(function (l) l.username);
845 var dialogText = this._getLocalizedString("userSelectText");
846 var dialogTitle = this._getLocalizedString("passwordChangeTitle");
847 var selectedIndex = { value: null };
848
849 // If user selects ok, outparam.value is set to the index
850 // of the selected username.
851 var ok = this._promptService.select(this._window,
852 dialogTitle, dialogText,
853 usernames.length, usernames,
854 selectedIndex);
855 if (ok) {
856 // Now that we know which login to change the password for,
857 // update the missing username info in the aNewLogin.
858
859 var selectedLogin = logins[selectedIndex.value];
860
861 this.log("Updating password for user " + selectedLogin.username);
862
863 aNewLogin.username = selectedLogin.username;
864 aNewLogin.usernameField = selectedLogin.usernameField;
865
866 this._pwmgr.modifyLogin(selectedLogin, aNewLogin);
867 }
868 },
869
870
871
872
873 /* ---------- Internal Methods ---------- */
874
875
876
877
878 /*
879 * _getNotifyBox
880 *
881 * Returns the notification box to this prompter, or null if there isn't
882 * a notification box available.
883 */
_getNotifyBox
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
884 _getNotifyBox : function () {
885 try {
886 // Get topmost window, in case we're in a frame.
887 var notifyWindow = this._window.top
888
889 // Some sites pop up a temporary login window, when disappears
890 // upon submission of credentials. We want to put the notification
891 // bar in the opener window if this seems to be happening.
892 if (notifyWindow.opener) {
893 var webnav = notifyWindow
894 .QueryInterface(Ci.nsIInterfaceRequestor)
895 .getInterface(Ci.nsIWebNavigation);
896 var chromeWin = webnav
897 .QueryInterface(Ci.nsIDocShellTreeItem)
898 .rootTreeItem
899 .QueryInterface(Ci.nsIInterfaceRequestor)
900 .getInterface(Ci.nsIDOMWindow);
901 var chromeDoc = chromeWin.document.documentElement;
902
903 // Check to see if the current window was opened with chrome
904 // disabled, and if so use the opener window. But if the window
905 // has been used to visit other pages (ie, has a history),
906 // assume it'll stick around and *don't* use the opener.
907 if (chromeDoc.getAttribute("chromehidden") &&
908 webnav.sessionHistory.count == 1) {
909 this.log("Using opener window for notification bar.");
910 notifyWindow = notifyWindow.opener;
911 }
912 }
913
914
915 // Find the <browser> which contains notifyWindow, by looking
916 // through all the open windows and all the <browsers> in each.
917 var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
918 getService(Ci.nsIWindowMediator);
919 var enumerator = wm.getEnumerator("navigator:browser");
920 var tabbrowser = null;
921 var foundBrowser = null;
922
923 while (!foundBrowser && enumerator.hasMoreElements()) {
924 var win = enumerator.getNext();
925 tabbrowser = win.getBrowser();
926 foundBrowser = tabbrowser.getBrowserForDocument(
927 notifyWindow.document);
928 }
929
930 // Return the notificationBox associated with the browser.
931 if (foundBrowser)
932 return tabbrowser.getNotificationBox(foundBrowser)
933
934 } catch (e) {
935 // If any errors happen, just assume no notification box.
936 this.log("No notification box available: " + e)
937 }
938
939 return null;
940 },
941
942
943 /*
944 * _repickSelectedLogin
945 *
946 * The user might enter a login that isn't the one we prefilled, but
947 * is the same as some other existing login. So, pick a login with a
948 * matching username, or return null.
949 */
_repickSelectedLogin
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
950 _repickSelectedLogin : function (foundLogins, username) {
951 for (var i = 0; i < foundLogins.length; i++)
952 if (foundLogins[i].username == username)
953 return foundLogins[i];
954 return null;
955 },
956
957
958 /*
959 * _getLocalizedString
960 *
961 * Can be called as:
962 * _getLocalizedString("key1");
963 * _getLocalizedString("key2", ["arg1"]);
964 * _getLocalizedString("key3", ["arg1", "arg2"]);
965 * (etc)
966 *
967 * Returns the localized string for the specified key,
968 * formatted if required.
969 *
970 */
_getLocalizedString
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
971 _getLocalizedString : function (key, formatArgs) {
972 if (formatArgs)
973 return this._strBundle.formatStringFromName(
974 key, formatArgs, formatArgs.length);
975 else
976 return this._strBundle.GetStringFromName(key);
977 },
978
979
980 /*
981 * _getFormattedHostname
982 *
983 * The aURI parameter may either be a string uri, or an nsIURI instance.
984 *
985 * Returns the hostname to use in a nsILoginInfo object (for example,
986 * "http://example.com").
987 */
_getFormattedHostname
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
988 _getFormattedHostname : function (aURI) {
989 var uri;
990 if (aURI instanceof Ci.nsIURI) {
991 uri = aURI;
992 } else {
993 uri = this._ioService.newURI(aURI, null, null);
994 }
995 var scheme = uri.scheme;
996
997 var hostname = scheme + "://" + uri.host;
998
999 // If the URI explicitly specified a port, only include it when
1000 // it's not the default. (We never want "http://foo.com:80")
1001 port = uri.port;
1002 if (port != -1) {
1003 var handler = this._ioService.getProtocolHandler(scheme);
1004 if (port != handler.defaultPort)
1005 hostname += ":" + port;
1006 }
1007
1008 return hostname;
1009 },
1010
1011 /*
1012 * _getAuthTarget
1013 *
1014 * Returns the hostname and realm for which authentication is being
1015 * requested, in the format expected to be used with nsILoginInfo.
1016 */
_getAuthTarget
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1017 _getAuthTarget : function (aChannel, aAuthInfo) {
1018 var hostname, realm;
1019
1020 // If our proxy is demanding authentication, don't use the
1021 // channel's actual destination.
1022 if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) {
1023 this.log("getAuthTarget is for proxy auth");
1024 if (!(aChannel instanceof Ci.nsIProxiedChannel))
1025 throw "proxy auth needs nsIProxiedChannel";
1026
1027 var info = aChannel.proxyInfo;
1028 if (!info)
1029 throw "proxy auth needs nsIProxyInfo";
1030
1031 // Proxies don't have a scheme, but we'll use "moz-proxy://"
1032 // so that it's more obvious what the login is for.
1033 var idnService = Cc["@mozilla.org/network/idn-service;1"].
1034 getService(Ci.nsIIDNService);
1035 hostname = "moz-proxy://" +
1036 idnService.convertUTF8toACE(info.host) +
1037 ":" + info.port;
1038 realm = aAuthInfo.realm;
1039 if (!realm)
1040 realm = hostname;
1041
1042 return [hostname, realm];
1043 }
1044
1045 hostname = this._getFormattedHostname(aChannel.URI);
1046
1047 // If a HTTP WWW-Authenticate header specified a realm, that value
1048 // will be available here. If it wasn't set or wasn't HTTP, we'll use
1049 // the formatted hostname instead.
1050 realm = aAuthInfo.realm;
1051 if (!realm)
1052 realm = hostname;
1053
1054 return [hostname, realm];
1055 },
1056
1057
1058 /**
1059 * Returns [username, password] as extracted from aAuthInfo (which
1060 * holds this info after having prompted the user).
1061 *
1062 * If the authentication was for a Windows domain, we'll prepend the
1063 * return username with the domain. (eg, "domain\user")
1064 */
_GetAuthInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1065 _GetAuthInfo : function (aAuthInfo) {
1066 var username, password;
1067
1068 var flags = aAuthInfo.flags;
1069 if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain)
1070 username = aAuthInfo.domain + "\\" + aAuthInfo.username;
1071 else
1072 username = aAuthInfo.username;
1073
1074 password = aAuthInfo.password;
1075
1076 return [username, password];
1077 },
1078
1079
1080 /**
1081 * Given a username (possibly in DOMAIN\user form) and password, parses the
1082 * domain out of the username if necessary and sets domain, username and
1083 * password on the auth information object.
1084 */
_SetAuthInfo
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1085 _SetAuthInfo : function (aAuthInfo, username, password) {
1086 var flags = aAuthInfo.flags;
1087 if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) {
1088 // Domain is separated from username by a backslash
1089 var idx = username.indexOf("\\");
1090 if (idx == -1) {
1091 aAuthInfo.username = username;
1092 } else {
1093 aAuthInfo.domain = username.substring(0, idx);
1094 aAuthInfo.username = username.substring(idx+1);
1095 }
1096 } else {
1097 aAuthInfo.username = username;
1098 }
1099 aAuthInfo.password = password;
1100 }
1101
1102 }; // end of LoginManagerPrompter implementation
1103
1104
1105 var component = [LoginManagerPromptFactory, LoginManagerPrompter];
NSGetModule
(0 calls, 0 incl. v-uS, 0 excl. v-uS)
1106 function NSGetModule(compMgr, fileSpec) {
1107 return XPCOMUtils.generateModule(component);
1108 }