gecko-dev/dom/media/PeerConnectionIdp.jsm
Andrew McCreight 5dec0e0beb Bug 1432992, part 1 - Remove definitions of Ci, Cr, Cc, and Cu. r=florian
This patch was autogenerated by my decomponents.py

It covers almost every file with the extension js, jsm, html, py,
xhtml, or xul.

It removes blank lines after removed lines, when the removed lines are
preceded by either blank lines or the start of a new block. The "start
of a new block" is defined fairly hackily: either the line starts with
//, ends with */, ends with {, <![CDATA[, """ or '''. The first two
cover comments, the third one covers JS, the fourth covers JS embedded
in XUL, and the final two cover JS embedded in Python. This also
applies if the removed line was the first line of the file.

It covers the pattern matching cases like "var {classes: Cc,
interfaces: Ci, utils: Cu, results: Cr} = Components;". It'll remove
the entire thing if they are all either Ci, Cr, Cc or Cu, or it will
remove the appropriate ones and leave the residue behind. If there's
only one behind, then it will turn it into a normal, non-pattern
matching variable definition. (For instance, "const { classes: Cc,
Constructor: CC, interfaces: Ci, utils: Cu } = Components" becomes
"const CC = Components.Constructor".)

MozReview-Commit-ID: DeSHcClQ7cG

--HG--
extra : rebase_source : d9c41878036c1ef7766ef5e91a7005025bc1d72b
2018-02-06 09:36:57 -08:00

340 lines
11 KiB
JavaScript

/* jshint moz:true, browser:true */
/* 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/. */
this.EXPORTED_SYMBOLS = ["PeerConnectionIdp"];
ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
ChromeUtils.defineModuleGetter(this, "IdpSandbox",
"resource://gre/modules/media/IdpSandbox.jsm");
/**
* Creates an IdP helper.
*
* @param win (object) the window we are working for
* @param timeout (int) the timeout in milliseconds
*/
function PeerConnectionIdp(win, timeout) {
this._win = win;
this._timeout = timeout || 5000;
this.provider = null;
this._resetAssertion();
}
(function() {
PeerConnectionIdp._mLinePattern = new RegExp("^m=", "m");
// attributes are funny, the 'a' is case sensitive, the name isn't
let pattern = "^a=[iI][dD][eE][nN][tT][iI][tT][yY]:(\\S+)";
PeerConnectionIdp._identityPattern = new RegExp(pattern, "m");
pattern = "^a=[fF][iI][nN][gG][eE][rR][pP][rR][iI][nN][tT]:(\\S+) (\\S+)";
PeerConnectionIdp._fingerprintPattern = new RegExp(pattern, "m");
})();
PeerConnectionIdp.prototype = {
get enabled() {
return !!this._idp;
},
_resetAssertion() {
this.assertion = null;
this.idpLoginUrl = null;
},
setIdentityProvider(provider, protocol, username) {
this._resetAssertion();
this.provider = provider;
this.protocol = protocol || "default";
this.username = username;
if (this._idp) {
if (this._idp.isSame(provider, protocol)) {
return; // noop
}
this._idp.stop();
}
this._idp = new IdpSandbox(provider, protocol, this._win);
},
// start the IdP and do some error fixup
start() {
return this._idp.start()
.catch(e => {
throw new this._win.DOMException(e.message, "IdpError");
});
},
close() {
this._resetAssertion();
this.provider = null;
this.protocol = null;
if (this._idp) {
this._idp.stop();
this._idp = null;
}
},
_getFingerprintsFromSdp(sdp) {
let fingerprints = {};
let m = sdp.match(PeerConnectionIdp._fingerprintPattern);
while (m) {
fingerprints[m[0]] = { algorithm: m[1], digest: m[2] };
sdp = sdp.substring(m.index + m[0].length);
m = sdp.match(PeerConnectionIdp._fingerprintPattern);
}
return Object.keys(fingerprints).map(k => fingerprints[k]);
},
_isValidAssertion(assertion) {
return assertion && assertion.idp &&
typeof assertion.idp.domain === "string" &&
(!assertion.idp.protocol ||
typeof assertion.idp.protocol === "string") &&
typeof assertion.assertion === "string";
},
_getIdentityFromSdp(sdp) {
// a=identity is session level
let idMatch;
let mLineMatch = sdp.match(PeerConnectionIdp._mLinePattern);
if (mLineMatch) {
let sessionLevel = sdp.substring(0, mLineMatch.index);
idMatch = sessionLevel.match(PeerConnectionIdp._identityPattern);
}
if (!idMatch) {
return undefined; // undefined === no identity
}
let assertion;
try {
assertion = JSON.parse(atob(idMatch[1]));
} catch (e) {
throw new this._win.DOMException("invalid identity assertion: " + e,
"InvalidSessionDescriptionError");
}
if (!this._isValidAssertion(assertion)) {
throw new this._win.DOMException("assertion missing idp/idp.domain/assertion",
"InvalidSessionDescriptionError");
}
return assertion;
},
/**
* Verifies the a=identity line the given SDP contains, if any.
* If the verification succeeds callback is called with the message from the
* IdP proxy as parameter, else (verification failed OR no a=identity line in
* SDP at all) null is passed to callback.
*
* Note that this only verifies that the SDP is coherent. We still rely on
* the fact that the RTCPeerConnection won't connect to a peer if the
* fingerprint of the certificate they offer doesn't appear in the SDP.
*/
verifyIdentityFromSDP(sdp, origin) {
let identity = this._getIdentityFromSdp(sdp);
let fingerprints = this._getFingerprintsFromSdp(sdp);
if (!identity || fingerprints.length <= 0) {
return this._win.Promise.resolve(); // undefined result = no identity
}
this.setIdentityProvider(identity.idp.domain, identity.idp.protocol);
return this._verifyIdentity(identity.assertion, fingerprints, origin);
},
/**
* Checks that the name in the identity provided by the IdP is OK.
*
* @param name (string) the name to validate
* @throws if the name isn't valid
*/
_validateName(name) {
let error = msg => {
throw new this._win.DOMException("assertion name error: " + msg,
"IdpError");
};
if (typeof name !== "string") {
error("name not a string");
}
let atIdx = name.indexOf("@");
if (atIdx <= 0) {
error("missing authority in name from IdP");
}
// no third party assertions... for now
let tail = name.substring(atIdx + 1);
// strip the port number, if present
let provider = this.provider;
let providerPortIdx = provider.indexOf(":");
if (providerPortIdx > 0) {
provider = provider.substring(0, providerPortIdx);
}
let idnService = Components.classes["@mozilla.org/network/idn-service;1"]
.getService(Components.interfaces.nsIIDNService);
if (idnService.convertUTF8toACE(tail) !==
idnService.convertUTF8toACE(provider)) {
error('name "' + name +
'" doesn\'t match IdP: "' + this.provider + '"');
}
},
/**
* Check the validation response. We are very defensive here when handling
* the message from the IdP proxy. That way, broken IdPs aren't likely to
* cause catastrophic damage.
*/
_checkValidation(validation, sdpFingerprints) {
let error = msg => {
throw new this._win.DOMException("IdP validation error: " + msg,
"IdpError");
};
if (!this.provider) {
error("IdP closed");
}
if (typeof validation !== "object" ||
typeof validation.contents !== "string" ||
typeof validation.identity !== "string") {
error("no payload in validation response");
}
let fingerprints;
try {
fingerprints = JSON.parse(validation.contents).fingerprint;
} catch (e) {
error("invalid JSON");
}
let isFingerprint = f =>
(typeof f.digest === "string") &&
(typeof f.algorithm === "string");
if (!Array.isArray(fingerprints) || !fingerprints.every(isFingerprint)) {
error("fingerprints must be an array of objects" +
" with digest and algorithm attributes");
}
// everything in `innerSet` is found in `outerSet`
let isSubsetOf = (outerSet, innerSet, comparator) => {
return innerSet.every(i => {
return outerSet.some(o => comparator(i, o));
});
};
let compareFingerprints = (a, b) => {
return (a.digest === b.digest) && (a.algorithm === b.algorithm);
};
if (!isSubsetOf(fingerprints, sdpFingerprints, compareFingerprints)) {
error("the fingerprints must be covered by the assertion");
}
this._validateName(validation.identity);
return validation;
},
/**
* Asks the IdP proxy to verify an identity assertion.
*/
_verifyIdentity(assertion, fingerprints, origin) {
let p = this.start()
.then(idp => this._wrapCrossCompartmentPromise(
idp.validateAssertion(assertion, origin)))
.then(validation => this._checkValidation(validation, fingerprints));
return this._applyTimeout(p);
},
/**
* Enriches the given SDP with an `a=identity` line. getIdentityAssertion()
* must have already run successfully, otherwise this does nothing to the sdp.
*/
addIdentityAttribute(sdp) {
if (!this.assertion) {
return sdp;
}
// yes, we assume that this matches; if it doesn't something is *wrong*
let match = sdp.match(PeerConnectionIdp._mLinePattern);
return sdp.substring(0, match.index) +
"a=identity:" + this.assertion + "\r\n" +
sdp.substring(match.index);
},
/**
* Asks the IdP proxy for an identity assertion. Don't call this unless you
* have checked .enabled, or you really like exceptions. Also, don't call
* this when another call is still running, because it's not certain which
* call will finish first and the final state will be similarly uncertain.
*/
getIdentityAssertion(fingerprint, origin) {
if (!this.enabled) {
throw new this._win.DOMException(
"no IdP set, call setIdentityProvider() to set one", "InvalidStateError");
}
let [algorithm, digest] = fingerprint.split(" ", 2);
let content = {
fingerprint: [{
algorithm,
digest
}]
};
this._resetAssertion();
let p = this.start()
.then(idp => this._wrapCrossCompartmentPromise(
idp.generateAssertion(JSON.stringify(content),
origin, this.username)))
.then(assertion => {
if (!this._isValidAssertion(assertion)) {
throw new this._win.DOMException("IdP generated invalid assertion",
"IdpError");
}
// save the base64+JSON assertion, since that is all that is used
this.assertion = btoa(JSON.stringify(assertion));
return this.assertion;
});
return this._applyTimeout(p);
},
/**
* Promises generated by the sandbox need to be very carefully treated so that
* they can chain into promises in the `this._win` compartment. Results need
* to be cloned across; errors need to be converted.
*/
_wrapCrossCompartmentPromise(sandboxPromise) {
return new this._win.Promise((resolve, reject) => {
sandboxPromise.then(
result => resolve(Cu.cloneInto(result, this._win)),
e => {
let message = "" + (e.message || JSON.stringify(e) || "IdP error");
if (e.name === "IdpLoginError") {
if (typeof e.loginUrl === "string") {
this.idpLoginUrl = e.loginUrl;
}
reject(new this._win.DOMException(message, "IdpLoginError"));
} else {
reject(new this._win.DOMException(message, "IdpError"));
}
});
});
},
/**
* Wraps a promise, adding a timeout guard on it so that it can't take longer
* than the specified time. Returns a promise that rejects if the timeout
* elapses before `p` resolves.
*/
_applyTimeout(p) {
let timeout = new this._win.Promise(
r => this._win.setTimeout(r, this._timeout))
.then(() => {
throw new this._win.DOMException("IdP timed out", "IdpError");
});
return this._win.Promise.race([ timeout, p ]);
}
};
this.PeerConnectionIdp = PeerConnectionIdp;