2015-02-22 00:57:20 +03:00
|
|
|
/* 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/. */
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
"use strict";
|
2015-02-22 00:57:20 +03:00
|
|
|
|
|
|
|
const {
|
|
|
|
classes: Cc,
|
|
|
|
interfaces: Ci,
|
|
|
|
utils: Cu,
|
|
|
|
results: Cr
|
|
|
|
} = Components;
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
2015-02-22 00:57:20 +03:00
|
|
|
|
|
|
|
/** This little class ensures that redirects maintain an https:// origin */
|
|
|
|
function RedirectHttpsOnly() {}
|
|
|
|
|
|
|
|
RedirectHttpsOnly.prototype = {
|
2017-06-28 22:41:50 +03:00
|
|
|
asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
|
|
|
|
if (newChannel.URI.scheme !== "https") {
|
2015-02-22 00:57:20 +03:00
|
|
|
callback.onRedirectVerifyCallback(Cr.NS_ERROR_ABORT);
|
|
|
|
} else {
|
|
|
|
callback.onRedirectVerifyCallback(Cr.NS_OK);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
getInterface(iid) {
|
2015-02-22 00:57:20 +03:00
|
|
|
return this.QueryInterface(iid);
|
|
|
|
},
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink])
|
|
|
|
};
|
|
|
|
|
|
|
|
/** This class loads a resource into a single string. ResourceLoader.load() is
|
|
|
|
* the entry point. */
|
|
|
|
function ResourceLoader(res, rej) {
|
|
|
|
this.resolve = res;
|
|
|
|
this.reject = rej;
|
2017-06-28 22:41:50 +03:00
|
|
|
this.data = "";
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
|
|
|
|
2015-04-16 00:19:21 +03:00
|
|
|
/** Loads the identified https:// URL. */
|
|
|
|
ResourceLoader.load = function(uri, doc) {
|
2015-02-22 00:57:20 +03:00
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let listener = new ResourceLoader(resolve, reject);
|
2016-01-22 22:40:32 +03:00
|
|
|
let ioChannel = NetUtil.newChannel({
|
2017-06-28 22:41:50 +03:00
|
|
|
uri,
|
2016-01-22 22:40:32 +03:00
|
|
|
loadingNode: doc,
|
|
|
|
securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
|
|
|
|
contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_SCRIPT
|
|
|
|
});
|
2015-04-16 00:19:21 +03:00
|
|
|
|
|
|
|
ioChannel.loadGroup = doc.documentLoadGroup.QueryInterface(Ci.nsILoadGroup);
|
2015-02-22 00:57:20 +03:00
|
|
|
ioChannel.notificationCallbacks = new RedirectHttpsOnly();
|
2016-01-22 22:40:32 +03:00
|
|
|
ioChannel.asyncOpen2(listener);
|
2015-02-22 00:57:20 +03:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
ResourceLoader.prototype = {
|
2017-06-28 22:41:50 +03:00
|
|
|
onDataAvailable(request, context, input, offset, count) {
|
|
|
|
let stream = Cc["@mozilla.org/scriptableinputstream;1"]
|
2015-02-22 00:57:20 +03:00
|
|
|
.createInstance(Ci.nsIScriptableInputStream);
|
|
|
|
stream.init(input);
|
|
|
|
this.data += stream.read(count);
|
|
|
|
},
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
onStartRequest(request, context) {},
|
2015-02-22 00:57:20 +03:00
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
onStopRequest(request, context, status) {
|
2015-02-22 00:57:20 +03:00
|
|
|
if (Components.isSuccessCode(status)) {
|
|
|
|
var statusCode = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
|
|
|
|
if (statusCode === 200) {
|
2017-06-28 22:41:50 +03:00
|
|
|
this.resolve({ request, data: this.data });
|
2015-02-22 00:57:20 +03:00
|
|
|
} else {
|
2017-06-28 22:41:50 +03:00
|
|
|
this.reject(new Error("Non-200 response from server: " + statusCode));
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
|
|
|
} else {
|
2017-06-28 22:41:50 +03:00
|
|
|
this.reject(new Error("Load failed: " + status));
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
getInterface(iid) {
|
2015-02-22 00:57:20 +03:00
|
|
|
return this.QueryInterface(iid);
|
|
|
|
},
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener])
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A simple implementation of the WorkerLocation interface.
|
|
|
|
*/
|
|
|
|
function createLocationFromURI(uri) {
|
|
|
|
return {
|
|
|
|
href: uri.spec,
|
2017-06-28 22:41:50 +03:00
|
|
|
protocol: uri.scheme + ":",
|
2015-02-22 00:57:20 +03:00
|
|
|
host: uri.host + ((uri.port >= 0) ?
|
2017-06-28 22:41:50 +03:00
|
|
|
(":" + uri.port) : ""),
|
2015-02-22 00:57:20 +03:00
|
|
|
port: uri.port,
|
|
|
|
hostname: uri.host,
|
2017-07-29 14:50:21 +03:00
|
|
|
pathname: uri.pathQueryRef.replace(/[#\?].*/, ""),
|
|
|
|
search: uri.pathQueryRef.replace(/^[^\?]*/, "").replace(/#.*/, ""),
|
2017-06-28 22:41:50 +03:00
|
|
|
hash: uri.hasRef ? ("#" + uri.ref) : "",
|
2015-02-22 00:57:20 +03:00
|
|
|
origin: uri.prePath,
|
2017-06-28 22:41:50 +03:00
|
|
|
toString() {
|
2015-02-22 00:57:20 +03:00
|
|
|
return uri.spec;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A javascript sandbox for running an IdP.
|
|
|
|
*
|
|
|
|
* @param domain (string) the domain of the IdP
|
|
|
|
* @param protocol (string?) the protocol of the IdP [default: 'default']
|
2015-08-29 01:40:44 +03:00
|
|
|
* @param win (obj) the current window
|
2015-02-22 00:57:20 +03:00
|
|
|
* @throws if the domain or protocol aren't valid
|
|
|
|
*/
|
2015-08-29 01:40:44 +03:00
|
|
|
function IdpSandbox(domain, protocol, win) {
|
2015-02-22 00:57:20 +03:00
|
|
|
this.source = IdpSandbox.createIdpUri(domain, protocol || "default");
|
|
|
|
this.active = null;
|
|
|
|
this.sandbox = null;
|
2015-08-29 01:40:44 +03:00
|
|
|
this.window = win;
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
IdpSandbox.checkDomain = function(domain) {
|
2017-06-28 22:41:50 +03:00
|
|
|
if (!domain || typeof domain !== "string") {
|
|
|
|
throw new Error("Invalid domain for identity provider: " +
|
|
|
|
"must be a non-zero length string");
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks that the IdP protocol is superficially sane. In particular, we don't
|
|
|
|
* want someone adding relative paths (e.g., '../../myuri'), which could be used
|
|
|
|
* to move outside of /.well-known/ and into space that they control.
|
|
|
|
*/
|
|
|
|
IdpSandbox.checkProtocol = function(protocol) {
|
2017-06-28 22:41:50 +03:00
|
|
|
let message = "Invalid protocol for identity provider: ";
|
|
|
|
if (!protocol || typeof protocol !== "string") {
|
|
|
|
throw new Error(message + "must be a non-zero length string");
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
|
|
|
if (decodeURIComponent(protocol).match(/[\/\\]/)) {
|
|
|
|
throw new Error(message + "must not include '/' or '\\'");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Turns a domain and protocol into a URI. This does some aggressive checking
|
|
|
|
* to make sure that we aren't being fooled somehow. Throws on fooling.
|
|
|
|
*/
|
|
|
|
IdpSandbox.createIdpUri = function(domain, protocol) {
|
|
|
|
IdpSandbox.checkDomain(domain);
|
|
|
|
IdpSandbox.checkProtocol(protocol);
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
let message = "Invalid IdP parameters: ";
|
2015-02-22 00:57:20 +03:00
|
|
|
try {
|
2017-06-28 22:41:50 +03:00
|
|
|
let wkIdp = "https://" + domain + "/.well-known/idp-proxy/" + protocol;
|
2018-01-03 18:11:03 +03:00
|
|
|
let uri = Services.io.newURI(wkIdp);
|
2015-02-22 00:57:20 +03:00
|
|
|
|
|
|
|
if (uri.hostPort !== domain) {
|
2017-06-28 22:41:50 +03:00
|
|
|
throw new Error(message + "domain is invalid");
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
2017-07-29 14:50:21 +03:00
|
|
|
if (uri.pathQueryRef.indexOf("/.well-known/idp-proxy/") !== 0) {
|
2017-06-28 22:41:50 +03:00
|
|
|
throw new Error(message + "must produce a /.well-known/idp-proxy/ URI");
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return uri;
|
2017-06-27 02:55:26 +03:00
|
|
|
} catch (e) {
|
2017-06-28 22:41:50 +03:00
|
|
|
if (typeof e.result !== "undefined" &&
|
2017-06-27 02:55:26 +03:00
|
|
|
e.result === Cr.NS_ERROR_MALFORMED_URI) {
|
2017-06-28 22:41:50 +03:00
|
|
|
throw new Error(message + "must produce a valid URI");
|
2017-06-27 02:55:26 +03:00
|
|
|
}
|
|
|
|
throw e;
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
IdpSandbox.prototype = {
|
2017-06-28 22:41:50 +03:00
|
|
|
isSame(domain, protocol) {
|
2015-02-22 00:57:20 +03:00
|
|
|
return this.source.spec === IdpSandbox.createIdpUri(domain, protocol).spec;
|
|
|
|
},
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
start() {
|
2015-02-22 00:57:20 +03:00
|
|
|
if (!this.active) {
|
2015-08-29 01:40:44 +03:00
|
|
|
this.active = ResourceLoader.load(this.source, this.window.document)
|
2015-02-22 00:57:20 +03:00
|
|
|
.then(result => this._createSandbox(result));
|
|
|
|
}
|
|
|
|
return this.active;
|
|
|
|
},
|
|
|
|
|
|
|
|
// Provides the sandbox with some useful facilities. Initially, this is only
|
|
|
|
// a minimal set; it is far easier to add more as the need arises, than to
|
|
|
|
// take them back if we discover a mistake.
|
2017-06-28 22:41:50 +03:00
|
|
|
_populateSandbox(uri) {
|
2015-02-22 00:57:21 +03:00
|
|
|
this.sandbox.location = Cu.cloneInto(createLocationFromURI(uri),
|
2015-02-22 00:57:20 +03:00
|
|
|
this.sandbox,
|
|
|
|
{ cloneFunctions: true });
|
|
|
|
},
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
_createSandbox(result) {
|
2015-02-22 00:57:20 +03:00
|
|
|
let principal = Services.scriptSecurityManager
|
|
|
|
.getChannelResultPrincipal(result.request);
|
|
|
|
|
|
|
|
this.sandbox = Cu.Sandbox(principal, {
|
2017-06-28 22:41:50 +03:00
|
|
|
sandboxName: "IdP-" + this.source.host,
|
2015-02-22 00:57:20 +03:00
|
|
|
wantComponents: false,
|
|
|
|
wantExportHelpers: false,
|
|
|
|
wantGlobalProperties: [
|
2017-06-28 22:41:50 +03:00
|
|
|
"indexedDB", "XMLHttpRequest", "TextEncoder", "TextDecoder",
|
|
|
|
"URL", "URLSearchParams", "atob", "btoa", "Blob", "crypto",
|
|
|
|
"rtcIdentityProvider", "fetch"
|
2015-02-22 00:57:20 +03:00
|
|
|
]
|
|
|
|
});
|
|
|
|
let registrar = this.sandbox.rtcIdentityProvider;
|
|
|
|
if (!Cu.isXrayWrapper(registrar)) {
|
2017-06-28 22:41:50 +03:00
|
|
|
throw new Error("IdP setup failed");
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
2015-02-22 00:57:21 +03:00
|
|
|
|
|
|
|
// have to use the ultimate URI, not the starting one to avoid
|
|
|
|
// that origin stealing from the one that redirected to it
|
|
|
|
this._populateSandbox(result.request.URI);
|
2015-08-29 01:40:44 +03:00
|
|
|
try {
|
|
|
|
Cu.evalInSandbox(result.data, this.sandbox,
|
2017-06-28 22:41:50 +03:00
|
|
|
"latest", result.request.URI.spec, 1);
|
2015-08-29 01:40:44 +03:00
|
|
|
} catch (e) {
|
2015-08-29 01:40:44 +03:00
|
|
|
// These can be passed straight on, because they are explicitly labelled
|
|
|
|
// as being IdP errors by the IdP and we drop line numbers as a result.
|
2017-06-28 22:41:50 +03:00
|
|
|
if (e.name === "IdpError" || e.name === "IdpLoginError") {
|
2015-08-29 01:40:44 +03:00
|
|
|
throw e;
|
|
|
|
}
|
2015-08-29 01:40:44 +03:00
|
|
|
this._logError(e);
|
2017-06-28 22:41:50 +03:00
|
|
|
throw new Error("Error in IdP, check console for details");
|
2015-08-29 01:40:44 +03:00
|
|
|
}
|
2015-02-22 00:57:20 +03:00
|
|
|
|
2016-03-24 08:12:56 +03:00
|
|
|
if (!registrar.hasIdp) {
|
2017-06-28 22:41:50 +03:00
|
|
|
throw new Error("IdP failed to call rtcIdentityProvider.register()");
|
2015-02-22 00:57:20 +03:00
|
|
|
}
|
|
|
|
return registrar;
|
|
|
|
},
|
|
|
|
|
2015-08-29 01:40:44 +03:00
|
|
|
// Capture all the details from the error and log them to the console. This
|
|
|
|
// can't rethrow anything else because that could leak information about the
|
|
|
|
// internal workings of the IdP across origins.
|
2017-06-28 22:41:50 +03:00
|
|
|
_logError(e) {
|
2015-08-29 01:40:44 +03:00
|
|
|
let winID = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
|
|
|
|
.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
|
|
|
let scriptError = Cc["@mozilla.org/scripterror;1"]
|
|
|
|
.createInstance(Ci.nsIScriptError);
|
|
|
|
scriptError.initWithWindowID(e.message, e.fileName, null,
|
|
|
|
e.lineNumber, e.columnNumber,
|
|
|
|
Ci.nsIScriptError.errorFlag,
|
|
|
|
"content javascript", winID);
|
2018-01-03 18:11:03 +03:00
|
|
|
Services.console.logMessage(scriptError);
|
2015-08-29 01:40:44 +03:00
|
|
|
},
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
stop() {
|
2015-02-22 00:57:20 +03:00
|
|
|
if (this.sandbox) {
|
|
|
|
Cu.nukeSandbox(this.sandbox);
|
|
|
|
}
|
|
|
|
this.sandbox = null;
|
|
|
|
this.active = null;
|
|
|
|
},
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
toString() {
|
2015-02-22 00:57:20 +03:00
|
|
|
return this.source.spec;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-06-28 22:41:50 +03:00
|
|
|
this.EXPORTED_SYMBOLS = ["IdpSandbox"];
|
2015-02-22 00:57:20 +03:00
|
|
|
this.IdpSandbox = IdpSandbox;
|