Bug 953875 - Use toolkit password manager, r=clokep.

This commit is contained in:
Florian Quèze 2012-01-25 15:25:58 +01:00
Родитель 5765ebd990
Коммит 72ad64fe67
21 изменённых файлов: 332 добавлений и 64 удалений

Просмотреть файл

@ -6,6 +6,10 @@ pref("messenger.startup.action", 1);
pref("messenger.accounts", "");
// Should the accounts service stored in the password manager the
// passwords that are currently stored in the preferences?
pref("messenger.accounts.convertOldPasswords", false);
// The intervals in seconds between automatic reconnection attempts
// The last value will be reused forever.
// A value of 0 means that there will be no more reconnection attempts.

Просмотреть файл

@ -241,7 +241,7 @@ interface imIAccount: prplIAccount {
attribute short firstConnectionState;
// FIXME password should be in password manager
// Passwords are stored in the toolkit Password Manager.
attribute AUTF8String password;
attribute AUTF8String alias;

Просмотреть файл

@ -47,11 +47,24 @@ const kAccountKeyPrefix = "account";
const kAccountOptionPrefPrefix = "options.";
const kPrefAccountName = "name";
const kPrefAccountPrpl = "prpl";
const kPrefAccountPassword = "password";
const kPrefAccountAutoLogin = "autoLogin";
const kPrefAccountAlias = "alias";
const kPrefAccountFirstConnectionState = "firstConnectionState";
const kPrefConvertOldPasswords = "messenger.accounts.convertOldPasswords";
const kPrefAccountPassword = "password";
XPCOMUtils.defineLazyGetter(this, "LoginManager", function()
Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager)
);
XPCOMUtils.defineLazyGetter(this, "_", function()
l10nHelper("chrome://chat/locale/accounts.properties")
);
var gUserCanceledMasterPasswordPrompt = false;
var gConvertingOldPasswords = false;
var SavePrefTimer = {
saveNow: function() {
if (this._timer) {
@ -167,6 +180,17 @@ function imAccount(aKey, aName, aPrplId)
if (this.firstConnectionState == Ci.imIAccount.FIRST_CONNECTION_PENDING)
this.firstConnectionState = Ci.imIAccount.FIRST_CONNECTION_CRASHED;
// Try to convert old passwords stored in the preferences.
// Don't try too hard if the user has canceled a master password prompt:
// we don't want to display several of theses prompts at startup.
if (gConvertingOldPasswords && !this.protocol.noPassword) {
try {
let password = this.prefBranch.getCharPref(kPrefAccountPassword);
if (password && !this.password)
this.password = password;
} catch (e) { /* No password saved in the prefs for this account. */ }
}
// Check for errors that should prevent connection attempts.
if (this._passwordRequired && !this.password)
this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
@ -187,7 +211,9 @@ imAccount.prototype = {
connectionErrorMessage: "",
_connectionErrorReason: Ci.prplIAccount.NO_ERROR,
get connectionErrorReason() {
if (this._connectionErrorReason != Ci.prplIAccount.NO_ERROR)
if (this._connectionErrorReason != Ci.prplIAccount.NO_ERROR &&
(this._connectionErrorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD ||
!this._password))
return this._connectionErrorReason;
else
return this.prplAccount.connectionErrorReason;
@ -377,17 +403,77 @@ imAccount.prototype = {
}
},
_password: "",
get password() {
if (this._password)
return this._password;
// Avoid prompting the user for the master password more than once at startup.
if (gUserCanceledMasterPasswordPrompt)
return "";
let passwordURI = "im://" + this.protocol.id;
let logins;
try {
return this.prefBranch.getCharPref(kPrefAccountPassword);
logins = LoginManager.findLogins({}, passwordURI, null, passwordURI);
} catch (e) {
this._handleMasterPasswordException(e);
return "";
}
let normalizedName = this.normalizedName;
for each (let login in logins) {
if (login.username == normalizedName) {
this._password = login.password;
if (this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD) {
// We have found a password for an account marked as missing password,
// re-check all others accounts missing a password. But first,
// remove the error on our own account to avoid re-checking it.
delete this._connectionErrorReason;
gAccountsService._checkIfPasswordStillMissing();
}
return this._password;
}
}
return "";
},
_checkIfPasswordStillMissing: function() {
if (this._connectionErrorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD ||
!this.password)
return;
delete this._connectionErrorReason;
this._sendUpdateNotification();
},
get _passwordRequired()
!this.protocol.noPassword && !this.protocol.passwordOptional,
set password(aPassword) {
this.prefBranch.setCharPref(kPrefAccountPassword, aPassword);
this._password = aPassword;
if (gUserCanceledMasterPasswordPrompt)
return;
let newLogin = Cc["@mozilla.org/login-manager/loginInfo;1"]
.createInstance(Ci.nsILoginInfo);
let passwordURI = "im://" + this.protocol.id;
newLogin.init(passwordURI, null, passwordURI, this.normalizedName,
aPassword, "", "");
try {
let logins = LoginManager.findLogins({}, passwordURI, null, passwordURI);
let saved = false;
for each (let login in logins) {
if (newLogin.matches(login, true)) {
if (aPassword)
LoginManager.modifyLogin(login, newLogin);
else
LoginManager.removeLogin(login);
saved = true;
break;
}
}
if (!saved && aPassword)
LoginManager.addLogin(newLogin);
} catch (e) {
this._handleMasterPasswordException(e);
}
this._connectionInfoChanged();
if (aPassword &&
this._connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)
@ -396,6 +482,13 @@ imAccount.prototype = {
this._connectionErrorReason = Ci.imIAccount.ERROR_MISSING_PASSWORD;
this._sendUpdateNotification();
},
_handleMasterPasswordException: function(aException) {
if (aException.result != Components.results.NS_ERROR_ABORT)
throw aException;
gUserCanceledMasterPasswordPrompt = true;
executeSoon(function () { gUserCanceledMasterPasswordPrompt = false; });
},
get autoLogin() {
let autoLogin = true;
@ -435,6 +528,17 @@ imAccount.prototype = {
},
remove: function() {
let login = Cc["@mozilla.org/login-manager/loginInfo;1"]
.createInstance(Ci.nsILoginInfo);
let passwordURI = "im://" + this.protocol.id;
login.init(passwordURI, null, passwordURI, this.normalizedName, "", "", "");
let logins = LoginManager.findLogins({}, passwordURI, null, passwordURI);
for each (let l in logins) {
if (login.matches(l, true)) {
LoginManager.removeLogin(l);
break;
}
}
this.unInit();
Services.contacts.forgetAccount(this.numericId);
this.prefBranch.deleteBranch("");
@ -465,7 +569,33 @@ imAccount.prototype = {
return this.prplAccount;
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
},
connect: function() { this._ensurePrplAccount.connect(); },
connect: function() {
if (this._passwordRequired) {
// If the previous connection attempt failed because we have a wrong password,
// clear the passwor cache so that if there's no password in the password
// manager the user gets prompted again.
if (this.connectionErrorReason == Ci.prplIAccount.ERROR_AUTHENTICATION_FAILED)
delete this._password;
let password = this.password;
if (!password) {
let prompts = Services.prompt;
let shouldSave = {value: false};
password = {value: ""};
if (!prompts.promptPassword(null, _("passwordPromptTitle", this.name),
_("passwordPromptText", this.name),
password, _("passwordPromptSaveCheckbox"),
shouldSave))
return;
if (shouldSave.value)
this.password = password.value;
else
this._password = password.value;
}
}
this._ensurePrplAccount.connect();
},
disconnect: function() {
if (this._statusObserver) {
this.statusInfo.removeObserver(this._statusObserver);
@ -545,12 +675,17 @@ imAccount.prototype = {
}
};
var gAccountsService = null;
function AccountsService() { }
AccountsService.prototype = {
initAccounts: function() {
this._initAutoLoginStatus();
this._accounts = [];
this._accountsById = {};
gAccountsService = this;
gConvertingOldPasswords =
Services.prefs.getBoolPref(kPrefConvertOldPasswords);
let accountList = this._accountList;
for each (let account in (accountList ? accountList.split(",") : [])) {
try {
@ -565,6 +700,11 @@ AccountsService.prototype = {
dump(e + " " + e.toSource() + "\n");
}
}
// If the user has canceled a master password prompt, we haven't
// been able to save any password, so the old password conversion
// still needs to happen.
if (gConvertingOldPasswords && !gUserCanceledMasterPasswordPrompt)
Services.prefs.setBoolPref(kPrefConvertOldPasswords, false);
this._prefObserver = this.observe.bind(this);
Services.prefs.addObserver(kPrefMessengerAccounts, this._prefObserver, false);
@ -597,6 +737,7 @@ AccountsService.prototype = {
unInitAccounts: function() {
for each (let account in this._accounts)
account.unInit();
gAccountsService = null;
delete this._accounts;
delete this._accountsById;
Services.prefs.removeObserver(kPrefMessengerAccounts, this._prefObserver);
@ -715,6 +856,18 @@ AccountsService.prototype = {
Services.obs.notifyObservers(this, "autologin-processed", null);
},
_checkingIfPasswordStillMissing: false,
_checkIfPasswordStillMissing: function() {
// Avoid recursion.
if (this._checkingIfPasswordStillMissing)
return;
this._checkingIfPasswordStillMissing = true;
for each (let account in this._accounts)
account._checkIfPasswordStillMissing();
delete this._checkingIfPasswordStillMissing;
},
getAccountById: function(aAccountId) {
if (aAccountId.indexOf(kAccountKeyPrefix) != 0)
throw Cr.NS_ERROR_INVALID_ARG;

Просмотреть файл

@ -0,0 +1,3 @@
passwordPromptTitle=Password for %S
passwordPromptText=Please enter your password for your account %S in order to connect it.
passwordPromptSaveCheckbox=Use Password Manager to remember this password.

Просмотреть файл

@ -2,6 +2,7 @@
@AB_CD@.jar:
% locale chat @AB_CD@ %locale/@AB_CD@/chat/
locale/@AB_CD@/chat/accounts.properties (%accounts.properties)
locale/@AB_CD@/chat/commands.properties (%commands.properties)
locale/@AB_CD@/chat/conversations.properties (%conversations.properties)
locale/@AB_CD@/chat/facebook.properties (%facebook.properties)

Просмотреть файл

@ -728,7 +728,7 @@ const GenericProtocolPrototype = {
get noPassword() false,
get newMailNotification() false,
get imagesInIM() false,
get passwordOptional() true,
get passwordOptional() false,
get usePointSize() true,
get registerNoScreenName() false,
get slashCommandsNative() false,

Просмотреть файл

@ -26,6 +26,7 @@ pref("general.autoScroll", true);
// 2 = check multi/single line controls
pref("layout.spellcheckDefault", 1);
pref("messenger.accounts.convertOldPasswords", true);
pref("messenger.accounts.promptOnDelete", true);
pref("messenger.buddies.showOffline", false);

Просмотреть файл

@ -141,12 +141,14 @@
text = bundle.getFormattedString(key + "UnknownPrpl",
[account.protocol.id]);
else if (errorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)
text = bundle.getString(key + "MissingPassword");
text = bundle.getString(key + "EnteringPasswordRequired");
else if (errorReason == Ci.imIAccount.ERROR_CRASHED)
text = bundle.getString(key + "CrashedAccount");
else
text = account.connectionErrorMessage;
text = bundle.getFormattedString(key, [text]);
if (errorReason != Ci.imIAccount.ERROR_MISSING_PASSWORD)
text = bundle.getFormattedString(key, [text]);
this.setAttribute("error", "true");
var error = document.getAnonymousElementByAttribute(this, "anonid",

Просмотреть файл

@ -74,7 +74,6 @@
<label value="&account.password;" control="password" flex="1"/>
<textbox id="password" flex="1" type="password"/>
</hbox>
<checkbox id="rememberPassword" label="&account.rememberPassword;" hidden="true"/>
<separator class="groove"/>

Просмотреть файл

@ -381,11 +381,13 @@ var accountWizard = {
rows.appendChild(this.createSummaryRow(label, this.username));
if (!this.proto.noPassword) {
this.password = this.getValue("password");
label = document.getElementById("passwordLabel").value;
var pass = "";
for (let i = 0; i < this.password.length; ++i)
pass += "*";
rows.appendChild(this.createSummaryRow(label, pass));
if (this.password) {
label = document.getElementById("passwordLabel").value;
var pass = "";
for (let i = 0; i < this.password.length; ++i)
pass += "*";
rows.appendChild(this.createSummaryRow(label, pass));
}
}
this.alias = this.getValue("alias");
if (this.alias) {
@ -439,7 +441,7 @@ var accountWizard = {
createAccount: function aw_createAccount() {
var acc = Services.accounts.createAccount(this.username, this.proto.id);
if (!this.proto.noPassword)
if (!this.proto.noPassword && this.password)
acc.password = this.password;
if (this.alias)
acc.alias = this.alias;

Просмотреть файл

@ -98,6 +98,8 @@
<label value="&accountPasswordField.label;" control="password" id="passwordLabel"/>
<textbox id="password" type="password"/>
</hbox>
<separator/>
<description id="passwordManagerDescription">&accountPasswordManager.label;</description>
</wizardpage>
<wizardpage id="accountadvanced" pageid="accountadvanced" next="accountsummary"

Просмотреть файл

@ -335,8 +335,7 @@ var gAccountManager = {
let isCommandDisabled =
(this.isOffline ||
(account.disconnected &&
(account.connectionErrorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL ||
account.connectionErrorReason == Ci.imIAccount.ERROR_MISSING_PASSWORD)));
account.connectionErrorReason == Ci.imIAccount.ERROR_UNKNOWN_PRPL));
[[activeCommandElt, isCommandDisabled],
[document.getElementById("cmd_moveup"), accountList.selectedIndex == 0],

Просмотреть файл

@ -338,7 +338,6 @@ Account.prototype = {
protocol: null,
password: "",
autoLogin: true,
rememberPassword: true,
alias: "",
proxyInfo: null,
connectionStageMsg: "",

Просмотреть файл

@ -39,6 +39,7 @@ var gPrivacyPane = {
init: function ()
{
this.updateDisabledState();
this._initMasterPasswordUI();
},
updateDisabledState: function ()
@ -90,5 +91,112 @@ var gPrivacyPane = {
protocolSvc.loadUrl(uri);
}
}
},
/**
* Initializes master password UI: the "use master password" checkbox, selects
* the master password button to show, and enables/disables it as necessary.
* The master password is controlled by various bits of NSS functionality,
* so the UI for it can't be controlled by the normal preference bindings.
*/
_initMasterPasswordUI: function ()
{
var noMP = !this._masterPasswordSet();
document.getElementById("changeMasterPassword").disabled = noMP;
document.getElementById("useMasterPassword").checked = !noMP;
},
/**
* Returns true if the user has a master password set and false otherwise.
*/
_masterPasswordSet: function ()
{
const Cc = Components.classes, Ci = Components.interfaces;
var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
getService(Ci.nsIPKCS11ModuleDB);
var slot = secmodDB.findSlotByName("");
if (slot) {
var status = slot.status;
var hasMP = status != Ci.nsIPKCS11Slot.SLOT_UNINITIALIZED &&
status != Ci.nsIPKCS11Slot.SLOT_READY;
return hasMP;
} else {
// XXX I have no bloody idea what this means
return false;
}
},
/**
* Enables/disables the master password button depending on the state of the
* "use master password" checkbox, and prompts for master password removal
* if one is set.
*/
updateMasterPasswordButton: function ()
{
var checkbox = document.getElementById("useMasterPassword");
var button = document.getElementById("changeMasterPassword");
button.disabled = !checkbox.checked;
// unchecking the checkbox should try to immediately remove the master
// password, because it's impossible to non-destructively remove the master
// password used to encrypt all the passwords without providing it (by
// design), and it would be extremely odd to pop up that dialog when the
// user closes the prefwindow and saves his settings
if (!checkbox.checked)
this._removeMasterPassword();
else
this.changeMasterPassword();
this._initMasterPasswordUI();
},
/**
* Displays the "remove master password" dialog to allow the user to remove
* the current master password. When the dialog is dismissed, master password
* UI is automatically updated.
*/
_removeMasterPassword: function ()
{
const Cc = Components.classes, Ci = Components.interfaces;
var secmodDB = Cc["@mozilla.org/security/pkcs11moduledb;1"].
getService(Ci.nsIPKCS11ModuleDB);
if (secmodDB.isFIPSEnabled) {
var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].
getService(Ci.nsIPromptService);
var bundle = document.getElementById("bundlePreferences");
promptService.alert(window,
bundle.getString("pw_change_failed_title"),
bundle.getString("pw_change2empty_in_fips_mode"));
}
else {
document.documentElement.openSubDialog("chrome://mozapps/content/preferences/removemp.xul",
"", null);
}
this._initMasterPasswordUI();
},
/**
* Displays a dialog in which the master password may be changed.
*/
changeMasterPassword: function ()
{
document.documentElement.openSubDialog("chrome://mozapps/content/preferences/changemp.xul",
"", null);
this._initMasterPasswordUI();
},
/**
* Shows the sites where the user has saved passwords and the associated
* login information.
*/
showPasswords: function ()
{
document.documentElement.openWindow("Toolkit:PasswordManager",
"chrome://passwordmgr/content/passwordManager.xul",
"", null);
}
};

Просмотреть файл

@ -63,6 +63,7 @@
<preference id="purple.logging.log_chats" name="purple.logging.log_chats" type="bool"/>
<preference id="purple.logging.log_ims" name="purple.logging.log_ims" type="bool"/>
<preference id="purple.logging.log_system" name="purple.logging.log_system" type="bool"/>
<preference id="pref.privacy.disable_button.view_passwords" name="pref.privacy.disable_button.view_passwords" type="bool"/>
</preferences>
<broadcaster id="idleReportingEnabled"/>
@ -117,6 +118,32 @@
</hbox>
</groupbox>
<!-- Passwords -->
<groupbox id="passwordsGroup" orient="vertical">
<caption label="&passwords.label;"/>
<description>&savedPasswords.intro;</description>
<hbox>
<spacer flex="1"/>
<button label="&savedPasswords.label;" accesskey="&savedPasswords.accesskey;"
oncommand="gPrivacyPane.showPasswords();"
preference="pref.privacy.disable_button.view_passwords"/>
</hbox>
<separator class="thin"/>
<description>&masterPassword.intro;</description>
<hbox>
<checkbox id="useMasterPassword" flex="1"
label="&useMasterPassword.label;" accesskey="&useMasterPassword.accesskey;"
oncommand="gPrivacyPane.updateMasterPasswordButton();"/>
<button id="changeMasterPassword"
label="&changeMasterPassword.label;" accesskey="&changeMasterPassword.accesskey;"
oncommand="gPrivacyPane.changeMasterPassword();"/>
</hbox>
</groupbox>
</prefpane>
</overlay>

Просмотреть файл

@ -4,7 +4,6 @@
<!ENTITY account.advanced "Advanced Options">
<!ENTITY account.name "Username:">
<!ENTITY account.password "Password:">
<!ENTITY account.rememberPassword "remember password">
<!ENTITY account.alias "Alias:">
<!ENTITY account.newMailNotification "Notify on new Mail">
<!ENTITY account.autojoin "Auto-Joined Channels:">

Просмотреть файл

@ -14,6 +14,7 @@
<!ENTITY accountPasswordTitle.label "Password">
<!ENTITY accountPasswordInfo.label "Please enter your password in the box below.">
<!ENTITY accountPasswordField.label "Password:">
<!ENTITY accountPasswordManager.label "The password entered here will be stored in the Password Manager. Leave this box empty if you want to be prompted for your password each time this account is connected.">
<!ENTITY accountAdvancedTitle.label "Advanced Options">
<!ENTITY accountAdvancedInfo.label "Feel free to skip this step if you want to.">

Просмотреть файл

@ -7,7 +7,7 @@ accountUsernameInfoWithDescription=Please enter the username (%S) for your %S ac
account.connection.error=Error: %S
account.connection.errorUnknownPrpl= No '%S' protocol plugin.
account.connection.errorMissingPassword=A password is required to connect this account.
account.connection.errorEnteringPasswordRequired=Entering a password is required to connect this account.
account.connection.errorCrashedAccount=A crash occurred while connecting this account.
account.connection.progress=Connecting: %S…
account.connecting=Connecting…

Просмотреть файл

@ -19,3 +19,14 @@
<!ENTITY logShowFolder.description "Open the folder containing the record files">
<!ENTITY logShowFolderButton.label "Show Log Folder…">
<!ENTITY logShowFolderButton.accesskey "L">
<!-- Passwords, see mail/chrome/messenger/preferences/security.dtd -->
<!ENTITY passwords.label "Passwords">
<!ENTITY savedPasswords.intro "&brandShortName; can remember passwords for all of your accounts.">
<!ENTITY useMasterPassword.label "Use a master password">
<!ENTITY useMasterPassword.accesskey "U">
<!ENTITY masterPassword.intro "A Master Password protects all your passwords, but you must enter it once per session.">
<!ENTITY changeMasterPassword.label "Change Master Password…">
<!ENTITY changeMasterPassword.accesskey "C">
<!ENTITY savedPasswords.label "Saved Passwords…">
<!ENTITY savedPasswords.accesskey "S">

Просмотреть файл

@ -1,42 +0,0 @@
<!ENTITY warnAddonInstall.label "Warn me when sites try to install add-ons">
<!ENTITY warnAddonInstall.accesskey "W">
<!-- LOCALIZATION NOTE (blockWebForgeries.label, blockAttackSites.label):
The methods by which forged (phished) and attack sites will be detected by
phishing providers will vary from human review to machine-based heuristics to a
combination of both, so it's important that these strings and
useDownloadedList.label convey the meaning "reported" (and not something like
"known").
-->
<!ENTITY blockAttackSites.label "Block reported attack sites">
<!ENTITY blockAttackSites.accesskey "k">
<!ENTITY blockWebForgeries.label "Block reported web forgeries">
<!ENTITY blockWebForgeries.accesskey "B">
<!ENTITY addonExceptions.label "Exceptions…">
<!ENTITY addonExceptions.accesskey "E">
<!ENTITY passwords.label "Passwords">
<!ENTITY rememberPasswords.label "Remember passwords for sites">
<!ENTITY rememberPasswords.accesskey "R">
<!ENTITY passwordExceptions.label "Exceptions…">
<!ENTITY passwordExceptions.accesskey "x">
<!ENTITY useMasterPassword.label "Use a master password">
<!ENTITY useMasterPassword.accesskey "U">
<!ENTITY changeMasterPassword.label "Change Master Password…">
<!ENTITY changeMasterPassword.accesskey "M">
<!ENTITY savedPasswords.label "Saved Passwords…">
<!ENTITY savedPasswords.accesskey "P">
<!ENTITY warnings.label "Warning Messages">
<!ENTITY chooseWarnings.label "Choose which warning messages you want to see while browsing the web">
<!ENTITY warningSettings.label "Settings…">
<!ENTITY warningSettings.accesskey "S">

Просмотреть файл

@ -37,7 +37,6 @@
locale/@AB_CD@/instantbird/preferences/preferences.dtd (%chrome/instantbird/preferences/preferences.dtd)
locale/@AB_CD@/instantbird/preferences/preferences.properties (%chrome/instantbird/preferences/preferences.properties)
locale/@AB_CD@/instantbird/preferences/privacy.dtd (%chrome/instantbird/preferences/privacy.dtd)
locale/@AB_CD@/instantbird/preferences/security.dtd (%chrome/instantbird/preferences/security.dtd)
locale/@AB_CD@/instantbird/preferences/tabs.dtd (%chrome/instantbird/preferences/tabs.dtd)
locale/@AB_CD@/instantbird/preferences/themes.dtd (%chrome/instantbird/preferences/themes.dtd)
locale/@AB_CD@/instantbird/preferences/themes.properties (%chrome/instantbird/preferences/themes.properties)