зеркало из https://github.com/mozilla/pjs.git
Bug 475053 - Implement asyncPromptAuth to fix multiple password prompt overlap, r=bzbarsky+dolske
This commit is contained in:
Родитель
2dab83cb32
Коммит
6c940bad06
|
@ -48,7 +48,7 @@ interface nsIAuthInformation;
|
|||
* It can be used to prompt users for authentication information, either
|
||||
* synchronously or asynchronously.
|
||||
*/
|
||||
[scriptable, uuid(447fc780-1d28-412a-91a1-466d48129c65)]
|
||||
[scriptable, uuid(651395EB-8612-4876-8AC0-A88D4DCE9E1E)]
|
||||
interface nsIAuthPrompt2 : nsISupports
|
||||
{
|
||||
/** @name Security Levels */
|
||||
|
@ -110,9 +110,23 @@ interface nsIAuthPrompt2 : nsISupports
|
|||
* dialog and MUST call nsIAuthPromptCallback::onAuthCancelled on the provided
|
||||
* callback.
|
||||
*
|
||||
* @throw NS_ERROR_NOT_IMPLEMENTED
|
||||
* Asynchronous authentication prompts are not supported;
|
||||
* the caller should fall back to promptUsernameAndPassword().
|
||||
* This implementation may:
|
||||
*
|
||||
* 1) Coalesce identical prompts. This means prompts that are guaranteed to
|
||||
* want the same auth information from the user. A single prompt will be
|
||||
* shown; then the callbacks for all the coalesced prompts will be notified
|
||||
* with the resulting auth information.
|
||||
* 2) Serialize prompts that are all in the same "context" (this might mean
|
||||
* application-wide, for a given window, or something else depending on
|
||||
* the user interface) so that the user is not deluged with prompts.
|
||||
*
|
||||
* @throw
|
||||
* This method may throw any exception when the prompt fails to queue e.g
|
||||
* because of out-of-memory error. It must not throw when the prompt
|
||||
* could already be potentially shown to the user. In that case information
|
||||
* about the failure has to come through the callback. This way we
|
||||
* prevent multiple dialogs shown to the user because consumer may fall
|
||||
* back to synchronous prompt on synchronous failure of this method.
|
||||
*/
|
||||
nsICancelable asyncPromptAuth(in nsIChannel aChannel,
|
||||
in nsIAuthPromptCallback aCallback,
|
||||
|
@ -120,5 +134,3 @@ interface nsIAuthPrompt2 : nsISupports
|
|||
in PRUint32 level,
|
||||
in nsIAuthInformation authInfo);
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
#include "nsAuthInformationHolder.h"
|
||||
#include "nsICacheService.h"
|
||||
#include "nsDNSPrefetch.h"
|
||||
#include "nsNetSegmentUtils.h"
|
||||
|
||||
// True if the local cache should be bypassed when processing a request.
|
||||
#define BYPASS_LOCAL_CACHE(loadFlags) \
|
||||
|
@ -125,6 +126,7 @@ nsHttpChannel::nsHttpChannel()
|
|||
, mTransactionReplaced(PR_FALSE)
|
||||
, mUploadStreamHasHeaders(PR_FALSE)
|
||||
, mAuthRetryPending(PR_FALSE)
|
||||
, mProxyAuth(PR_FALSE)
|
||||
, mSuppressDefensiveAuth(PR_FALSE)
|
||||
, mResuming(PR_FALSE)
|
||||
, mInitedCacheEntry(PR_FALSE)
|
||||
|
@ -3117,13 +3119,13 @@ nsHttpChannel::ProcessAuthentication(PRUint32 httpStatus)
|
|||
}
|
||||
|
||||
const char *challenges;
|
||||
PRBool proxyAuth = (httpStatus == 407);
|
||||
mProxyAuth = (httpStatus == 407);
|
||||
|
||||
nsresult rv = PrepareForAuthentication(proxyAuth);
|
||||
nsresult rv = PrepareForAuthentication(mProxyAuth);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
if (proxyAuth) {
|
||||
if (mProxyAuth) {
|
||||
// only allow a proxy challenge if we have a proxy server configured.
|
||||
// otherwise, we could inadvertantly expose the user's proxy
|
||||
// credentials to an origin server. We could attempt to proceed as
|
||||
|
@ -3148,12 +3150,24 @@ nsHttpChannel::ProcessAuthentication(PRUint32 httpStatus)
|
|||
NS_ENSURE_TRUE(challenges, NS_ERROR_UNEXPECTED);
|
||||
|
||||
nsCAutoString creds;
|
||||
rv = GetCredentials(challenges, proxyAuth, creds);
|
||||
if (NS_FAILED(rv))
|
||||
rv = GetCredentials(challenges, mProxyAuth, creds);
|
||||
if (rv == NS_ERROR_IN_PROGRESS) {
|
||||
// authentication prompt has been invoked and result
|
||||
// is expected asynchronously
|
||||
mAuthRetryPending = PR_TRUE;
|
||||
// suspend the transaction pump to stop receiving the
|
||||
// unauthenticated content data. We will throw that data
|
||||
// away when user provides credentials or resume the pump
|
||||
// when user refuses to authenticate.
|
||||
LOG(("Suspending the transaction, asynchronously prompting for credentials"));
|
||||
mTransactionPump->Suspend();
|
||||
return NS_OK;
|
||||
}
|
||||
else if (NS_FAILED(rv))
|
||||
LOG(("unable to authenticate\n"));
|
||||
else {
|
||||
// set the authentication credentials
|
||||
if (proxyAuth)
|
||||
if (mProxyAuth)
|
||||
mRequestHead.SetHeader(nsHttp::Proxy_Authorization, creds);
|
||||
else
|
||||
mRequestHead.SetHeader(nsHttp::Authorization, creds);
|
||||
|
@ -3280,6 +3294,15 @@ nsHttpChannel::GetCredentials(const char *challenges,
|
|||
|
||||
break;
|
||||
}
|
||||
else if (rv == NS_ERROR_IN_PROGRESS) {
|
||||
// authentication prompt has been invoked and result is
|
||||
// expected asynchronously, save current challenge being
|
||||
// processed and all remaining challenges to use later in
|
||||
// OnAuthAvailable and now immediately return
|
||||
mCurrentChallenge = challenge;
|
||||
mRemainingChallenges = eol ? eol+1 : nsnull;
|
||||
return rv;
|
||||
}
|
||||
|
||||
// reset the auth type and continuation state
|
||||
NS_IF_RELEASE(*currentContinuationState);
|
||||
|
@ -3299,6 +3322,43 @@ nsHttpChannel::GetCredentials(const char *challenges,
|
|||
return rv;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsHttpChannel::GetAuthorizationMembers(PRBool proxyAuth,
|
||||
nsCSubstring& scheme,
|
||||
const char*& host,
|
||||
PRInt32& port,
|
||||
nsCSubstring& path,
|
||||
nsHttpAuthIdentity*& ident,
|
||||
nsISupports**& continuationState)
|
||||
{
|
||||
if (proxyAuth) {
|
||||
NS_ASSERTION (mConnectionInfo->UsingHttpProxy(), "proxyAuth is true, but no HTTP proxy is configured!");
|
||||
|
||||
host = mConnectionInfo->ProxyHost();
|
||||
port = mConnectionInfo->ProxyPort();
|
||||
ident = &mProxyIdent;
|
||||
scheme.AssignLiteral("http");
|
||||
|
||||
continuationState = &mProxyAuthContinuationState;
|
||||
}
|
||||
else {
|
||||
host = mConnectionInfo->Host();
|
||||
port = mConnectionInfo->Port();
|
||||
ident = &mIdent;
|
||||
|
||||
nsresult rv;
|
||||
rv = GetCurrentPath(path);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = mURI->GetScheme(scheme);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
continuationState = &mAuthContinuationState;
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsHttpChannel::GetCredentialsForChallenge(const char *challenge,
|
||||
const char *authType,
|
||||
|
@ -3340,35 +3400,16 @@ nsHttpChannel::GetCredentialsForChallenge(const char *challenge,
|
|||
PRBool identFromURI = PR_FALSE;
|
||||
nsISupports **continuationState;
|
||||
|
||||
if (proxyAuth) {
|
||||
NS_ASSERTION (mConnectionInfo->UsingHttpProxy(), "proxyAuth is true, but no HTTP proxy is configured!");
|
||||
|
||||
host = mConnectionInfo->ProxyHost();
|
||||
port = mConnectionInfo->ProxyPort();
|
||||
ident = &mProxyIdent;
|
||||
scheme.AssignLiteral("http");
|
||||
|
||||
continuationState = &mProxyAuthContinuationState;
|
||||
}
|
||||
else {
|
||||
host = mConnectionInfo->Host();
|
||||
port = mConnectionInfo->Port();
|
||||
ident = &mIdent;
|
||||
|
||||
rv = GetCurrentPath(path);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
rv = mURI->GetScheme(scheme);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
rv = GetAuthorizationMembers(proxyAuth, scheme, host, port, path, ident, continuationState);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
if (!proxyAuth) {
|
||||
// if this is the first challenge, then try using the identity
|
||||
// specified in the URL.
|
||||
if (mIdent.IsEmpty()) {
|
||||
GetIdentityFromURI(authFlags, mIdent);
|
||||
identFromURI = !mIdent.IsEmpty();
|
||||
}
|
||||
|
||||
continuationState = &mAuthContinuationState;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -3622,25 +3663,165 @@ nsHttpChannel::PromptForIdentity(PRUint32 level,
|
|||
nsDependentCString(authType));
|
||||
if (!holder)
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
PRBool retval = PR_FALSE;
|
||||
rv = authPrompt->PromptAuth(this,
|
||||
level,
|
||||
holder, &retval);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
rv = authPrompt->AsyncPromptAuth(this, this, nsnull, level, holder,
|
||||
getter_AddRefs(mAsyncPromptAuthCancelable));
|
||||
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// indicate using this error code that authentication prompt
|
||||
// result is expected asynchronously
|
||||
rv = NS_ERROR_IN_PROGRESS;
|
||||
}
|
||||
else {
|
||||
// Fall back to synchronous prompt
|
||||
PRBool retval = PR_FALSE;
|
||||
rv = authPrompt->PromptAuth(this, level, holder, &retval);
|
||||
if (NS_FAILED(rv))
|
||||
return rv;
|
||||
|
||||
if (!retval)
|
||||
rv = NS_ERROR_ABORT;
|
||||
else
|
||||
holder->SetToHttpAuthIdentity(authFlags, ident);
|
||||
}
|
||||
|
||||
// remember that we successfully showed the user an auth dialog
|
||||
if (!proxyAuth)
|
||||
mSuppressDefensiveAuth = PR_TRUE;
|
||||
|
||||
if (!retval)
|
||||
rv = NS_ERROR_ABORT;
|
||||
else
|
||||
holder->SetToHttpAuthIdentity(authFlags, ident);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsHttpChannel::OnAuthAvailable(nsISupports *aContext,
|
||||
nsIAuthInformation *aAuthInfo)
|
||||
{
|
||||
LOG(("nsHttpChannel::OnAuthAvailable [this=%x]", this));
|
||||
mAsyncPromptAuthCancelable = nsnull;
|
||||
|
||||
nsresult rv;
|
||||
|
||||
const char *host;
|
||||
PRInt32 port;
|
||||
nsHttpAuthIdentity *ident;
|
||||
nsCAutoString path, scheme;
|
||||
nsISupports **continuationState;
|
||||
rv = GetAuthorizationMembers(mProxyAuth, scheme, host, port, path, ident, continuationState);
|
||||
if (NS_FAILED(rv))
|
||||
OnAuthCancelled(aContext, PR_FALSE);
|
||||
|
||||
nsCAutoString realm;
|
||||
ParseRealm(mCurrentChallenge.get(), realm);
|
||||
|
||||
nsHttpAuthCache *authCache = gHttpHandler->AuthCache();
|
||||
nsHttpAuthEntry *entry = nsnull;
|
||||
authCache->GetAuthEntryForDomain(scheme.get(), host, port, realm.get(), &entry);
|
||||
|
||||
nsCOMPtr<nsISupports> sessionStateGrip;
|
||||
if (entry)
|
||||
sessionStateGrip = entry->mMetaData;
|
||||
|
||||
nsAuthInformationHolder* holder =
|
||||
static_cast<nsAuthInformationHolder*>(aAuthInfo);
|
||||
ident->Set(holder->Domain().get(),
|
||||
holder->User().get(),
|
||||
holder->Password().get());
|
||||
|
||||
nsCAutoString unused;
|
||||
nsCOMPtr<nsIHttpAuthenticator> auth;
|
||||
rv = GetAuthenticator(mCurrentChallenge.get(), unused, getter_AddRefs(auth));
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_ASSERTION(PR_FALSE, "GetAuthenticator failed");
|
||||
OnAuthCancelled(aContext, PR_TRUE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsXPIDLCString creds;
|
||||
rv = GenCredsAndSetEntry(auth, mProxyAuth,
|
||||
scheme.get(), host, port, path.get(),
|
||||
realm.get(), mCurrentChallenge.get(), *ident, sessionStateGrip,
|
||||
getter_Copies(creds));
|
||||
|
||||
mCurrentChallenge.Truncate();
|
||||
if (NS_FAILED(rv)) {
|
||||
OnAuthCancelled(aContext, PR_TRUE);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return ContinueOnAuthAvailable(creds);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsHttpChannel::OnAuthCancelled(nsISupports *aContext,
|
||||
PRBool userCancel)
|
||||
{
|
||||
LOG(("nsHttpChannel::OnAuthCancelled [this=%x]", this));
|
||||
mAsyncPromptAuthCancelable = nsnull;
|
||||
if (userCancel) {
|
||||
if (!mRemainingChallenges.IsEmpty()) {
|
||||
// there are still some challenges to process, do so
|
||||
nsresult rv;
|
||||
|
||||
nsCAutoString creds;
|
||||
rv = GetCredentials(mRemainingChallenges.get(), mProxyAuth, creds);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
// GetCredentials loaded the credentials from the cache or
|
||||
// some other way in a synchronous manner, process those
|
||||
// credentials now
|
||||
mRemainingChallenges.Truncate();
|
||||
return ContinueOnAuthAvailable(creds);
|
||||
}
|
||||
else if (rv == NS_ERROR_IN_PROGRESS) {
|
||||
// GetCredentials successfully queued another authprompt for
|
||||
// a challenge from the list, we are now waiting for the user
|
||||
// to provide the credentials
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// otherwise, we failed...
|
||||
}
|
||||
|
||||
mRemainingChallenges.Truncate();
|
||||
|
||||
// ensure call of OnStartRequest of the current listener here,
|
||||
// it would not be called otherwise at all
|
||||
nsresult rv = CallOnStartRequest();
|
||||
|
||||
// drop mAuthRetryPending flag and resume the transaction
|
||||
// this resumes load of the unauthenticated content data
|
||||
mAuthRetryPending = PR_FALSE;
|
||||
LOG(("Resuming the transaction, user cancelled the auth dialog"));
|
||||
mTransactionPump->Resume();
|
||||
|
||||
if (NS_FAILED(rv))
|
||||
mTransactionPump->Cancel(rv);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsHttpChannel::ContinueOnAuthAvailable(const nsCSubstring& creds)
|
||||
{
|
||||
if (mProxyAuth)
|
||||
mRequestHead.SetHeader(nsHttp::Proxy_Authorization, creds);
|
||||
else
|
||||
mRequestHead.SetHeader(nsHttp::Authorization, creds);
|
||||
|
||||
// drop our remaining list of challenges. We don't need them, because we
|
||||
// have now authenticated against a challenge and will be sending that
|
||||
// information to the server (or proxy). If it doesn't accept our
|
||||
// authentication it'll respond with failure and resend the challenge list
|
||||
mRemainingChallenges.Truncate();
|
||||
|
||||
// setting mAuthRetryPending flag and resuming the transaction
|
||||
// triggers process of throwing away the unauthenticated data already
|
||||
// coming from the network
|
||||
mAuthRetryPending = PR_TRUE;
|
||||
LOG(("Resuming the transaction, we got credentials from user"));
|
||||
mTransactionPump->Resume();
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsHttpChannel::ConfirmAuth(const nsString &bundleKey, PRBool doYesNoPrompt)
|
||||
{
|
||||
|
@ -3896,6 +4077,7 @@ NS_INTERFACE_MAP_BEGIN(nsHttpChannel)
|
|||
NS_INTERFACE_MAP_ENTRY(nsITraceableChannel)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheContainer)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIApplicationCacheChannel)
|
||||
NS_INTERFACE_MAP_ENTRY(nsIAuthPromptCallback)
|
||||
NS_INTERFACE_MAP_END_INHERITING(nsHashPropertyBag)
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
@ -3943,6 +4125,8 @@ nsHttpChannel::Cancel(nsresult status)
|
|||
mTransactionPump->Cancel(status);
|
||||
if (mCachePump)
|
||||
mCachePump->Cancel(status);
|
||||
if (mAsyncPromptAuthCancelable)
|
||||
mAsyncPromptAuthCancelable->Cancel(status);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -4973,8 +5157,14 @@ nsHttpChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt, nsresult st
|
|||
// keep the connection around after the transaction is finished.
|
||||
//
|
||||
nsRefPtr<nsAHttpConnection> conn;
|
||||
if (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION))
|
||||
if (authRetry && (mCaps & NS_HTTP_STICKY_CONNECTION)) {
|
||||
conn = mTransaction->Connection();
|
||||
// This is so far a workaround to fix leak when reusing unpersistent
|
||||
// connection for authentication retry. See bug 459620 comment 4
|
||||
// for details.
|
||||
if (conn && !conn->IsPersistent())
|
||||
conn = nsnull;
|
||||
}
|
||||
|
||||
// at this point, we're done with the transaction
|
||||
NS_RELEASE(mTransaction);
|
||||
|
|
|
@ -84,6 +84,7 @@
|
|||
#include "nsICancelable.h"
|
||||
#include "nsIProxiedChannel.h"
|
||||
#include "nsITraceableChannel.h"
|
||||
#include "nsIAuthPromptCallback.h"
|
||||
|
||||
class nsHttpResponseHead;
|
||||
class nsAHttpConnection;
|
||||
|
@ -109,6 +110,7 @@ class nsHttpChannel : public nsHashPropertyBag
|
|||
, public nsIProxiedChannel
|
||||
, public nsITraceableChannel
|
||||
, public nsIApplicationCacheChannel
|
||||
, public nsIAuthPromptCallback
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
|
@ -130,6 +132,7 @@ public:
|
|||
NS_DECL_NSITRACEABLECHANNEL
|
||||
NS_DECL_NSIAPPLICATIONCACHECONTAINER
|
||||
NS_DECL_NSIAPPLICATIONCACHECHANNEL
|
||||
NS_DECL_NSIAUTHPROMPTCALLBACK
|
||||
|
||||
nsHttpChannel();
|
||||
virtual ~nsHttpChannel();
|
||||
|
@ -223,19 +226,38 @@ private:
|
|||
// auth specific methods
|
||||
nsresult PrepareForAuthentication(PRBool proxyAuth);
|
||||
nsresult GenCredsAndSetEntry(nsIHttpAuthenticator *, PRBool proxyAuth, const char *scheme, const char *host, PRInt32 port, const char *dir, const char *realm, const char *challenge, const nsHttpAuthIdentity &ident, nsCOMPtr<nsISupports> &session, char **result);
|
||||
nsresult GetCredentials(const char *challenges, PRBool proxyAuth, nsAFlatCString &creds);
|
||||
nsresult GetCredentialsForChallenge(const char *challenge, const char *scheme, PRBool proxyAuth, nsIHttpAuthenticator *auth, nsAFlatCString &creds);
|
||||
nsresult GetAuthenticator(const char *challenge, nsCString &scheme, nsIHttpAuthenticator **auth);
|
||||
void ParseRealm(const char *challenge, nsACString &realm);
|
||||
void GetIdentityFromURI(PRUint32 authFlags, nsHttpAuthIdentity&);
|
||||
/**
|
||||
* Following three methods return NS_ERROR_IN_PROGRESS when
|
||||
* nsIAuthPrompt2.asyncPromptAuth method is called. This result indicates
|
||||
* the user's decision will be gathered in a callback and is not an actual
|
||||
* error.
|
||||
*/
|
||||
nsresult GetCredentials(const char *challenges, PRBool proxyAuth, nsAFlatCString &creds);
|
||||
nsresult GetCredentialsForChallenge(const char *challenge, const char *scheme, PRBool proxyAuth, nsIHttpAuthenticator *auth, nsAFlatCString &creds);
|
||||
nsresult PromptForIdentity(PRUint32 level, PRBool proxyAuth, const char *realm, const char *authType, PRUint32 authFlags, nsHttpAuthIdentity &);
|
||||
|
||||
PRBool ConfirmAuth(const nsString &bundleKey, PRBool doYesNoPrompt);
|
||||
void CheckForSuperfluousAuth();
|
||||
void SetAuthorizationHeader(nsHttpAuthCache *, nsHttpAtom header, const char *scheme, const char *host, PRInt32 port, const char *path, nsHttpAuthIdentity &ident);
|
||||
void AddAuthorizationHeaders();
|
||||
nsresult GetCurrentPath(nsACString &);
|
||||
/**
|
||||
* Return all information needed to build authorization information,
|
||||
* all paramters except proxyAuth are out parameters. proxyAuth specifies
|
||||
* with what authorization we work (WWW or proxy).
|
||||
*/
|
||||
nsresult GetAuthorizationMembers(PRBool proxyAuth, nsCSubstring& scheme, const char*& host, PRInt32& port, nsCSubstring& path, nsHttpAuthIdentity*& ident, nsISupports**& continuationState);
|
||||
nsresult DoAuthRetry(nsAHttpConnection *);
|
||||
PRBool MustValidateBasedOnQueryUrl();
|
||||
/**
|
||||
* Method called to resume suspended transaction after we got credentials
|
||||
* from the user. Called from OnAuthAvailable callback or OnAuthCancelled
|
||||
* when credentials for next challenge were obtained synchronously.
|
||||
*/
|
||||
nsresult ContinueOnAuthAvailable(const nsCSubstring& creds);
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIURI> mOriginalURI;
|
||||
|
@ -293,6 +315,19 @@ private:
|
|||
nsHttpAuthIdentity mIdent;
|
||||
nsHttpAuthIdentity mProxyIdent;
|
||||
|
||||
// Reference to the prompt wating in prompt queue. The channel is
|
||||
// responsible to call its cancel method when user in any way cancels
|
||||
// this request.
|
||||
nsCOMPtr<nsICancelable> mAsyncPromptAuthCancelable;
|
||||
// Saved in GetCredentials when prompt is asynchronous, the first challenge
|
||||
// we obtained from the server with 401/407 response, will be processed in
|
||||
// OnAuthAvailable callback.
|
||||
nsCString mCurrentChallenge;
|
||||
// Saved in GetCredentials when prompt is asynchronous, remaning challenges
|
||||
// we have to process when user cancels the auth dialog for the current
|
||||
// challenge.
|
||||
nsCString mRemainingChallenges;
|
||||
|
||||
// Resumable channel specific data
|
||||
nsCString mEntityID;
|
||||
PRUint64 mStartPos;
|
||||
|
@ -329,6 +364,9 @@ private:
|
|||
PRUint32 mTransactionReplaced : 1;
|
||||
PRUint32 mUploadStreamHasHeaders : 1;
|
||||
PRUint32 mAuthRetryPending : 1;
|
||||
// True when we need to authenticate to proxy, i.e. when we get 407
|
||||
// response. Used in OnAuthAvailable and OnAuthCancelled callbacks.
|
||||
PRUint32 mProxyAuth : 1;
|
||||
PRUint32 mSuppressDefensiveAuth : 1;
|
||||
PRUint32 mResuming : 1;
|
||||
PRUint32 mInitedCacheEntry : 1;
|
||||
|
|
|
@ -50,18 +50,42 @@ Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
* Invoked by NS_NewAuthPrompter2()
|
||||
* [embedding/components/windowwatcher/src/nsPrompt.cpp]
|
||||
*/
|
||||
function LoginManagerPromptFactory() {}
|
||||
function LoginManagerPromptFactory() {
|
||||
var observerService = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
observerService.addObserver(this, "quit-application-granted", true);
|
||||
}
|
||||
|
||||
LoginManagerPromptFactory.prototype = {
|
||||
|
||||
classDescription : "LoginManagerPromptFactory",
|
||||
contractID : "@mozilla.org/passwordmanager/authpromptfactory;1",
|
||||
classID : Components.ID("{749e62f4-60ae-4569-a8a2-de78b649660e}"),
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory]),
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIPromptFactory, Ci.nsIObserver, Ci.nsISupportsWeakReference]),
|
||||
|
||||
_asyncPrompts : {},
|
||||
_asyncPromptInProgress : false,
|
||||
|
||||
observe : function (subject, topic, data) {
|
||||
if (topic == "quit-application-granted") {
|
||||
var asyncPrompts = this._asyncPrompts;
|
||||
this._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 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getPrompt : function (aWindow, aIID) {
|
||||
var prompt = new LoginManagerPrompter().QueryInterface(aIID);
|
||||
prompt.init(aWindow);
|
||||
prompt.init(aWindow, this);
|
||||
return prompt;
|
||||
}
|
||||
}; // end of LoginManagerPromptFactory implementation
|
||||
|
@ -98,6 +122,7 @@ LoginManagerPrompter.prototype = {
|
|||
Ci.nsIAuthPrompt2,
|
||||
Ci.nsILoginManagerPrompter]),
|
||||
|
||||
_factory : null,
|
||||
_window : null,
|
||||
_debug : false, // mirrors signon.debug
|
||||
|
||||
|
@ -165,6 +190,14 @@ LoginManagerPrompter.prototype = {
|
|||
return this.__ioService;
|
||||
},
|
||||
|
||||
__threadManager: null,
|
||||
get _threadManager() {
|
||||
if (!this.__threadManager)
|
||||
this.__threadManager = Cc["@mozilla.org/thread-manager;1"].
|
||||
getService(Ci.nsIThreadManager);
|
||||
return this.__threadManager;
|
||||
},
|
||||
|
||||
|
||||
__ellipsis : null,
|
||||
get _ellipsis() {
|
||||
|
@ -556,10 +589,114 @@ LoginManagerPrompter.prototype = {
|
|||
return ok;
|
||||
},
|
||||
|
||||
asyncPromptAuth : function () {
|
||||
return NS_ERROR_NOT_IMPLEMENTED;
|
||||
asyncPromptAuth : function (aChannel, aCallback, aContext, aLevel, aAuthInfo) {
|
||||
var cancelable = null;
|
||||
|
||||
try {
|
||||
this.log("===== asyncPromptAuth called =====");
|
||||
|
||||
// If the user submits a login but it fails, we need to remove the
|
||||
// notification bar that was displayed. Conveniently, the user will
|
||||
// be prompted for authentication again, which brings us here.
|
||||
var notifyBox = this._getNotifyBox();
|
||||
if (notifyBox)
|
||||
this._removeLoginNotifications(notifyBox);
|
||||
|
||||
cancelable = this._newAsyncPromptConsumer(aCallback, aContext);
|
||||
|
||||
var [hostname, httpRealm] = this._getAuthTarget(aChannel, aAuthInfo);
|
||||
|
||||
var hashKey = aLevel + "|" + hostname + "|" + httpRealm;
|
||||
this.log("Async prompt key = " + hashKey);
|
||||
var asyncPrompt = this._factory._asyncPrompts[hashKey];
|
||||
if (asyncPrompt) {
|
||||
this.log("Prompt bound to an existing one in the queue, callback = " + aCallback);
|
||||
asyncPrompt.consumers.push(cancelable);
|
||||
return cancelable;
|
||||
}
|
||||
|
||||
this.log("Adding new prompt to the queue, callback = " + aCallback);
|
||||
asyncPrompt = {
|
||||
consumers: [cancelable],
|
||||
channel: aChannel,
|
||||
authInfo: aAuthInfo,
|
||||
level: aLevel
|
||||
}
|
||||
|
||||
this._factory._asyncPrompts[hashKey] = asyncPrompt;
|
||||
this._doAsyncPrompt();
|
||||
}
|
||||
catch (e) {
|
||||
Components.utils.reportError("LoginManagerPrompter: " +
|
||||
"asyncPromptAuth: " + e + "\nFalling back to promptAuth\n");
|
||||
// Fail the prompt operation to let the consumer fall back
|
||||
// to synchronous promptAuth method
|
||||
throw e;
|
||||
}
|
||||
|
||||
return cancelable;
|
||||
},
|
||||
|
||||
_doAsyncPrompt : function() {
|
||||
if (this._factory._asyncPromptInProgress) {
|
||||
this.log("_doAsyncPrompt bypassed, already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the first prompt key we have in the queue
|
||||
var hashKey = null;
|
||||
for (hashKey in this._factory._asyncPrompts)
|
||||
break;
|
||||
|
||||
if (!hashKey) {
|
||||
this.log("_doAsyncPrompt:run bypassed, no prompts in the queue");
|
||||
return;
|
||||
}
|
||||
|
||||
this._factory._asyncPromptInProgress = true;
|
||||
|
||||
var self = this;
|
||||
var runnable = {
|
||||
run : function() {
|
||||
var ok = false;
|
||||
var prompt = self._factory._asyncPrompts[hashKey];
|
||||
try {
|
||||
self.log("_doAsyncPrompt:run - performing the prompt for '" + hashKey + "'");
|
||||
ok = self.promptAuth(
|
||||
prompt.channel,
|
||||
prompt.level,
|
||||
prompt.authInfo
|
||||
);
|
||||
} catch (e) {
|
||||
Components.utils.reportError("LoginManagerPrompter: " +
|
||||
"_doAsyncPrompt:run: " + e + "\n");
|
||||
}
|
||||
|
||||
delete self._factory._asyncPrompts[hashKey];
|
||||
self._factory._asyncPromptInProgress = false;
|
||||
|
||||
for each (var consumer in prompt.consumers) {
|
||||
if (!consumer.callback)
|
||||
// Not having a callback means that consumer didn't provide it
|
||||
// or canceled the notification
|
||||
continue;
|
||||
|
||||
self.log("Calling back to " + consumer.callback + " ok=" + ok);
|
||||
try {
|
||||
if (ok)
|
||||
consumer.callback.onAuthAvailable(consumer.context, prompt.authInfo);
|
||||
else
|
||||
consumer.callback.onAuthCancelled(consumer.context, true);
|
||||
} catch (e) { /* Throw away exceptions caused by callback */ }
|
||||
}
|
||||
self._doAsyncPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
this._threadManager.mainThread.dispatch(runnable,
|
||||
Ci.nsIThread.DISPATCH_NORMAL);
|
||||
this.log("_doAsyncPrompt:run dispatched");
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
@ -572,8 +709,9 @@ LoginManagerPrompter.prototype = {
|
|||
* init
|
||||
*
|
||||
*/
|
||||
init : function (aWindow) {
|
||||
init : function (aWindow, aFactory) {
|
||||
this._window = aWindow;
|
||||
this._factory = aFactory || null;
|
||||
|
||||
var prefBranch = Cc["@mozilla.org/preferences-service;1"].
|
||||
getService(Ci.nsIPrefService).getBranch("signon.");
|
||||
|
@ -1218,6 +1356,19 @@ LoginManagerPrompter.prototype = {
|
|||
aAuthInfo.username = username;
|
||||
}
|
||||
aAuthInfo.password = password;
|
||||
},
|
||||
|
||||
_newAsyncPromptConsumer : function(aCallback, aContext) {
|
||||
return {
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsICancelable]),
|
||||
callback: aCallback,
|
||||
context: aContext,
|
||||
cancel: function() {
|
||||
this.callback.onAuthCancelled(this.context, false);
|
||||
this.callback = null;
|
||||
this.context = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}; // end of LoginManagerPrompter implementation
|
||||
|
|
|
@ -74,6 +74,7 @@ MOCHI_TESTS = \
|
|||
test_bug_391514.html \
|
||||
test_bug_427033.html \
|
||||
test_bug_444968.html \
|
||||
test_prompt_async.html \
|
||||
test_notifications.html \
|
||||
test_prompt.html \
|
||||
test_xhr.html \
|
||||
|
@ -101,6 +102,7 @@ MOCHI_CONTENT = \
|
|||
subtst_notifications_8.html \
|
||||
subtst_notifications_9.html \
|
||||
subtst_notifications_10.html \
|
||||
subtst_prompt_async.html \
|
||||
$(NULL)
|
||||
|
||||
XPCSHELL_TESTS = unit
|
||||
|
|
|
@ -11,13 +11,16 @@ function handleRequest(request, response)
|
|||
|
||||
function reallyHandleRequest(request, response) {
|
||||
var match;
|
||||
var requestAuth = true;
|
||||
var requestAuth = true, requestProxyAuth = true;
|
||||
|
||||
// Allow the caller to drive how authentication is processed via the query.
|
||||
// Eg, http://localhost:8888/authenticate.sjs?user=foo&realm=bar
|
||||
var query = request.queryString;
|
||||
|
||||
var expected_user = "", expected_pass = "", realm = "mochitest";
|
||||
var proxy_expected_user = "", proxy_expected_pass = "", proxy_realm = "mochi-proxy";
|
||||
var huge = false;
|
||||
var authHeaderCount = 1;
|
||||
// user=xxx
|
||||
match = /user=([^&]*)/.exec(query);
|
||||
if (match)
|
||||
|
@ -33,6 +36,31 @@ function reallyHandleRequest(request, response) {
|
|||
if (match)
|
||||
realm = match[1];
|
||||
|
||||
// proxy_user=xxx
|
||||
match = /proxy_user=([^&]*)/.exec(query);
|
||||
if (match)
|
||||
proxy_expected_user = match[1];
|
||||
|
||||
// proxy_pass=xxx
|
||||
match = /proxy_pass=([^&]*)/.exec(query);
|
||||
if (match)
|
||||
proxy_expected_pass = match[1];
|
||||
|
||||
// proxy_realm=xxx
|
||||
match = /proxy_realm=([^&]*)/.exec(query);
|
||||
if (match)
|
||||
proxy_realm = match[1];
|
||||
|
||||
// huge=1
|
||||
match = /huge=1/.exec(query);
|
||||
if (match)
|
||||
huge = true;
|
||||
|
||||
// multiple=1
|
||||
match = /multiple=([^&]*)/.exec(query);
|
||||
if (match)
|
||||
authHeaderCount = match[1]+0;
|
||||
|
||||
|
||||
// Look for an authentication header, if any, in the request.
|
||||
//
|
||||
|
@ -56,16 +84,40 @@ function reallyHandleRequest(request, response) {
|
|||
actual_pass = match[2];
|
||||
}
|
||||
|
||||
var proxy_actual_user = "", proxy_actual_pass = "";
|
||||
if (request.hasHeader("Proxy-Authorization")) {
|
||||
authHeader = request.getHeader("Proxy-Authorization");
|
||||
match = /Basic (.+)/.exec(authHeader);
|
||||
if (match.length != 2)
|
||||
throw "Couldn't parse auth header: " + authHeader;
|
||||
|
||||
var userpass = base64ToString(match[1]); // no atob() :-(
|
||||
match = /(.*):(.*)/.exec(userpass);
|
||||
if (match.length != 3)
|
||||
throw "Couldn't decode auth header: " + userpass;
|
||||
proxy_actual_user = match[1];
|
||||
proxy_actual_pass = match[2];
|
||||
}
|
||||
|
||||
// Don't request authentication if the credentials we got were what we
|
||||
// expected.
|
||||
if (expected_user == actual_user &&
|
||||
expected_pass == actual_pass) {
|
||||
requestAuth = false;
|
||||
expected_pass == actual_pass) {
|
||||
requestAuth = false;
|
||||
}
|
||||
if (proxy_expected_user == proxy_actual_user &&
|
||||
proxy_expected_pass == proxy_actual_pass) {
|
||||
requestProxyAuth = false;
|
||||
}
|
||||
|
||||
if (requestAuth) {
|
||||
if (requestProxyAuth) {
|
||||
response.setStatusLine("1.0", 407, "Proxy authentication required");
|
||||
for (i = 0; i < authHeaderCount; ++i)
|
||||
response.setHeader("Proxy-Authenticate", "basic realm=\"" + proxy_realm + "\"", true);
|
||||
} else if (requestAuth) {
|
||||
response.setStatusLine("1.0", 401, "Authentication required");
|
||||
response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", false);
|
||||
for (i = 0; i < authHeaderCount; ++i)
|
||||
response.setHeader("WWW-Authenticate", "basic realm=\"" + realm + "\"", true);
|
||||
} else {
|
||||
response.setStatusLine("1.0", 200, "OK");
|
||||
}
|
||||
|
@ -73,9 +125,20 @@ function reallyHandleRequest(request, response) {
|
|||
response.setHeader("Content-Type", "application/xhtml+xml", false);
|
||||
response.write("<html xmlns='http://www.w3.org/1999/xhtml'>");
|
||||
response.write("<p>Login: <span id='ok'>" + (requestAuth ? "FAIL" : "PASS") + "</span></p>\n");
|
||||
response.write("<p>Proxy: <span id='proxy'>" + (requestProxyAuth ? "FAIL" : "PASS") + "</span></p>\n");
|
||||
response.write("<p>Auth: <span id='auth'>" + authHeader + "</span></p>\n");
|
||||
response.write("<p>User: <span id='user'>" + actual_user + "</span></p>\n");
|
||||
response.write("<p>Pass: <span id='pass'>" + actual_pass + "</span></p>\n");
|
||||
|
||||
if (huge) {
|
||||
response.write("<div style='display: none'>");
|
||||
for (i = 0; i < 100000; i++) {
|
||||
response.write("123456789\n");
|
||||
}
|
||||
response.write("</div>");
|
||||
response.write("<span id='footnote'>This is a footnote after the huge content fill</span>");
|
||||
}
|
||||
|
||||
response.write("</html>");
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Multiple auth request</title>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="iframe1" src="http://example.com/tests/toolkit/components/passwordmgr/test/authenticate.sjs?r=1&user=user3name&pass=user3pass&realm=mochirealm3&proxy_user=proxy_user2&proxy_pass=proxy_pass2&proxy_realm=proxy_realm2"></iframe>
|
||||
<iframe id="iframe2" src="http://example.com/tests/toolkit/components/passwordmgr/test/authenticate.sjs?r=2&user=user3name&pass=user3pass&realm=mochirealm3&proxy_user=proxy_user2&proxy_pass=proxy_pass2&proxy_realm=proxy_realm2"></iframe>
|
||||
<iframe id="iframe3" src="http://example.com/tests/toolkit/components/passwordmgr/test/authenticate.sjs?r=3&user=user3name&pass=user3pass&realm=mochirealm3&proxy_user=proxy_user2&proxy_pass=proxy_pass2&proxy_realm=proxy_realm2"></iframe>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,492 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>Test for Async Auth Prompt</title>
|
||||
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="prompt_common.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
|
||||
|
||||
// Class monitoring number of open dialog windows
|
||||
// It checks there is always open just a single dialog per application
|
||||
function dialogMonitor() {
|
||||
var observerService = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
observerService.addObserver(this, "domwindowopened", false);
|
||||
observerService.addObserver(this, "domwindowclosed", false);
|
||||
}
|
||||
|
||||
dialogMonitor.prototype = {
|
||||
windowsOpen : new Array(),
|
||||
windowsRegistered : 0,
|
||||
|
||||
QueryInterface : function (iid) {
|
||||
const interfaces = [Ci.nsIObserver,
|
||||
Ci.nsISupports];
|
||||
|
||||
if (!interfaces.some( function(v) { return iid.equals(v) } ))
|
||||
throw Components.results.NS_ERROR_NO_INTERFACE;
|
||||
return this;
|
||||
},
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
if (topic === "domwindowopened") {
|
||||
var win = subject.QueryInterface(Ci.nsIDOMWindow);
|
||||
this.windowsOpen.push(win);
|
||||
ok(this.windowsOpen.length == 1, "Didn't open more then one dialog at a time");
|
||||
this.windowsRegistered++;
|
||||
return;
|
||||
}
|
||||
if (topic === "domwindowclosed") {
|
||||
var win = subject.QueryInterface(Ci.nsIDOMWindow);
|
||||
for (p in this.windowsOpen)
|
||||
if (win == this.windowsOpen[p]) {
|
||||
this.windowsOpen.splice(p, 1);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
shutdown: function() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
var observerService = Cc["@mozilla.org/observer-service;1"].
|
||||
getService(Ci.nsIObserverService);
|
||||
observerService.removeObserver(this, "domwindowopened");
|
||||
observerService.removeObserver(this, "domwindowclosed");
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
while (this.windowsOpen.length)
|
||||
this.windowsOpen.shift();
|
||||
this.windowsRegistered = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var monitor = new dialogMonitor();
|
||||
|
||||
var pwmgr, logins = [];
|
||||
|
||||
function initLogins() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
|
||||
pwmgr = Cc["@mozilla.org/login-manager;1"].
|
||||
getService(Ci.nsILoginManager);
|
||||
|
||||
function addLogin(host, realm, user, pass) {
|
||||
var login = Cc["@mozilla.org/login-manager/loginInfo;1"].
|
||||
createInstance(Ci.nsILoginInfo);
|
||||
login.init(host, null, realm, user, pass, "", "");
|
||||
pwmgr.addLogin(login);
|
||||
logins.push(login);
|
||||
}
|
||||
|
||||
addLogin("moz-proxy://127.0.0.1:8888", "proxy_realm",
|
||||
"proxy_user", "proxy_pass");
|
||||
addLogin("moz-proxy://127.0.0.1:8888", "proxy_realm2",
|
||||
"proxy_user2", "proxy_pass2");
|
||||
addLogin("moz-proxy://127.0.0.1:8888", "proxy_realm3",
|
||||
"proxy_user3", "proxy_pass3");
|
||||
addLogin("moz-proxy://127.0.0.1:8888", "proxy_realm4",
|
||||
"proxy_user4", "proxy_pass4");
|
||||
addLogin("moz-proxy://127.0.0.1:8888", "proxy_realm5",
|
||||
"proxy_user5", "proxy_pass5");
|
||||
addLogin("http://example.com", "mochirealm",
|
||||
"user1name", "user1pass");
|
||||
addLogin("http://example.org", "mochirealm2",
|
||||
"user2name", "user2pass");
|
||||
addLogin("http://example.com", "mochirealm3",
|
||||
"user3name", "user3pass");
|
||||
addLogin("http://example.com", "mochirealm4",
|
||||
"user4name", "user4pass");
|
||||
addLogin("http://example.com", "mochirealm5",
|
||||
"user5name", "user5pass");
|
||||
addLogin("http://example.com", "mochirealm6",
|
||||
"user6name", "user6pass");
|
||||
}
|
||||
|
||||
function finishTest() {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
|
||||
ok(true, "finishTest removing testing logins...");
|
||||
for (i in logins)
|
||||
pwmgr.removeLogin(logins[i]);
|
||||
|
||||
var authMgr = Cc['@mozilla.org/network/http-auth-manager;1'].
|
||||
getService(Ci.nsIHttpAuthManager);
|
||||
authMgr.clearAll();
|
||||
|
||||
monitor.shutdown();
|
||||
SimpleTest.finish();
|
||||
}
|
||||
|
||||
|
||||
// --------------- Test loop spin ----------------
|
||||
var testNum = 1;
|
||||
var iframe1;
|
||||
var iframe2a;
|
||||
var iframe2b;
|
||||
window.onload = function () {
|
||||
iframe1 = document.getElementById("iframe1");
|
||||
iframe2a = document.getElementById("iframe2a");
|
||||
iframe2b = document.getElementById("iframe2b");
|
||||
iframe1.onload = onFrameLoad;
|
||||
iframe2a.onload = onFrameLoad;
|
||||
iframe2b.onload = onFrameLoad;
|
||||
|
||||
initLogins();
|
||||
doTest(testNum);
|
||||
}
|
||||
|
||||
var expectedLoads;
|
||||
var expectedDialogs;
|
||||
function onFrameLoad()
|
||||
{
|
||||
if (--expectedLoads == 0) {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
|
||||
// All pages expected to load has loaded, continue with the next test
|
||||
ok(true, "Expected frames loaded");
|
||||
|
||||
doCheck(testNum);
|
||||
monitor.reset();
|
||||
|
||||
testNum++;
|
||||
doTest(testNum);
|
||||
}
|
||||
}
|
||||
|
||||
function doTest(testNum)
|
||||
{
|
||||
var exampleCom = "http://example.com/tests/toolkit/components/passwordmgr/test/";
|
||||
var exampleOrg = "http://example.org/tests/toolkit/components/passwordmgr/test/";
|
||||
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
|
||||
switch (testNum)
|
||||
{
|
||||
case 1:
|
||||
// Load through a single proxy with authentication required 3 different
|
||||
// pages, first with one login, other two with their own different login.
|
||||
// We expect to show just a single dialog for proxy authentication and
|
||||
// then two dialogs to authenticate to login 1 and then login 2.
|
||||
ok(true, "doTest testNum 1");
|
||||
expectedLoads = 3;
|
||||
expectedDialogs = 3;
|
||||
iframe1.src = exampleCom + "authenticate.sjs?"+
|
||||
"r=1&"+
|
||||
"user=user1name&"+
|
||||
"pass=user1pass&"+
|
||||
"realm=mochirealm&"+
|
||||
"proxy_user=proxy_user&"+
|
||||
"proxy_pass=proxy_pass&"+
|
||||
"proxy_realm=proxy_realm";
|
||||
iframe2a.src = exampleOrg + "authenticate.sjs?"+
|
||||
"r=2&"+
|
||||
"user=user2name&"+
|
||||
"pass=user2pass&"+
|
||||
"realm=mochirealm2&"+
|
||||
"proxy_user=proxy_user&"+
|
||||
"proxy_pass=proxy_pass&"+
|
||||
"proxy_realm=proxy_realm";
|
||||
iframe2b.src = exampleOrg + "authenticate.sjs?"+
|
||||
"r=3&"+
|
||||
"user=user2name&"+
|
||||
"pass=user2pass&"+
|
||||
"realm=mochirealm2&"+
|
||||
"proxy_user=proxy_user&"+
|
||||
"proxy_pass=proxy_pass&"+
|
||||
"proxy_realm=proxy_realm";
|
||||
break;
|
||||
|
||||
case 2:
|
||||
// Load an iframe with 3 subpages all requiring the same login through
|
||||
// anuthenticated proxy. We expect 2 dialogs, proxy authentication
|
||||
// and web authentication.
|
||||
ok(true, "doTest testNum 2");
|
||||
expectedLoads = 3;
|
||||
expectedDialogs = 2;
|
||||
iframe1.src = exampleCom + "subtst_prompt_async.html";
|
||||
iframe2a.src = "about:blank";
|
||||
iframe2b.src = "about:blank";
|
||||
break;
|
||||
|
||||
case 3:
|
||||
// Load in the iframe page through unauthenticated proxy
|
||||
// and discard the proxy authentication. We expect to see
|
||||
// unauthenticated page content and just a single dialog.
|
||||
ok(true, "doTest testNum 3");
|
||||
expectedLoads = 1;
|
||||
expectedDialogs = 1;
|
||||
iframe1.src = exampleCom + "authenticate.sjs?"+
|
||||
"user=user4name&"+
|
||||
"pass=user4pass&"+
|
||||
"realm=mochirealm4&"+
|
||||
"proxy_user=proxy_user3&"+
|
||||
"proxy_pass=proxy_pass3&"+
|
||||
"proxy_realm=proxy_realm3";
|
||||
break;
|
||||
|
||||
case 4:
|
||||
// Reload the frame from previous step and pass the proxy authentication
|
||||
// but cancel the WWW authentication. We should get the proxy=ok and WWW=fail
|
||||
// content as a result.
|
||||
ok(true, "doTest testNum 4");
|
||||
expectedLoads = 1;
|
||||
expectedDialogs = 2;
|
||||
iframe1.contentDocument.location.reload();
|
||||
break;
|
||||
|
||||
case 5:
|
||||
// Same as the previous two steps but let the server generate
|
||||
// huge content load to check http channel is capable to handle
|
||||
// case when auth dialog is canceled or accepted before unauthenticated
|
||||
// content data is load from the server. (This would be better to
|
||||
// implement using delay of server response).
|
||||
ok(true, "doTest testNum 5");
|
||||
expectedLoads = 1;
|
||||
expectedDialogs = 1;
|
||||
iframe1.src = exampleCom + "authenticate.sjs?"+
|
||||
"user=user5name&"+
|
||||
"pass=user5pass&"+
|
||||
"realm=mochirealm5&"+
|
||||
"proxy_user=proxy_user4&"+
|
||||
"proxy_pass=proxy_pass4&"+
|
||||
"proxy_realm=proxy_realm4&"+
|
||||
"huge=1";
|
||||
break;
|
||||
|
||||
case 6:
|
||||
// Reload the frame from the previous step and let the proxy
|
||||
// authentication pass but WWW fail. We expect two dialogs
|
||||
// and an unathentiocated page content load.
|
||||
ok(true, "doTest testNum 6");
|
||||
expectedLoads = 1;
|
||||
expectedDialogs = 2;
|
||||
iframe1.contentDocument.location.reload();
|
||||
break;
|
||||
|
||||
case 7:
|
||||
// Reload again and let pass all authentication dialogs.
|
||||
// Check we get the authenticated content not broken by
|
||||
// the unauthenticated content.
|
||||
ok(true, "doTest testNum 7");
|
||||
expectedLoads = 1;
|
||||
expectedDialogs = 1;
|
||||
iframe1.contentDocument.location.reload();
|
||||
break;
|
||||
|
||||
case 8:
|
||||
// Check we proccess all challenges sent by server when
|
||||
// user cancels prompts
|
||||
ok(true, "doTest testNum 8");
|
||||
expectedLoads = 1;
|
||||
expectedDialogs = 5;
|
||||
iframe1.src = exampleCom + "authenticate.sjs?"+
|
||||
"user=user6name&"+
|
||||
"pass=user6pass&"+
|
||||
"realm=mochirealm6&"+
|
||||
"proxy_user=proxy_user5&"+
|
||||
"proxy_pass=proxy_pass5&"+
|
||||
"proxy_realm=proxy_realm5&"+
|
||||
"huge=1&"+
|
||||
"multiple=3";
|
||||
break;
|
||||
|
||||
case 9:
|
||||
finishTest();
|
||||
return;
|
||||
}
|
||||
|
||||
startCallbackTimer();
|
||||
}
|
||||
|
||||
function handleDialog(doc, testNum)
|
||||
{
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
var dialog = doc.getElementById("commonDialog");
|
||||
|
||||
switch (testNum)
|
||||
{
|
||||
case 1:
|
||||
case 2:
|
||||
dialog.acceptDialog();
|
||||
break;
|
||||
|
||||
case 3:
|
||||
dialog.cancelDialog();
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if (expectedDialogs == 2)
|
||||
dialog.acceptDialog();
|
||||
else
|
||||
dialog.cancelDialog();
|
||||
break;
|
||||
|
||||
case 5:
|
||||
dialog.cancelDialog();
|
||||
break;
|
||||
|
||||
case 6:
|
||||
if (expectedDialogs == 2)
|
||||
dialog.acceptDialog();
|
||||
else
|
||||
dialog.cancelDialog();
|
||||
break;
|
||||
|
||||
case 7:
|
||||
dialog.acceptDialog();
|
||||
break;
|
||||
|
||||
case 8:
|
||||
if (expectedDialogs == 3 || expectedDialogs == 1)
|
||||
dialog.acceptDialog();
|
||||
else
|
||||
dialog.cancelDialog();
|
||||
break;
|
||||
|
||||
default:
|
||||
ok(false, "Unhandled testNum "+testNum+" in handleDialog");
|
||||
}
|
||||
|
||||
if (--expectedDialogs > 0)
|
||||
startCallbackTimer();
|
||||
}
|
||||
|
||||
function doCheck(testNum)
|
||||
{
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
|
||||
switch (testNum)
|
||||
{
|
||||
case 1:
|
||||
ok(true, "doCheck testNum 1");
|
||||
is(monitor.windowsRegistered, 3, "Registered 3 open dialogs");
|
||||
var authok1 = iframe1.contentDocument.getElementById("ok").textContent;
|
||||
var proxyok1 = iframe1.contentDocument.getElementById("proxy").textContent;
|
||||
|
||||
var authok2a = iframe2a.contentDocument.getElementById("ok").textContent;
|
||||
var proxyok2a = iframe2a.contentDocument.getElementById("proxy").textContent;
|
||||
|
||||
var authok2b = iframe2b.contentDocument.getElementById("ok").textContent;
|
||||
var proxyok2b = iframe2b.contentDocument.getElementById("proxy").textContent;
|
||||
|
||||
is(authok1, "PASS", "WWW Authorization OK, frame1");
|
||||
is(authok2a, "PASS", "WWW Authorization OK, frame2a");
|
||||
is(authok2b, "PASS", "WWW Authorization OK, frame2b");
|
||||
is(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
is(proxyok2a, "PASS", "Proxy Authorization OK, frame2a");
|
||||
is(proxyok2b, "PASS", "Proxy Authorization OK, frame2b");
|
||||
break;
|
||||
|
||||
case 2:
|
||||
is(monitor.windowsRegistered, 2, "Registered 2 open dialogs");
|
||||
ok(true, "doCheck testNum 2");
|
||||
|
||||
function checkIframe(frame) {
|
||||
netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
|
||||
var authok = frame.contentDocument.getElementById("ok").textContent;
|
||||
var proxyok = frame.contentDocument.getElementById("proxy").textContent;
|
||||
|
||||
is(authok, "PASS", "WWW Authorization OK, " + frame.id);
|
||||
is(proxyok, "PASS", "Proxy Authorization OK, " + frame.id);
|
||||
}
|
||||
|
||||
checkIframe(iframe1.contentDocument.getElementById("iframe1"));
|
||||
checkIframe(iframe1.contentDocument.getElementById("iframe2"));
|
||||
checkIframe(iframe1.contentDocument.getElementById("iframe3"));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
ok(true, "doCheck testNum 3");
|
||||
is(monitor.windowsRegistered, 1, "Registered 1 open dialog");
|
||||
var authok1 = iframe1.contentDocument.getElementById("ok").textContent;
|
||||
var proxyok1 = iframe1.contentDocument.getElementById("proxy").textContent;
|
||||
|
||||
is(authok1, "FAIL", "WWW Authorization FAILED, frame1");
|
||||
is(proxyok1, "FAIL", "Proxy Authorization FAILED, frame1");
|
||||
break;
|
||||
|
||||
case 4:
|
||||
ok(true, "doCheck testNum 4");
|
||||
is(monitor.windowsRegistered, 2, "Registered 2 open dialogs");
|
||||
var authok1 = iframe1.contentDocument.getElementById("ok").textContent;
|
||||
var proxyok1 = iframe1.contentDocument.getElementById("proxy").textContent;
|
||||
|
||||
is(authok1, "FAIL", "WWW Authorization FAILED, frame1");
|
||||
is(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
break;
|
||||
|
||||
case 5:
|
||||
ok(true, "doCheck testNum 5");
|
||||
is(monitor.windowsRegistered, 1, "Registered 1 open dialog");
|
||||
var authok1 = iframe1.contentDocument.getElementById("ok").textContent;
|
||||
var proxyok1 = iframe1.contentDocument.getElementById("proxy").textContent;
|
||||
var footnote = iframe1.contentDocument.getElementById("footnote").textContent;
|
||||
|
||||
is(authok1, "FAIL", "WWW Authorization FAILED, frame1");
|
||||
is(proxyok1, "FAIL", "Proxy Authorization FAILED, frame1");
|
||||
is(footnote, "This is a footnote after the huge content fill",
|
||||
"Footnote present and loaded completely");
|
||||
break;
|
||||
|
||||
case 6:
|
||||
ok(true, "doCheck testNum 6");
|
||||
is(monitor.windowsRegistered, 2, "Registered 2 open dialogs");
|
||||
var authok1 = iframe1.contentDocument.getElementById("ok").textContent;
|
||||
var proxyok1 = iframe1.contentDocument.getElementById("proxy").textContent;
|
||||
var footnote = iframe1.contentDocument.getElementById("footnote").textContent;
|
||||
|
||||
is(authok1, "FAIL", "WWW Authorization FAILED, frame1");
|
||||
is(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
is(footnote, "This is a footnote after the huge content fill",
|
||||
"Footnote present and loaded completely");
|
||||
break;
|
||||
|
||||
case 7:
|
||||
ok(true, "doCheck testNum 7");
|
||||
is(monitor.windowsRegistered, 1, "Registered 1 open dialogs");
|
||||
var authok1 = iframe1.contentDocument.getElementById("ok").textContent;
|
||||
var proxyok1 = iframe1.contentDocument.getElementById("proxy").textContent;
|
||||
var footnote = iframe1.contentDocument.getElementById("footnote").textContent;
|
||||
|
||||
is(authok1, "PASS", "WWW Authorization OK, frame1");
|
||||
is(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
is(footnote, "This is a footnote after the huge content fill",
|
||||
"Footnote present and loaded completely");
|
||||
break;
|
||||
|
||||
case 8:
|
||||
ok(true, "doCheck testNum 8");
|
||||
is(monitor.windowsRegistered, 5, "Registered 5 open dialogs");
|
||||
var authok1 = iframe1.contentDocument.getElementById("ok").textContent;
|
||||
var proxyok1 = iframe1.contentDocument.getElementById("proxy").textContent;
|
||||
var footnote = iframe1.contentDocument.getElementById("footnote").textContent;
|
||||
|
||||
is(authok1, "PASS", "WWW Authorization OK, frame1");
|
||||
is(proxyok1, "PASS", "Proxy Authorization OK, frame1");
|
||||
is(footnote, "This is a footnote after the huge content fill",
|
||||
"Footnote present and loaded completely");
|
||||
break;
|
||||
|
||||
default:
|
||||
ok(false, "Unhandled testNum "+testNum+" in doCheck");
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<iframe id="iframe1"></iframe>
|
||||
<iframe id="iframe2a"></iframe>
|
||||
<iframe id="iframe2b"></iframe>
|
||||
</body>
|
||||
</html>
|
Загрузка…
Ссылка в новой задаче