mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-13 05:15:45 +00:00
d1dfdb8a14
MozReview-Commit-ID: Hhm4YBansg1 --HG-- extra : rebase_source : 3cb49159fa3f5dae12e4eb21f22e747850fdf184
158 lines
5.6 KiB
JavaScript
158 lines
5.6 KiB
JavaScript
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* vim: set sts=2 sw=2 et tw=80: */
|
|
"use strict";
|
|
|
|
var {Constructor: CC} = Components;
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
|
"resource://gre/modules/Services.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
|
|
"resource://services-common/utils.js");
|
|
XPCOMUtils.defineLazyPreferenceGetter(this, "redirectDomain",
|
|
"extensions.webextensions.identity.redirectDomain");
|
|
|
|
let CryptoHash = CC("@mozilla.org/security/hash;1", "nsICryptoHash", "initWithString");
|
|
|
|
Cu.importGlobalProperties(["URL", "XMLHttpRequest", "TextEncoder"]);
|
|
|
|
var {
|
|
promiseDocumentLoaded,
|
|
} = ExtensionUtils;
|
|
|
|
function computeHash(str) {
|
|
let byteArr = new TextEncoder().encode(str);
|
|
let hash = new CryptoHash("sha1");
|
|
hash.update(byteArr, byteArr.length);
|
|
return CommonUtils.bytesAsHex(hash.finish(false));
|
|
}
|
|
|
|
function checkRedirected(url, redirectURI) {
|
|
return new Promise((resolve, reject) => {
|
|
let xhr = new XMLHttpRequest();
|
|
xhr.open("HEAD", url);
|
|
// We expect this if the user has not authenticated.
|
|
xhr.onload = () => {
|
|
reject(0);
|
|
};
|
|
// An unexpected error happened, log for extension authors.
|
|
xhr.onerror = () => {
|
|
reject(xhr.status);
|
|
};
|
|
// Catch redirect to our redirect_uri before a new request is made.
|
|
xhr.channel.notificationCallbacks = {
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterfaceRequestor, Ci.nsIChannelEventSync]),
|
|
|
|
getInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink]),
|
|
|
|
asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
|
|
let responseURL = newChannel.URI.spec;
|
|
if (responseURL.startsWith(redirectURI)) {
|
|
resolve(responseURL);
|
|
// Cancel the redirect.
|
|
callback.onRedirectVerifyCallback(Components.results.NS_BINDING_ABORTED);
|
|
return;
|
|
}
|
|
callback.onRedirectVerifyCallback(Components.results.NS_OK);
|
|
},
|
|
};
|
|
xhr.send();
|
|
});
|
|
}
|
|
|
|
function openOAuthWindow(details, redirectURI) {
|
|
let args = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
|
|
let supportsStringPrefURL = Cc["@mozilla.org/supports-string;1"]
|
|
.createInstance(Ci.nsISupportsString);
|
|
supportsStringPrefURL.data = details.url;
|
|
args.appendElement(supportsStringPrefURL);
|
|
|
|
let window = Services.ww.openWindow(null,
|
|
Services.prefs.getCharPref("browser.chromeURL"),
|
|
"launchWebAuthFlow_dialog",
|
|
"chrome,location=yes,centerscreen,dialog=no,resizable=yes",
|
|
args);
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let wpl;
|
|
|
|
// If the user just closes the window we need to reject
|
|
function unloadlistener() {
|
|
window.removeEventListener("unload", unloadlistener);
|
|
window.gBrowser.removeTabsProgressListener(wpl);
|
|
reject({message: "User cancelled or denied access."});
|
|
}
|
|
|
|
wpl = {
|
|
onLocationChange(browser, webProgress, request, locationURI) {
|
|
if (locationURI.spec.startsWith(redirectURI)) {
|
|
resolve(locationURI.spec);
|
|
window.removeEventListener("unload", unloadlistener);
|
|
window.gBrowser.removeTabsProgressListener(wpl);
|
|
window.close();
|
|
}
|
|
},
|
|
onProgressChange() {},
|
|
onStatusChange() {},
|
|
onSecurityChange() {},
|
|
};
|
|
|
|
promiseDocumentLoaded(window.document).then(() => {
|
|
window.gBrowser.addTabsProgressListener(wpl);
|
|
window.addEventListener("unload", unloadlistener);
|
|
});
|
|
});
|
|
}
|
|
|
|
this.identity = class extends ExtensionAPI {
|
|
getAPI(context) {
|
|
let {extension} = context;
|
|
return {
|
|
identity: {
|
|
launchWebAuthFlow: function(details) {
|
|
// In OAuth2 the url should have a redirect_uri param, parse the url and grab it
|
|
let url, redirectURI;
|
|
try {
|
|
url = new URL(details.url);
|
|
} catch (e) {
|
|
return Promise.reject({message: "details.url is invalid"});
|
|
}
|
|
try {
|
|
redirectURI = new URL(url.searchParams.get("redirect_uri"));
|
|
if (!redirectURI) {
|
|
return Promise.reject({message: "redirect_uri is missing"});
|
|
}
|
|
} catch (e) {
|
|
return Promise.reject({message: "redirect_uri is invalid"});
|
|
}
|
|
if (!redirectURI.href.startsWith(this.getRedirectURL())) {
|
|
// Any url will work, but we suggest addons use getRedirectURL.
|
|
Services.console.logStringMessage("WebExtensions: redirect_uri should use browser.identity.getRedirectURL");
|
|
}
|
|
|
|
// If the request is automatically redirected the user has already
|
|
// authorized and we do not want to show the window.
|
|
return checkRedirected(details.url, redirectURI).catch((requestError) => {
|
|
// requestError is zero or xhr.status
|
|
if (requestError !== 0) {
|
|
Cu.reportError(`browser.identity auth check failed with ${requestError}`);
|
|
return Promise.reject({message: "Invalid request"});
|
|
}
|
|
if (!details.interactive) {
|
|
return Promise.reject({message: `Requires user interaction`});
|
|
}
|
|
|
|
return openOAuthWindow(details, redirectURI);
|
|
});
|
|
},
|
|
|
|
getRedirectURL: function(path = "") {
|
|
let hash = computeHash(extension.id);
|
|
let url = new URL(`https://${hash}.${redirectDomain}/`);
|
|
url.pathname = path;
|
|
return url.href;
|
|
},
|
|
},
|
|
};
|
|
}
|
|
};
|