Source code

Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
});
/**
* This class is a simplified version from the AuthRequestor used by the
* WebExtensions codebase at:
*
* The NetworkAuthListener will monitor the provided channel and will invoke the
* owner's `onAuthPrompt` end point whenever an auth challenge is requested.
*
* The owner will receive several callbacks to proceed with the prompt:
* - cancelAuthPrompt(): cancel the authentication attempt
* - forwardAuthPrompt(): forward the auth prompt request to the next
* notification callback. If no other custom callback is set, this will
* typically lead to show the auth prompt dialog in the browser UI.
* - provideAuthCredentials(username, password): attempt to authenticate with
* the provided username and password.
*
* Please note that the request will be blocked until the consumer calls one of
* the callbacks listed above. Make sure to eventually unblock the request if
* you implement `onAuthPrompt`.
*
* @param {nsIChannel} channel
* The channel to monitor.
* @param {object} owner
* The owner object, expected to implement `onAuthPrompt`.
*/
export class NetworkAuthListener {
constructor(channel, owner) {
this.notificationCallbacks = channel.notificationCallbacks;
this.loadGroupCallbacks =
channel.loadGroup && channel.loadGroup.notificationCallbacks;
this.owner = owner;
// Setup the channel's notificationCallbacks to be handled by this instance.
channel.notificationCallbacks = this;
}
asyncPromptAuth(channel, callback, context, level, authInfo) {
const isProxy = !!(authInfo.flags & authInfo.AUTH_PROXY);
const cancelAuthPrompt = () => {
if (channel.canceled) {
return;
}
try {
callback.onAuthCancelled(context, false);
} catch (e) {
console.error(`NetworkAuthListener failed to cancel auth prompt ${e}`);
}
};
const forwardAuthPrompt = () => {
if (channel.canceled) {
return;
}
const prompt = this.#getForwardPrompt(isProxy);
prompt.asyncPromptAuth(channel, callback, context, level, authInfo);
};
const provideAuthCredentials = (username, password) => {
if (channel.canceled) {
return;
}
authInfo.username = username;
authInfo.password = password;
try {
callback.onAuthAvailable(context, authInfo);
} catch (e) {
console.error(
`NetworkAuthListener failed to provide auth credentials ${e}`
);
}
};
const authDetails = {
isProxy,
realm: authInfo.realm,
scheme: authInfo.authenticationScheme,
};
const authCallbacks = {
cancelAuthPrompt,
forwardAuthPrompt,
provideAuthCredentials,
};
// The auth callbacks may only be called asynchronously after this method
// successfully returned.
lazy.setTimeout(() => this.#notifyOwner(authDetails, authCallbacks), 1);
return {
QueryInterface: ChromeUtils.generateQI(["nsICancelable"]),
cancel: cancelAuthPrompt,
};
}
getInterface(iid) {
if (iid.equals(Ci.nsIAuthPromptProvider) || iid.equals(Ci.nsIAuthPrompt2)) {
return this;
}
try {
return this.notificationCallbacks.getInterface(iid);
} catch (e) {}
throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
}
getAuthPrompt(reason, iid) {
// This should never get called without getInterface having been called first.
if (iid.equals(Ci.nsIAuthPrompt2)) {
return this;
}
return this.#getForwardedInterface(Ci.nsIAuthPromptProvider).getAuthPrompt(
reason,
iid
);
}
promptAuth(channel, level, authInfo) {
this.#getForwardedInterface(Ci.nsIAuthPrompt2).promptAuth(
channel,
level,
authInfo
);
}
#getForwardedInterface(iid) {
try {
return this.notificationCallbacks.getInterface(iid);
} catch (e) {
return this.loadGroupCallbacks.getInterface(iid);
}
}
#getForwardPrompt(isProxy) {
const reason = isProxy
? Ci.nsIAuthPromptProvider.PROMPT_PROXY
: Ci.nsIAuthPromptProvider.PROMPT_NORMAL;
for (const callbacks of [
this.notificationCallbacks,
this.loadGroupCallbacks,
]) {
try {
return callbacks
.getInterface(Ci.nsIAuthPromptProvider)
.getAuthPrompt(reason, Ci.nsIAuthPrompt2);
} catch (e) {}
try {
return callbacks.getInterface(Ci.nsIAuthPrompt2);
} catch (e) {}
}
throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
}
#notifyOwner(authDetails, authCallbacks) {
if (typeof this.owner.onAuthPrompt == "function") {
this.owner.onAuthPrompt(authDetails, authCallbacks);
} else {
console.error(
"NetworkObserver owner enabled the auth prompt listener " +
"but does not implement 'onAuthPrompt'. " +
"Forwarding the auth prompt to the next notification callback."
);
authCallbacks.forwardAuthPrompt();
}
}
}
NetworkAuthListener.prototype.QueryInterface = ChromeUtils.generateQI([
"nsIInterfaceRequestor",
"nsIAuthPromptProvider",
"nsIAuthPrompt2",
]);