summaryrefslogtreecommitdiffstats
path: root/devtools/shared/network-observer/NetworkAuthListener.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/shared/network-observer/NetworkAuthListener.sys.mjs')
-rw-r--r--devtools/shared/network-observer/NetworkAuthListener.sys.mjs185
1 files changed, 185 insertions, 0 deletions
diff --git a/devtools/shared/network-observer/NetworkAuthListener.sys.mjs b/devtools/shared/network-observer/NetworkAuthListener.sys.mjs
new file mode 100644
index 0000000000..2ab5517aa1
--- /dev/null
+++ b/devtools/shared/network-observer/NetworkAuthListener.sys.mjs
@@ -0,0 +1,185 @@
+/* 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, {
+ setTimeout: "resource://gre/modules/Timer.sys.mjs",
+});
+
+/**
+ * This class is a simplified version from the AuthRequestor used by the
+ * WebExtensions codebase at:
+ * https://searchfox.org/mozilla-central/rev/fd2325f5b2a5be8f8f2acf9307285f2b7de06582/toolkit/components/extensions/webrequest/WebRequest.sys.mjs#434-579
+ *
+ * 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;
+ }
+
+ // See https://searchfox.org/mozilla-central/source/netwerk/base/nsIAuthPrompt2.idl
+ 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);
+ }
+
+ // See https://searchfox.org/mozilla-central/source/netwerk/base/nsIAuthPromptProvider.idl
+ 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
+ );
+ }
+
+ // See https://searchfox.org/mozilla-central/source/netwerk/base/nsIAuthPrompt2.idl
+ 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",
+]);