1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
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/
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
14 * The Original Code is mozilla.org code.
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.
21 * Justin Dolske <dolske@mozilla.com> (original author)
22 * Ehsan Akhgari <ehsan.akhgari@gmail.com>
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.
36 * ***** END LICENSE BLOCK ***** */
39 const Cc
= Components
.classes
;
40 const Ci
= Components
.interfaces
;
41 const Cr
= Components
.results
;
43 Components
.utils
.import("resource://gre/modules/XPCOMUtils.jsm");
46 * LoginManagerPromptFactory
48 * Implements nsIPromptFactory
50 * Invoked by NS_NewAuthPrompter2()
51 * [embedding/components/windowwatcher/src/nsPrompt.cpp]
53 function LoginManagerPromptFactory() {}
55 LoginManagerPromptFactory
.prototype = {
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
]),
62 getPrompt : function (aWindow
, aIID
) {
63 var prompt
= new LoginManagerPrompter().QueryInterface(aIID
);
67 }; // end of LoginManagerPromptFactory implementation
72 /* ==================== LoginManagerPrompter ==================== */
78 * LoginManagerPrompter
80 * Implements interfaces for prompting the user to enter/save/change auth info.
82 * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox.
84 * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication
85 * (eg HTTP Authenticate, FTP login).
87 * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins
88 * found in HTML forms.
90 function LoginManagerPrompter() {}
92 LoginManagerPrompter
.prototype = {
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
,
99 Ci
.nsILoginManagerPrompter
]),
102 _debug
: false, // mirrors signon.debug
104 __pwmgr
: null, // Password Manager service
107 this.__pwmgr
= Cc
["@mozilla.org/login-manager;1"].
108 getService(Ci
.nsILoginManager
);
112 __logService
: null, // Console logging service, used for debugging.
114 if (!this.__logService
)
115 this.__logService
= Cc
["@mozilla.org/consoleservice;1"].
116 getService(Ci
.nsIConsoleService
);
117 return this.__logService
;
120 __promptService
: null, // Prompt service for user interaction
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
;
130 __strBundle
: null, // String bundle for L10N
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!";
141 return this.__strBundle
;
145 __brandBundle
: null, // String bundle for L10N
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!";
156 return this.__brandBundle
;
160 __ioService
: null, // IO service for string -> nsIURI conversion
162 if (!this.__ioService
)
163 this.__ioService
= Cc
["@mozilla.org/network/io-service;1"].
164 getService(Ci
.nsIIOService
);
165 return this.__ioService
;
171 if (!this.__ellipsis
) {
172 this.__ellipsis
= "\u2026";
174 var prefSvc
= Cc
["@mozilla.org/preferences-service;1"].
175 getService(Ci
.nsIPrefBranch
);
176 this.__ellipsis
= prefSvc
.getComplexValue("intl.ellipsis",
177 Ci
.nsIPrefLocalizedString
).data
;
180 return this.__ellipsis
;
184 // Whether we are in private browsing mode
185 get _inPrivateBrowsing() {
186 // The Private Browsing service might not be available.
188 var pbs
= Cc
["@mozilla.org/privatebrowsing;1"].
189 getService(Ci
.nsIPrivateBrowsingService
);
190 return pbs
.privateBrowsingEnabled
;
200 * Internal function for logging debug messages to the Error Console window.
202 log : function (message
) {
206 dump("Pwmgr Prompter: " + message
+ "\n");
207 this._logService
.logStringMessage("Pwmgr Prompter: " + message
);
213 /* ---------- nsIAuthPrompt prompts ---------- */
219 * Wrapper around the prompt service prompt. Saving random fields here
220 * doesn't really make sense and therefore isn't implemented.
222 prompt : function (aDialogTitle
, aText
, aPasswordRealm
,
223 aSavePassword
, aDefaultText
, aResult
) {
224 if (aSavePassword
!= Ci
.nsIAuthPrompt
.SAVE_PASSWORD_NEVER
)
225 throw Components
.results
.NS_ERROR_NOT_IMPLEMENTED
;
227 this.log("===== prompt() called =====");
230 aResult
.value
= aDefaultText
;
233 return this._promptService
.prompt(this._window
,
234 aDialogTitle
, aText
, aResult
, null, {});
239 * promptUsernameAndPassword
241 * Looks up a username and password in the database. Will prompt the user
242 * with a dialog, even if a username and password are found.
244 promptUsernameAndPassword : function (aDialogTitle
, aText
, aPasswordRealm
,
245 aSavePassword
, aUsername
, aPassword
) {
246 this.log("===== promptUsernameAndPassword() called =====");
248 if (aSavePassword
== Ci
.nsIAuthPrompt
.SAVE_PASSWORD_FOR_SESSION
)
249 throw Components
.results
.NS_ERROR_NOT_IMPLEMENTED
;
251 var selectedLogin
= null;
252 var checkBox
= { value
: false };
253 var checkBoxLabel
= null;
254 var [hostname
, realm
, unused
] = this._getRealmInfo(aPasswordRealm
);
256 // If hostname is null, we can't save this login.
258 var canRememberLogin
;
259 if (this._inPrivateBrowsing
)
260 canRememberLogin
= false;
262 canRememberLogin
= (aSavePassword
==
263 Ci
.nsIAuthPrompt
.SAVE_PASSWORD_PERMANENTLY
) &&
264 this._pwmgr
.getLoginSavingEnabled(hostname
);
266 // if checkBoxLabel is null, the checkbox won't be shown at all.
267 if (canRememberLogin
)
268 checkBoxLabel
= this._getLocalizedString("rememberPassword");
270 // Look for existing logins.
271 var foundLogins
= this._pwmgr
.findLogins({}, hostname
, null,
274 // XXX Like the original code, we can't deal with multiple
275 // account selection. (bug 227632)
276 if (foundLogins
.length
> 0) {
277 selectedLogin
= foundLogins
[0];
279 // If the caller provided a username, try to use it. If they
280 // provided only a password, this will try to find a password-only
281 // login (or return null if none exists).
283 selectedLogin
= this._repickSelectedLogin(foundLogins
,
287 checkBox
.value
= true;
288 aUsername
.value
= selectedLogin
.username
;
289 // If the caller provided a password, prefer it.
290 if (!aPassword
.value
)
291 aPassword
.value
= selectedLogin
.password
;
296 var ok
= this._promptService
.promptUsernameAndPassword(this._window
,
297 aDialogTitle
, aText
, aUsername
, aPassword
,
298 checkBoxLabel
, checkBox
);
300 if (!ok
|| !checkBox
.value
|| !hostname
)
303 if (!aPassword
.value
) {
304 this.log("No password entered, so won't offer to save.");
308 var newLogin
= Cc
["@mozilla.org/login-manager/loginInfo;1"].
309 createInstance(Ci
.nsILoginInfo
);
310 newLogin
.init(hostname
, null, realm
, aUsername
.value
, aPassword
.value
,
313 // XXX We can't prompt with multiple logins yet (bug 227632), so
314 // the entered login might correspond to an existing login
315 // other than the one we originally selected.
316 selectedLogin
= this._repickSelectedLogin(foundLogins
, aUsername
.value
);
318 // If we didn't find an existing login, or if the username
319 // changed, save as a new login.
320 if (!selectedLogin
) {
322 this.log("New login seen for " + realm
);
323 this._pwmgr
.addLogin(newLogin
);
324 } else if (aPassword
.value
!= selectedLogin
.password
) {
326 this.log("Updating password for " + realm
);
327 this._pwmgr
.modifyLogin(selectedLogin
, newLogin
);
329 this.log("Login unchanged, no further action needed.");
339 * If a password is found in the database for the password realm, it is
340 * returned straight away without displaying a dialog.
342 * If a password is not found in the database, the user will be prompted
343 * with a dialog with a text field and ok/cancel buttons. If the user
344 * allows it, then the password will be saved in the database.
346 promptPassword : function (aDialogTitle
, aText
, aPasswordRealm
,
347 aSavePassword
, aPassword
) {
348 this.log("===== promptPassword called() =====");
350 if (aSavePassword
== Ci
.nsIAuthPrompt
.SAVE_PASSWORD_FOR_SESSION
)
351 throw Components
.results
.NS_ERROR_NOT_IMPLEMENTED
;
353 var checkBox
= { value
: false };
354 var checkBoxLabel
= null;
355 var [hostname
, realm
, username
] = this._getRealmInfo(aPasswordRealm
);
357 // If hostname is null, we can't save this login.
358 if (hostname
&& !this._inPrivateBrowsing
) {
359 var canRememberLogin
= (aSavePassword
==
360 Ci
.nsIAuthPrompt
.SAVE_PASSWORD_PERMANENTLY
) &&
361 this._pwmgr
.getLoginSavingEnabled(hostname
);
363 // if checkBoxLabel is null, the checkbox won't be shown at all.
364 if (canRememberLogin
)
365 checkBoxLabel
= this._getLocalizedString("rememberPassword");
367 if (!aPassword
.value
) {
368 // Look for existing logins.
369 var foundLogins
= this._pwmgr
.findLogins({}, hostname
, null,
372 // XXX Like the original code, we can't deal with multiple
373 // account selection (bug 227632). We can deal with finding the
374 // account based on the supplied username - but in this case we'll
375 // just return the first match.
376 for (var i
= 0; i
< foundLogins
.length
; ++i
) {
377 if (foundLogins
[i
].username
== username
) {
378 aPassword
.value
= foundLogins
[i
].password
;
379 // wallet returned straight away, so this mimics that code
386 var ok
= this._promptService
.promptPassword(this._window
, aDialogTitle
,
388 checkBoxLabel
, checkBox
);
390 if (ok
&& checkBox
.value
&& hostname
&& aPassword
.value
) {
391 var newLogin
= Cc
["@mozilla.org/login-manager/loginInfo;1"].
392 createInstance(Ci
.nsILoginInfo
);
393 newLogin
.init(hostname
, null, realm
, username
,
394 aPassword
.value
, "", "");
396 this.log("New login seen for " + realm
);
398 this._pwmgr
.addLogin(newLogin
);
404 /* ---------- nsIAuthPrompt helpers ---------- */
408 * Given aRealmString, such as "http://user@example.com/foo", returns an
410 * - the formatted hostname
411 * - the realm (hostname + path)
412 * - the username, if present
414 * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S]
415 * channels, e.g. "example.com:80 (httprealm)", null is returned for all
416 * arguments to let callers know the login can't be saved because we don't
417 * know whether it's http or https.
419 _getRealmInfo : function (aRealmString
) {
420 var httpRealm
= /^.+ \(.+\)$/;
421 if (httpRealm
.test(aRealmString
))
422 return [null, null, null];
424 var uri
= this._ioService
.newURI(aRealmString
, null, null);
430 var formattedHostname
= this._getFormattedHostname(uri
);
432 return [formattedHostname
, formattedHostname
+ pathname
, uri
.username
];
435 /* ---------- nsIAuthPrompt2 prompts ---------- */
443 * Implementation of nsIAuthPrompt2.
445 * nsIChannel aChannel
447 * nsIAuthInformation aAuthInfo
449 promptAuth : function (aChannel
, aLevel
, aAuthInfo
) {
450 var selectedLogin
= null;
451 var checkbox
= { value
: false };
452 var checkboxLabel
= null;
453 var epicfail
= false;
457 this.log("===== promptAuth called =====");
459 // If the user submits a login but it fails, we need to remove the
460 // notification bar that was displayed. Conveniently, the user will
461 // be prompted for authentication again, which brings us here.
462 var notifyBox
= this._getNotifyBox();
464 this._removeLoginNotifications(notifyBox
);
466 var [hostname
, httpRealm
] = this._getAuthTarget(aChannel
, aAuthInfo
);
469 // Looks for existing logins to prefill the prompt with.
470 var foundLogins
= this._pwmgr
.findLogins({},
471 hostname
, null, httpRealm
);
472 this.log("found " + foundLogins
.length
+ " matching logins.");
474 // XXX Can't select from multiple accounts yet. (bug 227632)
475 if (foundLogins
.length
> 0) {
476 selectedLogin
= foundLogins
[0];
477 this._SetAuthInfo(aAuthInfo
, selectedLogin
.username
,
478 selectedLogin
.password
);
479 checkbox
.value
= true;
482 var canRememberLogin
= this._pwmgr
.getLoginSavingEnabled(hostname
);
483 if (this._inPrivateBrowsing
)
484 canRememberLogin
= false;
486 // if checkboxLabel is null, the checkbox won't be shown at all.
487 if (canRememberLogin
&& !notifyBox
)
488 checkboxLabel
= this._getLocalizedString("rememberPassword");
490 // Ignore any errors and display the prompt anyway.
492 Components
.utils
.reportError("LoginManagerPrompter: " +
493 "Epic fail in promptAuth: " + e
+ "\n");
496 var ok
= this._promptService
.promptAuth(this._window
, aChannel
,
497 aLevel
, aAuthInfo
, checkboxLabel
, checkbox
);
499 // If there's a notification box, use it to allow the user to
500 // determine if the login should be saved. If there isn't a
501 // notification box, only save the login if the user set the
502 // checkbox to do so.
503 var rememberLogin
= notifyBox
? canRememberLogin
: checkbox
.value
;
504 if (!ok
|| !rememberLogin
|| epicfail
)
508 var [username
, password
] = this._GetAuthInfo(aAuthInfo
);
511 this.log("No password entered, so won't offer to save.");
515 var newLogin
= Cc
["@mozilla.org/login-manager/loginInfo;1"].
516 createInstance(Ci
.nsILoginInfo
);
517 newLogin
.init(hostname
, null, httpRealm
,
518 username
, password
, "", "");
520 // XXX We can't prompt with multiple logins yet (bug 227632), so
521 // the entered login might correspond to an existing login
522 // other than the one we originally selected.
523 selectedLogin
= this._repickSelectedLogin(foundLogins
, username
);
525 // If we didn't find an existing login, or if the username
526 // changed, save as a new login.
527 if (!selectedLogin
) {
529 this.log("New login seen for " + username
+
530 " @ " + hostname
+ " (" + httpRealm
+ ")");
532 this._showSaveLoginNotification(notifyBox
, newLogin
);
534 this._pwmgr
.addLogin(newLogin
);
536 } else if (password
!= selectedLogin
.password
) {
538 this.log("Updating password for " + username
+
539 " @ " + hostname
+ " (" + httpRealm
+ ")");
541 this._showChangeLoginNotification(notifyBox
,
542 selectedLogin
, newLogin
);
544 this._pwmgr
.modifyLogin(selectedLogin
, newLogin
);
547 this.log("Login unchanged, no further action needed.");
550 Components
.utils
.reportError("LoginManagerPrompter: " +
551 "Fail2 in promptAuth: " + e
+ "\n");
557 asyncPromptAuth : function () {
558 return NS_ERROR_NOT_IMPLEMENTED
;
564 /* ---------- nsILoginManagerPrompter prompts ---------- */
573 init : function (aWindow
) {
574 this._window
= aWindow
;
576 var prefBranch
= Cc
["@mozilla.org/preferences-service;1"].
577 getService(Ci
.nsIPrefService
).getBranch("signon.");
578 this._debug
= prefBranch
.getBoolPref("debug");
579 this.log("===== initialized =====");
584 * promptToSavePassword
587 promptToSavePassword : function (aLogin
) {
588 var notifyBox
= this._getNotifyBox();
591 this._showSaveLoginNotification(notifyBox
, aLogin
);
593 this._showSaveLoginDialog(aLogin
);
598 * _showLoginNotification
600 * Displays a notification bar.
603 _showLoginNotification : function (aNotifyBox
, aName
, aText
, aButtons
) {
604 var oldBar
= aNotifyBox
.getNotificationWithValue(aName
);
605 const priority
= aNotifyBox
.PRIORITY_INFO_MEDIUM
;
607 this.log("Adding new " + aName
+ " notification bar");
608 var newBar
= aNotifyBox
.appendNotification(
610 "chrome://mozapps/skin/passwordmgr/key.png",
613 // The page we're going to hasn't loaded yet, so we want to persist
614 // across the first location change.
615 newBar
.persistence
++;
617 // Sites like Gmail perform a funky redirect dance before you end up
618 // at the post-authentication page. I don't see a good way to
619 // heuristically determine when to ignore such location changes, so
620 // we'll try ignoring location changes based on a time interval.
621 newBar
.timeout
= Date
.now() + 20000; // 20 seconds
624 this.log("(...and removing old " + aName
+ " notification bar)");
625 aNotifyBox
.removeNotification(oldBar
);
631 * _showSaveLoginNotification
633 * Displays a notification bar (rather than a popup), to allow the user to
634 * save the specified login. This allows the user to see the results of
635 * their login, and only save a login which they know worked.
638 _showSaveLoginNotification : function (aNotifyBox
, aLogin
) {
640 // Ugh. We can't use the strings from the popup window, because they
641 // have the access key marked in the string (eg "Mo&zilla"), along
642 // with some weird rules for handling access keys that do not occur
643 // in the string, for L10N. See commonDialog.js's setLabelForNode().
644 var neverButtonText
=
645 this._getLocalizedString("notifyBarNeverForSiteButtonText");
646 var neverButtonAccessKey
=
647 this._getLocalizedString("notifyBarNeverForSiteButtonAccessKey");
648 var rememberButtonText
=
649 this._getLocalizedString("notifyBarRememberButtonText");
650 var rememberButtonAccessKey
=
651 this._getLocalizedString("notifyBarRememberButtonAccessKey");
652 var notNowButtonText
=
653 this._getLocalizedString("notifyBarNotNowButtonText");
654 var notNowButtonAccessKey
=
655 this._getLocalizedString("notifyBarNotNowButtonAccessKey");
658 this._brandBundle
.GetStringFromName("brandShortName");
659 var displayHost
= this._getShortDisplayHost(aLogin
.hostname
);
660 var notificationText
;
661 if (aLogin
.username
) {
662 var displayUser
= this._sanitizeUsername(aLogin
.username
);
663 notificationText
= this._getLocalizedString(
665 [brandShortName
, displayUser
, displayHost
]);
667 notificationText
= this._getLocalizedString(
668 "saveLoginTextNoUsername",
669 [brandShortName
, displayHost
]);
672 // The callbacks in |buttons| have a closure to access the variables
673 // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
674 // without a getService() call.
675 var pwmgr
= this._pwmgr
;
681 label
: rememberButtonText
,
682 accessKey
: rememberButtonAccessKey
,
684 callback: function(aNotificationBar
, aButton
) {
685 pwmgr
.addLogin(aLogin
);
689 // "Never for this site" button
691 label
: neverButtonText
,
692 accessKey
: neverButtonAccessKey
,
694 callback: function(aNotificationBar
, aButton
) {
695 pwmgr
.setLoginSavingEnabled(aLogin
.hostname
, false);
701 label
: notNowButtonText
,
702 accessKey
: notNowButtonAccessKey
,
704 callback: function() { /* NOP */ }
708 this._showLoginNotification(aNotifyBox
, "password-save",
709 notificationText
, buttons
);
714 * _removeLoginNotifications
717 _removeLoginNotifications : function (aNotifyBox
) {
718 var oldBar
= aNotifyBox
.getNotificationWithValue("password-save");
720 this.log("Removing save-password notification bar.");
721 aNotifyBox
.removeNotification(oldBar
);
724 oldBar
= aNotifyBox
.getNotificationWithValue("password-change");
726 this.log("Removing change-password notification bar.");
727 aNotifyBox
.removeNotification(oldBar
);
733 * _showSaveLoginDialog
735 * Called when we detect a new login in a form submission,
736 * asks the user what to do.
739 _showSaveLoginDialog : function (aLogin
) {
740 const buttonFlags
= Ci
.nsIPrompt
.BUTTON_POS_1_DEFAULT
+
741 (Ci
.nsIPrompt
.BUTTON_TITLE_IS_STRING
* Ci
.nsIPrompt
.BUTTON_POS_0
) +
742 (Ci
.nsIPrompt
.BUTTON_TITLE_IS_STRING
* Ci
.nsIPrompt
.BUTTON_POS_1
) +
743 (Ci
.nsIPrompt
.BUTTON_TITLE_IS_STRING
* Ci
.nsIPrompt
.BUTTON_POS_2
);
746 this._brandBundle
.GetStringFromName("brandShortName");
747 var displayHost
= this._getShortDisplayHost(aLogin
.hostname
);
750 if (aLogin
.username
) {
751 var displayUser
= this._sanitizeUsername(aLogin
.username
);
752 dialogText
= this._getLocalizedString(
754 [brandShortName
, displayUser
, displayHost
]);
756 dialogText
= this._getLocalizedString(
757 "saveLoginTextNoUsername",
758 [brandShortName
, displayHost
]);
760 var dialogTitle
= this._getLocalizedString(
761 "savePasswordTitle");
762 var neverButtonText
= this._getLocalizedString(
763 "neverForSiteButtonText");
764 var rememberButtonText
= this._getLocalizedString(
765 "rememberButtonText");
766 var notNowButtonText
= this._getLocalizedString(
769 this.log("Prompting user to save/ignore login");
770 var userChoice
= this._promptService
.confirmEx(this._window
,
771 dialogTitle
, dialogText
,
772 buttonFlags
, rememberButtonText
,
773 notNowButtonText
, neverButtonText
,
776 // 0 - Save the login
777 // 1 - Ignore the login this time
778 // 2 - Never save logins for this site
779 if (userChoice
== 2) {
780 this.log("Disabling " + aLogin
.hostname
+ " logins by request.");
781 this._pwmgr
.setLoginSavingEnabled(aLogin
.hostname
, false);
782 } else if (userChoice
== 0) {
783 this.log("Saving login for " + aLogin
.hostname
);
784 this._pwmgr
.addLogin(aLogin
);
786 // userChoice == 1 --> just ignore the login.
787 this.log("Ignoring login.");
793 * promptToChangePassword
795 * Called when we think we detect a password change for an existing
796 * login, when the form being submitted contains multiple password
800 promptToChangePassword : function (aOldLogin
, aNewLogin
) {
801 var notifyBox
= this._getNotifyBox();
804 this._showChangeLoginNotification(notifyBox
, aOldLogin
, aNewLogin
);
806 this._showChangeLoginDialog(aOldLogin
, aNewLogin
);
811 * _showChangeLoginNotification
813 * Shows the Change Password notification bar.
816 _showChangeLoginNotification : function (aNotifyBox
, aOldLogin
, aNewLogin
) {
817 var notificationText
;
818 if (aOldLogin
.username
)
819 notificationText
= this._getLocalizedString(
820 "passwordChangeText",
821 [aOldLogin
.username
]);
823 notificationText
= this._getLocalizedString(
824 "passwordChangeTextNoUser");
826 var changeButtonText
=
827 this._getLocalizedString("notifyBarChangeButtonText");
828 var changeButtonAccessKey
=
829 this._getLocalizedString("notifyBarChangeButtonAccessKey");
830 var dontChangeButtonText
=
831 this._getLocalizedString("notifyBarDontChangeButtonText");
832 var dontChangeButtonAccessKey
=
833 this._getLocalizedString("notifyBarDontChangeButtonAccessKey");
835 // The callbacks in |buttons| have a closure to access the variables
836 // in scope here; set one to |this._pwmgr| so we can get back to pwmgr
837 // without a getService() call.
838 var pwmgr
= this._pwmgr
;
843 label
: changeButtonText
,
844 accessKey
: changeButtonAccessKey
,
846 callback: function(aNotificationBar
, aButton
) {
847 pwmgr
.modifyLogin(aOldLogin
, aNewLogin
);
853 label
: dontChangeButtonText
,
854 accessKey
: dontChangeButtonAccessKey
,
856 callback: function(aNotificationBar
, aButton
) {
862 this._showLoginNotification(aNotifyBox
, "password-change",
863 notificationText
, buttons
);
868 * _showChangeLoginDialog
870 * Shows the Change Password dialog.
873 _showChangeLoginDialog : function (aOldLogin
, aNewLogin
) {
874 const buttonFlags
= Ci
.nsIPrompt
.STD_YES_NO_BUTTONS
;
877 if (aOldLogin
.username
)
878 dialogText
= this._getLocalizedString(
879 "passwordChangeText",
880 [aOldLogin
.username
]);
882 dialogText
= this._getLocalizedString(
883 "passwordChangeTextNoUser");
885 var dialogTitle
= this._getLocalizedString(
886 "passwordChangeTitle");
888 // returns 0 for yes, 1 for no.
889 var ok
= !this._promptService
.confirmEx(this._window
,
890 dialogTitle
, dialogText
, buttonFlags
,
894 this.log("Updating password for user " + aOldLogin
.username
);
895 this._pwmgr
.modifyLogin(aOldLogin
, aNewLogin
);
901 * promptToChangePasswordWithUsernames
903 * Called when we detect a password change in a form submission, but we
904 * don't know which existing login (username) it's for. Asks the user
905 * to select a username and confirm the password change.
907 * Note: The caller doesn't know the username for aNewLogin, so this
908 * function fills in .username and .usernameField with the values
909 * from the login selected by the user.
911 * Note; XPCOM stupidity: |count| is just |logins.length|.
913 promptToChangePasswordWithUsernames : function (logins
, count
, aNewLogin
) {
914 const buttonFlags
= Ci
.nsIPrompt
.STD_YES_NO_BUTTONS
;
916 var usernames
= logins
.map(function (l
) l
.username
);
917 var dialogText
= this._getLocalizedString("userSelectText");
918 var dialogTitle
= this._getLocalizedString("passwordChangeTitle");
919 var selectedIndex
= { value
: null };
921 // If user selects ok, outparam.value is set to the index
922 // of the selected username.
923 var ok
= this._promptService
.select(this._window
,
924 dialogTitle
, dialogText
,
925 usernames
.length
, usernames
,
928 // Now that we know which login to change the password for,
929 // update the missing username info in the aNewLogin.
931 var selectedLogin
= logins
[selectedIndex
.value
];
933 this.log("Updating password for user " + selectedLogin
.username
);
935 aNewLogin
.username
= selectedLogin
.username
;
936 aNewLogin
.usernameField
= selectedLogin
.usernameField
;
938 this._pwmgr
.modifyLogin(selectedLogin
, aNewLogin
);
945 /* ---------- Internal Methods ---------- */
953 * Returns the notification box to this prompter, or null if there isn't
954 * a notification box available.
956 _getNotifyBox : function () {
957 var notifyBox
= null;
959 // Given a content DOM window, returns the chrome window it's in.
960 function getChromeWindow(aWindow
) {
961 var chromeWin
= aWindow
962 .QueryInterface(Ci
.nsIInterfaceRequestor
)
963 .getInterface(Ci
.nsIWebNavigation
)
964 .QueryInterface(Ci
.nsIDocShellTreeItem
)
966 .QueryInterface(Ci
.nsIInterfaceRequestor
)
967 .getInterface(Ci
.nsIDOMWindow
)
968 .QueryInterface(Ci
.nsIDOMChromeWindow
);
973 // Get topmost window, in case we're in a frame.
974 var notifyWindow
= this._window
.top
976 // Some sites pop up a temporary login window, when disappears
977 // upon submission of credentials. We want to put the notification
978 // bar in the opener window if this seems to be happening.
979 if (notifyWindow
.opener
) {
980 var chromeDoc
= getChromeWindow(notifyWindow
)
981 .document
.documentElement
;
982 var webnav
= notifyWindow
983 .QueryInterface(Ci
.nsIInterfaceRequestor
)
984 .getInterface(Ci
.nsIWebNavigation
);
986 // Check to see if the current window was opened with chrome
987 // disabled, and if so use the opener window. But if the window
988 // has been used to visit other pages (ie, has a history),
989 // assume it'll stick around and *don't* use the opener.
990 if (chromeDoc
.getAttribute("chromehidden") &&
991 webnav
.sessionHistory
.count
== 1) {
992 this.log("Using opener window for notification bar.");
993 notifyWindow
= notifyWindow
.opener
;
998 // Get the chrome window for the content window we're using.
999 // .wrappedJSObject needed here -- see bug 422974 comment 5.
1000 var chromeWin
= getChromeWindow(notifyWindow
).wrappedJSObject
;
1002 if (chromeWin
.getNotificationBox
)
1003 notifyBox
= chromeWin
.getNotificationBox(notifyWindow
);
1005 this.log("getNotificationBox() not available on window");
1008 // If any errors happen, just assume no notification box.
1009 this.log("No notification box available: " + e
)
1017 * _repickSelectedLogin
1019 * The user might enter a login that isn't the one we prefilled, but
1020 * is the same as some other existing login. So, pick a login with a
1021 * matching username, or return null.
1023 _repickSelectedLogin : function (foundLogins
, username
) {
1024 for (var i
= 0; i
< foundLogins
.length
; i
++)
1025 if (foundLogins
[i
].username
== username
)
1026 return foundLogins
[i
];
1032 * _getLocalizedString
1035 * _getLocalizedString("key1");
1036 * _getLocalizedString("key2", ["arg1"]);
1037 * _getLocalizedString("key3", ["arg1", "arg2"]);
1040 * Returns the localized string for the specified key,
1041 * formatted if required.
1044 _getLocalizedString : function (key
, formatArgs
) {
1046 return this._strBundle
.formatStringFromName(
1047 key
, formatArgs
, formatArgs
.length
);
1049 return this._strBundle
.GetStringFromName(key
);
1056 * Sanitizes the specified username, by stripping quotes and truncating if
1057 * it's too long. This helps prevent an evil site from messing with the
1058 * "save password?" prompt too much.
1060 _sanitizeUsername : function (username
) {
1061 if (username
.length
> 30) {
1062 username
= username
.substring(0, 30);
1063 username
+= this._ellipsis
;
1065 return username
.replace(/['"]/g, "");
1070 * _getFormattedHostname
1072 * The aURI parameter may either be a string uri, or an nsIURI instance.
1074 * Returns the hostname to use in a nsILoginInfo object (for example,
1075 * "http://example.com").
1077 _getFormattedHostname : function (aURI
) {
1079 if (aURI
instanceof Ci
.nsIURI
) {
1082 uri
= this._ioService
.newURI(aURI
, null, null);
1084 var scheme
= uri
.scheme
;
1086 var hostname
= scheme
+ "://" + uri
.host
;
1088 // If the URI explicitly specified a port, only include it when
1089 // it's not the default. (We never want "http://foo.com:80")
1092 var handler
= this._ioService
.getProtocolHandler(scheme
);
1093 if (port
!= handler
.defaultPort
)
1094 hostname
+= ":" + port
;
1102 * _getShortDisplayHost
1104 * Converts a login's hostname field (a URL) to a short string for
1105 * prompting purposes. Eg, "http://foo.com" --> "foo.com", or
1106 * "ftp://www.site.co.uk" --> "site.co.uk".
1108 _getShortDisplayHost: function (aURIString
) {
1111 var eTLDService
= Cc
["@mozilla.org/network/effective-tld-service;1"].
1112 getService(Ci
.nsIEffectiveTLDService
);
1113 var idnService
= Cc
["@mozilla.org/network/idn-service;1"].
1114 getService(Ci
.nsIIDNService
);
1116 var uri
= this._ioService
.newURI(aURIString
, null, null);
1117 var baseDomain
= eTLDService
.getBaseDomain(uri
);
1118 displayHost
= idnService
.convertToDisplayIDN(baseDomain
, {});
1120 this.log("_getShortDisplayHost couldn't process " + aURIString
);
1124 displayHost
= aURIString
;
1133 * Returns the hostname and realm for which authentication is being
1134 * requested, in the format expected to be used with nsILoginInfo.
1136 _getAuthTarget : function (aChannel
, aAuthInfo
) {
1137 var hostname
, realm
;
1139 // If our proxy is demanding authentication, don't use the
1140 // channel's actual destination.
1141 if (aAuthInfo
.flags
& Ci
.nsIAuthInformation
.AUTH_PROXY
) {
1142 this.log("getAuthTarget is for proxy auth");
1143 if (!(aChannel
instanceof Ci
.nsIProxiedChannel
))
1144 throw "proxy auth needs nsIProxiedChannel";
1146 var info
= aChannel
.proxyInfo
;
1148 throw "proxy auth needs nsIProxyInfo";
1150 // Proxies don't have a scheme, but we'll use "moz-proxy://"
1151 // so that it's more obvious what the login is for.
1152 var idnService
= Cc
["@mozilla.org/network/idn-service;1"].
1153 getService(Ci
.nsIIDNService
);
1154 hostname
= "moz-proxy://" +
1155 idnService
.convertUTF8toACE(info
.host
) +
1157 realm
= aAuthInfo
.realm
;
1161 return [hostname
, realm
];
1164 hostname
= this._getFormattedHostname(aChannel
.URI
);
1166 // If a HTTP WWW-Authenticate header specified a realm, that value
1167 // will be available here. If it wasn't set or wasn't HTTP, we'll use
1168 // the formatted hostname instead.
1169 realm
= aAuthInfo
.realm
;
1173 return [hostname
, realm
];
1178 * Returns [username, password] as extracted from aAuthInfo (which
1179 * holds this info after having prompted the user).
1181 * If the authentication was for a Windows domain, we'll prepend the
1182 * return username with the domain. (eg, "domain\user")
1184 _GetAuthInfo : function (aAuthInfo
) {
1185 var username
, password
;
1187 var flags
= aAuthInfo
.flags
;
1188 if (flags
& Ci
.nsIAuthInformation
.NEED_DOMAIN
&& aAuthInfo
.domain
)
1189 username
= aAuthInfo
.domain
+ "\\" + aAuthInfo
.username
;
1191 username
= aAuthInfo
.username
;
1193 password
= aAuthInfo
.password
;
1195 return [username
, password
];
1200 * Given a username (possibly in DOMAIN\user form) and password, parses the
1201 * domain out of the username if necessary and sets domain, username and
1202 * password on the auth information object.
1204 _SetAuthInfo : function (aAuthInfo
, username
, password
) {
1205 var flags
= aAuthInfo
.flags
;
1206 if (flags
& Ci
.nsIAuthInformation
.NEED_DOMAIN
) {
1207 // Domain is separated from username by a backslash
1208 var idx
= username
.indexOf("\\");
1210 aAuthInfo
.username
= username
;
1212 aAuthInfo
.domain
= username
.substring(0, idx
);
1213 aAuthInfo
.username
= username
.substring(idx
+1);
1216 aAuthInfo
.username
= username
;
1218 aAuthInfo
.password
= password
;
1221 }; // end of LoginManagerPrompter implementation
1224 var component
= [LoginManagerPromptFactory
, LoginManagerPrompter
];
1225 function NSGetModule(compMgr
, fileSpec
) {
1226 return XPCOMUtils
.generateModule(component
);