1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1488 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 + 1.9 +const Cc = Components.classes; 1.10 +const Ci = Components.interfaces; 1.11 +const Cr = Components.results; 1.12 + 1.13 +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); 1.14 +Components.utils.import("resource://gre/modules/Services.jsm"); 1.15 +Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); 1.16 + 1.17 +/* 1.18 + * LoginManagerPromptFactory 1.19 + * 1.20 + * Implements nsIPromptFactory 1.21 + * 1.22 + * Invoked by [toolkit/components/prompts/src/nsPrompter.js] 1.23 + */ 1.24 +function LoginManagerPromptFactory() { 1.25 + Services.obs.addObserver(this, "quit-application-granted", true); 1.26 + Services.obs.addObserver(this, "passwordmgr-crypto-login", true); 1.27 + Services.obs.addObserver(this, "passwordmgr-crypto-loginCanceled", true); 1.28 +} 1.29 + 1.30 +LoginManagerPromptFactory.prototype = { 1.31 + 1.32 + classID : Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"), 1.33 + QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIObserver, Ci.nsISupportsWeakReference]), 1.34 + 1.35 + _debug : false, 1.36 + _asyncPrompts : {}, 1.37 + _asyncPromptInProgress : false, 1.38 + 1.39 + observe : function (subject, topic, data) { 1.40 + this.log("Observed: " + topic); 1.41 + if (topic == "quit-application-granted") { 1.42 + this._cancelPendingPrompts(); 1.43 + } else if (topic == "passwordmgr-crypto-login") { 1.44 + // Start processing the deferred prompters. 1.45 + this._doAsyncPrompt(); 1.46 + } else if (topic == "passwordmgr-crypto-loginCanceled") { 1.47 + // User canceled a Master Password prompt, so go ahead and cancel 1.48 + // all pending auth prompts to avoid nagging over and over. 1.49 + this._cancelPendingPrompts(); 1.50 + } 1.51 + }, 1.52 + 1.53 + getPrompt : function (aWindow, aIID) { 1.54 + var prefBranch = Services.prefs.getBranch("signon."); 1.55 + this._debug = prefBranch.getBoolPref("debug"); 1.56 + 1.57 + var prompt = new LoginManagerPrompter().QueryInterface(aIID); 1.58 + prompt.init(aWindow, this); 1.59 + return prompt; 1.60 + }, 1.61 + 1.62 + _doAsyncPrompt : function() { 1.63 + if (this._asyncPromptInProgress) { 1.64 + this.log("_doAsyncPrompt bypassed, already in progress"); 1.65 + return; 1.66 + } 1.67 + 1.68 + // Find the first prompt key we have in the queue 1.69 + var hashKey = null; 1.70 + for (hashKey in this._asyncPrompts) 1.71 + break; 1.72 + 1.73 + if (!hashKey) { 1.74 + this.log("_doAsyncPrompt:run bypassed, no prompts in the queue"); 1.75 + return; 1.76 + } 1.77 + 1.78 + // If login manger has logins for this host, defer prompting if we're 1.79 + // already waiting on a master password entry. 1.80 + var prompt = this._asyncPrompts[hashKey]; 1.81 + var prompter = prompt.prompter; 1.82 + var [hostname, httpRealm] = prompter._getAuthTarget(prompt.channel, prompt.authInfo); 1.83 + var hasLogins = (prompter._pwmgr.countLogins(hostname, null, httpRealm) > 0); 1.84 + if (hasLogins && prompter._pwmgr.uiBusy) { 1.85 + this.log("_doAsyncPrompt:run bypassed, master password UI busy"); 1.86 + return; 1.87 + } 1.88 + 1.89 + this._asyncPromptInProgress = true; 1.90 + prompt.inProgress = true; 1.91 + 1.92 + var self = this; 1.93 + 1.94 + var runnable = { 1.95 + run : function() { 1.96 + var ok = false; 1.97 + try { 1.98 + self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'"); 1.99 + ok = prompter.promptAuth(prompt.channel, 1.100 + prompt.level, 1.101 + prompt.authInfo); 1.102 + } catch (e if (e instanceof Components.Exception) && 1.103 + e.result == Cr.NS_ERROR_NOT_AVAILABLE) { 1.104 + self.log("_doAsyncPrompt:run bypassed, UI is not available in this context"); 1.105 + } catch (e) { 1.106 + Components.utils.reportError("LoginManagerPrompter: " + 1.107 + "_doAsyncPrompt:run: " + e + "\n"); 1.108 + } 1.109 + 1.110 + delete self._asyncPrompts[hashKey]; 1.111 + prompt.inProgress = false; 1.112 + self._asyncPromptInProgress = false; 1.113 + 1.114 + for each (var consumer in prompt.consumers) { 1.115 + if (!consumer.callback) 1.116 + // Not having a callback means that consumer didn't provide it 1.117 + // or canceled the notification 1.118 + continue; 1.119 + 1.120 + self.log("Calling back to " + consumer.callback + " ok=" + ok); 1.121 + try { 1.122 + if (ok) 1.123 + consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo); 1.124 + else 1.125 + consumer.callback.onAuthCancelled(consumer.context, true); 1.126 + } catch (e) { /* Throw away exceptions caused by callback */ } 1.127 + } 1.128 + self._doAsyncPrompt(); 1.129 + } 1.130 + } 1.131 + 1.132 + Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL); 1.133 + this.log("_doAsyncPrompt:run dispatched"); 1.134 + }, 1.135 + 1.136 + 1.137 + _cancelPendingPrompts : function() { 1.138 + this.log("Canceling all pending prompts..."); 1.139 + var asyncPrompts = this._asyncPrompts; 1.140 + this.__proto__._asyncPrompts = {}; 1.141 + 1.142 + for each (var prompt in asyncPrompts) { 1.143 + // Watch out! If this prompt is currently prompting, let it handle 1.144 + // notifying the callbacks of success/failure, since it's already 1.145 + // asking the user for input. Reusing a callback can be crashy. 1.146 + if (prompt.inProgress) { 1.147 + this.log("skipping a prompt in progress"); 1.148 + continue; 1.149 + } 1.150 + 1.151 + for each (var consumer in prompt.consumers) { 1.152 + if (!consumer.callback) 1.153 + continue; 1.154 + 1.155 + this.log("Canceling async auth prompt callback " + consumer.callback); 1.156 + try { 1.157 + consumer.callback.onAuthCancelled(consumer.context, true); 1.158 + } catch (e) { /* Just ignore exceptions from the callback */ } 1.159 + } 1.160 + } 1.161 + }, 1.162 + 1.163 + 1.164 + log : function (message) { 1.165 + if (!this._debug) 1.166 + return; 1.167 + 1.168 + dump("Pwmgr PromptFactory: " + message + "\n"); 1.169 + Services.console.logStringMessage("Pwmgr PrompFactory: " + message); 1.170 + } 1.171 +}; // end of LoginManagerPromptFactory implementation 1.172 + 1.173 + 1.174 + 1.175 + 1.176 +/* ==================== LoginManagerPrompter ==================== */ 1.177 + 1.178 + 1.179 + 1.180 + 1.181 +/* 1.182 + * LoginManagerPrompter 1.183 + * 1.184 + * Implements interfaces for prompting the user to enter/save/change auth info. 1.185 + * 1.186 + * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox. 1.187 + * 1.188 + * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication 1.189 + * (eg HTTP Authenticate, FTP login). 1.190 + * 1.191 + * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins 1.192 + * found in HTML forms. 1.193 + */ 1.194 +function LoginManagerPrompter() {} 1.195 + 1.196 +LoginManagerPrompter.prototype = { 1.197 + 1.198 + classID : Components.ID("{8aa66d77-1bbb-45a6-991e-b8f47751c291}"), 1.199 + QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPrompt, 1.200 + Ci.nsIAuthPrompt2, 1.201 + Ci.nsILoginManagerPrompter]), 1.202 + 1.203 + _factory : null, 1.204 + _window : null, 1.205 + _debug : false, // mirrors signon.debug 1.206 + 1.207 + __pwmgr : null, // Password Manager service 1.208 + get _pwmgr() { 1.209 + if (!this.__pwmgr) 1.210 + this.__pwmgr = Cc["@mozilla.org/login-manager;1"]. 1.211 + getService(Ci.nsILoginManager); 1.212 + return this.__pwmgr; 1.213 + }, 1.214 + 1.215 + __promptService : null, // Prompt service for user interaction 1.216 + get _promptService() { 1.217 + if (!this.__promptService) 1.218 + this.__promptService = 1.219 + Cc["@mozilla.org/embedcomp/prompt-service;1"]. 1.220 + getService(Ci.nsIPromptService2); 1.221 + return this.__promptService; 1.222 + }, 1.223 + 1.224 + 1.225 + __strBundle : null, // String bundle for L10N 1.226 + get _strBundle() { 1.227 + if (!this.__strBundle) { 1.228 + var bunService = Cc["@mozilla.org/intl/stringbundle;1"]. 1.229 + getService(Ci.nsIStringBundleService); 1.230 + this.__strBundle = bunService.createBundle( 1.231 + "chrome://passwordmgr/locale/passwordmgr.properties"); 1.232 + if (!this.__strBundle) 1.233 + throw "String bundle for Login Manager not present!"; 1.234 + } 1.235 + 1.236 + return this.__strBundle; 1.237 + }, 1.238 + 1.239 + 1.240 + __ellipsis : null, 1.241 + get _ellipsis() { 1.242 + if (!this.__ellipsis) { 1.243 + this.__ellipsis = "\u2026"; 1.244 + try { 1.245 + this.__ellipsis = Services.prefs.getComplexValue( 1.246 + "intl.ellipsis", Ci.nsIPrefLocalizedString).data; 1.247 + } catch (e) { } 1.248 + } 1.249 + return this.__ellipsis; 1.250 + }, 1.251 + 1.252 + 1.253 + // Whether we are in private browsing mode 1.254 + get _inPrivateBrowsing() { 1.255 + if (this._window) { 1.256 + return PrivateBrowsingUtils.isWindowPrivate(this._window); 1.257 + } else { 1.258 + // If we don't that we're in private browsing mode if the caller did 1.259 + // not provide a window. The callers which really care about this 1.260 + // will indeed pass down a window to us, and for those who don't, 1.261 + // we can just assume that we don't want to save the entered login 1.262 + // information. 1.263 + return true; 1.264 + } 1.265 + }, 1.266 + 1.267 + 1.268 + /* 1.269 + * log 1.270 + * 1.271 + * Internal function for logging debug messages to the Error Console window. 1.272 + */ 1.273 + log : function (message) { 1.274 + if (!this._debug) 1.275 + return; 1.276 + 1.277 + dump("Pwmgr Prompter: " + message + "\n"); 1.278 + Services.console.logStringMessage("Pwmgr Prompter: " + message); 1.279 + }, 1.280 + 1.281 + 1.282 + 1.283 + 1.284 + /* ---------- nsIAuthPrompt prompts ---------- */ 1.285 + 1.286 + 1.287 + /* 1.288 + * prompt 1.289 + * 1.290 + * Wrapper around the prompt service prompt. Saving random fields here 1.291 + * doesn't really make sense and therefore isn't implemented. 1.292 + */ 1.293 + prompt : function (aDialogTitle, aText, aPasswordRealm, 1.294 + aSavePassword, aDefaultText, aResult) { 1.295 + if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER) 1.296 + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; 1.297 + 1.298 + this.log("===== prompt() called ====="); 1.299 + 1.300 + if (aDefaultText) { 1.301 + aResult.value = aDefaultText; 1.302 + } 1.303 + 1.304 + return this._promptService.prompt(this._window, 1.305 + aDialogTitle, aText, aResult, null, {}); 1.306 + }, 1.307 + 1.308 + 1.309 + /* 1.310 + * promptUsernameAndPassword 1.311 + * 1.312 + * Looks up a username and password in the database. Will prompt the user 1.313 + * with a dialog, even if a username and password are found. 1.314 + */ 1.315 + promptUsernameAndPassword : function (aDialogTitle, aText, aPasswordRealm, 1.316 + aSavePassword, aUsername, aPassword) { 1.317 + this.log("===== promptUsernameAndPassword() called ====="); 1.318 + 1.319 + if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION) 1.320 + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; 1.321 + 1.322 + var selectedLogin = null; 1.323 + var checkBox = { value : false }; 1.324 + var checkBoxLabel = null; 1.325 + var [hostname, realm, unused] = this._getRealmInfo(aPasswordRealm); 1.326 + 1.327 + // If hostname is null, we can't save this login. 1.328 + if (hostname) { 1.329 + var canRememberLogin; 1.330 + if (this._inPrivateBrowsing) 1.331 + canRememberLogin = false; 1.332 + else 1.333 + canRememberLogin = (aSavePassword == 1.334 + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) && 1.335 + this._pwmgr.getLoginSavingEnabled(hostname); 1.336 + 1.337 + // if checkBoxLabel is null, the checkbox won't be shown at all. 1.338 + if (canRememberLogin) 1.339 + checkBoxLabel = this._getLocalizedString("rememberPassword"); 1.340 + 1.341 + // Look for existing logins. 1.342 + var foundLogins = this._pwmgr.findLogins({}, hostname, null, 1.343 + realm); 1.344 + 1.345 + // XXX Like the original code, we can't deal with multiple 1.346 + // account selection. (bug 227632) 1.347 + if (foundLogins.length > 0) { 1.348 + selectedLogin = foundLogins[0]; 1.349 + 1.350 + // If the caller provided a username, try to use it. If they 1.351 + // provided only a password, this will try to find a password-only 1.352 + // login (or return null if none exists). 1.353 + if (aUsername.value) 1.354 + selectedLogin = this._repickSelectedLogin(foundLogins, 1.355 + aUsername.value); 1.356 + 1.357 + if (selectedLogin) { 1.358 + checkBox.value = true; 1.359 + aUsername.value = selectedLogin.username; 1.360 + // If the caller provided a password, prefer it. 1.361 + if (!aPassword.value) 1.362 + aPassword.value = selectedLogin.password; 1.363 + } 1.364 + } 1.365 + } 1.366 + 1.367 + var ok = this._promptService.promptUsernameAndPassword(this._window, 1.368 + aDialogTitle, aText, aUsername, aPassword, 1.369 + checkBoxLabel, checkBox); 1.370 + 1.371 + if (!ok || !checkBox.value || !hostname) 1.372 + return ok; 1.373 + 1.374 + if (!aPassword.value) { 1.375 + this.log("No password entered, so won't offer to save."); 1.376 + return ok; 1.377 + } 1.378 + 1.379 + // XXX We can't prompt with multiple logins yet (bug 227632), so 1.380 + // the entered login might correspond to an existing login 1.381 + // other than the one we originally selected. 1.382 + selectedLogin = this._repickSelectedLogin(foundLogins, aUsername.value); 1.383 + 1.384 + // If we didn't find an existing login, or if the username 1.385 + // changed, save as a new login. 1.386 + if (!selectedLogin) { 1.387 + // add as new 1.388 + this.log("New login seen for " + realm); 1.389 + var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. 1.390 + createInstance(Ci.nsILoginInfo); 1.391 + newLogin.init(hostname, null, realm, 1.392 + aUsername.value, aPassword.value, "", ""); 1.393 + this._pwmgr.addLogin(newLogin); 1.394 + } else if (aPassword.value != selectedLogin.password) { 1.395 + // update password 1.396 + this.log("Updating password for " + realm); 1.397 + this._updateLogin(selectedLogin, aPassword.value); 1.398 + } else { 1.399 + this.log("Login unchanged, no further action needed."); 1.400 + this._updateLogin(selectedLogin); 1.401 + } 1.402 + 1.403 + return ok; 1.404 + }, 1.405 + 1.406 + 1.407 + /* 1.408 + * promptPassword 1.409 + * 1.410 + * If a password is found in the database for the password realm, it is 1.411 + * returned straight away without displaying a dialog. 1.412 + * 1.413 + * If a password is not found in the database, the user will be prompted 1.414 + * with a dialog with a text field and ok/cancel buttons. If the user 1.415 + * allows it, then the password will be saved in the database. 1.416 + */ 1.417 + promptPassword : function (aDialogTitle, aText, aPasswordRealm, 1.418 + aSavePassword, aPassword) { 1.419 + this.log("===== promptPassword called() ====="); 1.420 + 1.421 + if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION) 1.422 + throw Components.results.NS_ERROR_NOT_IMPLEMENTED; 1.423 + 1.424 + var checkBox = { value : false }; 1.425 + var checkBoxLabel = null; 1.426 + var [hostname, realm, username] = this._getRealmInfo(aPasswordRealm); 1.427 + 1.428 + username = decodeURIComponent(username); 1.429 + 1.430 + // If hostname is null, we can't save this login. 1.431 + if (hostname && !this._inPrivateBrowsing) { 1.432 + var canRememberLogin = (aSavePassword == 1.433 + Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) && 1.434 + this._pwmgr.getLoginSavingEnabled(hostname); 1.435 + 1.436 + // if checkBoxLabel is null, the checkbox won't be shown at all. 1.437 + if (canRememberLogin) 1.438 + checkBoxLabel = this._getLocalizedString("rememberPassword"); 1.439 + 1.440 + if (!aPassword.value) { 1.441 + // Look for existing logins. 1.442 + var foundLogins = this._pwmgr.findLogins({}, hostname, null, 1.443 + realm); 1.444 + 1.445 + // XXX Like the original code, we can't deal with multiple 1.446 + // account selection (bug 227632). We can deal with finding the 1.447 + // account based on the supplied username - but in this case we'll 1.448 + // just return the first match. 1.449 + for (var i = 0; i < foundLogins.length; ++i) { 1.450 + if (foundLogins[i].username == username) { 1.451 + aPassword.value = foundLogins[i].password; 1.452 + // wallet returned straight away, so this mimics that code 1.453 + return true; 1.454 + } 1.455 + } 1.456 + } 1.457 + } 1.458 + 1.459 + var ok = this._promptService.promptPassword(this._window, aDialogTitle, 1.460 + aText, aPassword, 1.461 + checkBoxLabel, checkBox); 1.462 + 1.463 + if (ok && checkBox.value && hostname && aPassword.value) { 1.464 + var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. 1.465 + createInstance(Ci.nsILoginInfo); 1.466 + newLogin.init(hostname, null, realm, username, 1.467 + aPassword.value, "", ""); 1.468 + 1.469 + this.log("New login seen for " + realm); 1.470 + 1.471 + this._pwmgr.addLogin(newLogin); 1.472 + } 1.473 + 1.474 + return ok; 1.475 + }, 1.476 + 1.477 + /* ---------- nsIAuthPrompt helpers ---------- */ 1.478 + 1.479 + 1.480 + /** 1.481 + * Given aRealmString, such as "http://user@example.com/foo", returns an 1.482 + * array of: 1.483 + * - the formatted hostname 1.484 + * - the realm (hostname + path) 1.485 + * - the username, if present 1.486 + * 1.487 + * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S] 1.488 + * channels, e.g. "example.com:80 (httprealm)", null is returned for all 1.489 + * arguments to let callers know the login can't be saved because we don't 1.490 + * know whether it's http or https. 1.491 + */ 1.492 + _getRealmInfo : function (aRealmString) { 1.493 + var httpRealm = /^.+ \(.+\)$/; 1.494 + if (httpRealm.test(aRealmString)) 1.495 + return [null, null, null]; 1.496 + 1.497 + var uri = Services.io.newURI(aRealmString, null, null); 1.498 + var pathname = ""; 1.499 + 1.500 + if (uri.path != "/") 1.501 + pathname = uri.path; 1.502 + 1.503 + var formattedHostname = this._getFormattedHostname(uri); 1.504 + 1.505 + return [formattedHostname, formattedHostname + pathname, uri.username]; 1.506 + }, 1.507 + 1.508 + /* ---------- nsIAuthPrompt2 prompts ---------- */ 1.509 + 1.510 + 1.511 + 1.512 + 1.513 + /* 1.514 + * promptAuth 1.515 + * 1.516 + * Implementation of nsIAuthPrompt2. 1.517 + * 1.518 + * nsIChannel aChannel 1.519 + * int aLevel 1.520 + * nsIAuthInformation aAuthInfo 1.521 + */ 1.522 + promptAuth : function (aChannel, aLevel, aAuthInfo) { 1.523 + var selectedLogin = null; 1.524 + var checkbox = { value : false }; 1.525 + var checkboxLabel = null; 1.526 + var epicfail = false; 1.527 + var canAutologin = false; 1.528 + 1.529 + try { 1.530 + 1.531 + this.log("===== promptAuth called ====="); 1.532 + 1.533 + // If the user submits a login but it fails, we need to remove the 1.534 + // notification bar that was displayed. Conveniently, the user will 1.535 + // be prompted for authentication again, which brings us here. 1.536 + this._removeLoginNotifications(); 1.537 + 1.538 + var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo); 1.539 + 1.540 + 1.541 + // Looks for existing logins to prefill the prompt with. 1.542 + var foundLogins = this._pwmgr.findLogins({}, 1.543 + hostname, null, httpRealm); 1.544 + this.log("found " + foundLogins.length + " matching logins."); 1.545 + 1.546 + // XXX Can't select from multiple accounts yet. (bug 227632) 1.547 + if (foundLogins.length > 0) { 1.548 + selectedLogin = foundLogins[0]; 1.549 + this._SetAuthInfo(aAuthInfo, selectedLogin.username, 1.550 + selectedLogin.password); 1.551 + 1.552 + // Allow automatic proxy login 1.553 + if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY && 1.554 + !(aAuthInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) && 1.555 + Services.prefs.getBoolPref("signon.autologin.proxy") && 1.556 + !this._inPrivateBrowsing) { 1.557 + 1.558 + this.log("Autologin enabled, skipping auth prompt."); 1.559 + canAutologin = true; 1.560 + } 1.561 + 1.562 + checkbox.value = true; 1.563 + } 1.564 + 1.565 + var canRememberLogin = this._pwmgr.getLoginSavingEnabled(hostname); 1.566 + if (this._inPrivateBrowsing) 1.567 + canRememberLogin = false; 1.568 + 1.569 + // if checkboxLabel is null, the checkbox won't be shown at all. 1.570 + var notifyBox = this._getNotifyBox(); 1.571 + if (canRememberLogin && !notifyBox) 1.572 + checkboxLabel = this._getLocalizedString("rememberPassword"); 1.573 + } catch (e) { 1.574 + // Ignore any errors and display the prompt anyway. 1.575 + epicfail = true; 1.576 + Components.utils.reportError("LoginManagerPrompter: " + 1.577 + "Epic fail in promptAuth: " + e + "\n"); 1.578 + } 1.579 + 1.580 + var ok = canAutologin || 1.581 + this._promptService.promptAuth(this._window, 1.582 + aChannel, aLevel, aAuthInfo, 1.583 + checkboxLabel, checkbox); 1.584 + 1.585 + // If there's a notification box, use it to allow the user to 1.586 + // determine if the login should be saved. If there isn't a 1.587 + // notification box, only save the login if the user set the 1.588 + // checkbox to do so. 1.589 + var rememberLogin = notifyBox ? canRememberLogin : checkbox.value; 1.590 + if (!ok || !rememberLogin || epicfail) 1.591 + return ok; 1.592 + 1.593 + try { 1.594 + var [username, password] = this._GetAuthInfo(aAuthInfo); 1.595 + 1.596 + if (!password) { 1.597 + this.log("No password entered, so won't offer to save."); 1.598 + return ok; 1.599 + } 1.600 + 1.601 + // XXX We can't prompt with multiple logins yet (bug 227632), so 1.602 + // the entered login might correspond to an existing login 1.603 + // other than the one we originally selected. 1.604 + selectedLogin = this._repickSelectedLogin(foundLogins, username); 1.605 + 1.606 + // If we didn't find an existing login, or if the username 1.607 + // changed, save as a new login. 1.608 + if (!selectedLogin) { 1.609 + this.log("New login seen for " + username + 1.610 + " @ " + hostname + " (" + httpRealm + ")"); 1.611 + 1.612 + var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. 1.613 + createInstance(Ci.nsILoginInfo); 1.614 + newLogin.init(hostname, null, httpRealm, 1.615 + username, password, "", ""); 1.616 + var notifyObj = this._getPopupNote() || notifyBox; 1.617 + if (notifyObj) 1.618 + this._showSaveLoginNotification(notifyObj, newLogin); 1.619 + else 1.620 + this._pwmgr.addLogin(newLogin); 1.621 + 1.622 + } else if (password != selectedLogin.password) { 1.623 + 1.624 + this.log("Updating password for " + username + 1.625 + " @ " + hostname + " (" + httpRealm + ")"); 1.626 + var notifyObj = this._getPopupNote() || notifyBox; 1.627 + if (notifyObj) 1.628 + this._showChangeLoginNotification(notifyObj, 1.629 + selectedLogin, password); 1.630 + else 1.631 + this._updateLogin(selectedLogin, password); 1.632 + 1.633 + } else { 1.634 + this.log("Login unchanged, no further action needed."); 1.635 + this._updateLogin(selectedLogin); 1.636 + } 1.637 + } catch (e) { 1.638 + Components.utils.reportError("LoginManagerPrompter: " + 1.639 + "Fail2 in promptAuth: " + e + "\n"); 1.640 + } 1.641 + 1.642 + return ok; 1.643 + }, 1.644 + 1.645 + asyncPromptAuth : function (aChannel, aCallback, aContext, aLevel, aAuthInfo) { 1.646 + var cancelable = null; 1.647 + 1.648 + try { 1.649 + this.log("===== asyncPromptAuth called ====="); 1.650 + 1.651 + // If the user submits a login but it fails, we need to remove the 1.652 + // notification bar that was displayed. Conveniently, the user will 1.653 + // be prompted for authentication again, which brings us here. 1.654 + this._removeLoginNotifications(); 1.655 + 1.656 + cancelable = this._newAsyncPromptConsumer(aCallback, aContext); 1.657 + 1.658 + var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo); 1.659 + 1.660 + var hashKey = aLevel + "|" + hostname + "|" + httpRealm; 1.661 + this.log("Async prompt key = " + hashKey); 1.662 + var asyncPrompt = this._factory._asyncPrompts[hashKey]; 1.663 + if (asyncPrompt) { 1.664 + this.log("Prompt bound to an existing one in the queue, callback = " + aCallback); 1.665 + asyncPrompt.consumers.push(cancelable); 1.666 + return cancelable; 1.667 + } 1.668 + 1.669 + this.log("Adding new prompt to the queue, callback = " + aCallback); 1.670 + asyncPrompt = { 1.671 + consumers: [cancelable], 1.672 + channel: aChannel, 1.673 + authInfo: aAuthInfo, 1.674 + level: aLevel, 1.675 + inProgress : false, 1.676 + prompter: this 1.677 + } 1.678 + 1.679 + this._factory._asyncPrompts[hashKey] = asyncPrompt; 1.680 + this._factory._doAsyncPrompt(); 1.681 + } 1.682 + catch (e) { 1.683 + Components.utils.reportError("LoginManagerPrompter: " + 1.684 + "asyncPromptAuth: " + e + "\nFalling back to promptAuth\n"); 1.685 + // Fail the prompt operation to let the consumer fall back 1.686 + // to synchronous promptAuth method 1.687 + throw e; 1.688 + } 1.689 + 1.690 + return cancelable; 1.691 + }, 1.692 + 1.693 + 1.694 + 1.695 + 1.696 + /* ---------- nsILoginManagerPrompter prompts ---------- */ 1.697 + 1.698 + 1.699 + 1.700 + 1.701 + /* 1.702 + * init 1.703 + * 1.704 + */ 1.705 + init : function (aWindow, aFactory) { 1.706 + this._window = aWindow; 1.707 + this._factory = aFactory || null; 1.708 + 1.709 + var prefBranch = Services.prefs.getBranch("signon."); 1.710 + this._debug = prefBranch.getBoolPref("debug"); 1.711 + this.log("===== initialized ====="); 1.712 + }, 1.713 + 1.714 + 1.715 + /* 1.716 + * promptToSavePassword 1.717 + * 1.718 + */ 1.719 + promptToSavePassword : function (aLogin) { 1.720 + var notifyObj = this._getPopupNote() || this._getNotifyBox(); 1.721 + 1.722 + if (notifyObj) 1.723 + this._showSaveLoginNotification(notifyObj, aLogin); 1.724 + else 1.725 + this._showSaveLoginDialog(aLogin); 1.726 + }, 1.727 + 1.728 + 1.729 + /* 1.730 + * _showLoginNotification 1.731 + * 1.732 + * Displays a notification bar. 1.733 + * 1.734 + */ 1.735 + _showLoginNotification : function (aNotifyBox, aName, aText, aButtons) { 1.736 + var oldBar = aNotifyBox.getNotificationWithValue(aName); 1.737 + const priority = aNotifyBox.PRIORITY_INFO_MEDIUM; 1.738 + 1.739 + this.log("Adding new " + aName + " notification bar"); 1.740 + var newBar = aNotifyBox.appendNotification( 1.741 + aText, aName, 1.742 + "chrome://mozapps/skin/passwordmgr/key.png", 1.743 + priority, aButtons); 1.744 + 1.745 + // The page we're going to hasn't loaded yet, so we want to persist 1.746 + // across the first location change. 1.747 + newBar.persistence++; 1.748 + 1.749 + // Sites like Gmail perform a funky redirect dance before you end up 1.750 + // at the post-authentication page. I don't see a good way to 1.751 + // heuristically determine when to ignore such location changes, so 1.752 + // we'll try ignoring location changes based on a time interval. 1.753 + newBar.timeout = Date.now() + 20000; // 20 seconds 1.754 + 1.755 + if (oldBar) { 1.756 + this.log("(...and removing old " + aName + " notification bar)"); 1.757 + aNotifyBox.removeNotification(oldBar); 1.758 + } 1.759 + }, 1.760 + 1.761 + 1.762 + /* 1.763 + * _showSaveLoginNotification 1.764 + * 1.765 + * Displays a notification bar or a popup notification, to allow the user 1.766 + * to save the specified login. This allows the user to see the results of 1.767 + * their login, and only save a login which they know worked. 1.768 + * 1.769 + * @param aNotifyObj 1.770 + * A notification box or a popup notification. 1.771 + */ 1.772 + _showSaveLoginNotification : function (aNotifyObj, aLogin) { 1.773 + 1.774 + // Ugh. We can't use the strings from the popup window, because they 1.775 + // have the access key marked in the string (eg "Mo&zilla"), along 1.776 + // with some weird rules for handling access keys that do not occur 1.777 + // in the string, for L10N. See commonDialog.js's setLabelForNode(). 1.778 + var neverButtonText = 1.779 + this._getLocalizedString("notifyBarNeverRememberButtonText"); 1.780 + var neverButtonAccessKey = 1.781 + this._getLocalizedString("notifyBarNeverRememberButtonAccessKey"); 1.782 + var rememberButtonText = 1.783 + this._getLocalizedString("notifyBarRememberPasswordButtonText"); 1.784 + var rememberButtonAccessKey = 1.785 + this._getLocalizedString("notifyBarRememberPasswordButtonAccessKey"); 1.786 + 1.787 + var displayHost = this._getShortDisplayHost(aLogin.hostname); 1.788 + var notificationText; 1.789 + if (aLogin.username) { 1.790 + var displayUser = this._sanitizeUsername(aLogin.username); 1.791 + notificationText = this._getLocalizedString( 1.792 + "rememberPasswordMsg", 1.793 + [displayUser, displayHost]); 1.794 + } else { 1.795 + notificationText = this._getLocalizedString( 1.796 + "rememberPasswordMsgNoUsername", 1.797 + [displayHost]); 1.798 + } 1.799 + 1.800 + // The callbacks in |buttons| have a closure to access the variables 1.801 + // in scope here; set one to |this._pwmgr| so we can get back to pwmgr 1.802 + // without a getService() call. 1.803 + var pwmgr = this._pwmgr; 1.804 + 1.805 + // Notification is a PopupNotification 1.806 + if (aNotifyObj == this._getPopupNote()) { 1.807 + // "Remember" button 1.808 + var mainAction = { 1.809 + label: rememberButtonText, 1.810 + accessKey: rememberButtonAccessKey, 1.811 + callback: function(aNotifyObj, aButton) { 1.812 + pwmgr.addLogin(aLogin); 1.813 + browser.focus(); 1.814 + } 1.815 + }; 1.816 + 1.817 + var secondaryActions = [ 1.818 + // "Never for this site" button 1.819 + { 1.820 + label: neverButtonText, 1.821 + accessKey: neverButtonAccessKey, 1.822 + callback: function(aNotifyObj, aButton) { 1.823 + pwmgr.setLoginSavingEnabled(aLogin.hostname, false); 1.824 + browser.focus(); 1.825 + } 1.826 + } 1.827 + ]; 1.828 + 1.829 + var notifyWin = this._getNotifyWindow(); 1.830 + var chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject; 1.831 + var browser = chromeWin.gBrowser. 1.832 + getBrowserForDocument(notifyWin.top.document); 1.833 + 1.834 + aNotifyObj.show(browser, "password-save", notificationText, 1.835 + "password-notification-icon", mainAction, 1.836 + secondaryActions, { timeout: Date.now() + 10000, 1.837 + persistWhileVisible: true }); 1.838 + } else { 1.839 + var notNowButtonText = 1.840 + this._getLocalizedString("notifyBarNotNowButtonText"); 1.841 + var notNowButtonAccessKey = 1.842 + this._getLocalizedString("notifyBarNotNowButtonAccessKey"); 1.843 + var buttons = [ 1.844 + // "Remember" button 1.845 + { 1.846 + label: rememberButtonText, 1.847 + accessKey: rememberButtonAccessKey, 1.848 + popup: null, 1.849 + callback: function(aNotifyObj, aButton) { 1.850 + pwmgr.addLogin(aLogin); 1.851 + } 1.852 + }, 1.853 + 1.854 + // "Never for this site" button 1.855 + { 1.856 + label: neverButtonText, 1.857 + accessKey: neverButtonAccessKey, 1.858 + popup: null, 1.859 + callback: function(aNotifyObj, aButton) { 1.860 + pwmgr.setLoginSavingEnabled(aLogin.hostname, false); 1.861 + } 1.862 + }, 1.863 + 1.864 + // "Not now" button 1.865 + { 1.866 + label: notNowButtonText, 1.867 + accessKey: notNowButtonAccessKey, 1.868 + popup: null, 1.869 + callback: function() { /* NOP */ } 1.870 + } 1.871 + ]; 1.872 + 1.873 + this._showLoginNotification(aNotifyObj, "password-save", 1.874 + notificationText, buttons); 1.875 + } 1.876 + }, 1.877 + 1.878 + 1.879 + /* 1.880 + * _removeLoginNotifications 1.881 + * 1.882 + */ 1.883 + _removeLoginNotifications : function () { 1.884 + var popupNote = this._getPopupNote(); 1.885 + if (popupNote) 1.886 + popupNote = popupNote.getNotification("password-save"); 1.887 + if (popupNote) 1.888 + popupNote.remove(); 1.889 + 1.890 + var notifyBox = this._getNotifyBox(); 1.891 + if (notifyBox) { 1.892 + var oldBar = notifyBox.getNotificationWithValue("password-save"); 1.893 + if (oldBar) { 1.894 + this.log("Removing save-password notification bar."); 1.895 + notifyBox.removeNotification(oldBar); 1.896 + } 1.897 + 1.898 + oldBar = notifyBox.getNotificationWithValue("password-change"); 1.899 + if (oldBar) { 1.900 + this.log("Removing change-password notification bar."); 1.901 + notifyBox.removeNotification(oldBar); 1.902 + } 1.903 + } 1.904 + }, 1.905 + 1.906 + 1.907 + /* 1.908 + * _showSaveLoginDialog 1.909 + * 1.910 + * Called when we detect a new login in a form submission, 1.911 + * asks the user what to do. 1.912 + * 1.913 + */ 1.914 + _showSaveLoginDialog : function (aLogin) { 1.915 + const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT + 1.916 + (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) + 1.917 + (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) + 1.918 + (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2); 1.919 + 1.920 + var displayHost = this._getShortDisplayHost(aLogin.hostname); 1.921 + 1.922 + var dialogText; 1.923 + if (aLogin.username) { 1.924 + var displayUser = this._sanitizeUsername(aLogin.username); 1.925 + dialogText = this._getLocalizedString( 1.926 + "rememberPasswordMsg", 1.927 + [displayUser, displayHost]); 1.928 + } else { 1.929 + dialogText = this._getLocalizedString( 1.930 + "rememberPasswordMsgNoUsername", 1.931 + [displayHost]); 1.932 + 1.933 + } 1.934 + var dialogTitle = this._getLocalizedString( 1.935 + "savePasswordTitle"); 1.936 + var neverButtonText = this._getLocalizedString( 1.937 + "neverForSiteButtonText"); 1.938 + var rememberButtonText = this._getLocalizedString( 1.939 + "rememberButtonText"); 1.940 + var notNowButtonText = this._getLocalizedString( 1.941 + "notNowButtonText"); 1.942 + 1.943 + this.log("Prompting user to save/ignore login"); 1.944 + var userChoice = this._promptService.confirmEx(this._window, 1.945 + dialogTitle, dialogText, 1.946 + buttonFlags, rememberButtonText, 1.947 + notNowButtonText, neverButtonText, 1.948 + null, {}); 1.949 + // Returns: 1.950 + // 0 - Save the login 1.951 + // 1 - Ignore the login this time 1.952 + // 2 - Never save logins for this site 1.953 + if (userChoice == 2) { 1.954 + this.log("Disabling " + aLogin.hostname + " logins by request."); 1.955 + this._pwmgr.setLoginSavingEnabled(aLogin.hostname, false); 1.956 + } else if (userChoice == 0) { 1.957 + this.log("Saving login for " + aLogin.hostname); 1.958 + this._pwmgr.addLogin(aLogin); 1.959 + } else { 1.960 + // userChoice == 1 --> just ignore the login. 1.961 + this.log("Ignoring login."); 1.962 + } 1.963 + }, 1.964 + 1.965 + 1.966 + /* 1.967 + * promptToChangePassword 1.968 + * 1.969 + * Called when we think we detect a password change for an existing 1.970 + * login, when the form being submitted contains multiple password 1.971 + * fields. 1.972 + * 1.973 + */ 1.974 + promptToChangePassword : function (aOldLogin, aNewLogin) { 1.975 + var notifyObj = this._getPopupNote() || this._getNotifyBox(); 1.976 + 1.977 + if (notifyObj) 1.978 + this._showChangeLoginNotification(notifyObj, aOldLogin, 1.979 + aNewLogin.password); 1.980 + else 1.981 + this._showChangeLoginDialog(aOldLogin, aNewLogin.password); 1.982 + }, 1.983 + 1.984 + 1.985 + /* 1.986 + * _showChangeLoginNotification 1.987 + * 1.988 + * Shows the Change Password notification bar or popup notification. 1.989 + * 1.990 + * @param aNotifyObj 1.991 + * A notification box or a popup notification. 1.992 + */ 1.993 + _showChangeLoginNotification : function (aNotifyObj, aOldLogin, aNewPassword) { 1.994 + var notificationText; 1.995 + if (aOldLogin.username) { 1.996 + var displayUser = this._sanitizeUsername(aOldLogin.username); 1.997 + notificationText = this._getLocalizedString( 1.998 + "updatePasswordMsg", 1.999 + [displayUser]); 1.1000 + } else { 1.1001 + notificationText = this._getLocalizedString( 1.1002 + "updatePasswordMsgNoUser"); 1.1003 + } 1.1004 + 1.1005 + var changeButtonText = 1.1006 + this._getLocalizedString("notifyBarUpdateButtonText"); 1.1007 + var changeButtonAccessKey = 1.1008 + this._getLocalizedString("notifyBarUpdateButtonAccessKey"); 1.1009 + 1.1010 + // The callbacks in |buttons| have a closure to access the variables 1.1011 + // in scope here; set one to |this._pwmgr| so we can get back to pwmgr 1.1012 + // without a getService() call. 1.1013 + var self = this; 1.1014 + 1.1015 + // Notification is a PopupNotification 1.1016 + if (aNotifyObj == this._getPopupNote()) { 1.1017 + // "Yes" button 1.1018 + var mainAction = { 1.1019 + label: changeButtonText, 1.1020 + accessKey: changeButtonAccessKey, 1.1021 + popup: null, 1.1022 + callback: function(aNotifyObj, aButton) { 1.1023 + self._updateLogin(aOldLogin, aNewPassword); 1.1024 + } 1.1025 + }; 1.1026 + 1.1027 + var notifyWin = this._getNotifyWindow(); 1.1028 + var chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject; 1.1029 + var browser = chromeWin.gBrowser. 1.1030 + getBrowserForDocument(notifyWin.top.document); 1.1031 + 1.1032 + aNotifyObj.show(browser, "password-change", notificationText, 1.1033 + "password-notification-icon", mainAction, 1.1034 + null, { timeout: Date.now() + 10000, 1.1035 + persistWhileVisible: true }); 1.1036 + } else { 1.1037 + var dontChangeButtonText = 1.1038 + this._getLocalizedString("notifyBarDontChangeButtonText"); 1.1039 + var dontChangeButtonAccessKey = 1.1040 + this._getLocalizedString("notifyBarDontChangeButtonAccessKey"); 1.1041 + var buttons = [ 1.1042 + // "Yes" button 1.1043 + { 1.1044 + label: changeButtonText, 1.1045 + accessKey: changeButtonAccessKey, 1.1046 + popup: null, 1.1047 + callback: function(aNotifyObj, aButton) { 1.1048 + self._updateLogin(aOldLogin, aNewPassword); 1.1049 + } 1.1050 + }, 1.1051 + 1.1052 + // "No" button 1.1053 + { 1.1054 + label: dontChangeButtonText, 1.1055 + accessKey: dontChangeButtonAccessKey, 1.1056 + popup: null, 1.1057 + callback: function(aNotifyObj, aButton) { 1.1058 + // do nothing 1.1059 + } 1.1060 + } 1.1061 + ]; 1.1062 + 1.1063 + this._showLoginNotification(aNotifyObj, "password-change", 1.1064 + notificationText, buttons); 1.1065 + } 1.1066 + }, 1.1067 + 1.1068 + 1.1069 + /* 1.1070 + * _showChangeLoginDialog 1.1071 + * 1.1072 + * Shows the Change Password dialog. 1.1073 + * 1.1074 + */ 1.1075 + _showChangeLoginDialog : function (aOldLogin, aNewPassword) { 1.1076 + const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS; 1.1077 + 1.1078 + var dialogText; 1.1079 + if (aOldLogin.username) 1.1080 + dialogText = this._getLocalizedString( 1.1081 + "updatePasswordMsg", 1.1082 + [aOldLogin.username]); 1.1083 + else 1.1084 + dialogText = this._getLocalizedString( 1.1085 + "updatePasswordMsgNoUser"); 1.1086 + 1.1087 + var dialogTitle = this._getLocalizedString( 1.1088 + "passwordChangeTitle"); 1.1089 + 1.1090 + // returns 0 for yes, 1 for no. 1.1091 + var ok = !this._promptService.confirmEx(this._window, 1.1092 + dialogTitle, dialogText, buttonFlags, 1.1093 + null, null, null, 1.1094 + null, {}); 1.1095 + if (ok) { 1.1096 + this.log("Updating password for user " + aOldLogin.username); 1.1097 + this._updateLogin(aOldLogin, aNewPassword); 1.1098 + } 1.1099 + }, 1.1100 + 1.1101 + 1.1102 + /* 1.1103 + * promptToChangePasswordWithUsernames 1.1104 + * 1.1105 + * Called when we detect a password change in a form submission, but we 1.1106 + * don't know which existing login (username) it's for. Asks the user 1.1107 + * to select a username and confirm the password change. 1.1108 + * 1.1109 + * Note: The caller doesn't know the username for aNewLogin, so this 1.1110 + * function fills in .username and .usernameField with the values 1.1111 + * from the login selected by the user. 1.1112 + * 1.1113 + * Note; XPCOM stupidity: |count| is just |logins.length|. 1.1114 + */ 1.1115 + promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) { 1.1116 + const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS; 1.1117 + 1.1118 + var usernames = logins.map(function (l) l.username); 1.1119 + var dialogText = this._getLocalizedString("userSelectText"); 1.1120 + var dialogTitle = this._getLocalizedString("passwordChangeTitle"); 1.1121 + var selectedIndex = { value: null }; 1.1122 + 1.1123 + // If user selects ok, outparam.value is set to the index 1.1124 + // of the selected username. 1.1125 + var ok = this._promptService.select(this._window, 1.1126 + dialogTitle, dialogText, 1.1127 + usernames.length, usernames, 1.1128 + selectedIndex); 1.1129 + if (ok) { 1.1130 + // Now that we know which login to use, modify its password. 1.1131 + var selectedLogin = logins[selectedIndex.value]; 1.1132 + this.log("Updating password for user " + selectedLogin.username); 1.1133 + this._updateLogin(selectedLogin, aNewLogin.password); 1.1134 + } 1.1135 + }, 1.1136 + 1.1137 + 1.1138 + 1.1139 + 1.1140 + /* ---------- Internal Methods ---------- */ 1.1141 + 1.1142 + 1.1143 + 1.1144 + 1.1145 + /* 1.1146 + * _updateLogin 1.1147 + */ 1.1148 + _updateLogin : function (login, newPassword) { 1.1149 + var now = Date.now(); 1.1150 + var propBag = Cc["@mozilla.org/hash-property-bag;1"]. 1.1151 + createInstance(Ci.nsIWritablePropertyBag); 1.1152 + if (newPassword) { 1.1153 + propBag.setProperty("password", newPassword); 1.1154 + // Explicitly set the password change time here (even though it would 1.1155 + // be changed automatically), to ensure that it's exactly the same 1.1156 + // value as timeLastUsed. 1.1157 + propBag.setProperty("timePasswordChanged", now); 1.1158 + } 1.1159 + propBag.setProperty("timeLastUsed", now); 1.1160 + propBag.setProperty("timesUsedIncrement", 1); 1.1161 + this._pwmgr.modifyLogin(login, propBag); 1.1162 + }, 1.1163 + 1.1164 + 1.1165 + /* 1.1166 + * _getChromeWindow 1.1167 + * 1.1168 + * Given a content DOM window, returns the chrome window it's in. 1.1169 + */ 1.1170 + _getChromeWindow: function (aWindow) { 1.1171 + var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) 1.1172 + .getInterface(Ci.nsIWebNavigation) 1.1173 + .QueryInterface(Ci.nsIDocShell) 1.1174 + .chromeEventHandler.ownerDocument.defaultView; 1.1175 + return chromeWin; 1.1176 + }, 1.1177 + 1.1178 + 1.1179 + /* 1.1180 + * _getNotifyWindow 1.1181 + */ 1.1182 + _getNotifyWindow: function () { 1.1183 + 1.1184 + try { 1.1185 + // Get topmost window, in case we're in a frame. 1.1186 + var notifyWin = this._window.top; 1.1187 + 1.1188 + // Some sites pop up a temporary login window, when disappears 1.1189 + // upon submission of credentials. We want to put the notification 1.1190 + // bar in the opener window if this seems to be happening. 1.1191 + if (notifyWin.opener) { 1.1192 + var chromeDoc = this._getChromeWindow(notifyWin). 1.1193 + document.documentElement; 1.1194 + var webnav = notifyWin. 1.1195 + QueryInterface(Ci.nsIInterfaceRequestor). 1.1196 + getInterface(Ci.nsIWebNavigation); 1.1197 + 1.1198 + // Check to see if the current window was opened with chrome 1.1199 + // disabled, and if so use the opener window. But if the window 1.1200 + // has been used to visit other pages (ie, has a history), 1.1201 + // assume it'll stick around and *don't* use the opener. 1.1202 + if (chromeDoc.getAttribute("chromehidden") && 1.1203 + webnav.sessionHistory.count == 1) { 1.1204 + this.log("Using opener window for notification bar."); 1.1205 + notifyWin = notifyWin.opener; 1.1206 + } 1.1207 + } 1.1208 + 1.1209 + return notifyWin; 1.1210 + 1.1211 + } catch (e) { 1.1212 + // If any errors happen, just assume no notification box. 1.1213 + this.log("Unable to get notify window"); 1.1214 + return null; 1.1215 + } 1.1216 + }, 1.1217 + 1.1218 + 1.1219 + /* 1.1220 + * _getPopupNote 1.1221 + * 1.1222 + * Returns the popup notification to this prompter, 1.1223 + * or null if there isn't one available. 1.1224 + */ 1.1225 + _getPopupNote : function () { 1.1226 + let popupNote = null; 1.1227 + 1.1228 + try { 1.1229 + let notifyWin = this._getNotifyWindow(); 1.1230 + 1.1231 + // Get the chrome window for the content window we're using. 1.1232 + // .wrappedJSObject needed here -- see bug 422974 comment 5. 1.1233 + let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject; 1.1234 + 1.1235 + popupNote = chromeWin.PopupNotifications; 1.1236 + } catch (e) { 1.1237 + this.log("Popup notifications not available on window"); 1.1238 + } 1.1239 + 1.1240 + return popupNote; 1.1241 + }, 1.1242 + 1.1243 + 1.1244 + /* 1.1245 + * _getNotifyBox 1.1246 + * 1.1247 + * Returns the notification box to this prompter, or null if there isn't 1.1248 + * a notification box available. 1.1249 + */ 1.1250 + _getNotifyBox : function () { 1.1251 + let notifyBox = null; 1.1252 + 1.1253 + try { 1.1254 + let notifyWin = this._getNotifyWindow(); 1.1255 + 1.1256 + // Get the chrome window for the content window we're using. 1.1257 + // .wrappedJSObject needed here -- see bug 422974 comment 5. 1.1258 + let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject; 1.1259 + 1.1260 + notifyBox = chromeWin.getNotificationBox(notifyWin); 1.1261 + } catch (e) { 1.1262 + this.log("Notification bars not available on window"); 1.1263 + } 1.1264 + 1.1265 + return notifyBox; 1.1266 + }, 1.1267 + 1.1268 + 1.1269 + /* 1.1270 + * _repickSelectedLogin 1.1271 + * 1.1272 + * The user might enter a login that isn't the one we prefilled, but 1.1273 + * is the same as some other existing login. So, pick a login with a 1.1274 + * matching username, or return null. 1.1275 + */ 1.1276 + _repickSelectedLogin : function (foundLogins, username) { 1.1277 + for (var i = 0; i < foundLogins.length; i++) 1.1278 + if (foundLogins[i].username == username) 1.1279 + return foundLogins[i]; 1.1280 + return null; 1.1281 + }, 1.1282 + 1.1283 + 1.1284 + /* 1.1285 + * _getLocalizedString 1.1286 + * 1.1287 + * Can be called as: 1.1288 + * _getLocalizedString("key1"); 1.1289 + * _getLocalizedString("key2", ["arg1"]); 1.1290 + * _getLocalizedString("key3", ["arg1", "arg2"]); 1.1291 + * (etc) 1.1292 + * 1.1293 + * Returns the localized string for the specified key, 1.1294 + * formatted if required. 1.1295 + * 1.1296 + */ 1.1297 + _getLocalizedString : function (key, formatArgs) { 1.1298 + if (formatArgs) 1.1299 + return this._strBundle.formatStringFromName( 1.1300 + key, formatArgs, formatArgs.length); 1.1301 + else 1.1302 + return this._strBundle.GetStringFromName(key); 1.1303 + }, 1.1304 + 1.1305 + 1.1306 + /* 1.1307 + * _sanitizeUsername 1.1308 + * 1.1309 + * Sanitizes the specified username, by stripping quotes and truncating if 1.1310 + * it's too long. This helps prevent an evil site from messing with the 1.1311 + * "save password?" prompt too much. 1.1312 + */ 1.1313 + _sanitizeUsername : function (username) { 1.1314 + if (username.length > 30) { 1.1315 + username = username.substring(0, 30); 1.1316 + username += this._ellipsis; 1.1317 + } 1.1318 + return username.replace(/['"]/g, ""); 1.1319 + }, 1.1320 + 1.1321 + 1.1322 + /* 1.1323 + * _getFormattedHostname 1.1324 + * 1.1325 + * The aURI parameter may either be a string uri, or an nsIURI instance. 1.1326 + * 1.1327 + * Returns the hostname to use in a nsILoginInfo object (for example, 1.1328 + * "http://example.com"). 1.1329 + */ 1.1330 + _getFormattedHostname : function (aURI) { 1.1331 + var uri; 1.1332 + if (aURI instanceof Ci.nsIURI) { 1.1333 + uri = aURI; 1.1334 + } else { 1.1335 + uri = Services.io.newURI(aURI, null, null); 1.1336 + } 1.1337 + var scheme = uri.scheme; 1.1338 + 1.1339 + var hostname = scheme + "://" + uri.host; 1.1340 + 1.1341 + // If the URI explicitly specified a port, only include it when 1.1342 + // it's not the default. (We never want "http://foo.com:80") 1.1343 + port = uri.port; 1.1344 + if (port != -1) { 1.1345 + var handler = Services.io.getProtocolHandler(scheme); 1.1346 + if (port != handler.defaultPort) 1.1347 + hostname += ":" + port; 1.1348 + } 1.1349 + 1.1350 + return hostname; 1.1351 + }, 1.1352 + 1.1353 + 1.1354 + /* 1.1355 + * _getShortDisplayHost 1.1356 + * 1.1357 + * Converts a login's hostname field (a URL) to a short string for 1.1358 + * prompting purposes. Eg, "http://foo.com" --> "foo.com", or 1.1359 + * "ftp://www.site.co.uk" --> "site.co.uk". 1.1360 + */ 1.1361 + _getShortDisplayHost: function (aURIString) { 1.1362 + var displayHost; 1.1363 + 1.1364 + var eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"]. 1.1365 + getService(Ci.nsIEffectiveTLDService); 1.1366 + var idnService = Cc["@mozilla.org/network/idn-service;1"]. 1.1367 + getService(Ci.nsIIDNService); 1.1368 + try { 1.1369 + var uri = Services.io.newURI(aURIString, null, null); 1.1370 + var baseDomain = eTLDService.getBaseDomain(uri); 1.1371 + displayHost = idnService.convertToDisplayIDN(baseDomain, {}); 1.1372 + } catch (e) { 1.1373 + this.log("_getShortDisplayHost couldn't process " + aURIString); 1.1374 + } 1.1375 + 1.1376 + if (!displayHost) 1.1377 + displayHost = aURIString; 1.1378 + 1.1379 + return displayHost; 1.1380 + }, 1.1381 + 1.1382 + 1.1383 + /* 1.1384 + * _getAuthTarget 1.1385 + * 1.1386 + * Returns the hostname and realm for which authentication is being 1.1387 + * requested, in the format expected to be used with nsILoginInfo. 1.1388 + */ 1.1389 + _getAuthTarget : function (aChannel, aAuthInfo) { 1.1390 + var hostname, realm; 1.1391 + 1.1392 + // If our proxy is demanding authentication, don't use the 1.1393 + // channel's actual destination. 1.1394 + if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) { 1.1395 + this.log("getAuthTarget is for proxy auth"); 1.1396 + if (!(aChannel instanceof Ci.nsIProxiedChannel)) 1.1397 + throw "proxy auth needs nsIProxiedChannel"; 1.1398 + 1.1399 + var info = aChannel.proxyInfo; 1.1400 + if (!info) 1.1401 + throw "proxy auth needs nsIProxyInfo"; 1.1402 + 1.1403 + // Proxies don't have a scheme, but we'll use "moz-proxy://" 1.1404 + // so that it's more obvious what the login is for. 1.1405 + var idnService = Cc["@mozilla.org/network/idn-service;1"]. 1.1406 + getService(Ci.nsIIDNService); 1.1407 + hostname = "moz-proxy://" + 1.1408 + idnService.convertUTF8toACE(info.host) + 1.1409 + ":" + info.port; 1.1410 + realm = aAuthInfo.realm; 1.1411 + if (!realm) 1.1412 + realm = hostname; 1.1413 + 1.1414 + return [hostname, realm]; 1.1415 + } 1.1416 + 1.1417 + hostname = this._getFormattedHostname(aChannel.URI); 1.1418 + 1.1419 + // If a HTTP WWW-Authenticate header specified a realm, that value 1.1420 + // will be available here. If it wasn't set or wasn't HTTP, we'll use 1.1421 + // the formatted hostname instead. 1.1422 + realm = aAuthInfo.realm; 1.1423 + if (!realm) 1.1424 + realm = hostname; 1.1425 + 1.1426 + return [hostname, realm]; 1.1427 + }, 1.1428 + 1.1429 + 1.1430 + /** 1.1431 + * Returns [username, password] as extracted from aAuthInfo (which 1.1432 + * holds this info after having prompted the user). 1.1433 + * 1.1434 + * If the authentication was for a Windows domain, we'll prepend the 1.1435 + * return username with the domain. (eg, "domain\user") 1.1436 + */ 1.1437 + _GetAuthInfo : function (aAuthInfo) { 1.1438 + var username, password; 1.1439 + 1.1440 + var flags = aAuthInfo.flags; 1.1441 + if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain) 1.1442 + username = aAuthInfo.domain + "\\" + aAuthInfo.username; 1.1443 + else 1.1444 + username = aAuthInfo.username; 1.1445 + 1.1446 + password = aAuthInfo.password; 1.1447 + 1.1448 + return [username, password]; 1.1449 + }, 1.1450 + 1.1451 + 1.1452 + /** 1.1453 + * Given a username (possibly in DOMAIN\user form) and password, parses the 1.1454 + * domain out of the username if necessary and sets domain, username and 1.1455 + * password on the auth information object. 1.1456 + */ 1.1457 + _SetAuthInfo : function (aAuthInfo, username, password) { 1.1458 + var flags = aAuthInfo.flags; 1.1459 + if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) { 1.1460 + // Domain is separated from username by a backslash 1.1461 + var idx = username.indexOf("\\"); 1.1462 + if (idx == -1) { 1.1463 + aAuthInfo.username = username; 1.1464 + } else { 1.1465 + aAuthInfo.domain = username.substring(0, idx); 1.1466 + aAuthInfo.username = username.substring(idx+1); 1.1467 + } 1.1468 + } else { 1.1469 + aAuthInfo.username = username; 1.1470 + } 1.1471 + aAuthInfo.password = password; 1.1472 + }, 1.1473 + 1.1474 + _newAsyncPromptConsumer : function(aCallback, aContext) { 1.1475 + return { 1.1476 + QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), 1.1477 + callback: aCallback, 1.1478 + context: aContext, 1.1479 + cancel: function() { 1.1480 + this.callback.onAuthCancelled(this.context, false); 1.1481 + this.callback = null; 1.1482 + this.context = null; 1.1483 + } 1.1484 + } 1.1485 + } 1.1486 + 1.1487 +}; // end of LoginManagerPrompter implementation 1.1488 + 1.1489 + 1.1490 +var component = [LoginManagerPromptFactory, LoginManagerPrompter]; 1.1491 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component);