| /* ***** BEGIN LICENSE BLOCK ***** |
| * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
| * |
| * The contents of this file are subject to the Mozilla Public License Version |
| * 1.1 (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * http://www.mozilla.org/MPL/ |
| * |
| * Software distributed under the License is distributed on an "AS IS" basis, |
| * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
| * for the specific language governing rights and limitations under the |
| * License. |
| * |
| * The Original Code is mozilla.org code. |
| * |
| * The Initial Developer of the Original Code is Mozilla Corporation. |
| * Portions created by the Initial Developer are Copyright (C) 2007 |
| * the Initial Developer. All Rights Reserved. |
| * |
| * Contributor(s): |
| * Justin Dolske <dolske@mozilla.com> (original author) |
| * Ehsan Akhgari <ehsan.akhgari@gmail.com> |
| * |
| * Alternatively, the contents of this file may be used under the terms of |
| * either the GNU General Public License Version 2 or later (the "GPL"), or |
| * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
| * in which case the provisions of the GPL or the LGPL are applicable instead |
| * of those above. If you wish to allow use of your version of this file only |
| * under the terms of either the GPL or the LGPL, and not to allow others to |
| * use your version of this file under the terms of the MPL, indicate your |
| * decision by deleting the provisions above and replace them with the notice |
| * and other provisions required by the GPL or the LGPL. If you do not delete |
| * the provisions above, a recipient may use your version of this file under |
| * the terms of any one of the MPL, the GPL or the LGPL. |
| * |
| * ***** END LICENSE BLOCK ***** */ |
| |
| |
| const Cc = Components.classes; |
| const Ci = Components.interfaces; |
| const Cr = Components.results; |
| |
| Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
| |
| /* |
| * LoginManagerPromptFactory |
| * |
| * Implements nsIPromptFactory |
| * |
| * Invoked by NS_NewAuthPrompter2() |
| * [embedding/components/windowwatcher/src/nsPrompt.cpp] |
| */ |
| function LoginManagerPromptFactory() {} |
| |
| LoginManagerPromptFactory.prototype = { |
| |
| classDescription : "LoginManagerPromptFactory", |
| contractID : "@mozilla.org/passwordmanager/authpromptfactory;1", |
| classID : Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"), |
| QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory]), |
| |
| getPrompt : function (aWindow, aIID) { |
| var prompt = new LoginManagerPrompter().QueryInterface(aIID); |
| prompt.init(aWindow); |
| return prompt; |
| } |
| }; // end of LoginManagerPromptFactory implementation |
| |
| |
| |
| |
| /* ==================== LoginManagerPrompter ==================== */ |
| |
| |
| |
| |
| /* |
| * LoginManagerPrompter |
| * |
| * Implements interfaces for prompting the user to enter/save/change auth info. |
| * |
| * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox. |
| * |
| * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication |
| * (eg HTTP Authenticate, FTP login). |
| * |
| * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins |
| * found in HTML forms. |
| */ |
| function LoginManagerPrompter() {} |
| |
| LoginManagerPrompter.prototype = { |
| |
| classDescription : "LoginManagerPrompter", |
| contractID : "@mozilla.org/login-manager/prompter;1", |
| classID : Components.ID("{8aa66d77-1bbb-45a6-991e-b8f47751c291}"), |
| QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPrompt, |
| Ci.nsIAuthPrompt2, |
| Ci.nsILoginManagerPrompter]), |
| |
| _window : null, |
| _debug : false, // mirrors signon.debug |
| |
| __pwmgr : null, // Password Manager service |
| get _pwmgr() { |
| if (!this.__pwmgr) |
| this.__pwmgr = Cc["@mozilla.org/login-manager;1"]. |
| getService(Ci.nsILoginManager); |
| return this.__pwmgr; |
| }, |
| |
| __logService : null, // Console logging service, used for debugging. |
| get _logService() { |
| if (!this.__logService) |
| this.__logService = Cc["@mozilla.org/consoleservice;1"]. |
| getService(Ci.nsIConsoleService); |
| return this.__logService; |
| }, |
| |
| __promptService : null, // Prompt service for user interaction |
| get _promptService() { |
| if (!this.__promptService) |
| this.__promptService = |
| Cc["@mozilla.org/embedcomp/prompt-service;1"]. |
| getService(Ci.nsIPromptService2); |
| return this.__promptService; |
| }, |
| |
| |
| __strBundle : null, // String bundle for L10N |
| get _strBundle() { |
| if (!this.__strBundle) { |
| var bunService = Cc["@mozilla.org/intl/stringbundle;1"]. |
| getService(Ci.nsIStringBundleService); |
| this.__strBundle = bunService.createBundle( |
| "chrome://passwordmgr/locale/passwordmgr.properties"); |
| if (!this.__strBundle) |
| throw "String bundle for Login Manager not present!"; |
| } |
| |
| return this.__strBundle; |
| }, |
| |
| |
| __brandBundle : null, // String bundle for L10N |
| get _brandBundle() { |
| if (!this.__brandBundle) { |
| var bunService = Cc["@mozilla.org/intl/stringbundle;1"]. |
| getService(Ci.nsIStringBundleService); |
| this.__brandBundle = bunService.createBundle( |
| "chrome://branding/locale/brand.properties"); |
| if (!this.__brandBundle) |
| throw "Branding string bundle not present!"; |
| } |
| |
| return this.__brandBundle; |
| }, |
| |
| |
| __ioService: null, // IO service for string -> nsIURI conversion |
| get _ioService() { |
| if (!this.__ioService) |
| this.__ioService = Cc["@mozilla.org/network/io-service;1"]. |
| getService(Ci.nsIIOService); |
| return this.__ioService; |
| }, |
| |
| |
| /* |
| * log |
| * |
| * Internal function for logging debug messages to the Error Console window. |
| */ |
| log : function (message) { |
| if (!this._debug) |
| return; |
| |
| dump("Pwmgr Prompter: " + message + "\n"); |
| this._logService.logStringMessage("Pwmgr Prompter: " + message); |
| }, |
| |
| |
| |
| |
| /* ---------- nsIAuthPrompt prompts ---------- */ |
| |
| |
| /* |
| * prompt |
| * |
| * Wrapper around the prompt service prompt. Saving random fields here |
| * doesn't really make sense and therefore isn't implemented. |
| */ |
| prompt : function (aDialogTitle, aText, aPasswordRealm, |
| aSavePassword, aDefaultText, aResult) { |
| if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER) |
| throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
| |
| this.log("===== prompt() called ====="); |
| |
| if (aDefaultText) { |
| aResult.value = aDefaultText; |
| } |
| |
| return this._promptService.prompt(this._window, |
| aDialogTitle, aText, aResult, null, {}); |
| }, |
| |
| |
| /* |
| * promptUsernameAndPassword |
| * |
| * Looks up a username and password in the database. Will prompt the user |
| * with a dialog, even if a username and password are found. |
| */ |
| promptUsernameAndPassword : function (aDialogTitle, aText, aPasswordRealm, |
| aSavePassword, aUsername, aPassword) { |
| this.log("===== promptUsernameAndPassword() called ====="); |
| |
| if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION) |
| throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
| |
| var selectedLogin = null; |
| var checkBox = { value : false }; |
| var checkBoxLabel = null; |
| var [hostname, realm, unused] = this._getRealmInfo(aPasswordRealm); |
| |
| // If hostname is null, we can't save this login. |
| if (hostname) { |
| var canRememberLogin = (aSavePassword == |
| Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) && |
| this._pwmgr.getLoginSavingEnabled(hostname); |
| |
| // if checkBoxLabel is null, the checkbox won't be shown at all. |
| if (canRememberLogin) |
| checkBoxLabel = this._getLocalizedString("rememberPassword"); |
| |
| // Look for existing logins. |
| var foundLogins = this._pwmgr.findLogins({}, hostname, null, |
| realm); |
| |
| // XXX Like the original code, we can't deal with multiple |
| // account selection. (bug 227632) |
| if (foundLogins.length > 0) { |
| selectedLogin = foundLogins[0]; |
| |
| // If the caller provided a username, try to use it. If they |
| // provided only a password, this will try to find a password-only |
| // login (or return null if none exists). |
| if (aUsername.value) |
| selectedLogin = this._repickSelectedLogin(foundLogins, |
| aUsername.value); |
| |
| if (selectedLogin) { |
| checkBox.value = true; |
| aUsername.value = selectedLogin.username; |
| // If the caller provided a password, prefer it. |
| if (!aPassword.value) |
| aPassword.value = selectedLogin.password; |
| } |
| } |
| } |
| |
| var ok = this._promptService.promptUsernameAndPassword(this._window, |
| aDialogTitle, aText, aUsername, aPassword, |
| checkBoxLabel, checkBox); |
| |
| if (!ok || !checkBox.value || !hostname) |
| return ok; |
| |
| var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. |
| createInstance(Ci.nsILoginInfo); |
| newLogin.init(hostname, null, realm, aUsername.value, aPassword.value, |
| "", ""); |
| |
| // XXX We can't prompt with multiple logins yet (bug 227632), so |
| // the entered login might correspond to an existing login |
| // other than the one we originally selected. |
| selectedLogin = this._repickSelectedLogin(foundLogins, aUsername.value); |
| |
| // If we didn't find an existing login, or if the username |
| // changed, save as a new login. |
| if (!selectedLogin) { |
| // add as new |
| this.log("New login seen for " + realm); |
| this._pwmgr.addLogin(newLogin); |
| } else if (aPassword.value != selectedLogin.password) { |
| // update password |
| this.log("Updating password for " + realm); |
| this._pwmgr.modifyLogin(selectedLogin, newLogin); |
| } else { |
| this.log("Login unchanged, no further action needed."); |
| } |
| |
| return ok; |
| }, |
| |
| |
| /* |
| * promptPassword |
| * |
| * If a password is found in the database for the password realm, it is |
| * returned straight away without displaying a dialog. |
| * |
| * If a password is not found in the database, the user will be prompted |
| * with a dialog with a text field and ok/cancel buttons. If the user |
| * allows it, then the password will be saved in the database. |
| */ |
| promptPassword : function (aDialogTitle, aText, aPasswordRealm, |
| aSavePassword, aPassword) { |
| this.log("===== promptPassword called() ====="); |
| |
| if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION) |
| throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
| |
| var checkBox = { value : false }; |
| var checkBoxLabel = null; |
| var [hostname, realm, username] = this._getRealmInfo(aPasswordRealm); |
| |
| // If hostname is null, we can't save this login. |
| if (hostname) { |
| var canRememberLogin = (aSavePassword == |
| Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) && |
| this._pwmgr.getLoginSavingEnabled(hostname); |
| |
| // if checkBoxLabel is null, the checkbox won't be shown at all. |
| if (canRememberLogin) |
| checkBoxLabel = this._getLocalizedString("rememberPassword"); |
| |
| if (!aPassword.value) { |
| // Look for existing logins. |
| var foundLogins = this._pwmgr.findLogins({}, hostname, null, |
| realm); |
| |
| // XXX Like the original code, we can't deal with multiple |
| // account selection (bug 227632). We can deal with finding the |
| // account based on the supplied username - but in this case we'll |
| // just return the first match. |
| for (var i = 0; i < foundLogins.length; ++i) { |
| if (foundLogins[i].username == username) { |
| aPassword.value = foundLogins[i].password; |
| // wallet returned straight away, so this mimics that code |
| return true; |
| } |
| } |
| } |
| } |
| |
| var ok = this._promptService.promptPassword(this._window, aDialogTitle, |
| aText, aPassword, |
| checkBoxLabel, checkBox); |
| |
| if (ok && checkBox.value && hostname) { |
| var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. |
| createInstance(Ci.nsILoginInfo); |
| newLogin.init(hostname, null, realm, username, |
| aPassword.value, "", ""); |
| |
| this.log("New login seen for " + realm); |
| |
| this._pwmgr.addLogin(newLogin); |
| } |
| |
| return ok; |
| }, |
| |
| /* ---------- nsIAuthPrompt helpers ---------- */ |
| |
| |
| /** |
| * Given aRealmString, such as "http://user@example.com/foo", returns an |
| * array of: |
| * - the formatted hostname |
| * - the realm (hostname + path) |
| * - the username, if present |
| * |
| * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S] |
| * channels, e.g. "example.com:80 (httprealm)", null is returned for all |
| * arguments to let callers know the login can't be saved because we don't |
| * know whether it's http or https. |
| */ |
| _getRealmInfo : function (aRealmString) { |
| var httpRealm = /^.+ \(.+\)$/; |
| if (httpRealm.test(aRealmString)) |
| return [null, null, null]; |
| |
| var uri = this._ioService.newURI(aRealmString, null, null); |
| var pathname = ""; |
| |
| if (uri.path != "/") |
| pathname = uri.path; |
| |
| var formattedHostname = this._getFormattedHostname(uri); |
| |
| return [formattedHostname, formattedHostname + pathname, uri.username]; |
| }, |
| |
| /* ---------- nsIAuthPrompt2 prompts ---------- */ |
| |
| |
| |
| |
| /* |
| * promptAuth |
| * |
| * Implementation of nsIAuthPrompt2. |
| * |
| * nsIChannel aChannel |
| * int aLevel |
| * nsIAuthInformation aAuthInfo |
| */ |
| promptAuth : function (aChannel, aLevel, aAuthInfo) { |
| var selectedLogin = null; |
| var checkbox = { value : false }; |
| var checkboxLabel = null; |
| var epicfail = false; |
| |
| try { |
| |
| this.log("===== promptAuth called ====="); |
| |
| // If the user submits a login but it fails, we need to remove the |
| // notification bar that was displayed. Conveniently, the user will |
| // be prompted for authentication again, which brings us here. |
| var notifyBox = this._getNotifyBox(); |
| if (notifyBox) |
| this._removeSaveLoginNotification(notifyBox); |
| |
| var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo); |
| |
| |
| // Looks for existing logins to prefill the prompt with. |
| var foundLogins = this._pwmgr.findLogins({}, |
| hostname, null, httpRealm); |
| this.log("found " + foundLogins.length + " matching logins."); |
| |
| // XXX Can't select from multiple accounts yet. (bug 227632) |
| if (foundLogins.length > 0) { |
| selectedLogin = foundLogins[0]; |
| this._SetAuthInfo(aAuthInfo, selectedLogin.username, |
| selectedLogin.password); |
| checkbox.value = true; |
| } |
| |
| var canRememberLogin = this._pwmgr.getLoginSavingEnabled(hostname); |
| |
| // if checkboxLabel is null, the checkbox won't be shown at all. |
| if (canRememberLogin && !notifyBox) |
| checkboxLabel = this._getLocalizedString("rememberPassword"); |
| } catch (e) { |
| // Ignore any errors and display the prompt anyway. |
| epicfail = true; |
| Components.utils.reportError("LoginManagerPrompter: " + |
| "Epic fail in promptAuth: " + e + "\n"); |
| } |
| |
| var ok = this._promptService.promptAuth(this._window, aChannel, |
| aLevel, aAuthInfo, checkboxLabel, checkbox); |
| |
| // If there's a notification box, use it to allow the user to |
| // determine if the login should be saved. If there isn't a |
| // notification box, only save the login if the user set the |
| // checkbox to do so. |
| var rememberLogin = notifyBox ? canRememberLogin : checkbox.value; |
| if (!ok || !rememberLogin || epicfail) |
| return ok; |
| |
| try { |
| var [username, password] = this._GetAuthInfo(aAuthInfo); |
| |
| var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. |
| createInstance(Ci.nsILoginInfo); |
| newLogin.init(hostname, null, httpRealm, |
| username, password, "", ""); |
| |
| // XXX We can't prompt with multiple logins yet (bug 227632), so |
| // the entered login might correspond to an existing login |
| // other than the one we originally selected. |
| selectedLogin = this._repickSelectedLogin(foundLogins, username); |
| |
| // If we didn't find an existing login, or if the username |
| // changed, save as a new login. |
| if (!selectedLogin) { |
| // add as new |
| this.log("New login seen for " + username + |
| " @ " + hostname + " (" + httpRealm + ")"); |
| if (notifyBox) |
| this._showSaveLoginNotification(notifyBox, newLogin); |
| else |
| this._pwmgr.addLogin(newLogin); |
| |
| } else if (password != selectedLogin.password) { |
| |
| this.log("Updating password for " + username + |
| " @ " + hostname + " (" + httpRealm + ")"); |
| // update password |
| this._pwmgr.modifyLogin(selectedLogin, newLogin); |
| |
| } else { |
| this.log("Login unchanged, no further action needed."); |
| } |
| } catch (e) { |
| Components.utils.reportError("LoginManagerPrompter: " + |
| "Fail2 in promptAuth: " + e + "\n"); |
| } |
| |
| return ok; |
| }, |
| |
| asyncPromptAuth : function () { |
| return NS_ERROR_NOT_IMPLEMENTED; |
| }, |
| |
| |
| |
| |
| /* ---------- nsILoginManagerPrompter prompts ---------- */ |
| |
| |
| |
| |
| /* |
| * init |
| * |
| */ |
| init : function (aWindow) { |
| this._window = aWindow; |
| |
| var prefBranch = Cc["@mozilla.org/preferences-service;1"]. |
| getService(Ci.nsIPrefService).getBranch("signon."); |
| this._debug = prefBranch.getBoolPref("debug"); |
| this.log("===== initialized ====="); |
| }, |
| |
| |
| /* |
| * promptToSavePassword |
| * |
| */ |
| promptToSavePassword : function (aLogin) { |
| var notifyBox = this._getNotifyBox(); |
| |
| if (notifyBox) |
| this._showSaveLoginNotification(notifyBox, aLogin); |
| else |
| this._showSaveLoginDialog(aLogin); |
| }, |
| |
| |
| /* |
| * _showLoginNotification |
| * |
| * Displays a notification bar. |
| * |
| */ |
| _showLoginNotification : function (aNotifyBox, aName, aText, aButtons) { |
| var oldBar = aNotifyBox.getNotificationWithValue(aName); |
| const priority = aNotifyBox.PRIORITY_INFO_MEDIUM; |
| |
| this.log("Adding new " + aName + " notification bar"); |
| var newBar = aNotifyBox.appendNotification( |
| aText, aName, |
| "chrome://mozapps/skin/passwordmgr/key.png", |
| priority, aButtons); |
| |
| // The page we're going to hasn't loaded yet, so we want to persist |
| // across the first location change. |
| newBar.persistence++; |
| |
| // Sites like Gmail perform a funky redirect dance before you end up |
| // at the post-authentication page. I don't see a good way to |
| // heuristically determine when to ignore such location changes, so |
| // we'll try ignoring location changes based on a time interval. |
| newBar.timeout = Date.now() + 20000; // 20 seconds |
| |
| if (oldBar) { |
| this.log("(...and removing old " + aName + " notification bar)"); |
| aNotifyBox.removeNotification(oldBar); |
| } |
| }, |
| |
| |
| /* |
| * _showSaveLoginNotification |
| * |
| * Displays a notification bar (rather than a popup), to allow the user to |
| * save the specified login. This allows the user to see the results of |
| * their login, and only save a login which they know worked. |
| * |
| */ |
| _showSaveLoginNotification : function (aNotifyBox, aLogin) { |
| |
| // Ugh. We can't use the strings from the popup window, because they |
| // have the access key marked in the string (eg "Mo&zilla"), along |
| // with some weird rules for handling access keys that do not occur |
| // in the string, for L10N. See commonDialog.js's setLabelForNode(). |
| var neverButtonText = |
| this._getLocalizedString("notifyBarNeverForSiteButtonText"); |
| var neverButtonAccessKey = |
| this._getLocalizedString("notifyBarNeverForSiteButtonAccessKey"); |
| var rememberButtonText = |
| this._getLocalizedString("notifyBarRememberButtonText"); |
| var rememberButtonAccessKey = |
| this._getLocalizedString("notifyBarRememberButtonAccessKey"); |
| var notNowButtonText = |
| this._getLocalizedString("notifyBarNotNowButtonText"); |
| var notNowButtonAccessKey = |
| this._getLocalizedString("notifyBarNotNowButtonAccessKey"); |
| |
| var brandShortName = |
| this._brandBundle.GetStringFromName("brandShortName"); |
| var notificationText = this._getLocalizedString( |
| "savePasswordText", [brandShortName]); |
| |
| // The callbacks in |buttons| have a closure to access the variables |
| // in scope here; set one to |this._pwmgr| so we can get back to pwmgr |
| // without a getService() call. |
| var pwmgr = this._pwmgr; |
| |
| |
| var buttons = [ |
| // "Remember" button |
| { |
| label: rememberButtonText, |
| accessKey: rememberButtonAccessKey, |
| popup: null, |
| callback: function(aNotificationBar, aButton) { |
| pwmgr.addLogin(aLogin); |
| } |
| }, |
| |
| // "Never for this site" button |
| { |
| label: neverButtonText, |
| accessKey: neverButtonAccessKey, |
| popup: null, |
| callback: function(aNotificationBar, aButton) { |
| pwmgr.setLoginSavingEnabled(aLogin.hostname, false); |
| } |
| }, |
| |
| // "Not now" button |
| { |
| label: notNowButtonText, |
| accessKey: notNowButtonAccessKey, |
| popup: null, |
| callback: function() { /* NOP */ } |
| } |
| ]; |
| |
| this._showLoginNotification(aNotifyBox, "password-save", |
| notificationText, buttons); |
| }, |
| |
| |
| /* |
| * _removeSaveLoginNotification |
| * |
| */ |
| _removeSaveLoginNotification : function (aNotifyBox) { |
| |
| var oldBar = aNotifyBox.getNotificationWithValue("password-save"); |
| |
| if (oldBar) { |
| this.log("Removing save-password notification bar."); |
| aNotifyBox.removeNotification(oldBar); |
| } |
| }, |
| |
| |
| /* |
| * _showSaveLoginDialog |
| * |
| * Called when we detect a new login in a form submission, |
| * asks the user what to do. |
| * |
| */ |
| _showSaveLoginDialog : function (aLogin) { |
| const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT + |
| (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) + |
| (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) + |
| (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2); |
| |
| var brandShortName = |
| this._brandBundle.GetStringFromName("brandShortName"); |
| |
| var dialogText = this._getLocalizedString( |
| "savePasswordText", [brandShortName]); |
| var dialogTitle = this._getLocalizedString( |
| "savePasswordTitle"); |
| var neverButtonText = this._getLocalizedString( |
| "neverForSiteButtonText"); |
| var rememberButtonText = this._getLocalizedString( |
| "rememberButtonText"); |
| var notNowButtonText = this._getLocalizedString( |
| "notNowButtonText"); |
| |
| this.log("Prompting user to save/ignore login"); |
| var userChoice = this._promptService.confirmEx(this._window, |
| dialogTitle, dialogText, |
| buttonFlags, rememberButtonText, |
| notNowButtonText, neverButtonText, |
| null, {}); |
| // Returns: |
| // 0 - Save the login |
| // 1 - Ignore the login this time |
| // 2 - Never save logins for this site |
| if (userChoice == 2) { |
| this.log("Disabling " + aLogin.hostname + " logins by request."); |
| this._pwmgr.setLoginSavingEnabled(aLogin.hostname, false); |
| } else if (userChoice == 0) { |
| this.log("Saving login for " + aLogin.hostname); |
| this._pwmgr.addLogin(aLogin); |
| } else { |
| // userChoice == 1 --> just ignore the login. |
| this.log("Ignoring login."); |
| } |
| }, |
| |
| |
| /* |
| * promptToChangePassword |
| * |
| * Called when we think we detect a password change for an existing |
| * login, when the form being submitted contains multiple password |
| * fields. |
| * |
| */ |
| promptToChangePassword : function (aOldLogin, aNewLogin) { |
| var notifyBox = this._getNotifyBox(); |
| |
| if (notifyBox) |
| this._showChangeLoginNotification(notifyBox, aOldLogin, aNewLogin); |
| else |
| this._showChangeLoginDialog(aOldLogin, aNewLogin); |
| }, |
| |
| |
| /* |
| * _showChangeLoginNotification |
| * |
| * Shows the Change Password notification bar. |
| * |
| */ |
| _showChangeLoginNotification : function (aNotifyBox, aOldLogin, aNewLogin) { |
| var notificationText; |
| if (aOldLogin.username) |
| notificationText = this._getLocalizedString( |
| "passwordChangeText", |
| [aOldLogin.username]); |
| else |
| notificationText = this._getLocalizedString( |
| "passwordChangeTextNoUser"); |
| |
| var changeButtonText = |
| this._getLocalizedString("notifyBarChangeButtonText"); |
| var changeButtonAccessKey = |
| this._getLocalizedString("notifyBarChangeButtonAccessKey"); |
| var dontChangeButtonText = |
| this._getLocalizedString("notifyBarDontChangeButtonText"); |
| var dontChangeButtonAccessKey = |
| this._getLocalizedString("notifyBarDontChangeButtonAccessKey"); |
| |
| // The callbacks in |buttons| have a closure to access the variables |
| // in scope here; set one to |this._pwmgr| so we can get back to pwmgr |
| // without a getService() call. |
| var pwmgr = this._pwmgr; |
| |
| var buttons = [ |
| // "Yes" button |
| { |
| label: changeButtonText, |
| accessKey: changeButtonAccessKey, |
| popup: null, |
| callback: function(aNotificationBar, aButton) { |
| pwmgr.modifyLogin(aOldLogin, aNewLogin); |
| } |
| }, |
| |
| // "No" button |
| { |
| label: dontChangeButtonText, |
| accessKey: dontChangeButtonAccessKey, |
| popup: null, |
| callback: function(aNotificationBar, aButton) { |
| // do nothing |
| } |
| } |
| ]; |
| |
| this._showLoginNotification(aNotifyBox, "password-change", |
| notificationText, buttons); |
| }, |
| |
| |
| /* |
| * _showChangeLoginDialog |
| * |
| * Shows the Change Password dialog. |
| * |
| */ |
| _showChangeLoginDialog : function (aOldLogin, aNewLogin) { |
| const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS; |
| |
| var dialogText; |
| if (aOldLogin.username) |
| dialogText = this._getLocalizedString( |
| "passwordChangeText", |
| [aOldLogin.username]); |
| else |
| dialogText = this._getLocalizedString( |
| "passwordChangeTextNoUser"); |
| |
| var dialogTitle = this._getLocalizedString( |
| "passwordChangeTitle"); |
| |
| // returns 0 for yes, 1 for no. |
| var ok = !this._promptService.confirmEx(this._window, |
| dialogTitle, dialogText, buttonFlags, |
| null, null, null, |
| null, {}); |
| if (ok) { |
| this.log("Updating password for user " + aOldLogin.username); |
| this._pwmgr.modifyLogin(aOldLogin, aNewLogin); |
| } |
| }, |
| |
| |
| /* |
| * promptToChangePasswordWithUsernames |
| * |
| * Called when we detect a password change in a form submission, but we |
| * don't know which existing login (username) it's for. Asks the user |
| * to select a username and confirm the password change. |
| * |
| * Note: The caller doesn't know the username for aNewLogin, so this |
| * function fills in .username and .usernameField with the values |
| * from the login selected by the user. |
| * |
| * Note; XPCOM stupidity: |count| is just |logins.length|. |
| */ |
| promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) { |
| const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS; |
| |
| var usernames = logins.map(function (l) l.username); |
| var dialogText = this._getLocalizedString("userSelectText"); |
| var dialogTitle = this._getLocalizedString("passwordChangeTitle"); |
| var selectedIndex = { value: null }; |
| |
| // If user selects ok, outparam.value is set to the index |
| // of the selected username. |
| var ok = this._promptService.select(this._window, |
| dialogTitle, dialogText, |
| usernames.length, usernames, |
| selectedIndex); |
| if (ok) { |
| // Now that we know which login to change the password for, |
| // update the missing username info in the aNewLogin. |
| |
| var selectedLogin = logins[selectedIndex.value]; |
| |
| this.log("Updating password for user " + selectedLogin.username); |
| |
| aNewLogin.username = selectedLogin.username; |
| aNewLogin.usernameField = selectedLogin.usernameField; |
| |
| this._pwmgr.modifyLogin(selectedLogin, aNewLogin); |
| } |
| }, |
| |
| |
| |
| |
| /* ---------- Internal Methods ---------- */ |
| |
| |
| |
| |
| /* |
| * _getNotifyBox |
| * |
| * Returns the notification box to this prompter, or null if there isn't |
| * a notification box available. |
| */ |
| _getNotifyBox : function () { |
| try { |
| // Get topmost window, in case we're in a frame. |
| var notifyWindow = this._window.top |
| |
| // Some sites pop up a temporary login window, when disappears |
| // upon submission of credentials. We want to put the notification |
| // bar in the opener window if this seems to be happening. |
| if (notifyWindow.opener) { |
| var webnav = notifyWindow |
| .QueryInterface(Ci.nsIInterfaceRequestor) |
| .getInterface(Ci.nsIWebNavigation); |
| var chromeWin = webnav |
| .QueryInterface(Ci.nsIDocShellTreeItem) |
| .rootTreeItem |
| .QueryInterface(Ci.nsIInterfaceRequestor) |
| .getInterface(Ci.nsIDOMWindow); |
| var chromeDoc = chromeWin.document.documentElement; |
| |
| // Check to see if the current window was opened with chrome |
| // disabled, and if so use the opener window. But if the window |
| // has been used to visit other pages (ie, has a history), |
| // assume it'll stick around and *don't* use the opener. |
| if (chromeDoc.getAttribute("chromehidden") && |
| webnav.sessionHistory.count == 1) { |
| this.log("Using opener window for notification bar."); |
| notifyWindow = notifyWindow.opener; |
| } |
| } |
| |
| |
| // Find the <browser> which contains notifyWindow, by looking |
| // through all the open windows and all the <browsers> in each. |
| var wm = Cc["@mozilla.org/appshell/window-mediator;1"]. |
| getService(Ci.nsIWindowMediator); |
| var enumerator = wm.getEnumerator("navigator:browser"); |
| var tabbrowser = null; |
| var foundBrowser = null; |
| |
| while (!foundBrowser && enumerator.hasMoreElements()) { |
| var win = enumerator.getNext(); |
| tabbrowser = win.getBrowser(); |
| foundBrowser = tabbrowser.getBrowserForDocument( |
| notifyWindow.document); |
| } |
| |
| // Return the notificationBox associated with the browser. |
| if (foundBrowser) |
| return tabbrowser.getNotificationBox(foundBrowser) |
| |
| } catch (e) { |
| // If any errors happen, just assume no notification box. |
| this.log("No notification box available: " + e) |
| } |
| |
| return null; |
| }, |
| |
| |
| /* |
| * _repickSelectedLogin |
| * |
| * The user might enter a login that isn't the one we prefilled, but |
| * is the same as some other existing login. So, pick a login with a |
| * matching username, or return null. |
| */ |
| _repickSelectedLogin : function (foundLogins, username) { |
| for (var i = 0; i < foundLogins.length; i++) |
| if (foundLogins[i].username == username) |
| return foundLogins[i]; |
| return null; |
| }, |
| |
| |
| /* |
| * _getLocalizedString |
| * |
| * Can be called as: |
| * _getLocalizedString("key1"); |
| * _getLocalizedString("key2", ["arg1"]); |
| * _getLocalizedString("key3", ["arg1", "arg2"]); |
| * (etc) |
| * |
| * Returns the localized string for the specified key, |
| * formatted if required. |
| * |
| */ |
| _getLocalizedString : function (key, formatArgs) { |
| if (formatArgs) |
| return this._strBundle.formatStringFromName( |
| key, formatArgs, formatArgs.length); |
| else |
| return this._strBundle.GetStringFromName(key); |
| }, |
| |
| |
| /* |
| * _getFormattedHostname |
| * |
| * The aURI parameter may either be a string uri, or an nsIURI instance. |
| * |
| * Returns the hostname to use in a nsILoginInfo object (for example, |
| * "http://example.com"). |
| */ |
| _getFormattedHostname : function (aURI) { |
| var uri; |
| if (aURI instanceof Ci.nsIURI) { |
| uri = aURI; |
| } else { |
| uri = this._ioService.newURI(aURI, null, null); |
| } |
| var scheme = uri.scheme; |
| |
| var hostname = scheme + "://" + uri.host; |
| |
| // If the URI explicitly specified a port, only include it when |
| // it's not the default. (We never want "http://foo.com:80") |
| port = uri.port; |
| if (port != -1) { |
| var handler = this._ioService.getProtocolHandler(scheme); |
| if (port != handler.defaultPort) |
| hostname += ":" + port; |
| } |
| |
| return hostname; |
| }, |
| |
| /* |
| * _getAuthTarget |
| * |
| * Returns the hostname and realm for which authentication is being |
| * requested, in the format expected to be used with nsILoginInfo. |
| */ |
| _getAuthTarget : function (aChannel, aAuthInfo) { |
| var hostname, realm; |
| |
| // If our proxy is demanding authentication, don't use the |
| // channel's actual destination. |
| if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) { |
| this.log("getAuthTarget is for proxy auth"); |
| if (!(aChannel instanceof Ci.nsIProxiedChannel)) |
| throw "proxy auth needs nsIProxiedChannel"; |
| |
| var info = aChannel.proxyInfo; |
| if (!info) |
| throw "proxy auth needs nsIProxyInfo"; |
| |
| // Proxies don't have a scheme, but we'll use "moz-proxy://" |
| // so that it's more obvious what the login is for. |
| var idnService = Cc["@mozilla.org/network/idn-service;1"]. |
| getService(Ci.nsIIDNService); |
| hostname = "moz-proxy://" + |
| idnService.convertUTF8toACE(info.host) + |
| ":" + info.port; |
| realm = aAuthInfo.realm; |
| if (!realm) |
| realm = hostname; |
| |
| return [hostname, realm]; |
| } |
| |
| hostname = this._getFormattedHostname(aChannel.URI); |
| |
| // If a HTTP WWW-Authenticate header specified a realm, that value |
| // will be available here. If it wasn't set or wasn't HTTP, we'll use |
| // the formatted hostname instead. |
| realm = aAuthInfo.realm; |
| if (!realm) |
| realm = hostname; |
| |
| return [hostname, realm]; |
| }, |
| |
| |
| /** |
| * Returns [username, password] as extracted from aAuthInfo (which |
| * holds this info after having prompted the user). |
| * |
| * If the authentication was for a Windows domain, we'll prepend the |
| * return username with the domain. (eg, "domain\user") |
| */ |
| _GetAuthInfo : function (aAuthInfo) { |
| var username, password; |
| |
| var flags = aAuthInfo.flags; |
| if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain) |
| username = aAuthInfo.domain + "\\" + aAuthInfo.username; |
| else |
| username = aAuthInfo.username; |
| |
| password = aAuthInfo.password; |
| |
| return [username, password]; |
| }, |
| |
| |
| /** |
| * Given a username (possibly in DOMAIN\user form) and password, parses the |
| * domain out of the username if necessary and sets domain, username and |
| * password on the auth information object. |
| */ |
| _SetAuthInfo : function (aAuthInfo, username, password) { |
| var flags = aAuthInfo.flags; |
| if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) { |
| // Domain is separated from username by a backslash |
| var idx = username.indexOf("\\"); |
| if (idx == -1) { |
| aAuthInfo.username = username; |
| } else { |
| aAuthInfo.domain = username.substring(0, idx); |
| aAuthInfo.username = username.substring(idx+1); |
| } |
| } else { |
| aAuthInfo.username = username; |
| } |
| aAuthInfo.password = password; |
| } |
| |
| }; // end of LoginManagerPrompter implementation |
| |
| |
| var component = [LoginManagerPromptFactory, LoginManagerPrompter]; |
| function NSGetModule(compMgr, fileSpec) { |
| return XPCOMUtils.generateModule(component); |
| } |