зеркало из https://github.com/mozilla/gecko-dev.git
Bug 499233 - multiple master password prompts triggered by filling form logins in multiple tabs. r=zpao, a=sdwilsh
This commit is contained in:
Родитель
5772854f67
Коммит
cc499d58a9
|
@ -44,7 +44,7 @@ interface nsIDOMHTMLInputElement;
|
|||
interface nsIDOMHTMLFormElement;
|
||||
interface nsIPropertyBag;
|
||||
|
||||
[scriptable, uuid(30534ff7-fb95-45c5-8336-5448638f2aa1)]
|
||||
[scriptable, uuid(1f02142f-7e3f-4d02-b3e0-495c5f83ad7d)]
|
||||
|
||||
interface nsILoginManager : nsISupports {
|
||||
|
||||
|
@ -267,6 +267,11 @@ interface nsILoginManager : nsISupports {
|
|||
*/
|
||||
void searchLogins(out unsigned long count, in nsIPropertyBag matchData,
|
||||
[retval, array, size_is(count)] out nsILoginInfo logins);
|
||||
|
||||
/**
|
||||
* True when a master password prompt is being displayed.
|
||||
*/
|
||||
readonly attribute boolean uiBusy;
|
||||
};
|
||||
|
||||
%{C++
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
|
||||
#include "nsISupports.idl"
|
||||
|
||||
[scriptable, uuid(1ddbe67d-a216-4915-9e0a-3e1b95b7126c)]
|
||||
[scriptable, uuid(73f85239-421d-4d34-8d9c-79cf820ea1e6)]
|
||||
|
||||
interface nsILoginManagerCrypto : nsISupports {
|
||||
|
||||
|
@ -70,4 +70,20 @@ interface nsILoginManagerCrypto : nsISupports {
|
|||
* encrypted with some other key).
|
||||
*/
|
||||
AString decrypt(in AString cipherText);
|
||||
|
||||
/**
|
||||
* uiBusy
|
||||
*
|
||||
* True when a master password prompt is being displayed.
|
||||
*/
|
||||
readonly attribute boolean uiBusy;
|
||||
|
||||
/**
|
||||
* isLoggedIn
|
||||
*
|
||||
* Current login state of the token used for encryption. If the user is
|
||||
* not logged in, performing a crypto operation will result in a master
|
||||
* password prompt.
|
||||
*/
|
||||
readonly attribute boolean isLoggedIn;
|
||||
};
|
||||
|
|
|
@ -41,7 +41,7 @@ interface nsIFile;
|
|||
interface nsILoginInfo;
|
||||
interface nsIPropertyBag;
|
||||
|
||||
[scriptable, uuid(e66c97cd-3bcf-4eee-9937-38f650372d77)]
|
||||
[scriptable, uuid(32a4f9f1-60a8-4971-b54e-71ad661483ae)]
|
||||
|
||||
/*
|
||||
* NOTE: This interface is intended to be implemented by modules
|
||||
|
@ -284,4 +284,8 @@ interface nsILoginManagerStorage : nsISupports {
|
|||
*/
|
||||
unsigned long countLogins(in AString aHostname, in AString aActionURL,
|
||||
in AString aHttpRealm);
|
||||
/**
|
||||
* True when a master password prompt is being shown.
|
||||
*/
|
||||
readonly attribute boolean uiBusy;
|
||||
};
|
||||
|
|
|
@ -51,6 +51,16 @@ LoginManagerCrypto_SDR.prototype = {
|
|||
classID : Components.ID("{dc6c2976-0f73-4f1f-b9ff-3d72b4e28309}"),
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerCrypto]),
|
||||
|
||||
__sdrSlot : null, // PKCS#11 slot being used by the SDR.
|
||||
get _sdrSlot() {
|
||||
if (!this.__sdrSlot) {
|
||||
let modules = Cc["@mozilla.org/security/pkcs11moduledb;1"].
|
||||
getService(Ci.nsIPKCS11ModuleDB);
|
||||
this.__sdrSlot = modules.findSlotByName("");
|
||||
}
|
||||
return this.__sdrSlot;
|
||||
},
|
||||
|
||||
__decoderRing : null, // nsSecretDecoderRing service
|
||||
get _decoderRing() {
|
||||
if (!this.__decoderRing)
|
||||
|
@ -73,7 +83,8 @@ LoginManagerCrypto_SDR.prototype = {
|
|||
this.__utfConverter = null;
|
||||
},
|
||||
|
||||
_debug : false, // mirrors signon.debug
|
||||
_debug : false, // mirrors signon.debug
|
||||
_uiBusy : false,
|
||||
|
||||
|
||||
/*
|
||||
|
@ -120,6 +131,10 @@ LoginManagerCrypto_SDR.prototype = {
|
|||
encrypt : function (plainText) {
|
||||
let cipherText = null;
|
||||
|
||||
let wasLoggedIn = this.isLoggedIn;
|
||||
let canceledMP = false;
|
||||
|
||||
this._uiBusy = true;
|
||||
try {
|
||||
let plainOctet = this._utfConverter.ConvertFromUnicode(plainText);
|
||||
plainOctet += this._utfConverter.Finish();
|
||||
|
@ -128,10 +143,19 @@ LoginManagerCrypto_SDR.prototype = {
|
|||
this.log("Failed to encrypt string. (" + e.name + ")");
|
||||
// If the user clicks Cancel, we get NS_ERROR_FAILURE.
|
||||
// (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
|
||||
if (e.result == Cr.NS_ERROR_FAILURE)
|
||||
if (e.result == Cr.NS_ERROR_FAILURE) {
|
||||
canceledMP = true;
|
||||
throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
|
||||
else
|
||||
} else {
|
||||
throw Components.Exception("Couldn't encrypt string", Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
} finally {
|
||||
this._uiBusy = false;
|
||||
// If we triggered a master password prompt, notify observers.
|
||||
if (!wasLoggedIn && this.isLoggedIn)
|
||||
this._notifyObservers("passwordmgr-crypto-login");
|
||||
else if (canceledMP)
|
||||
this._notifyObservers("passwordmgr-crypto-loginCanceled");
|
||||
}
|
||||
return cipherText;
|
||||
},
|
||||
|
@ -148,6 +172,10 @@ LoginManagerCrypto_SDR.prototype = {
|
|||
decrypt : function (cipherText) {
|
||||
let plainText = null;
|
||||
|
||||
let wasLoggedIn = this.isLoggedIn;
|
||||
let canceledMP = false;
|
||||
|
||||
this._uiBusy = true;
|
||||
try {
|
||||
let plainOctet;
|
||||
if (cipherText.charAt(0) == '~') {
|
||||
|
@ -170,14 +198,55 @@ LoginManagerCrypto_SDR.prototype = {
|
|||
// If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
|
||||
// Wrong passwords are handled by the decoderRing reprompting;
|
||||
// we get no notification.
|
||||
if (e.result == Cr.NS_ERROR_NOT_AVAILABLE)
|
||||
if (e.result == Cr.NS_ERROR_NOT_AVAILABLE) {
|
||||
canceledMP = true;
|
||||
throw Components.Exception("User canceled master password entry", Cr.NS_ERROR_ABORT);
|
||||
else
|
||||
} else {
|
||||
throw Components.Exception("Couldn't decrypt string", Cr.NS_ERROR_FAILURE);
|
||||
}
|
||||
} finally {
|
||||
this._uiBusy = false;
|
||||
// If we triggered a master password prompt, notify observers.
|
||||
if (!wasLoggedIn && this.isLoggedIn)
|
||||
this._notifyObservers("passwordmgr-crypto-login");
|
||||
else if (canceledMP)
|
||||
this._notifyObservers("passwordmgr-crypto-loginCanceled");
|
||||
}
|
||||
|
||||
return plainText;
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* uiBusy
|
||||
*/
|
||||
get uiBusy() {
|
||||
return this._uiBusy;
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* isLoggedIn
|
||||
*/
|
||||
get isLoggedIn() {
|
||||
let status = this._sdrSlot.status;
|
||||
this.log("SDR slot status is " + status);
|
||||
if (status == Ci.nsIPKCS11Slot.SLOT_READY ||
|
||||
status == Ci.nsIPKCS11Slot.SLOT_LOGGED_IN)
|
||||
return true;
|
||||
if (status == Ci.nsIPKCS11Slot.SLOT_NOT_LOGGED_IN)
|
||||
return false;
|
||||
throw Components.Exception("unexpected slot status: " + status, Cr.NS_ERROR_FAILURE);
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _notifyObservers
|
||||
*/
|
||||
_notifyObservers : function(topic) {
|
||||
this.log("Prompted for a master password, notifying for " + topic);
|
||||
Services.obs.notifyObservers(null, topic, null);
|
||||
},
|
||||
}; // end of nsLoginManagerCrypto_SDR implementation
|
||||
|
||||
let component = [LoginManagerCrypto_SDR];
|
||||
|
|
|
@ -169,8 +169,6 @@ LoginManager.prototype = {
|
|||
getService(Ci.nsIWebProgress);
|
||||
progress.addProgressListener(this._webProgressListener,
|
||||
Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT);
|
||||
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
@ -347,6 +345,9 @@ LoginManager.prototype = {
|
|||
var [usernameField, passwordField, ignored] =
|
||||
this._pwmgr._getFormFields(acForm, false);
|
||||
if (usernameField == acInputField && passwordField) {
|
||||
// This shouldn't trigger a master password prompt,
|
||||
// because we don't attach to the input until after we
|
||||
// successfully obtain logins for the form.
|
||||
this._pwmgr._fillForm(acForm, true, true, true, null);
|
||||
} else {
|
||||
this._pwmgr.log("Oops, form changed before AC invoked");
|
||||
|
@ -517,6 +518,14 @@ LoginManager.prototype = {
|
|||
},
|
||||
|
||||
|
||||
/*
|
||||
* uiBusy
|
||||
*/
|
||||
get uiBusy() {
|
||||
return this._storage.uiBusy;
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* getLoginSavingEnabled
|
||||
*
|
||||
|
@ -561,7 +570,7 @@ LoginManager.prototype = {
|
|||
// aElement is nsIDOMHTMLInputElement
|
||||
|
||||
if (!this._remember)
|
||||
return false;
|
||||
return null;
|
||||
|
||||
this.log("AutoCompleteSearch invoked. Search is: " + aSearchString);
|
||||
|
||||
|
@ -596,6 +605,9 @@ LoginManager.prototype = {
|
|||
var origin = this._getPasswordOrigin(doc.documentURI);
|
||||
var actionOrigin = this._getActionOrigin(aElement.form);
|
||||
|
||||
// This shouldn't trigger a master password prompt, because we
|
||||
// don't attach to the input until after we successfully obtain
|
||||
// logins for the form.
|
||||
var logins = this.findLogins({}, origin, actionOrigin, null);
|
||||
var matchingLogins = [];
|
||||
|
||||
|
@ -993,6 +1005,37 @@ LoginManager.prototype = {
|
|||
if (!this.countLogins(formOrigin, "", null))
|
||||
return;
|
||||
|
||||
// If we're currently displaying a master password prompt, defer
|
||||
// processing this document until the user handles the prompt.
|
||||
if (this.uiBusy) {
|
||||
this.log("deferring fillDoc for " + doc.documentURI);
|
||||
let self = this;
|
||||
let observer = {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
|
||||
|
||||
observe: function (subject, topic, data) {
|
||||
self.log("Got deferred fillDoc notification: " + topic);
|
||||
// Only run observer once.
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-login");
|
||||
Services.obs.removeObserver(this, "passwordmgr-crypto-loginCanceled");
|
||||
if (topic == "passwordmgr-crypto-loginCanceled")
|
||||
return;
|
||||
self._fillDocument(doc);
|
||||
},
|
||||
handleEvent : function (event) {
|
||||
// Not expected to be called
|
||||
}
|
||||
};
|
||||
// Trickyness follows: We want an observer, but don't want it to
|
||||
// cause leaks. So add the observer with a weak reference, and use
|
||||
// a dummy event listener (a strong reference) to keep it alive
|
||||
// until the document is destroyed.
|
||||
Services.obs.addObserver(observer, "passwordmgr-crypto-login", true);
|
||||
Services.obs.addObserver(observer, "passwordmgr-crypto-loginCanceled", true);
|
||||
doc.addEventListener("mozCleverClosureHack", observer, false);
|
||||
return;
|
||||
}
|
||||
|
||||
this.log("fillDocument processing " + forms.length +
|
||||
" forms on " + doc.documentURI);
|
||||
|
||||
|
|
|
@ -52,6 +52,8 @@ Components.utils.import("resource://gre/modules/Services.jsm");
|
|||
*/
|
||||
function LoginManagerPromptFactory() {
|
||||
Services.obs.addObserver(this, "quit-application-granted", true);
|
||||
Services.obs.addObserver(this, "passwordmgr-crypto-login", true);
|
||||
Services.obs.addObserver(this, "passwordmgr-crypto-loginCanceled", true);
|
||||
}
|
||||
|
||||
LoginManagerPromptFactory.prototype = {
|
||||
|
@ -64,19 +66,16 @@ LoginManagerPromptFactory.prototype = {
|
|||
_asyncPromptInProgress : false,
|
||||
|
||||
observe : function (subject, topic, data) {
|
||||
this.log("Observed: " + topic);
|
||||
if (topic == "quit-application-granted") {
|
||||
var asyncPrompts = this._asyncPrompts;
|
||||
this.__proto__._asyncPrompts = {};
|
||||
for each (var asyncPrompt in asyncPrompts) {
|
||||
for each (var consumer in asyncPrompt.consumers) {
|
||||
if (consumer.callback) {
|
||||
this.log("Canceling async auth prompt callback " + consumer.callback);
|
||||
try {
|
||||
consumer.callback.onAuthCancelled(consumer.context, true);
|
||||
} catch (e) { /* Just ignore exceptions from the callback */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
this._cancelPendingPrompts();
|
||||
} else if (topic == "passwordmgr-crypto-login") {
|
||||
// Start processing the deferred prompters.
|
||||
this._doAsyncPrompt();
|
||||
} else if (topic == "passwordmgr-crypto-loginCanceled") {
|
||||
// User canceled a Master Password prompt, so go ahead and cancel
|
||||
// all pending auth prompts to avoid nagging over and over.
|
||||
this._cancelPendingPrompts();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -105,24 +104,37 @@ LoginManagerPromptFactory.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
// If login manger has logins for this host, defer prompting if we're
|
||||
// already waiting on a master password entry.
|
||||
var prompt = this._asyncPrompts[hashKey];
|
||||
var prompter = prompt.prompter;
|
||||
var [hostname, httpRealm] = prompter._getAuthTarget(prompt.channel, prompt.authInfo);
|
||||
var hasLogins = (prompter._pwmgr.countLogins(hostname, null, httpRealm) > 0);
|
||||
if (hasLogins && prompter._pwmgr.uiBusy) {
|
||||
this.log("_doAsyncPrompt:run bypassed, master password UI busy");
|
||||
return;
|
||||
}
|
||||
|
||||
this._asyncPromptInProgress = true;
|
||||
prompt.inProgress = true;
|
||||
|
||||
var self = this;
|
||||
|
||||
var runnable = {
|
||||
run : function() {
|
||||
var ok = false;
|
||||
var prompt = self._asyncPrompts[hashKey];
|
||||
try {
|
||||
self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
|
||||
ok = prompt.prompter.promptAuth(prompt.channel,
|
||||
prompt.level,
|
||||
prompt.authInfo);
|
||||
ok = prompter.promptAuth(prompt.channel,
|
||||
prompt.level,
|
||||
prompt.authInfo);
|
||||
} catch (e) {
|
||||
Components.utils.reportError("LoginManagerPrompter: " +
|
||||
"_doAsyncPrompt:run: " + e + "\n");
|
||||
}
|
||||
|
||||
delete self._asyncPrompts[hashKey];
|
||||
prompt.inProgress = false;
|
||||
self._asyncPromptInProgress = false;
|
||||
|
||||
for each (var consumer in prompt.consumers) {
|
||||
|
@ -147,6 +159,34 @@ LoginManagerPromptFactory.prototype = {
|
|||
this.log("_doAsyncPrompt:run dispatched");
|
||||
},
|
||||
|
||||
|
||||
_cancelPendingPrompts : function() {
|
||||
this.log("Canceling all pending prompts...");
|
||||
var asyncPrompts = this._asyncPrompts;
|
||||
this.__proto__._asyncPrompts = {};
|
||||
|
||||
for each (var prompt in asyncPrompts) {
|
||||
// Watch out! If this prompt is currently prompting, let it handle
|
||||
// notifying the callbacks of success/failure, since it's already
|
||||
// asking the user for input. Reusing a callback can be crashy.
|
||||
if (prompt.inProgress) {
|
||||
this.log("skipping a prompt in progress");
|
||||
continue;
|
||||
}
|
||||
|
||||
for each (var consumer in prompt.consumers) {
|
||||
if (!consumer.callback)
|
||||
continue;
|
||||
|
||||
this.log("Canceling async auth prompt callback " + consumer.callback);
|
||||
try {
|
||||
consumer.callback.onAuthCancelled(consumer.context, true);
|
||||
} catch (e) { /* Just ignore exceptions from the callback */ }
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
log : function (message) {
|
||||
if (!this._debug)
|
||||
return;
|
||||
|
@ -672,6 +712,7 @@ LoginManagerPrompter.prototype = {
|
|||
channel: aChannel,
|
||||
authInfo: aAuthInfo,
|
||||
level: aLevel,
|
||||
inProgress : false,
|
||||
prompter: this
|
||||
}
|
||||
|
||||
|
|
|
@ -498,6 +498,10 @@ LoginManagerStorage_legacy.prototype = {
|
|||
return count;
|
||||
},
|
||||
|
||||
get uiBusy() {
|
||||
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -772,6 +772,14 @@ LoginManagerStorage_mozStorage.prototype = {
|
|||
},
|
||||
|
||||
|
||||
/*
|
||||
* uiBusy
|
||||
*/
|
||||
get uiBusy() {
|
||||
return this._crypto.uiBusy;
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* _sendNotification
|
||||
*
|
||||
|
|
|
@ -75,6 +75,8 @@ MOCHI_TESTS = \
|
|||
test_bug_391514.html \
|
||||
test_bug_427033.html \
|
||||
test_bug_444968.html \
|
||||
test_master_password.html \
|
||||
test_master_password_cleanup.html \
|
||||
test_prompt_async.html \
|
||||
test_notifications.html \
|
||||
test_prompt.html \
|
||||
|
@ -93,6 +95,7 @@ MOCHI_CONTENT = \
|
|||
subtst_privbrowsing_2.html \
|
||||
subtst_privbrowsing_3.html \
|
||||
subtst_privbrowsing_4.html \
|
||||
subtst_master_pass.html \
|
||||
subtst_notifications_1.html \
|
||||
subtst_notifications_2.html \
|
||||
subtst_notifications_3.html \
|
||||
|
|
|
@ -163,3 +163,38 @@ function commonInit() {
|
|||
disabledHosts = pwmgr.getAllDisabledHosts();
|
||||
is(disabledHosts.length, 0, "Checking for no disabled hosts");
|
||||
}
|
||||
|
||||
const masterPassword = "omgsecret!";
|
||||
|
||||
function enableMasterPassword() {
|
||||
setMasterPassword(true);
|
||||
}
|
||||
|
||||
function disableMasterPassword() {
|
||||
setMasterPassword(false);
|
||||
}
|
||||
|
||||
function setMasterPassword(enable) {
|
||||
var oldPW, newPW;
|
||||
if (enable) {
|
||||
oldPW = "";
|
||||
newPW = masterPassword;
|
||||
} else {
|
||||
oldPW = masterPassword;
|
||||
newPW = "";
|
||||
}
|
||||
// Set master password. Note that this does not log you in, so the next
|
||||
// invocation of pwmgr can trigger a MP prompt.
|
||||
|
||||
var pk11db = Cc["@mozilla.org/security/pk11tokendb;1"].
|
||||
getService(Ci.nsIPK11TokenDB)
|
||||
var token = pk11db.findTokenByName("");
|
||||
ok(true, "change from " + oldPW + " to " + newPW);
|
||||
token.changePassword(oldPW, newPW);
|
||||
}
|
||||
|
||||
function logoutMasterPassword() {
|
||||
var sdr = Cc["@mozilla.org/security/sdr;1"].
|
||||
getService(Ci.nsISecretDecoderRing);
|
||||
sdr.logoutAndTeardown();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<h2>MP subtest</h2>
|
||||
This form triggers a MP and gets filled in.<br>
|
||||
<form>
|
||||
Username: <input type="text" id="userfield" name="u"><br>
|
||||
Password: <input type="password" id="passfield" name="p"><br>
|
||||
</form>
|
|
@ -0,0 +1,279 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Login Manager</title>
|
||||
<script type="text/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="pwmgr_common.js"></script>
|
||||
<script type="text/javascript" src="prompt_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
Login Manager test: master password.
|
||||
<p id="display"></p>
|
||||
|
||||
<div id="content" style="display: none">
|
||||
<iframe id="iframe1"></iframe>
|
||||
<iframe id="iframe2"></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
commonInit();
|
||||
var testNum = 1;
|
||||
|
||||
/*
|
||||
* handleDialog
|
||||
*
|
||||
* Invoked a short period of time after calling startCallbackTimer(), and
|
||||
* allows testing the actual auth dialog while it's being displayed. Tests
|
||||
* should call startCallbackTimer() each time the auth dialog is expected (the
|
||||
* timer is a one-shot).
|
||||
*/
|
||||
function handleDialog(doc, testNum) {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
ok(true, "handleDialog running for test " + testNum);
|
||||
|
||||
var clickOK = true;
|
||||
var doNothing = false;
|
||||
var passfield = doc.getElementById("password1Textbox");
|
||||
var dialog = doc.getElementById("commonDialog");
|
||||
|
||||
switch(testNum) {
|
||||
case 1:
|
||||
is(passfield.getAttribute("value"), "", "Checking empty prompt");
|
||||
passfield.setAttribute("value", masterPassword);
|
||||
is(passfield.getAttribute("value"), masterPassword, "Checking filled prompt");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
clickOK = false;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
is(passfield.getAttribute("value"), "", "Checking empty prompt");
|
||||
passfield.setAttribute("value", masterPassword);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
doNothing = true;
|
||||
break;
|
||||
|
||||
case 5:
|
||||
is(passfield.getAttribute("value"), "", "Checking empty prompt");
|
||||
passfield.setAttribute("value", masterPassword);
|
||||
break;
|
||||
|
||||
default:
|
||||
ok(false, "Uhh, unhandled switch for testNum #" + testNum);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!doNothing) {
|
||||
if (clickOK)
|
||||
dialog.acceptDialog();
|
||||
else
|
||||
dialog.cancelDialog();
|
||||
}
|
||||
|
||||
ok(true, "handleDialog done");
|
||||
didDialog = true;
|
||||
|
||||
if (testNum == 4)
|
||||
checkTest4A();
|
||||
}
|
||||
|
||||
|
||||
function startTest1() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
|
||||
ok(pwcrypt.isLoggedIn, "should be initially logged in (no MP)");
|
||||
enableMasterPassword();
|
||||
ok(!pwcrypt.isLoggedIn, "should be logged out after setting MP");
|
||||
|
||||
// --- Test 1 ---
|
||||
// Trigger a MP prompt via the API
|
||||
startCallbackTimer();
|
||||
var logins = pwmgr.getAllLogins();
|
||||
ok(didDialog, "handleDialog was invoked");
|
||||
is(logins.length, 3, "expected number of logins");
|
||||
|
||||
ok(pwcrypt.isLoggedIn, "should be logged in after MP prompt");
|
||||
logoutMasterPassword();
|
||||
ok(!pwcrypt.isLoggedIn, "should be logged out");
|
||||
|
||||
// --- Test 2 ---
|
||||
// Try again but click cancel.
|
||||
testNum++;
|
||||
startCallbackTimer();
|
||||
var failedAsExpected = false;
|
||||
logins = null;
|
||||
try {
|
||||
logins = pwmgr.getAllLogins();
|
||||
} catch (e) { failedAsExpected = true; }
|
||||
ok(didDialog, "handleDialog was invoked");
|
||||
ok(failedAsExpected, "getAllLogins should have thrown");
|
||||
is(logins, null, "shouldn't have gotten logins");
|
||||
ok(!pwcrypt.isLoggedIn, "should still be logged out");
|
||||
|
||||
// --- Test 3 ---
|
||||
// Load a single iframe to trigger a MP
|
||||
testNum++;
|
||||
|
||||
// Note that because DOMContentLoaded is dispatched synchronously, the
|
||||
// document's load event is blocked until after the MP entry (because
|
||||
// pwmgr's listener doesn't return until after it processes the form,
|
||||
// which is blocked waiting on a MP entry).
|
||||
iframe1.onload = checkTest3;
|
||||
iframe1.src = exampleCom + "subtst_master_pass.html";
|
||||
startCallbackTimer();
|
||||
}
|
||||
|
||||
function checkTest3() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
ok(true, "checkTest3 starting");
|
||||
ok(didDialog, "handleDialog was invoked");
|
||||
|
||||
// check contents of iframe1 fields
|
||||
var u = iframe1.contentDocument.getElementById("userfield");
|
||||
var p = iframe1.contentDocument.getElementById("passfield");
|
||||
is(u.value, "user1", "checking expected user to have been filled in");
|
||||
is(p.value, "pass1", "checking expected pass to have been filled in");
|
||||
|
||||
ok(pwcrypt.isLoggedIn, "should be logged in");
|
||||
logoutMasterPassword();
|
||||
ok(!pwcrypt.isLoggedIn, "should be logged out");
|
||||
|
||||
|
||||
// --- Test 4 ---
|
||||
// first part of loading 2 MP-triggering iframes
|
||||
testNum++;
|
||||
iframe1.onload = checkTest4C;
|
||||
iframe1.src = exampleOrg + "subtst_master_pass.html";
|
||||
// start the callback, but we'll not enter the MP, just call checkTest4A
|
||||
startCallbackTimer();
|
||||
}
|
||||
|
||||
function checkTest4A() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
ok(true, "checkTest4A starting");
|
||||
ok(didDialog, "handleDialog was invoked");
|
||||
|
||||
// check contents of iframe1 fields
|
||||
var u = iframe1.contentDocument.getElementById("userfield");
|
||||
var p = iframe1.contentDocument.getElementById("passfield");
|
||||
is(u.value, "", "checking expected empty user");
|
||||
is(p.value, "", "checking expected empty pass");
|
||||
|
||||
|
||||
ok(!pwcrypt.isLoggedIn, "should be logged out");
|
||||
|
||||
// XXX check that there's 1 MP window open
|
||||
|
||||
// Load another iframe with a login form
|
||||
// This should detect that there's already a pending MP prompt, and not
|
||||
// put up a second one. The load event will fire.
|
||||
iframe2.onload = checkTest4B;
|
||||
iframe2.src = exampleCom + "subtst_master_pass.html";
|
||||
}
|
||||
|
||||
function checkTest4B() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
ok(true, "checkTest4B starting");
|
||||
// iframe2 should load without having triggered a MP prompt (because one
|
||||
// is already waiting)
|
||||
|
||||
// check contents of iframe2 fields
|
||||
var u = iframe2.contentDocument.getElementById("userfield");
|
||||
var p = iframe2.contentDocument.getElementById("passfield");
|
||||
is(u.value, "", "checking expected empty user");
|
||||
is(p.value, "", "checking expected empty pass");
|
||||
|
||||
// XXX check that there's 1 MP window open
|
||||
ok(!pwcrypt.isLoggedIn, "should be logged out");
|
||||
|
||||
// Ok, now enter the MP. The MP prompt is already up, but we'll just reuse startCallBackTimer.
|
||||
// --- Test 5 ---
|
||||
testNum++;
|
||||
startCallbackTimer();
|
||||
}
|
||||
|
||||
function checkTest4C() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
// iframe1 finally loads after the MP entry.
|
||||
ok(true, "checkTest4C starting");
|
||||
ok(didDialog, "handleDialog was invoked");
|
||||
|
||||
// We shouldn't have to worry about iframe1's load event racing with
|
||||
// filling of iframe2's data. We notify observers synchronously, so
|
||||
// iframe2's observer will process iframe2 before iframe1 even finishes
|
||||
// processing the form (which is blocking its load event).
|
||||
ok(pwcrypt.isLoggedIn, "should be logged in");
|
||||
|
||||
// check contents of iframe1 fields
|
||||
var u = iframe1.contentDocument.getElementById("userfield");
|
||||
var p = iframe1.contentDocument.getElementById("passfield");
|
||||
is(u.value, "user2", "checking expected user to have been filled in");
|
||||
is(p.value, "pass2", "checking expected pass to have been filled in");
|
||||
|
||||
// check contents of iframe2 fields
|
||||
u = iframe2.contentDocument.getElementById("userfield");
|
||||
p = iframe2.contentDocument.getElementById("passfield");
|
||||
is(u.value, "user1", "checking expected user to have been filled in");
|
||||
is(p.value, "pass1", "checking expected pass to have been filled in");
|
||||
|
||||
finishTest();
|
||||
}
|
||||
|
||||
// XXX do a test5ABC with clicking cancel?
|
||||
|
||||
function finishTest() {
|
||||
disableMasterPassword();
|
||||
ok(pwcrypt.isLoggedIn, "should be logged in");
|
||||
|
||||
pwmgr.removeLogin(login1);
|
||||
pwmgr.removeLogin(login2);
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
|
||||
// Get the pwmgr service
|
||||
var pwmgr = Cc["@mozilla.org/login-manager;1"].
|
||||
getService(Ci.nsILoginManager);
|
||||
ok(pwmgr != null, "pwmgr getService()");
|
||||
|
||||
var pwcrypt = Cc["@mozilla.org/login-manager/crypto/SDR;1"].
|
||||
getService(Ci.nsILoginManagerCrypto);
|
||||
ok(pwcrypt != null, "pwcrypt getService()");
|
||||
|
||||
var nsLoginInfo = new Components.Constructor("@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
|
||||
ok(nsLoginInfo != null, "nsLoginInfo constructor");
|
||||
|
||||
var exampleCom = "http://example.com/tests/toolkit/components/passwordmgr/test/";
|
||||
var exampleOrg = "http://example.org/tests/toolkit/components/passwordmgr/test/";
|
||||
|
||||
var login1 = new nsLoginInfo();
|
||||
var login2 = new nsLoginInfo();
|
||||
|
||||
login1.init("http://example.com", "http://example.com", null,
|
||||
"user1", "pass1", "uname", "pword");
|
||||
login2.init("http://example.org", "http://example.org", null,
|
||||
"user2", "pass2", "uname", "pword");
|
||||
|
||||
pwmgr.addLogin(login1);
|
||||
pwmgr.addLogin(login2);
|
||||
|
||||
var iframe1 = document.getElementById("iframe1");
|
||||
var iframe2 = document.getElementById("iframe2");
|
||||
|
||||
window.onload = startTest1;
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Login Manager</title>
|
||||
<script type="text/javascript" src="/MochiKit/packed.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="pwmgr_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
Login Manager test: master password cleanup
|
||||
<p id="display"></p>
|
||||
|
||||
<div id="content" style="display: none">
|
||||
<iframe id="iframe1"></iframe>
|
||||
<iframe id="iframe2"></iframe>
|
||||
</div>
|
||||
|
||||
<pre id="test">
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
|
||||
/*
|
||||
* The entire purpose of this test is to make sure that, if the previous master
|
||||
* password test did not complete successfully, we don't screw up other pwmgr
|
||||
* tests by having a master password unexpectedly set.
|
||||
*/
|
||||
function cleanup() {
|
||||
ok(true, "ensuring MP is cleared");
|
||||
|
||||
var pk11db = Cc["@mozilla.org/security/pk11tokendb;1"].
|
||||
getService(Ci.nsIPK11TokenDB);
|
||||
var token = pk11db.getInternalKeyToken();
|
||||
|
||||
if (!token.checkPassword("")) {
|
||||
ok(true, "Oops! MP still set, clearing it...");
|
||||
disableMasterPassword();
|
||||
} else {
|
||||
ok(true, "Master password already cleared.");
|
||||
}
|
||||
|
||||
ok(true, "done.");
|
||||
}
|
||||
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
cleanup();
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
Загрузка…
Ссылка в новой задаче