Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | |
michael@0 | 6 | const Cc = Components.classes; |
michael@0 | 7 | const Ci = Components.interfaces; |
michael@0 | 8 | const Cr = Components.results; |
michael@0 | 9 | |
michael@0 | 10 | Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 11 | Components.utils.import("resource://gre/modules/Services.jsm"); |
michael@0 | 12 | Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); |
michael@0 | 13 | |
michael@0 | 14 | /* |
michael@0 | 15 | * LoginManagerPromptFactory |
michael@0 | 16 | * |
michael@0 | 17 | * Implements nsIPromptFactory |
michael@0 | 18 | * |
michael@0 | 19 | * Invoked by [toolkit/components/prompts/src/nsPrompter.js] |
michael@0 | 20 | */ |
michael@0 | 21 | function LoginManagerPromptFactory() { |
michael@0 | 22 | Services.obs.addObserver(this, "quit-application-granted", true); |
michael@0 | 23 | Services.obs.addObserver(this, "passwordmgr-crypto-login", true); |
michael@0 | 24 | Services.obs.addObserver(this, "passwordmgr-crypto-loginCanceled", true); |
michael@0 | 25 | } |
michael@0 | 26 | |
michael@0 | 27 | LoginManagerPromptFactory.prototype = { |
michael@0 | 28 | |
michael@0 | 29 | classID : Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"), |
michael@0 | 30 | QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIObserver, Ci.nsISupportsWeakReference]), |
michael@0 | 31 | |
michael@0 | 32 | _debug : false, |
michael@0 | 33 | _asyncPrompts : {}, |
michael@0 | 34 | _asyncPromptInProgress : false, |
michael@0 | 35 | |
michael@0 | 36 | observe : function (subject, topic, data) { |
michael@0 | 37 | this.log("Observed: " + topic); |
michael@0 | 38 | if (topic == "quit-application-granted") { |
michael@0 | 39 | this._cancelPendingPrompts(); |
michael@0 | 40 | } else if (topic == "passwordmgr-crypto-login") { |
michael@0 | 41 | // Start processing the deferred prompters. |
michael@0 | 42 | this._doAsyncPrompt(); |
michael@0 | 43 | } else if (topic == "passwordmgr-crypto-loginCanceled") { |
michael@0 | 44 | // User canceled a Master Password prompt, so go ahead and cancel |
michael@0 | 45 | // all pending auth prompts to avoid nagging over and over. |
michael@0 | 46 | this._cancelPendingPrompts(); |
michael@0 | 47 | } |
michael@0 | 48 | }, |
michael@0 | 49 | |
michael@0 | 50 | getPrompt : function (aWindow, aIID) { |
michael@0 | 51 | var prefBranch = Services.prefs.getBranch("signon."); |
michael@0 | 52 | this._debug = prefBranch.getBoolPref("debug"); |
michael@0 | 53 | |
michael@0 | 54 | var prompt = new LoginManagerPrompter().QueryInterface(aIID); |
michael@0 | 55 | prompt.init(aWindow, this); |
michael@0 | 56 | return prompt; |
michael@0 | 57 | }, |
michael@0 | 58 | |
michael@0 | 59 | _doAsyncPrompt : function() { |
michael@0 | 60 | if (this._asyncPromptInProgress) { |
michael@0 | 61 | this.log("_doAsyncPrompt bypassed, already in progress"); |
michael@0 | 62 | return; |
michael@0 | 63 | } |
michael@0 | 64 | |
michael@0 | 65 | // Find the first prompt key we have in the queue |
michael@0 | 66 | var hashKey = null; |
michael@0 | 67 | for (hashKey in this._asyncPrompts) |
michael@0 | 68 | break; |
michael@0 | 69 | |
michael@0 | 70 | if (!hashKey) { |
michael@0 | 71 | this.log("_doAsyncPrompt:run bypassed, no prompts in the queue"); |
michael@0 | 72 | return; |
michael@0 | 73 | } |
michael@0 | 74 | |
michael@0 | 75 | // If login manger has logins for this host, defer prompting if we're |
michael@0 | 76 | // already waiting on a master password entry. |
michael@0 | 77 | var prompt = this._asyncPrompts[hashKey]; |
michael@0 | 78 | var prompter = prompt.prompter; |
michael@0 | 79 | var [hostname, httpRealm] = prompter._getAuthTarget(prompt.channel, prompt.authInfo); |
michael@0 | 80 | var hasLogins = (prompter._pwmgr.countLogins(hostname, null, httpRealm) > 0); |
michael@0 | 81 | if (hasLogins && prompter._pwmgr.uiBusy) { |
michael@0 | 82 | this.log("_doAsyncPrompt:run bypassed, master password UI busy"); |
michael@0 | 83 | return; |
michael@0 | 84 | } |
michael@0 | 85 | |
michael@0 | 86 | this._asyncPromptInProgress = true; |
michael@0 | 87 | prompt.inProgress = true; |
michael@0 | 88 | |
michael@0 | 89 | var self = this; |
michael@0 | 90 | |
michael@0 | 91 | var runnable = { |
michael@0 | 92 | run : function() { |
michael@0 | 93 | var ok = false; |
michael@0 | 94 | try { |
michael@0 | 95 | self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'"); |
michael@0 | 96 | ok = prompter.promptAuth(prompt.channel, |
michael@0 | 97 | prompt.level, |
michael@0 | 98 | prompt.authInfo); |
michael@0 | 99 | } catch (e if (e instanceof Components.Exception) && |
michael@0 | 100 | e.result == Cr.NS_ERROR_NOT_AVAILABLE) { |
michael@0 | 101 | self.log("_doAsyncPrompt:run bypassed, UI is not available in this context"); |
michael@0 | 102 | } catch (e) { |
michael@0 | 103 | Components.utils.reportError("LoginManagerPrompter: " + |
michael@0 | 104 | "_doAsyncPrompt:run: " + e + "\n"); |
michael@0 | 105 | } |
michael@0 | 106 | |
michael@0 | 107 | delete self._asyncPrompts[hashKey]; |
michael@0 | 108 | prompt.inProgress = false; |
michael@0 | 109 | self._asyncPromptInProgress = false; |
michael@0 | 110 | |
michael@0 | 111 | for each (var consumer in prompt.consumers) { |
michael@0 | 112 | if (!consumer.callback) |
michael@0 | 113 | // Not having a callback means that consumer didn't provide it |
michael@0 | 114 | // or canceled the notification |
michael@0 | 115 | continue; |
michael@0 | 116 | |
michael@0 | 117 | self.log("Calling back to " + consumer.callback + " ok=" + ok); |
michael@0 | 118 | try { |
michael@0 | 119 | if (ok) |
michael@0 | 120 | consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo); |
michael@0 | 121 | else |
michael@0 | 122 | consumer.callback.onAuthCancelled(consumer.context, true); |
michael@0 | 123 | } catch (e) { /* Throw away exceptions caused by callback */ } |
michael@0 | 124 | } |
michael@0 | 125 | self._doAsyncPrompt(); |
michael@0 | 126 | } |
michael@0 | 127 | } |
michael@0 | 128 | |
michael@0 | 129 | Services.tm.mainThread.dispatch(runnable, Ci.nsIThread.DISPATCH_NORMAL); |
michael@0 | 130 | this.log("_doAsyncPrompt:run dispatched"); |
michael@0 | 131 | }, |
michael@0 | 132 | |
michael@0 | 133 | |
michael@0 | 134 | _cancelPendingPrompts : function() { |
michael@0 | 135 | this.log("Canceling all pending prompts..."); |
michael@0 | 136 | var asyncPrompts = this._asyncPrompts; |
michael@0 | 137 | this.__proto__._asyncPrompts = {}; |
michael@0 | 138 | |
michael@0 | 139 | for each (var prompt in asyncPrompts) { |
michael@0 | 140 | // Watch out! If this prompt is currently prompting, let it handle |
michael@0 | 141 | // notifying the callbacks of success/failure, since it's already |
michael@0 | 142 | // asking the user for input. Reusing a callback can be crashy. |
michael@0 | 143 | if (prompt.inProgress) { |
michael@0 | 144 | this.log("skipping a prompt in progress"); |
michael@0 | 145 | continue; |
michael@0 | 146 | } |
michael@0 | 147 | |
michael@0 | 148 | for each (var consumer in prompt.consumers) { |
michael@0 | 149 | if (!consumer.callback) |
michael@0 | 150 | continue; |
michael@0 | 151 | |
michael@0 | 152 | this.log("Canceling async auth prompt callback " + consumer.callback); |
michael@0 | 153 | try { |
michael@0 | 154 | consumer.callback.onAuthCancelled(consumer.context, true); |
michael@0 | 155 | } catch (e) { /* Just ignore exceptions from the callback */ } |
michael@0 | 156 | } |
michael@0 | 157 | } |
michael@0 | 158 | }, |
michael@0 | 159 | |
michael@0 | 160 | |
michael@0 | 161 | log : function (message) { |
michael@0 | 162 | if (!this._debug) |
michael@0 | 163 | return; |
michael@0 | 164 | |
michael@0 | 165 | dump("Pwmgr PromptFactory: " + message + "\n"); |
michael@0 | 166 | Services.console.logStringMessage("Pwmgr PrompFactory: " + message); |
michael@0 | 167 | } |
michael@0 | 168 | }; // end of LoginManagerPromptFactory implementation |
michael@0 | 169 | |
michael@0 | 170 | |
michael@0 | 171 | |
michael@0 | 172 | |
michael@0 | 173 | /* ==================== LoginManagerPrompter ==================== */ |
michael@0 | 174 | |
michael@0 | 175 | |
michael@0 | 176 | |
michael@0 | 177 | |
michael@0 | 178 | /* |
michael@0 | 179 | * LoginManagerPrompter |
michael@0 | 180 | * |
michael@0 | 181 | * Implements interfaces for prompting the user to enter/save/change auth info. |
michael@0 | 182 | * |
michael@0 | 183 | * nsIAuthPrompt: Used by SeaMonkey, Thunderbird, but not Firefox. |
michael@0 | 184 | * |
michael@0 | 185 | * nsIAuthPrompt2: Is invoked by a channel for protocol-based authentication |
michael@0 | 186 | * (eg HTTP Authenticate, FTP login). |
michael@0 | 187 | * |
michael@0 | 188 | * nsILoginManagerPrompter: Used by Login Manager for saving/changing logins |
michael@0 | 189 | * found in HTML forms. |
michael@0 | 190 | */ |
michael@0 | 191 | function LoginManagerPrompter() {} |
michael@0 | 192 | |
michael@0 | 193 | LoginManagerPrompter.prototype = { |
michael@0 | 194 | |
michael@0 | 195 | classID : Components.ID("{8aa66d77-1bbb-45a6-991e-b8f47751c291}"), |
michael@0 | 196 | QueryInterface : XPCOMUtils.generateQI([Ci.nsIAuthPrompt, |
michael@0 | 197 | Ci.nsIAuthPrompt2, |
michael@0 | 198 | Ci.nsILoginManagerPrompter]), |
michael@0 | 199 | |
michael@0 | 200 | _factory : null, |
michael@0 | 201 | _window : null, |
michael@0 | 202 | _debug : false, // mirrors signon.debug |
michael@0 | 203 | |
michael@0 | 204 | __pwmgr : null, // Password Manager service |
michael@0 | 205 | get _pwmgr() { |
michael@0 | 206 | if (!this.__pwmgr) |
michael@0 | 207 | this.__pwmgr = Cc["@mozilla.org/login-manager;1"]. |
michael@0 | 208 | getService(Ci.nsILoginManager); |
michael@0 | 209 | return this.__pwmgr; |
michael@0 | 210 | }, |
michael@0 | 211 | |
michael@0 | 212 | __promptService : null, // Prompt service for user interaction |
michael@0 | 213 | get _promptService() { |
michael@0 | 214 | if (!this.__promptService) |
michael@0 | 215 | this.__promptService = |
michael@0 | 216 | Cc["@mozilla.org/embedcomp/prompt-service;1"]. |
michael@0 | 217 | getService(Ci.nsIPromptService2); |
michael@0 | 218 | return this.__promptService; |
michael@0 | 219 | }, |
michael@0 | 220 | |
michael@0 | 221 | |
michael@0 | 222 | __strBundle : null, // String bundle for L10N |
michael@0 | 223 | get _strBundle() { |
michael@0 | 224 | if (!this.__strBundle) { |
michael@0 | 225 | var bunService = Cc["@mozilla.org/intl/stringbundle;1"]. |
michael@0 | 226 | getService(Ci.nsIStringBundleService); |
michael@0 | 227 | this.__strBundle = bunService.createBundle( |
michael@0 | 228 | "chrome://passwordmgr/locale/passwordmgr.properties"); |
michael@0 | 229 | if (!this.__strBundle) |
michael@0 | 230 | throw "String bundle for Login Manager not present!"; |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | return this.__strBundle; |
michael@0 | 234 | }, |
michael@0 | 235 | |
michael@0 | 236 | |
michael@0 | 237 | __ellipsis : null, |
michael@0 | 238 | get _ellipsis() { |
michael@0 | 239 | if (!this.__ellipsis) { |
michael@0 | 240 | this.__ellipsis = "\u2026"; |
michael@0 | 241 | try { |
michael@0 | 242 | this.__ellipsis = Services.prefs.getComplexValue( |
michael@0 | 243 | "intl.ellipsis", Ci.nsIPrefLocalizedString).data; |
michael@0 | 244 | } catch (e) { } |
michael@0 | 245 | } |
michael@0 | 246 | return this.__ellipsis; |
michael@0 | 247 | }, |
michael@0 | 248 | |
michael@0 | 249 | |
michael@0 | 250 | // Whether we are in private browsing mode |
michael@0 | 251 | get _inPrivateBrowsing() { |
michael@0 | 252 | if (this._window) { |
michael@0 | 253 | return PrivateBrowsingUtils.isWindowPrivate(this._window); |
michael@0 | 254 | } else { |
michael@0 | 255 | // If we don't that we're in private browsing mode if the caller did |
michael@0 | 256 | // not provide a window. The callers which really care about this |
michael@0 | 257 | // will indeed pass down a window to us, and for those who don't, |
michael@0 | 258 | // we can just assume that we don't want to save the entered login |
michael@0 | 259 | // information. |
michael@0 | 260 | return true; |
michael@0 | 261 | } |
michael@0 | 262 | }, |
michael@0 | 263 | |
michael@0 | 264 | |
michael@0 | 265 | /* |
michael@0 | 266 | * log |
michael@0 | 267 | * |
michael@0 | 268 | * Internal function for logging debug messages to the Error Console window. |
michael@0 | 269 | */ |
michael@0 | 270 | log : function (message) { |
michael@0 | 271 | if (!this._debug) |
michael@0 | 272 | return; |
michael@0 | 273 | |
michael@0 | 274 | dump("Pwmgr Prompter: " + message + "\n"); |
michael@0 | 275 | Services.console.logStringMessage("Pwmgr Prompter: " + message); |
michael@0 | 276 | }, |
michael@0 | 277 | |
michael@0 | 278 | |
michael@0 | 279 | |
michael@0 | 280 | |
michael@0 | 281 | /* ---------- nsIAuthPrompt prompts ---------- */ |
michael@0 | 282 | |
michael@0 | 283 | |
michael@0 | 284 | /* |
michael@0 | 285 | * prompt |
michael@0 | 286 | * |
michael@0 | 287 | * Wrapper around the prompt service prompt. Saving random fields here |
michael@0 | 288 | * doesn't really make sense and therefore isn't implemented. |
michael@0 | 289 | */ |
michael@0 | 290 | prompt : function (aDialogTitle, aText, aPasswordRealm, |
michael@0 | 291 | aSavePassword, aDefaultText, aResult) { |
michael@0 | 292 | if (aSavePassword != Ci.nsIAuthPrompt.SAVE_PASSWORD_NEVER) |
michael@0 | 293 | throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 294 | |
michael@0 | 295 | this.log("===== prompt() called ====="); |
michael@0 | 296 | |
michael@0 | 297 | if (aDefaultText) { |
michael@0 | 298 | aResult.value = aDefaultText; |
michael@0 | 299 | } |
michael@0 | 300 | |
michael@0 | 301 | return this._promptService.prompt(this._window, |
michael@0 | 302 | aDialogTitle, aText, aResult, null, {}); |
michael@0 | 303 | }, |
michael@0 | 304 | |
michael@0 | 305 | |
michael@0 | 306 | /* |
michael@0 | 307 | * promptUsernameAndPassword |
michael@0 | 308 | * |
michael@0 | 309 | * Looks up a username and password in the database. Will prompt the user |
michael@0 | 310 | * with a dialog, even if a username and password are found. |
michael@0 | 311 | */ |
michael@0 | 312 | promptUsernameAndPassword : function (aDialogTitle, aText, aPasswordRealm, |
michael@0 | 313 | aSavePassword, aUsername, aPassword) { |
michael@0 | 314 | this.log("===== promptUsernameAndPassword() called ====="); |
michael@0 | 315 | |
michael@0 | 316 | if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION) |
michael@0 | 317 | throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 318 | |
michael@0 | 319 | var selectedLogin = null; |
michael@0 | 320 | var checkBox = { value : false }; |
michael@0 | 321 | var checkBoxLabel = null; |
michael@0 | 322 | var [hostname, realm, unused] = this._getRealmInfo(aPasswordRealm); |
michael@0 | 323 | |
michael@0 | 324 | // If hostname is null, we can't save this login. |
michael@0 | 325 | if (hostname) { |
michael@0 | 326 | var canRememberLogin; |
michael@0 | 327 | if (this._inPrivateBrowsing) |
michael@0 | 328 | canRememberLogin = false; |
michael@0 | 329 | else |
michael@0 | 330 | canRememberLogin = (aSavePassword == |
michael@0 | 331 | Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) && |
michael@0 | 332 | this._pwmgr.getLoginSavingEnabled(hostname); |
michael@0 | 333 | |
michael@0 | 334 | // if checkBoxLabel is null, the checkbox won't be shown at all. |
michael@0 | 335 | if (canRememberLogin) |
michael@0 | 336 | checkBoxLabel = this._getLocalizedString("rememberPassword"); |
michael@0 | 337 | |
michael@0 | 338 | // Look for existing logins. |
michael@0 | 339 | var foundLogins = this._pwmgr.findLogins({}, hostname, null, |
michael@0 | 340 | realm); |
michael@0 | 341 | |
michael@0 | 342 | // XXX Like the original code, we can't deal with multiple |
michael@0 | 343 | // account selection. (bug 227632) |
michael@0 | 344 | if (foundLogins.length > 0) { |
michael@0 | 345 | selectedLogin = foundLogins[0]; |
michael@0 | 346 | |
michael@0 | 347 | // If the caller provided a username, try to use it. If they |
michael@0 | 348 | // provided only a password, this will try to find a password-only |
michael@0 | 349 | // login (or return null if none exists). |
michael@0 | 350 | if (aUsername.value) |
michael@0 | 351 | selectedLogin = this._repickSelectedLogin(foundLogins, |
michael@0 | 352 | aUsername.value); |
michael@0 | 353 | |
michael@0 | 354 | if (selectedLogin) { |
michael@0 | 355 | checkBox.value = true; |
michael@0 | 356 | aUsername.value = selectedLogin.username; |
michael@0 | 357 | // If the caller provided a password, prefer it. |
michael@0 | 358 | if (!aPassword.value) |
michael@0 | 359 | aPassword.value = selectedLogin.password; |
michael@0 | 360 | } |
michael@0 | 361 | } |
michael@0 | 362 | } |
michael@0 | 363 | |
michael@0 | 364 | var ok = this._promptService.promptUsernameAndPassword(this._window, |
michael@0 | 365 | aDialogTitle, aText, aUsername, aPassword, |
michael@0 | 366 | checkBoxLabel, checkBox); |
michael@0 | 367 | |
michael@0 | 368 | if (!ok || !checkBox.value || !hostname) |
michael@0 | 369 | return ok; |
michael@0 | 370 | |
michael@0 | 371 | if (!aPassword.value) { |
michael@0 | 372 | this.log("No password entered, so won't offer to save."); |
michael@0 | 373 | return ok; |
michael@0 | 374 | } |
michael@0 | 375 | |
michael@0 | 376 | // XXX We can't prompt with multiple logins yet (bug 227632), so |
michael@0 | 377 | // the entered login might correspond to an existing login |
michael@0 | 378 | // other than the one we originally selected. |
michael@0 | 379 | selectedLogin = this._repickSelectedLogin(foundLogins, aUsername.value); |
michael@0 | 380 | |
michael@0 | 381 | // If we didn't find an existing login, or if the username |
michael@0 | 382 | // changed, save as a new login. |
michael@0 | 383 | if (!selectedLogin) { |
michael@0 | 384 | // add as new |
michael@0 | 385 | this.log("New login seen for " + realm); |
michael@0 | 386 | var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. |
michael@0 | 387 | createInstance(Ci.nsILoginInfo); |
michael@0 | 388 | newLogin.init(hostname, null, realm, |
michael@0 | 389 | aUsername.value, aPassword.value, "", ""); |
michael@0 | 390 | this._pwmgr.addLogin(newLogin); |
michael@0 | 391 | } else if (aPassword.value != selectedLogin.password) { |
michael@0 | 392 | // update password |
michael@0 | 393 | this.log("Updating password for " + realm); |
michael@0 | 394 | this._updateLogin(selectedLogin, aPassword.value); |
michael@0 | 395 | } else { |
michael@0 | 396 | this.log("Login unchanged, no further action needed."); |
michael@0 | 397 | this._updateLogin(selectedLogin); |
michael@0 | 398 | } |
michael@0 | 399 | |
michael@0 | 400 | return ok; |
michael@0 | 401 | }, |
michael@0 | 402 | |
michael@0 | 403 | |
michael@0 | 404 | /* |
michael@0 | 405 | * promptPassword |
michael@0 | 406 | * |
michael@0 | 407 | * If a password is found in the database for the password realm, it is |
michael@0 | 408 | * returned straight away without displaying a dialog. |
michael@0 | 409 | * |
michael@0 | 410 | * If a password is not found in the database, the user will be prompted |
michael@0 | 411 | * with a dialog with a text field and ok/cancel buttons. If the user |
michael@0 | 412 | * allows it, then the password will be saved in the database. |
michael@0 | 413 | */ |
michael@0 | 414 | promptPassword : function (aDialogTitle, aText, aPasswordRealm, |
michael@0 | 415 | aSavePassword, aPassword) { |
michael@0 | 416 | this.log("===== promptPassword called() ====="); |
michael@0 | 417 | |
michael@0 | 418 | if (aSavePassword == Ci.nsIAuthPrompt.SAVE_PASSWORD_FOR_SESSION) |
michael@0 | 419 | throw Components.results.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 420 | |
michael@0 | 421 | var checkBox = { value : false }; |
michael@0 | 422 | var checkBoxLabel = null; |
michael@0 | 423 | var [hostname, realm, username] = this._getRealmInfo(aPasswordRealm); |
michael@0 | 424 | |
michael@0 | 425 | username = decodeURIComponent(username); |
michael@0 | 426 | |
michael@0 | 427 | // If hostname is null, we can't save this login. |
michael@0 | 428 | if (hostname && !this._inPrivateBrowsing) { |
michael@0 | 429 | var canRememberLogin = (aSavePassword == |
michael@0 | 430 | Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY) && |
michael@0 | 431 | this._pwmgr.getLoginSavingEnabled(hostname); |
michael@0 | 432 | |
michael@0 | 433 | // if checkBoxLabel is null, the checkbox won't be shown at all. |
michael@0 | 434 | if (canRememberLogin) |
michael@0 | 435 | checkBoxLabel = this._getLocalizedString("rememberPassword"); |
michael@0 | 436 | |
michael@0 | 437 | if (!aPassword.value) { |
michael@0 | 438 | // Look for existing logins. |
michael@0 | 439 | var foundLogins = this._pwmgr.findLogins({}, hostname, null, |
michael@0 | 440 | realm); |
michael@0 | 441 | |
michael@0 | 442 | // XXX Like the original code, we can't deal with multiple |
michael@0 | 443 | // account selection (bug 227632). We can deal with finding the |
michael@0 | 444 | // account based on the supplied username - but in this case we'll |
michael@0 | 445 | // just return the first match. |
michael@0 | 446 | for (var i = 0; i < foundLogins.length; ++i) { |
michael@0 | 447 | if (foundLogins[i].username == username) { |
michael@0 | 448 | aPassword.value = foundLogins[i].password; |
michael@0 | 449 | // wallet returned straight away, so this mimics that code |
michael@0 | 450 | return true; |
michael@0 | 451 | } |
michael@0 | 452 | } |
michael@0 | 453 | } |
michael@0 | 454 | } |
michael@0 | 455 | |
michael@0 | 456 | var ok = this._promptService.promptPassword(this._window, aDialogTitle, |
michael@0 | 457 | aText, aPassword, |
michael@0 | 458 | checkBoxLabel, checkBox); |
michael@0 | 459 | |
michael@0 | 460 | if (ok && checkBox.value && hostname && aPassword.value) { |
michael@0 | 461 | var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. |
michael@0 | 462 | createInstance(Ci.nsILoginInfo); |
michael@0 | 463 | newLogin.init(hostname, null, realm, username, |
michael@0 | 464 | aPassword.value, "", ""); |
michael@0 | 465 | |
michael@0 | 466 | this.log("New login seen for " + realm); |
michael@0 | 467 | |
michael@0 | 468 | this._pwmgr.addLogin(newLogin); |
michael@0 | 469 | } |
michael@0 | 470 | |
michael@0 | 471 | return ok; |
michael@0 | 472 | }, |
michael@0 | 473 | |
michael@0 | 474 | /* ---------- nsIAuthPrompt helpers ---------- */ |
michael@0 | 475 | |
michael@0 | 476 | |
michael@0 | 477 | /** |
michael@0 | 478 | * Given aRealmString, such as "http://user@example.com/foo", returns an |
michael@0 | 479 | * array of: |
michael@0 | 480 | * - the formatted hostname |
michael@0 | 481 | * - the realm (hostname + path) |
michael@0 | 482 | * - the username, if present |
michael@0 | 483 | * |
michael@0 | 484 | * If aRealmString is in the format produced by NS_GetAuthKey for HTTP[S] |
michael@0 | 485 | * channels, e.g. "example.com:80 (httprealm)", null is returned for all |
michael@0 | 486 | * arguments to let callers know the login can't be saved because we don't |
michael@0 | 487 | * know whether it's http or https. |
michael@0 | 488 | */ |
michael@0 | 489 | _getRealmInfo : function (aRealmString) { |
michael@0 | 490 | var httpRealm = /^.+ \(.+\)$/; |
michael@0 | 491 | if (httpRealm.test(aRealmString)) |
michael@0 | 492 | return [null, null, null]; |
michael@0 | 493 | |
michael@0 | 494 | var uri = Services.io.newURI(aRealmString, null, null); |
michael@0 | 495 | var pathname = ""; |
michael@0 | 496 | |
michael@0 | 497 | if (uri.path != "/") |
michael@0 | 498 | pathname = uri.path; |
michael@0 | 499 | |
michael@0 | 500 | var formattedHostname = this._getFormattedHostname(uri); |
michael@0 | 501 | |
michael@0 | 502 | return [formattedHostname, formattedHostname + pathname, uri.username]; |
michael@0 | 503 | }, |
michael@0 | 504 | |
michael@0 | 505 | /* ---------- nsIAuthPrompt2 prompts ---------- */ |
michael@0 | 506 | |
michael@0 | 507 | |
michael@0 | 508 | |
michael@0 | 509 | |
michael@0 | 510 | /* |
michael@0 | 511 | * promptAuth |
michael@0 | 512 | * |
michael@0 | 513 | * Implementation of nsIAuthPrompt2. |
michael@0 | 514 | * |
michael@0 | 515 | * nsIChannel aChannel |
michael@0 | 516 | * int aLevel |
michael@0 | 517 | * nsIAuthInformation aAuthInfo |
michael@0 | 518 | */ |
michael@0 | 519 | promptAuth : function (aChannel, aLevel, aAuthInfo) { |
michael@0 | 520 | var selectedLogin = null; |
michael@0 | 521 | var checkbox = { value : false }; |
michael@0 | 522 | var checkboxLabel = null; |
michael@0 | 523 | var epicfail = false; |
michael@0 | 524 | var canAutologin = false; |
michael@0 | 525 | |
michael@0 | 526 | try { |
michael@0 | 527 | |
michael@0 | 528 | this.log("===== promptAuth called ====="); |
michael@0 | 529 | |
michael@0 | 530 | // If the user submits a login but it fails, we need to remove the |
michael@0 | 531 | // notification bar that was displayed. Conveniently, the user will |
michael@0 | 532 | // be prompted for authentication again, which brings us here. |
michael@0 | 533 | this._removeLoginNotifications(); |
michael@0 | 534 | |
michael@0 | 535 | var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo); |
michael@0 | 536 | |
michael@0 | 537 | |
michael@0 | 538 | // Looks for existing logins to prefill the prompt with. |
michael@0 | 539 | var foundLogins = this._pwmgr.findLogins({}, |
michael@0 | 540 | hostname, null, httpRealm); |
michael@0 | 541 | this.log("found " + foundLogins.length + " matching logins."); |
michael@0 | 542 | |
michael@0 | 543 | // XXX Can't select from multiple accounts yet. (bug 227632) |
michael@0 | 544 | if (foundLogins.length > 0) { |
michael@0 | 545 | selectedLogin = foundLogins[0]; |
michael@0 | 546 | this._SetAuthInfo(aAuthInfo, selectedLogin.username, |
michael@0 | 547 | selectedLogin.password); |
michael@0 | 548 | |
michael@0 | 549 | // Allow automatic proxy login |
michael@0 | 550 | if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY && |
michael@0 | 551 | !(aAuthInfo.flags & Ci.nsIAuthInformation.PREVIOUS_FAILED) && |
michael@0 | 552 | Services.prefs.getBoolPref("signon.autologin.proxy") && |
michael@0 | 553 | !this._inPrivateBrowsing) { |
michael@0 | 554 | |
michael@0 | 555 | this.log("Autologin enabled, skipping auth prompt."); |
michael@0 | 556 | canAutologin = true; |
michael@0 | 557 | } |
michael@0 | 558 | |
michael@0 | 559 | checkbox.value = true; |
michael@0 | 560 | } |
michael@0 | 561 | |
michael@0 | 562 | var canRememberLogin = this._pwmgr.getLoginSavingEnabled(hostname); |
michael@0 | 563 | if (this._inPrivateBrowsing) |
michael@0 | 564 | canRememberLogin = false; |
michael@0 | 565 | |
michael@0 | 566 | // if checkboxLabel is null, the checkbox won't be shown at all. |
michael@0 | 567 | var notifyBox = this._getNotifyBox(); |
michael@0 | 568 | if (canRememberLogin && !notifyBox) |
michael@0 | 569 | checkboxLabel = this._getLocalizedString("rememberPassword"); |
michael@0 | 570 | } catch (e) { |
michael@0 | 571 | // Ignore any errors and display the prompt anyway. |
michael@0 | 572 | epicfail = true; |
michael@0 | 573 | Components.utils.reportError("LoginManagerPrompter: " + |
michael@0 | 574 | "Epic fail in promptAuth: " + e + "\n"); |
michael@0 | 575 | } |
michael@0 | 576 | |
michael@0 | 577 | var ok = canAutologin || |
michael@0 | 578 | this._promptService.promptAuth(this._window, |
michael@0 | 579 | aChannel, aLevel, aAuthInfo, |
michael@0 | 580 | checkboxLabel, checkbox); |
michael@0 | 581 | |
michael@0 | 582 | // If there's a notification box, use it to allow the user to |
michael@0 | 583 | // determine if the login should be saved. If there isn't a |
michael@0 | 584 | // notification box, only save the login if the user set the |
michael@0 | 585 | // checkbox to do so. |
michael@0 | 586 | var rememberLogin = notifyBox ? canRememberLogin : checkbox.value; |
michael@0 | 587 | if (!ok || !rememberLogin || epicfail) |
michael@0 | 588 | return ok; |
michael@0 | 589 | |
michael@0 | 590 | try { |
michael@0 | 591 | var [username, password] = this._GetAuthInfo(aAuthInfo); |
michael@0 | 592 | |
michael@0 | 593 | if (!password) { |
michael@0 | 594 | this.log("No password entered, so won't offer to save."); |
michael@0 | 595 | return ok; |
michael@0 | 596 | } |
michael@0 | 597 | |
michael@0 | 598 | // XXX We can't prompt with multiple logins yet (bug 227632), so |
michael@0 | 599 | // the entered login might correspond to an existing login |
michael@0 | 600 | // other than the one we originally selected. |
michael@0 | 601 | selectedLogin = this._repickSelectedLogin(foundLogins, username); |
michael@0 | 602 | |
michael@0 | 603 | // If we didn't find an existing login, or if the username |
michael@0 | 604 | // changed, save as a new login. |
michael@0 | 605 | if (!selectedLogin) { |
michael@0 | 606 | this.log("New login seen for " + username + |
michael@0 | 607 | " @ " + hostname + " (" + httpRealm + ")"); |
michael@0 | 608 | |
michael@0 | 609 | var newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]. |
michael@0 | 610 | createInstance(Ci.nsILoginInfo); |
michael@0 | 611 | newLogin.init(hostname, null, httpRealm, |
michael@0 | 612 | username, password, "", ""); |
michael@0 | 613 | var notifyObj = this._getPopupNote() || notifyBox; |
michael@0 | 614 | if (notifyObj) |
michael@0 | 615 | this._showSaveLoginNotification(notifyObj, newLogin); |
michael@0 | 616 | else |
michael@0 | 617 | this._pwmgr.addLogin(newLogin); |
michael@0 | 618 | |
michael@0 | 619 | } else if (password != selectedLogin.password) { |
michael@0 | 620 | |
michael@0 | 621 | this.log("Updating password for " + username + |
michael@0 | 622 | " @ " + hostname + " (" + httpRealm + ")"); |
michael@0 | 623 | var notifyObj = this._getPopupNote() || notifyBox; |
michael@0 | 624 | if (notifyObj) |
michael@0 | 625 | this._showChangeLoginNotification(notifyObj, |
michael@0 | 626 | selectedLogin, password); |
michael@0 | 627 | else |
michael@0 | 628 | this._updateLogin(selectedLogin, password); |
michael@0 | 629 | |
michael@0 | 630 | } else { |
michael@0 | 631 | this.log("Login unchanged, no further action needed."); |
michael@0 | 632 | this._updateLogin(selectedLogin); |
michael@0 | 633 | } |
michael@0 | 634 | } catch (e) { |
michael@0 | 635 | Components.utils.reportError("LoginManagerPrompter: " + |
michael@0 | 636 | "Fail2 in promptAuth: " + e + "\n"); |
michael@0 | 637 | } |
michael@0 | 638 | |
michael@0 | 639 | return ok; |
michael@0 | 640 | }, |
michael@0 | 641 | |
michael@0 | 642 | asyncPromptAuth : function (aChannel, aCallback, aContext, aLevel, aAuthInfo) { |
michael@0 | 643 | var cancelable = null; |
michael@0 | 644 | |
michael@0 | 645 | try { |
michael@0 | 646 | this.log("===== asyncPromptAuth called ====="); |
michael@0 | 647 | |
michael@0 | 648 | // If the user submits a login but it fails, we need to remove the |
michael@0 | 649 | // notification bar that was displayed. Conveniently, the user will |
michael@0 | 650 | // be prompted for authentication again, which brings us here. |
michael@0 | 651 | this._removeLoginNotifications(); |
michael@0 | 652 | |
michael@0 | 653 | cancelable = this._newAsyncPromptConsumer(aCallback, aContext); |
michael@0 | 654 | |
michael@0 | 655 | var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo); |
michael@0 | 656 | |
michael@0 | 657 | var hashKey = aLevel + "|" + hostname + "|" + httpRealm; |
michael@0 | 658 | this.log("Async prompt key = " + hashKey); |
michael@0 | 659 | var asyncPrompt = this._factory._asyncPrompts[hashKey]; |
michael@0 | 660 | if (asyncPrompt) { |
michael@0 | 661 | this.log("Prompt bound to an existing one in the queue, callback = " + aCallback); |
michael@0 | 662 | asyncPrompt.consumers.push(cancelable); |
michael@0 | 663 | return cancelable; |
michael@0 | 664 | } |
michael@0 | 665 | |
michael@0 | 666 | this.log("Adding new prompt to the queue, callback = " + aCallback); |
michael@0 | 667 | asyncPrompt = { |
michael@0 | 668 | consumers: [cancelable], |
michael@0 | 669 | channel: aChannel, |
michael@0 | 670 | authInfo: aAuthInfo, |
michael@0 | 671 | level: aLevel, |
michael@0 | 672 | inProgress : false, |
michael@0 | 673 | prompter: this |
michael@0 | 674 | } |
michael@0 | 675 | |
michael@0 | 676 | this._factory._asyncPrompts[hashKey] = asyncPrompt; |
michael@0 | 677 | this._factory._doAsyncPrompt(); |
michael@0 | 678 | } |
michael@0 | 679 | catch (e) { |
michael@0 | 680 | Components.utils.reportError("LoginManagerPrompter: " + |
michael@0 | 681 | "asyncPromptAuth: " + e + "\nFalling back to promptAuth\n"); |
michael@0 | 682 | // Fail the prompt operation to let the consumer fall back |
michael@0 | 683 | // to synchronous promptAuth method |
michael@0 | 684 | throw e; |
michael@0 | 685 | } |
michael@0 | 686 | |
michael@0 | 687 | return cancelable; |
michael@0 | 688 | }, |
michael@0 | 689 | |
michael@0 | 690 | |
michael@0 | 691 | |
michael@0 | 692 | |
michael@0 | 693 | /* ---------- nsILoginManagerPrompter prompts ---------- */ |
michael@0 | 694 | |
michael@0 | 695 | |
michael@0 | 696 | |
michael@0 | 697 | |
michael@0 | 698 | /* |
michael@0 | 699 | * init |
michael@0 | 700 | * |
michael@0 | 701 | */ |
michael@0 | 702 | init : function (aWindow, aFactory) { |
michael@0 | 703 | this._window = aWindow; |
michael@0 | 704 | this._factory = aFactory || null; |
michael@0 | 705 | |
michael@0 | 706 | var prefBranch = Services.prefs.getBranch("signon."); |
michael@0 | 707 | this._debug = prefBranch.getBoolPref("debug"); |
michael@0 | 708 | this.log("===== initialized ====="); |
michael@0 | 709 | }, |
michael@0 | 710 | |
michael@0 | 711 | |
michael@0 | 712 | /* |
michael@0 | 713 | * promptToSavePassword |
michael@0 | 714 | * |
michael@0 | 715 | */ |
michael@0 | 716 | promptToSavePassword : function (aLogin) { |
michael@0 | 717 | var notifyObj = this._getPopupNote() || this._getNotifyBox(); |
michael@0 | 718 | |
michael@0 | 719 | if (notifyObj) |
michael@0 | 720 | this._showSaveLoginNotification(notifyObj, aLogin); |
michael@0 | 721 | else |
michael@0 | 722 | this._showSaveLoginDialog(aLogin); |
michael@0 | 723 | }, |
michael@0 | 724 | |
michael@0 | 725 | |
michael@0 | 726 | /* |
michael@0 | 727 | * _showLoginNotification |
michael@0 | 728 | * |
michael@0 | 729 | * Displays a notification bar. |
michael@0 | 730 | * |
michael@0 | 731 | */ |
michael@0 | 732 | _showLoginNotification : function (aNotifyBox, aName, aText, aButtons) { |
michael@0 | 733 | var oldBar = aNotifyBox.getNotificationWithValue(aName); |
michael@0 | 734 | const priority = aNotifyBox.PRIORITY_INFO_MEDIUM; |
michael@0 | 735 | |
michael@0 | 736 | this.log("Adding new " + aName + " notification bar"); |
michael@0 | 737 | var newBar = aNotifyBox.appendNotification( |
michael@0 | 738 | aText, aName, |
michael@0 | 739 | "chrome://mozapps/skin/passwordmgr/key.png", |
michael@0 | 740 | priority, aButtons); |
michael@0 | 741 | |
michael@0 | 742 | // The page we're going to hasn't loaded yet, so we want to persist |
michael@0 | 743 | // across the first location change. |
michael@0 | 744 | newBar.persistence++; |
michael@0 | 745 | |
michael@0 | 746 | // Sites like Gmail perform a funky redirect dance before you end up |
michael@0 | 747 | // at the post-authentication page. I don't see a good way to |
michael@0 | 748 | // heuristically determine when to ignore such location changes, so |
michael@0 | 749 | // we'll try ignoring location changes based on a time interval. |
michael@0 | 750 | newBar.timeout = Date.now() + 20000; // 20 seconds |
michael@0 | 751 | |
michael@0 | 752 | if (oldBar) { |
michael@0 | 753 | this.log("(...and removing old " + aName + " notification bar)"); |
michael@0 | 754 | aNotifyBox.removeNotification(oldBar); |
michael@0 | 755 | } |
michael@0 | 756 | }, |
michael@0 | 757 | |
michael@0 | 758 | |
michael@0 | 759 | /* |
michael@0 | 760 | * _showSaveLoginNotification |
michael@0 | 761 | * |
michael@0 | 762 | * Displays a notification bar or a popup notification, to allow the user |
michael@0 | 763 | * to save the specified login. This allows the user to see the results of |
michael@0 | 764 | * their login, and only save a login which they know worked. |
michael@0 | 765 | * |
michael@0 | 766 | * @param aNotifyObj |
michael@0 | 767 | * A notification box or a popup notification. |
michael@0 | 768 | */ |
michael@0 | 769 | _showSaveLoginNotification : function (aNotifyObj, aLogin) { |
michael@0 | 770 | |
michael@0 | 771 | // Ugh. We can't use the strings from the popup window, because they |
michael@0 | 772 | // have the access key marked in the string (eg "Mo&zilla"), along |
michael@0 | 773 | // with some weird rules for handling access keys that do not occur |
michael@0 | 774 | // in the string, for L10N. See commonDialog.js's setLabelForNode(). |
michael@0 | 775 | var neverButtonText = |
michael@0 | 776 | this._getLocalizedString("notifyBarNeverRememberButtonText"); |
michael@0 | 777 | var neverButtonAccessKey = |
michael@0 | 778 | this._getLocalizedString("notifyBarNeverRememberButtonAccessKey"); |
michael@0 | 779 | var rememberButtonText = |
michael@0 | 780 | this._getLocalizedString("notifyBarRememberPasswordButtonText"); |
michael@0 | 781 | var rememberButtonAccessKey = |
michael@0 | 782 | this._getLocalizedString("notifyBarRememberPasswordButtonAccessKey"); |
michael@0 | 783 | |
michael@0 | 784 | var displayHost = this._getShortDisplayHost(aLogin.hostname); |
michael@0 | 785 | var notificationText; |
michael@0 | 786 | if (aLogin.username) { |
michael@0 | 787 | var displayUser = this._sanitizeUsername(aLogin.username); |
michael@0 | 788 | notificationText = this._getLocalizedString( |
michael@0 | 789 | "rememberPasswordMsg", |
michael@0 | 790 | [displayUser, displayHost]); |
michael@0 | 791 | } else { |
michael@0 | 792 | notificationText = this._getLocalizedString( |
michael@0 | 793 | "rememberPasswordMsgNoUsername", |
michael@0 | 794 | [displayHost]); |
michael@0 | 795 | } |
michael@0 | 796 | |
michael@0 | 797 | // The callbacks in |buttons| have a closure to access the variables |
michael@0 | 798 | // in scope here; set one to |this._pwmgr| so we can get back to pwmgr |
michael@0 | 799 | // without a getService() call. |
michael@0 | 800 | var pwmgr = this._pwmgr; |
michael@0 | 801 | |
michael@0 | 802 | // Notification is a PopupNotification |
michael@0 | 803 | if (aNotifyObj == this._getPopupNote()) { |
michael@0 | 804 | // "Remember" button |
michael@0 | 805 | var mainAction = { |
michael@0 | 806 | label: rememberButtonText, |
michael@0 | 807 | accessKey: rememberButtonAccessKey, |
michael@0 | 808 | callback: function(aNotifyObj, aButton) { |
michael@0 | 809 | pwmgr.addLogin(aLogin); |
michael@0 | 810 | browser.focus(); |
michael@0 | 811 | } |
michael@0 | 812 | }; |
michael@0 | 813 | |
michael@0 | 814 | var secondaryActions = [ |
michael@0 | 815 | // "Never for this site" button |
michael@0 | 816 | { |
michael@0 | 817 | label: neverButtonText, |
michael@0 | 818 | accessKey: neverButtonAccessKey, |
michael@0 | 819 | callback: function(aNotifyObj, aButton) { |
michael@0 | 820 | pwmgr.setLoginSavingEnabled(aLogin.hostname, false); |
michael@0 | 821 | browser.focus(); |
michael@0 | 822 | } |
michael@0 | 823 | } |
michael@0 | 824 | ]; |
michael@0 | 825 | |
michael@0 | 826 | var notifyWin = this._getNotifyWindow(); |
michael@0 | 827 | var chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject; |
michael@0 | 828 | var browser = chromeWin.gBrowser. |
michael@0 | 829 | getBrowserForDocument(notifyWin.top.document); |
michael@0 | 830 | |
michael@0 | 831 | aNotifyObj.show(browser, "password-save", notificationText, |
michael@0 | 832 | "password-notification-icon", mainAction, |
michael@0 | 833 | secondaryActions, { timeout: Date.now() + 10000, |
michael@0 | 834 | persistWhileVisible: true }); |
michael@0 | 835 | } else { |
michael@0 | 836 | var notNowButtonText = |
michael@0 | 837 | this._getLocalizedString("notifyBarNotNowButtonText"); |
michael@0 | 838 | var notNowButtonAccessKey = |
michael@0 | 839 | this._getLocalizedString("notifyBarNotNowButtonAccessKey"); |
michael@0 | 840 | var buttons = [ |
michael@0 | 841 | // "Remember" button |
michael@0 | 842 | { |
michael@0 | 843 | label: rememberButtonText, |
michael@0 | 844 | accessKey: rememberButtonAccessKey, |
michael@0 | 845 | popup: null, |
michael@0 | 846 | callback: function(aNotifyObj, aButton) { |
michael@0 | 847 | pwmgr.addLogin(aLogin); |
michael@0 | 848 | } |
michael@0 | 849 | }, |
michael@0 | 850 | |
michael@0 | 851 | // "Never for this site" button |
michael@0 | 852 | { |
michael@0 | 853 | label: neverButtonText, |
michael@0 | 854 | accessKey: neverButtonAccessKey, |
michael@0 | 855 | popup: null, |
michael@0 | 856 | callback: function(aNotifyObj, aButton) { |
michael@0 | 857 | pwmgr.setLoginSavingEnabled(aLogin.hostname, false); |
michael@0 | 858 | } |
michael@0 | 859 | }, |
michael@0 | 860 | |
michael@0 | 861 | // "Not now" button |
michael@0 | 862 | { |
michael@0 | 863 | label: notNowButtonText, |
michael@0 | 864 | accessKey: notNowButtonAccessKey, |
michael@0 | 865 | popup: null, |
michael@0 | 866 | callback: function() { /* NOP */ } |
michael@0 | 867 | } |
michael@0 | 868 | ]; |
michael@0 | 869 | |
michael@0 | 870 | this._showLoginNotification(aNotifyObj, "password-save", |
michael@0 | 871 | notificationText, buttons); |
michael@0 | 872 | } |
michael@0 | 873 | }, |
michael@0 | 874 | |
michael@0 | 875 | |
michael@0 | 876 | /* |
michael@0 | 877 | * _removeLoginNotifications |
michael@0 | 878 | * |
michael@0 | 879 | */ |
michael@0 | 880 | _removeLoginNotifications : function () { |
michael@0 | 881 | var popupNote = this._getPopupNote(); |
michael@0 | 882 | if (popupNote) |
michael@0 | 883 | popupNote = popupNote.getNotification("password-save"); |
michael@0 | 884 | if (popupNote) |
michael@0 | 885 | popupNote.remove(); |
michael@0 | 886 | |
michael@0 | 887 | var notifyBox = this._getNotifyBox(); |
michael@0 | 888 | if (notifyBox) { |
michael@0 | 889 | var oldBar = notifyBox.getNotificationWithValue("password-save"); |
michael@0 | 890 | if (oldBar) { |
michael@0 | 891 | this.log("Removing save-password notification bar."); |
michael@0 | 892 | notifyBox.removeNotification(oldBar); |
michael@0 | 893 | } |
michael@0 | 894 | |
michael@0 | 895 | oldBar = notifyBox.getNotificationWithValue("password-change"); |
michael@0 | 896 | if (oldBar) { |
michael@0 | 897 | this.log("Removing change-password notification bar."); |
michael@0 | 898 | notifyBox.removeNotification(oldBar); |
michael@0 | 899 | } |
michael@0 | 900 | } |
michael@0 | 901 | }, |
michael@0 | 902 | |
michael@0 | 903 | |
michael@0 | 904 | /* |
michael@0 | 905 | * _showSaveLoginDialog |
michael@0 | 906 | * |
michael@0 | 907 | * Called when we detect a new login in a form submission, |
michael@0 | 908 | * asks the user what to do. |
michael@0 | 909 | * |
michael@0 | 910 | */ |
michael@0 | 911 | _showSaveLoginDialog : function (aLogin) { |
michael@0 | 912 | const buttonFlags = Ci.nsIPrompt.BUTTON_POS_1_DEFAULT + |
michael@0 | 913 | (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_0) + |
michael@0 | 914 | (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_1) + |
michael@0 | 915 | (Ci.nsIPrompt.BUTTON_TITLE_IS_STRING * Ci.nsIPrompt.BUTTON_POS_2); |
michael@0 | 916 | |
michael@0 | 917 | var displayHost = this._getShortDisplayHost(aLogin.hostname); |
michael@0 | 918 | |
michael@0 | 919 | var dialogText; |
michael@0 | 920 | if (aLogin.username) { |
michael@0 | 921 | var displayUser = this._sanitizeUsername(aLogin.username); |
michael@0 | 922 | dialogText = this._getLocalizedString( |
michael@0 | 923 | "rememberPasswordMsg", |
michael@0 | 924 | [displayUser, displayHost]); |
michael@0 | 925 | } else { |
michael@0 | 926 | dialogText = this._getLocalizedString( |
michael@0 | 927 | "rememberPasswordMsgNoUsername", |
michael@0 | 928 | [displayHost]); |
michael@0 | 929 | |
michael@0 | 930 | } |
michael@0 | 931 | var dialogTitle = this._getLocalizedString( |
michael@0 | 932 | "savePasswordTitle"); |
michael@0 | 933 | var neverButtonText = this._getLocalizedString( |
michael@0 | 934 | "neverForSiteButtonText"); |
michael@0 | 935 | var rememberButtonText = this._getLocalizedString( |
michael@0 | 936 | "rememberButtonText"); |
michael@0 | 937 | var notNowButtonText = this._getLocalizedString( |
michael@0 | 938 | "notNowButtonText"); |
michael@0 | 939 | |
michael@0 | 940 | this.log("Prompting user to save/ignore login"); |
michael@0 | 941 | var userChoice = this._promptService.confirmEx(this._window, |
michael@0 | 942 | dialogTitle, dialogText, |
michael@0 | 943 | buttonFlags, rememberButtonText, |
michael@0 | 944 | notNowButtonText, neverButtonText, |
michael@0 | 945 | null, {}); |
michael@0 | 946 | // Returns: |
michael@0 | 947 | // 0 - Save the login |
michael@0 | 948 | // 1 - Ignore the login this time |
michael@0 | 949 | // 2 - Never save logins for this site |
michael@0 | 950 | if (userChoice == 2) { |
michael@0 | 951 | this.log("Disabling " + aLogin.hostname + " logins by request."); |
michael@0 | 952 | this._pwmgr.setLoginSavingEnabled(aLogin.hostname, false); |
michael@0 | 953 | } else if (userChoice == 0) { |
michael@0 | 954 | this.log("Saving login for " + aLogin.hostname); |
michael@0 | 955 | this._pwmgr.addLogin(aLogin); |
michael@0 | 956 | } else { |
michael@0 | 957 | // userChoice == 1 --> just ignore the login. |
michael@0 | 958 | this.log("Ignoring login."); |
michael@0 | 959 | } |
michael@0 | 960 | }, |
michael@0 | 961 | |
michael@0 | 962 | |
michael@0 | 963 | /* |
michael@0 | 964 | * promptToChangePassword |
michael@0 | 965 | * |
michael@0 | 966 | * Called when we think we detect a password change for an existing |
michael@0 | 967 | * login, when the form being submitted contains multiple password |
michael@0 | 968 | * fields. |
michael@0 | 969 | * |
michael@0 | 970 | */ |
michael@0 | 971 | promptToChangePassword : function (aOldLogin, aNewLogin) { |
michael@0 | 972 | var notifyObj = this._getPopupNote() || this._getNotifyBox(); |
michael@0 | 973 | |
michael@0 | 974 | if (notifyObj) |
michael@0 | 975 | this._showChangeLoginNotification(notifyObj, aOldLogin, |
michael@0 | 976 | aNewLogin.password); |
michael@0 | 977 | else |
michael@0 | 978 | this._showChangeLoginDialog(aOldLogin, aNewLogin.password); |
michael@0 | 979 | }, |
michael@0 | 980 | |
michael@0 | 981 | |
michael@0 | 982 | /* |
michael@0 | 983 | * _showChangeLoginNotification |
michael@0 | 984 | * |
michael@0 | 985 | * Shows the Change Password notification bar or popup notification. |
michael@0 | 986 | * |
michael@0 | 987 | * @param aNotifyObj |
michael@0 | 988 | * A notification box or a popup notification. |
michael@0 | 989 | */ |
michael@0 | 990 | _showChangeLoginNotification : function (aNotifyObj, aOldLogin, aNewPassword) { |
michael@0 | 991 | var notificationText; |
michael@0 | 992 | if (aOldLogin.username) { |
michael@0 | 993 | var displayUser = this._sanitizeUsername(aOldLogin.username); |
michael@0 | 994 | notificationText = this._getLocalizedString( |
michael@0 | 995 | "updatePasswordMsg", |
michael@0 | 996 | [displayUser]); |
michael@0 | 997 | } else { |
michael@0 | 998 | notificationText = this._getLocalizedString( |
michael@0 | 999 | "updatePasswordMsgNoUser"); |
michael@0 | 1000 | } |
michael@0 | 1001 | |
michael@0 | 1002 | var changeButtonText = |
michael@0 | 1003 | this._getLocalizedString("notifyBarUpdateButtonText"); |
michael@0 | 1004 | var changeButtonAccessKey = |
michael@0 | 1005 | this._getLocalizedString("notifyBarUpdateButtonAccessKey"); |
michael@0 | 1006 | |
michael@0 | 1007 | // The callbacks in |buttons| have a closure to access the variables |
michael@0 | 1008 | // in scope here; set one to |this._pwmgr| so we can get back to pwmgr |
michael@0 | 1009 | // without a getService() call. |
michael@0 | 1010 | var self = this; |
michael@0 | 1011 | |
michael@0 | 1012 | // Notification is a PopupNotification |
michael@0 | 1013 | if (aNotifyObj == this._getPopupNote()) { |
michael@0 | 1014 | // "Yes" button |
michael@0 | 1015 | var mainAction = { |
michael@0 | 1016 | label: changeButtonText, |
michael@0 | 1017 | accessKey: changeButtonAccessKey, |
michael@0 | 1018 | popup: null, |
michael@0 | 1019 | callback: function(aNotifyObj, aButton) { |
michael@0 | 1020 | self._updateLogin(aOldLogin, aNewPassword); |
michael@0 | 1021 | } |
michael@0 | 1022 | }; |
michael@0 | 1023 | |
michael@0 | 1024 | var notifyWin = this._getNotifyWindow(); |
michael@0 | 1025 | var chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject; |
michael@0 | 1026 | var browser = chromeWin.gBrowser. |
michael@0 | 1027 | getBrowserForDocument(notifyWin.top.document); |
michael@0 | 1028 | |
michael@0 | 1029 | aNotifyObj.show(browser, "password-change", notificationText, |
michael@0 | 1030 | "password-notification-icon", mainAction, |
michael@0 | 1031 | null, { timeout: Date.now() + 10000, |
michael@0 | 1032 | persistWhileVisible: true }); |
michael@0 | 1033 | } else { |
michael@0 | 1034 | var dontChangeButtonText = |
michael@0 | 1035 | this._getLocalizedString("notifyBarDontChangeButtonText"); |
michael@0 | 1036 | var dontChangeButtonAccessKey = |
michael@0 | 1037 | this._getLocalizedString("notifyBarDontChangeButtonAccessKey"); |
michael@0 | 1038 | var buttons = [ |
michael@0 | 1039 | // "Yes" button |
michael@0 | 1040 | { |
michael@0 | 1041 | label: changeButtonText, |
michael@0 | 1042 | accessKey: changeButtonAccessKey, |
michael@0 | 1043 | popup: null, |
michael@0 | 1044 | callback: function(aNotifyObj, aButton) { |
michael@0 | 1045 | self._updateLogin(aOldLogin, aNewPassword); |
michael@0 | 1046 | } |
michael@0 | 1047 | }, |
michael@0 | 1048 | |
michael@0 | 1049 | // "No" button |
michael@0 | 1050 | { |
michael@0 | 1051 | label: dontChangeButtonText, |
michael@0 | 1052 | accessKey: dontChangeButtonAccessKey, |
michael@0 | 1053 | popup: null, |
michael@0 | 1054 | callback: function(aNotifyObj, aButton) { |
michael@0 | 1055 | // do nothing |
michael@0 | 1056 | } |
michael@0 | 1057 | } |
michael@0 | 1058 | ]; |
michael@0 | 1059 | |
michael@0 | 1060 | this._showLoginNotification(aNotifyObj, "password-change", |
michael@0 | 1061 | notificationText, buttons); |
michael@0 | 1062 | } |
michael@0 | 1063 | }, |
michael@0 | 1064 | |
michael@0 | 1065 | |
michael@0 | 1066 | /* |
michael@0 | 1067 | * _showChangeLoginDialog |
michael@0 | 1068 | * |
michael@0 | 1069 | * Shows the Change Password dialog. |
michael@0 | 1070 | * |
michael@0 | 1071 | */ |
michael@0 | 1072 | _showChangeLoginDialog : function (aOldLogin, aNewPassword) { |
michael@0 | 1073 | const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS; |
michael@0 | 1074 | |
michael@0 | 1075 | var dialogText; |
michael@0 | 1076 | if (aOldLogin.username) |
michael@0 | 1077 | dialogText = this._getLocalizedString( |
michael@0 | 1078 | "updatePasswordMsg", |
michael@0 | 1079 | [aOldLogin.username]); |
michael@0 | 1080 | else |
michael@0 | 1081 | dialogText = this._getLocalizedString( |
michael@0 | 1082 | "updatePasswordMsgNoUser"); |
michael@0 | 1083 | |
michael@0 | 1084 | var dialogTitle = this._getLocalizedString( |
michael@0 | 1085 | "passwordChangeTitle"); |
michael@0 | 1086 | |
michael@0 | 1087 | // returns 0 for yes, 1 for no. |
michael@0 | 1088 | var ok = !this._promptService.confirmEx(this._window, |
michael@0 | 1089 | dialogTitle, dialogText, buttonFlags, |
michael@0 | 1090 | null, null, null, |
michael@0 | 1091 | null, {}); |
michael@0 | 1092 | if (ok) { |
michael@0 | 1093 | this.log("Updating password for user " + aOldLogin.username); |
michael@0 | 1094 | this._updateLogin(aOldLogin, aNewPassword); |
michael@0 | 1095 | } |
michael@0 | 1096 | }, |
michael@0 | 1097 | |
michael@0 | 1098 | |
michael@0 | 1099 | /* |
michael@0 | 1100 | * promptToChangePasswordWithUsernames |
michael@0 | 1101 | * |
michael@0 | 1102 | * Called when we detect a password change in a form submission, but we |
michael@0 | 1103 | * don't know which existing login (username) it's for. Asks the user |
michael@0 | 1104 | * to select a username and confirm the password change. |
michael@0 | 1105 | * |
michael@0 | 1106 | * Note: The caller doesn't know the username for aNewLogin, so this |
michael@0 | 1107 | * function fills in .username and .usernameField with the values |
michael@0 | 1108 | * from the login selected by the user. |
michael@0 | 1109 | * |
michael@0 | 1110 | * Note; XPCOM stupidity: |count| is just |logins.length|. |
michael@0 | 1111 | */ |
michael@0 | 1112 | promptToChangePasswordWithUsernames : function (logins, count, aNewLogin) { |
michael@0 | 1113 | const buttonFlags = Ci.nsIPrompt.STD_YES_NO_BUTTONS; |
michael@0 | 1114 | |
michael@0 | 1115 | var usernames = logins.map(function (l) l.username); |
michael@0 | 1116 | var dialogText = this._getLocalizedString("userSelectText"); |
michael@0 | 1117 | var dialogTitle = this._getLocalizedString("passwordChangeTitle"); |
michael@0 | 1118 | var selectedIndex = { value: null }; |
michael@0 | 1119 | |
michael@0 | 1120 | // If user selects ok, outparam.value is set to the index |
michael@0 | 1121 | // of the selected username. |
michael@0 | 1122 | var ok = this._promptService.select(this._window, |
michael@0 | 1123 | dialogTitle, dialogText, |
michael@0 | 1124 | usernames.length, usernames, |
michael@0 | 1125 | selectedIndex); |
michael@0 | 1126 | if (ok) { |
michael@0 | 1127 | // Now that we know which login to use, modify its password. |
michael@0 | 1128 | var selectedLogin = logins[selectedIndex.value]; |
michael@0 | 1129 | this.log("Updating password for user " + selectedLogin.username); |
michael@0 | 1130 | this._updateLogin(selectedLogin, aNewLogin.password); |
michael@0 | 1131 | } |
michael@0 | 1132 | }, |
michael@0 | 1133 | |
michael@0 | 1134 | |
michael@0 | 1135 | |
michael@0 | 1136 | |
michael@0 | 1137 | /* ---------- Internal Methods ---------- */ |
michael@0 | 1138 | |
michael@0 | 1139 | |
michael@0 | 1140 | |
michael@0 | 1141 | |
michael@0 | 1142 | /* |
michael@0 | 1143 | * _updateLogin |
michael@0 | 1144 | */ |
michael@0 | 1145 | _updateLogin : function (login, newPassword) { |
michael@0 | 1146 | var now = Date.now(); |
michael@0 | 1147 | var propBag = Cc["@mozilla.org/hash-property-bag;1"]. |
michael@0 | 1148 | createInstance(Ci.nsIWritablePropertyBag); |
michael@0 | 1149 | if (newPassword) { |
michael@0 | 1150 | propBag.setProperty("password", newPassword); |
michael@0 | 1151 | // Explicitly set the password change time here (even though it would |
michael@0 | 1152 | // be changed automatically), to ensure that it's exactly the same |
michael@0 | 1153 | // value as timeLastUsed. |
michael@0 | 1154 | propBag.setProperty("timePasswordChanged", now); |
michael@0 | 1155 | } |
michael@0 | 1156 | propBag.setProperty("timeLastUsed", now); |
michael@0 | 1157 | propBag.setProperty("timesUsedIncrement", 1); |
michael@0 | 1158 | this._pwmgr.modifyLogin(login, propBag); |
michael@0 | 1159 | }, |
michael@0 | 1160 | |
michael@0 | 1161 | |
michael@0 | 1162 | /* |
michael@0 | 1163 | * _getChromeWindow |
michael@0 | 1164 | * |
michael@0 | 1165 | * Given a content DOM window, returns the chrome window it's in. |
michael@0 | 1166 | */ |
michael@0 | 1167 | _getChromeWindow: function (aWindow) { |
michael@0 | 1168 | var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1169 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 1170 | .QueryInterface(Ci.nsIDocShell) |
michael@0 | 1171 | .chromeEventHandler.ownerDocument.defaultView; |
michael@0 | 1172 | return chromeWin; |
michael@0 | 1173 | }, |
michael@0 | 1174 | |
michael@0 | 1175 | |
michael@0 | 1176 | /* |
michael@0 | 1177 | * _getNotifyWindow |
michael@0 | 1178 | */ |
michael@0 | 1179 | _getNotifyWindow: function () { |
michael@0 | 1180 | |
michael@0 | 1181 | try { |
michael@0 | 1182 | // Get topmost window, in case we're in a frame. |
michael@0 | 1183 | var notifyWin = this._window.top; |
michael@0 | 1184 | |
michael@0 | 1185 | // Some sites pop up a temporary login window, when disappears |
michael@0 | 1186 | // upon submission of credentials. We want to put the notification |
michael@0 | 1187 | // bar in the opener window if this seems to be happening. |
michael@0 | 1188 | if (notifyWin.opener) { |
michael@0 | 1189 | var chromeDoc = this._getChromeWindow(notifyWin). |
michael@0 | 1190 | document.documentElement; |
michael@0 | 1191 | var webnav = notifyWin. |
michael@0 | 1192 | QueryInterface(Ci.nsIInterfaceRequestor). |
michael@0 | 1193 | getInterface(Ci.nsIWebNavigation); |
michael@0 | 1194 | |
michael@0 | 1195 | // Check to see if the current window was opened with chrome |
michael@0 | 1196 | // disabled, and if so use the opener window. But if the window |
michael@0 | 1197 | // has been used to visit other pages (ie, has a history), |
michael@0 | 1198 | // assume it'll stick around and *don't* use the opener. |
michael@0 | 1199 | if (chromeDoc.getAttribute("chromehidden") && |
michael@0 | 1200 | webnav.sessionHistory.count == 1) { |
michael@0 | 1201 | this.log("Using opener window for notification bar."); |
michael@0 | 1202 | notifyWin = notifyWin.opener; |
michael@0 | 1203 | } |
michael@0 | 1204 | } |
michael@0 | 1205 | |
michael@0 | 1206 | return notifyWin; |
michael@0 | 1207 | |
michael@0 | 1208 | } catch (e) { |
michael@0 | 1209 | // If any errors happen, just assume no notification box. |
michael@0 | 1210 | this.log("Unable to get notify window"); |
michael@0 | 1211 | return null; |
michael@0 | 1212 | } |
michael@0 | 1213 | }, |
michael@0 | 1214 | |
michael@0 | 1215 | |
michael@0 | 1216 | /* |
michael@0 | 1217 | * _getPopupNote |
michael@0 | 1218 | * |
michael@0 | 1219 | * Returns the popup notification to this prompter, |
michael@0 | 1220 | * or null if there isn't one available. |
michael@0 | 1221 | */ |
michael@0 | 1222 | _getPopupNote : function () { |
michael@0 | 1223 | let popupNote = null; |
michael@0 | 1224 | |
michael@0 | 1225 | try { |
michael@0 | 1226 | let notifyWin = this._getNotifyWindow(); |
michael@0 | 1227 | |
michael@0 | 1228 | // Get the chrome window for the content window we're using. |
michael@0 | 1229 | // .wrappedJSObject needed here -- see bug 422974 comment 5. |
michael@0 | 1230 | let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject; |
michael@0 | 1231 | |
michael@0 | 1232 | popupNote = chromeWin.PopupNotifications; |
michael@0 | 1233 | } catch (e) { |
michael@0 | 1234 | this.log("Popup notifications not available on window"); |
michael@0 | 1235 | } |
michael@0 | 1236 | |
michael@0 | 1237 | return popupNote; |
michael@0 | 1238 | }, |
michael@0 | 1239 | |
michael@0 | 1240 | |
michael@0 | 1241 | /* |
michael@0 | 1242 | * _getNotifyBox |
michael@0 | 1243 | * |
michael@0 | 1244 | * Returns the notification box to this prompter, or null if there isn't |
michael@0 | 1245 | * a notification box available. |
michael@0 | 1246 | */ |
michael@0 | 1247 | _getNotifyBox : function () { |
michael@0 | 1248 | let notifyBox = null; |
michael@0 | 1249 | |
michael@0 | 1250 | try { |
michael@0 | 1251 | let notifyWin = this._getNotifyWindow(); |
michael@0 | 1252 | |
michael@0 | 1253 | // Get the chrome window for the content window we're using. |
michael@0 | 1254 | // .wrappedJSObject needed here -- see bug 422974 comment 5. |
michael@0 | 1255 | let chromeWin = this._getChromeWindow(notifyWin).wrappedJSObject; |
michael@0 | 1256 | |
michael@0 | 1257 | notifyBox = chromeWin.getNotificationBox(notifyWin); |
michael@0 | 1258 | } catch (e) { |
michael@0 | 1259 | this.log("Notification bars not available on window"); |
michael@0 | 1260 | } |
michael@0 | 1261 | |
michael@0 | 1262 | return notifyBox; |
michael@0 | 1263 | }, |
michael@0 | 1264 | |
michael@0 | 1265 | |
michael@0 | 1266 | /* |
michael@0 | 1267 | * _repickSelectedLogin |
michael@0 | 1268 | * |
michael@0 | 1269 | * The user might enter a login that isn't the one we prefilled, but |
michael@0 | 1270 | * is the same as some other existing login. So, pick a login with a |
michael@0 | 1271 | * matching username, or return null. |
michael@0 | 1272 | */ |
michael@0 | 1273 | _repickSelectedLogin : function (foundLogins, username) { |
michael@0 | 1274 | for (var i = 0; i < foundLogins.length; i++) |
michael@0 | 1275 | if (foundLogins[i].username == username) |
michael@0 | 1276 | return foundLogins[i]; |
michael@0 | 1277 | return null; |
michael@0 | 1278 | }, |
michael@0 | 1279 | |
michael@0 | 1280 | |
michael@0 | 1281 | /* |
michael@0 | 1282 | * _getLocalizedString |
michael@0 | 1283 | * |
michael@0 | 1284 | * Can be called as: |
michael@0 | 1285 | * _getLocalizedString("key1"); |
michael@0 | 1286 | * _getLocalizedString("key2", ["arg1"]); |
michael@0 | 1287 | * _getLocalizedString("key3", ["arg1", "arg2"]); |
michael@0 | 1288 | * (etc) |
michael@0 | 1289 | * |
michael@0 | 1290 | * Returns the localized string for the specified key, |
michael@0 | 1291 | * formatted if required. |
michael@0 | 1292 | * |
michael@0 | 1293 | */ |
michael@0 | 1294 | _getLocalizedString : function (key, formatArgs) { |
michael@0 | 1295 | if (formatArgs) |
michael@0 | 1296 | return this._strBundle.formatStringFromName( |
michael@0 | 1297 | key, formatArgs, formatArgs.length); |
michael@0 | 1298 | else |
michael@0 | 1299 | return this._strBundle.GetStringFromName(key); |
michael@0 | 1300 | }, |
michael@0 | 1301 | |
michael@0 | 1302 | |
michael@0 | 1303 | /* |
michael@0 | 1304 | * _sanitizeUsername |
michael@0 | 1305 | * |
michael@0 | 1306 | * Sanitizes the specified username, by stripping quotes and truncating if |
michael@0 | 1307 | * it's too long. This helps prevent an evil site from messing with the |
michael@0 | 1308 | * "save password?" prompt too much. |
michael@0 | 1309 | */ |
michael@0 | 1310 | _sanitizeUsername : function (username) { |
michael@0 | 1311 | if (username.length > 30) { |
michael@0 | 1312 | username = username.substring(0, 30); |
michael@0 | 1313 | username += this._ellipsis; |
michael@0 | 1314 | } |
michael@0 | 1315 | return username.replace(/['"]/g, ""); |
michael@0 | 1316 | }, |
michael@0 | 1317 | |
michael@0 | 1318 | |
michael@0 | 1319 | /* |
michael@0 | 1320 | * _getFormattedHostname |
michael@0 | 1321 | * |
michael@0 | 1322 | * The aURI parameter may either be a string uri, or an nsIURI instance. |
michael@0 | 1323 | * |
michael@0 | 1324 | * Returns the hostname to use in a nsILoginInfo object (for example, |
michael@0 | 1325 | * "http://example.com"). |
michael@0 | 1326 | */ |
michael@0 | 1327 | _getFormattedHostname : function (aURI) { |
michael@0 | 1328 | var uri; |
michael@0 | 1329 | if (aURI instanceof Ci.nsIURI) { |
michael@0 | 1330 | uri = aURI; |
michael@0 | 1331 | } else { |
michael@0 | 1332 | uri = Services.io.newURI(aURI, null, null); |
michael@0 | 1333 | } |
michael@0 | 1334 | var scheme = uri.scheme; |
michael@0 | 1335 | |
michael@0 | 1336 | var hostname = scheme + "://" + uri.host; |
michael@0 | 1337 | |
michael@0 | 1338 | // If the URI explicitly specified a port, only include it when |
michael@0 | 1339 | // it's not the default. (We never want "http://foo.com:80") |
michael@0 | 1340 | port = uri.port; |
michael@0 | 1341 | if (port != -1) { |
michael@0 | 1342 | var handler = Services.io.getProtocolHandler(scheme); |
michael@0 | 1343 | if (port != handler.defaultPort) |
michael@0 | 1344 | hostname += ":" + port; |
michael@0 | 1345 | } |
michael@0 | 1346 | |
michael@0 | 1347 | return hostname; |
michael@0 | 1348 | }, |
michael@0 | 1349 | |
michael@0 | 1350 | |
michael@0 | 1351 | /* |
michael@0 | 1352 | * _getShortDisplayHost |
michael@0 | 1353 | * |
michael@0 | 1354 | * Converts a login's hostname field (a URL) to a short string for |
michael@0 | 1355 | * prompting purposes. Eg, "http://foo.com" --> "foo.com", or |
michael@0 | 1356 | * "ftp://www.site.co.uk" --> "site.co.uk". |
michael@0 | 1357 | */ |
michael@0 | 1358 | _getShortDisplayHost: function (aURIString) { |
michael@0 | 1359 | var displayHost; |
michael@0 | 1360 | |
michael@0 | 1361 | var eTLDService = Cc["@mozilla.org/network/effective-tld-service;1"]. |
michael@0 | 1362 | getService(Ci.nsIEffectiveTLDService); |
michael@0 | 1363 | var idnService = Cc["@mozilla.org/network/idn-service;1"]. |
michael@0 | 1364 | getService(Ci.nsIIDNService); |
michael@0 | 1365 | try { |
michael@0 | 1366 | var uri = Services.io.newURI(aURIString, null, null); |
michael@0 | 1367 | var baseDomain = eTLDService.getBaseDomain(uri); |
michael@0 | 1368 | displayHost = idnService.convertToDisplayIDN(baseDomain, {}); |
michael@0 | 1369 | } catch (e) { |
michael@0 | 1370 | this.log("_getShortDisplayHost couldn't process " + aURIString); |
michael@0 | 1371 | } |
michael@0 | 1372 | |
michael@0 | 1373 | if (!displayHost) |
michael@0 | 1374 | displayHost = aURIString; |
michael@0 | 1375 | |
michael@0 | 1376 | return displayHost; |
michael@0 | 1377 | }, |
michael@0 | 1378 | |
michael@0 | 1379 | |
michael@0 | 1380 | /* |
michael@0 | 1381 | * _getAuthTarget |
michael@0 | 1382 | * |
michael@0 | 1383 | * Returns the hostname and realm for which authentication is being |
michael@0 | 1384 | * requested, in the format expected to be used with nsILoginInfo. |
michael@0 | 1385 | */ |
michael@0 | 1386 | _getAuthTarget : function (aChannel, aAuthInfo) { |
michael@0 | 1387 | var hostname, realm; |
michael@0 | 1388 | |
michael@0 | 1389 | // If our proxy is demanding authentication, don't use the |
michael@0 | 1390 | // channel's actual destination. |
michael@0 | 1391 | if (aAuthInfo.flags & Ci.nsIAuthInformation.AUTH_PROXY) { |
michael@0 | 1392 | this.log("getAuthTarget is for proxy auth"); |
michael@0 | 1393 | if (!(aChannel instanceof Ci.nsIProxiedChannel)) |
michael@0 | 1394 | throw "proxy auth needs nsIProxiedChannel"; |
michael@0 | 1395 | |
michael@0 | 1396 | var info = aChannel.proxyInfo; |
michael@0 | 1397 | if (!info) |
michael@0 | 1398 | throw "proxy auth needs nsIProxyInfo"; |
michael@0 | 1399 | |
michael@0 | 1400 | // Proxies don't have a scheme, but we'll use "moz-proxy://" |
michael@0 | 1401 | // so that it's more obvious what the login is for. |
michael@0 | 1402 | var idnService = Cc["@mozilla.org/network/idn-service;1"]. |
michael@0 | 1403 | getService(Ci.nsIIDNService); |
michael@0 | 1404 | hostname = "moz-proxy://" + |
michael@0 | 1405 | idnService.convertUTF8toACE(info.host) + |
michael@0 | 1406 | ":" + info.port; |
michael@0 | 1407 | realm = aAuthInfo.realm; |
michael@0 | 1408 | if (!realm) |
michael@0 | 1409 | realm = hostname; |
michael@0 | 1410 | |
michael@0 | 1411 | return [hostname, realm]; |
michael@0 | 1412 | } |
michael@0 | 1413 | |
michael@0 | 1414 | hostname = this._getFormattedHostname(aChannel.URI); |
michael@0 | 1415 | |
michael@0 | 1416 | // If a HTTP WWW-Authenticate header specified a realm, that value |
michael@0 | 1417 | // will be available here. If it wasn't set or wasn't HTTP, we'll use |
michael@0 | 1418 | // the formatted hostname instead. |
michael@0 | 1419 | realm = aAuthInfo.realm; |
michael@0 | 1420 | if (!realm) |
michael@0 | 1421 | realm = hostname; |
michael@0 | 1422 | |
michael@0 | 1423 | return [hostname, realm]; |
michael@0 | 1424 | }, |
michael@0 | 1425 | |
michael@0 | 1426 | |
michael@0 | 1427 | /** |
michael@0 | 1428 | * Returns [username, password] as extracted from aAuthInfo (which |
michael@0 | 1429 | * holds this info after having prompted the user). |
michael@0 | 1430 | * |
michael@0 | 1431 | * If the authentication was for a Windows domain, we'll prepend the |
michael@0 | 1432 | * return username with the domain. (eg, "domain\user") |
michael@0 | 1433 | */ |
michael@0 | 1434 | _GetAuthInfo : function (aAuthInfo) { |
michael@0 | 1435 | var username, password; |
michael@0 | 1436 | |
michael@0 | 1437 | var flags = aAuthInfo.flags; |
michael@0 | 1438 | if (flags & Ci.nsIAuthInformation.NEED_DOMAIN && aAuthInfo.domain) |
michael@0 | 1439 | username = aAuthInfo.domain + "\\" + aAuthInfo.username; |
michael@0 | 1440 | else |
michael@0 | 1441 | username = aAuthInfo.username; |
michael@0 | 1442 | |
michael@0 | 1443 | password = aAuthInfo.password; |
michael@0 | 1444 | |
michael@0 | 1445 | return [username, password]; |
michael@0 | 1446 | }, |
michael@0 | 1447 | |
michael@0 | 1448 | |
michael@0 | 1449 | /** |
michael@0 | 1450 | * Given a username (possibly in DOMAIN\user form) and password, parses the |
michael@0 | 1451 | * domain out of the username if necessary and sets domain, username and |
michael@0 | 1452 | * password on the auth information object. |
michael@0 | 1453 | */ |
michael@0 | 1454 | _SetAuthInfo : function (aAuthInfo, username, password) { |
michael@0 | 1455 | var flags = aAuthInfo.flags; |
michael@0 | 1456 | if (flags & Ci.nsIAuthInformation.NEED_DOMAIN) { |
michael@0 | 1457 | // Domain is separated from username by a backslash |
michael@0 | 1458 | var idx = username.indexOf("\\"); |
michael@0 | 1459 | if (idx == -1) { |
michael@0 | 1460 | aAuthInfo.username = username; |
michael@0 | 1461 | } else { |
michael@0 | 1462 | aAuthInfo.domain = username.substring(0, idx); |
michael@0 | 1463 | aAuthInfo.username = username.substring(idx+1); |
michael@0 | 1464 | } |
michael@0 | 1465 | } else { |
michael@0 | 1466 | aAuthInfo.username = username; |
michael@0 | 1467 | } |
michael@0 | 1468 | aAuthInfo.password = password; |
michael@0 | 1469 | }, |
michael@0 | 1470 | |
michael@0 | 1471 | _newAsyncPromptConsumer : function(aCallback, aContext) { |
michael@0 | 1472 | return { |
michael@0 | 1473 | QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]), |
michael@0 | 1474 | callback: aCallback, |
michael@0 | 1475 | context: aContext, |
michael@0 | 1476 | cancel: function() { |
michael@0 | 1477 | this.callback.onAuthCancelled(this.context, false); |
michael@0 | 1478 | this.callback = null; |
michael@0 | 1479 | this.context = null; |
michael@0 | 1480 | } |
michael@0 | 1481 | } |
michael@0 | 1482 | } |
michael@0 | 1483 | |
michael@0 | 1484 | }; // end of LoginManagerPrompter implementation |
michael@0 | 1485 | |
michael@0 | 1486 | |
michael@0 | 1487 | var component = [LoginManagerPromptFactory, LoginManagerPrompter]; |
michael@0 | 1488 | this.NSGetFactory = XPCOMUtils.generateNSGetFactory(component); |