mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 975144 - Moving to fold identity errors into the promises we return, r=jib
This commit is contained in:
parent
d6c2324c6d
commit
34940092a3
@ -185,8 +185,8 @@ IdpSandbox.prototype = {
|
||||
// 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.
|
||||
_populateSandbox: function() {
|
||||
this.sandbox.location = Cu.cloneInto(createLocationFromURI(this.source),
|
||||
_populateSandbox: function(uri) {
|
||||
this.sandbox.location = Cu.cloneInto(createLocationFromURI(uri),
|
||||
this.sandbox,
|
||||
{ cloneFunctions: true });
|
||||
},
|
||||
@ -205,12 +205,14 @@ IdpSandbox.prototype = {
|
||||
'rtcIdentityProvider'
|
||||
]
|
||||
});
|
||||
this._populateSandbox();
|
||||
|
||||
let registrar = this.sandbox.rtcIdentityProvider;
|
||||
if (!Cu.isXrayWrapper(registrar)) {
|
||||
throw new Error('IdP setup failed');
|
||||
}
|
||||
|
||||
// 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);
|
||||
// putting a javascript version of 1.8 here seems fragile
|
||||
Cu.evalInSandbox(result.data, this.sandbox,
|
||||
'1.8', result.request.URI.spec, 1);
|
||||
|
@ -309,7 +309,6 @@ function RTCPeerConnection() {
|
||||
|
||||
this._localType = null;
|
||||
this._remoteType = null;
|
||||
this._peerIdentity = null;
|
||||
|
||||
// States
|
||||
this._iceGatheringState = this._iceConnectionState = "new";
|
||||
@ -387,15 +386,16 @@ RTCPeerConnection.prototype = {
|
||||
},
|
||||
|
||||
_initIdp: function() {
|
||||
this._peerIdentity = new this._win.Promise((resolve, reject) => {
|
||||
this._resolvePeerIdentity = resolve;
|
||||
this._rejectPeerIdentity = reject;
|
||||
});
|
||||
this._lastIdentityValidation = this._win.Promise.resolve();
|
||||
|
||||
let prefName = "media.peerconnection.identity.timeout";
|
||||
let idpTimeout = Services.prefs.getIntPref(prefName);
|
||||
let warn = this.logWarning.bind(this);
|
||||
let idpErrorReport = (type, args) => {
|
||||
this.dispatchEvent(
|
||||
new this._win.RTCPeerConnectionIdentityErrorEvent(type, args));
|
||||
};
|
||||
this._localIdp = new PeerConnectionIdp(idpTimeout, warn, idpErrorReport);
|
||||
this._remoteIdp = new PeerConnectionIdp(idpTimeout, warn, idpErrorReport);
|
||||
this._localIdp = new PeerConnectionIdp(this._win, idpTimeout);
|
||||
this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout);
|
||||
},
|
||||
|
||||
// Add a function to the internal operations chain.
|
||||
@ -566,6 +566,15 @@ RTCPeerConnection.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
_addIdentityAssertion: function(p, origin) {
|
||||
if (this._localIdp.enabled) {
|
||||
return this._localIdp.getIdentityAssertion(this._impl.fingerprint, origin)
|
||||
.then(() => p)
|
||||
.then(sdp => this._localIdp.addIdentityAttribute(sdp));
|
||||
}
|
||||
return p;
|
||||
},
|
||||
|
||||
createOffer: function(optionsOrOnSuccess, onError, options) {
|
||||
let onSuccess;
|
||||
if (typeof optionsOrOnSuccess == "function") {
|
||||
@ -574,13 +583,12 @@ RTCPeerConnection.prototype = {
|
||||
options = optionsOrOnSuccess;
|
||||
}
|
||||
return this._legacyCatch(onSuccess, onError, () => {
|
||||
|
||||
// TODO: Remove old constraint-like RTCOptions support soon (Bug 1064223).
|
||||
// Note that webidl bindings make o.mandatory implicit but not o.optional.
|
||||
function convertLegacyOptions(o) {
|
||||
// Detect (mandatory OR optional) AND no other top-level members.
|
||||
let lcy = ((o.mandatory && Object.keys(o.mandatory).length) || o.optional) &&
|
||||
Object.keys(o).length == (o.mandatory? 1 : 0) + (o.optional? 1 : 0);
|
||||
Object.keys(o).length == (o.mandatory? 1 : 0) + (o.optional? 1 : 0);
|
||||
if (!lcy) {
|
||||
return false;
|
||||
}
|
||||
@ -613,21 +621,30 @@ RTCPeerConnection.prototype = {
|
||||
|
||||
if (options && convertLegacyOptions(options)) {
|
||||
this.logWarning(
|
||||
"Mandatory/optional in createOffer options is deprecated! Use " +
|
||||
"Mandatory/optional in createOffer options is deprecated! Use " +
|
||||
JSON.stringify(options) + " instead (note the case difference)!",
|
||||
null, 0);
|
||||
null, 0);
|
||||
}
|
||||
return this._chain(() => new this._win.Promise((resolve, reject) => {
|
||||
this._onCreateOfferSuccess = resolve;
|
||||
this._onCreateOfferFailure = reject;
|
||||
this._impl.createOffer(options);
|
||||
}));
|
||||
|
||||
let origin = Cu.getWebIDLCallerPrincipal().origin;
|
||||
return this._chain(() => {
|
||||
let p = new this._win.Promise((resolve, reject) => {
|
||||
this._onCreateOfferSuccess = resolve;
|
||||
this._onCreateOfferFailure = reject;
|
||||
this._impl.createOffer(options);
|
||||
});
|
||||
p = this._addIdentityAssertion(p, origin);
|
||||
return p.then(
|
||||
sdp => new this._win.mozRTCSessionDescription({ type: "offer", sdp: sdp }));
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
createAnswer: function(onSuccess, onError) {
|
||||
return this._legacyCatch(onSuccess, onError, () => {
|
||||
return this._chain(() => new this._win.Promise((resolve, reject) => {
|
||||
let origin = Cu.getWebIDLCallerPrincipal().origin;
|
||||
return this._chain(() => {
|
||||
let p = new this._win.Promise((resolve, reject) => {
|
||||
// We give up line-numbers in errors by doing this here, but do all
|
||||
// state-checks inside the chain, to support the legacy feature that
|
||||
// callers don't have to wait for setRemoteDescription to finish.
|
||||
@ -642,10 +659,16 @@ RTCPeerConnection.prototype = {
|
||||
this._onCreateAnswerSuccess = resolve;
|
||||
this._onCreateAnswerFailure = reject;
|
||||
this._impl.createAnswer();
|
||||
}));
|
||||
});
|
||||
p = this._addIdentityAssertion(p, origin);
|
||||
return p.then(sdp => {
|
||||
return new this._win.mozRTCSessionDescription({ type: "answer", sdp: sdp });
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
setLocalDescription: function(desc, onSuccess, onError) {
|
||||
return this._legacyCatch(onSuccess, onError, () => {
|
||||
this._localType = desc.type;
|
||||
@ -674,6 +697,50 @@ RTCPeerConnection.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
_validateIdentity: function(sdp, origin) {
|
||||
let expectedIdentity;
|
||||
|
||||
// Only run a single identity verification at a time. We have to do this to
|
||||
// avoid problems with the fact that identity validation doesn't block the
|
||||
// resolution of setRemoteDescription().
|
||||
let validation = this._lastIdentityValidation
|
||||
.then(() => this._remoteIdp.verifyIdentityFromSDP(sdp, origin))
|
||||
.then(msg => {
|
||||
expectedIdentity = this._impl.peerIdentity;
|
||||
// If this pc has an identity already, then the identity in sdp must match
|
||||
if (expectedIdentity && (!msg || msg.identity !== expectedIdentity)) {
|
||||
this.close();
|
||||
throw new this._win.DOMException(
|
||||
"Peer Identity mismatch, expected: " + expectedIdentity,
|
||||
"IncompatibleSessionDescriptionError");
|
||||
}
|
||||
if (msg) {
|
||||
// Set new identity and generate an event.
|
||||
this._impl.peerIdentity = msg.identity;
|
||||
let assertion = new this._win.RTCIdentityAssertion(
|
||||
this._remoteIdp.provider, msg.identity);
|
||||
this._resolvePeerIdentity(assertion);
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this._rejectPeerIdentity(e);
|
||||
// If we don't expect a specific peer identity, failure to get a valid
|
||||
// peer identity is not a terminal state, so replace the promise to
|
||||
// allow another attempt.
|
||||
if (!this._impl.peerIdentity) {
|
||||
this._peerIdentity = new this._win.Promise((resolve, reject) => {
|
||||
this._resolvePeerIdentity = resolve;
|
||||
this._rejectPeerIdentity = reject;
|
||||
});
|
||||
}
|
||||
throw e;
|
||||
});
|
||||
this._lastIdentityValidation = validation.catch(() => {});
|
||||
|
||||
// Only wait for IdP validation if we need identity matching
|
||||
return expectedIdentity ? validation : this._win.Promise.resolve();
|
||||
},
|
||||
|
||||
setRemoteDescription: function(desc, onSuccess, onError) {
|
||||
return this._legacyCatch(onSuccess, onError, () => {
|
||||
this._remoteType = desc.type;
|
||||
@ -692,41 +759,21 @@ RTCPeerConnection.prototype = {
|
||||
default:
|
||||
throw new this._win.DOMException(
|
||||
"Invalid type " + desc.type + " provided to setRemoteDescription",
|
||||
"InvalidParameterError");
|
||||
"InvalidParameterError");
|
||||
}
|
||||
|
||||
// Get caller's origin before chaining and pass it in
|
||||
// Get caller's origin before hitting the promise chain
|
||||
let origin = Cu.getWebIDLCallerPrincipal().origin;
|
||||
|
||||
return this._chain(() => {
|
||||
let expectedIdentity = this._impl.peerIdentity;
|
||||
|
||||
// Do setRemoteDescription and identity validation in parallel
|
||||
let p = new this._win.Promise((resolve, reject) => {
|
||||
// Do setRemoteDescription and identity validation in parallel
|
||||
return this._chain(() => this._win.Promise.all([
|
||||
new this._win.Promise((resolve, reject) => {
|
||||
this._onSetRemoteDescriptionSuccess = resolve;
|
||||
this._onSetRemoteDescriptionFailure = reject;
|
||||
this._impl.setRemoteDescription(type, desc.sdp);
|
||||
});
|
||||
|
||||
let pp = this._remoteIdp.verifyIdentityFromSDP(desc.sdp, origin)
|
||||
.then(msg => {
|
||||
// If this pc has an identity already, then identity in sdp must match
|
||||
if (expectedIdentity && (!msg || msg.identity !== expectedIdentity)) {
|
||||
throw new this._win.DOMException(
|
||||
"Peer Identity mismatch, expected: " + expectedIdentity,
|
||||
"IncompatibleSessionDescriptionError");
|
||||
}
|
||||
if (msg) {
|
||||
// Set new identity and generate an event.
|
||||
this._impl.peerIdentity = msg.identity;
|
||||
this._peerIdentity = new this._win.RTCIdentityAssertion(
|
||||
this._remoteIdp.provider, msg.identity);
|
||||
this.dispatchEvent(new this._win.Event("peeridentity"));
|
||||
}
|
||||
});
|
||||
// Only wait for Idp validation if we need identity matching.
|
||||
return expectedIdentity? this._win.Promise.all([p, pp]).then(() => {}) : p;
|
||||
});
|
||||
}),
|
||||
this._validateIdentity(desc.sdp, origin)
|
||||
])).then(() => {}); // must return undefined
|
||||
});
|
||||
},
|
||||
|
||||
@ -735,20 +782,11 @@ RTCPeerConnection.prototype = {
|
||||
this._localIdp.setIdentityProvider(provider, protocol, username);
|
||||
},
|
||||
|
||||
_gotIdentityAssertion: function(assertion) {
|
||||
if (!assertion) {
|
||||
return;
|
||||
}
|
||||
let args = { assertion: assertion };
|
||||
let ev = new this._win.RTCPeerConnectionIdentityEvent("identityresult", args);
|
||||
this.dispatchEvent(ev);
|
||||
},
|
||||
|
||||
getIdentityAssertion: function() {
|
||||
this._checkClosed();
|
||||
|
||||
this._localIdp.getIdentityAssertion(this._impl.fingerprint)
|
||||
.then(assertion => this._gotIdentityAssertion(assertion));
|
||||
let origin = Cu.getWebIDLCallerPrincipal().origin;
|
||||
return this._chain(() => {
|
||||
return this._localIdp.getIdentityAssertion(this._impl.fingerprint, origin);
|
||||
});
|
||||
},
|
||||
|
||||
updateIce: function(config) {
|
||||
@ -902,6 +940,7 @@ RTCPeerConnection.prototype = {
|
||||
},
|
||||
|
||||
get peerIdentity() { return this._peerIdentity; },
|
||||
get idpLoginUrl() { return this._localIdp.idpLoginUrl; },
|
||||
get id() { return this._impl.id; },
|
||||
set id(s) { this._impl.id = s; },
|
||||
get iceGatheringState() { return this._iceGatheringState; },
|
||||
@ -1039,21 +1078,7 @@ PeerConnectionObserver.prototype = {
|
||||
},
|
||||
|
||||
onCreateOfferSuccess: function(sdp) {
|
||||
let pc = this._dompc;
|
||||
let idp = pc._localIdp;
|
||||
|
||||
if (idp.enabled) {
|
||||
idp.getIdentityAssertion(pc._impl.fingerprint)
|
||||
.then(assertion => {
|
||||
pc._gotIdentityAssertion(assertion);
|
||||
sdp = idp.addIdentityAttribute(sdp);
|
||||
pc._onCreateOfferSuccess(new pc._win.mozRTCSessionDescription({ type: "offer",
|
||||
sdp: sdp }));
|
||||
}, e => {}); // errors are handled in the IdP
|
||||
} else {
|
||||
pc._onCreateOfferSuccess(new pc._win.mozRTCSessionDescription({ type: "offer",
|
||||
sdp: sdp }));
|
||||
}
|
||||
this._dompc._onCreateOfferSuccess(sdp);
|
||||
},
|
||||
|
||||
onCreateOfferError: function(code, message) {
|
||||
@ -1061,21 +1086,7 @@ PeerConnectionObserver.prototype = {
|
||||
},
|
||||
|
||||
onCreateAnswerSuccess: function(sdp) {
|
||||
let pc = this._dompc;
|
||||
let idp = pc._localIdp;
|
||||
|
||||
if (idp.enabled) {
|
||||
idp.getIdentityAssertion(pc._impl.fingerprint)
|
||||
.then(assertion => {
|
||||
pc._gotIdentityAssertion(assertion);
|
||||
sdp = idp.addIdentityAttribute(sdp);
|
||||
pc._onCreateAnswerSuccess(new pc._win.mozRTCSessionDescription({ type: "answer",
|
||||
sdp: sdp }));
|
||||
}, e => {});
|
||||
} else {
|
||||
pc._onCreateAnswerSuccess(new pc._win.mozRTCSessionDescription({ type: "answer",
|
||||
sdp: sdp }));
|
||||
}
|
||||
this._dompc._onCreateAnswerSuccess(sdp);
|
||||
},
|
||||
|
||||
onCreateAnswerError: function(code, message) {
|
||||
|
@ -12,36 +12,18 @@ Cu.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, 'IdpSandbox',
|
||||
'resource://gre/modules/media/IdpSandbox.jsm');
|
||||
|
||||
function TimerResolver(resolve) {
|
||||
this.notify = resolve;
|
||||
}
|
||||
TimerResolver.prototype = {
|
||||
getInterface: function(iid) {
|
||||
return this.QueryInterface(iid);
|
||||
},
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback])
|
||||
}
|
||||
function delay(t) {
|
||||
return new Promise(resolve => {
|
||||
let timer = Cc['@mozilla.org/timer;1'].getService(Ci.nsITimer);
|
||||
timer.initWithCallback(new TimerResolver(resolve), t, 0); // One shot
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an IdP helper.
|
||||
*
|
||||
* @param win (object) the window we are working for
|
||||
* @param timeout (int) the timeout in milliseconds
|
||||
* @param warningFunc (function) somewhere to dump warning messages
|
||||
* @param dispatchErrorFunc (function(string, dict)) somewhere to dump errors
|
||||
*/
|
||||
function PeerConnectionIdp(timeout, warningFunc, dispatchErrorFunc) {
|
||||
function PeerConnectionIdp(win, timeout) {
|
||||
this._win = win;
|
||||
this._timeout = timeout || 5000;
|
||||
this._warning = warningFunc;
|
||||
this._dispatchError = dispatchErrorFunc;
|
||||
|
||||
this.assertion = null;
|
||||
this.provider = null;
|
||||
this._resetAssertion();
|
||||
}
|
||||
|
||||
(function() {
|
||||
@ -58,7 +40,13 @@ PeerConnectionIdp.prototype = {
|
||||
return !!this._idp;
|
||||
},
|
||||
|
||||
_resetAssertion: function() {
|
||||
this.assertion = null;
|
||||
this.idpLoginUrl = null;
|
||||
},
|
||||
|
||||
setIdentityProvider: function(provider, protocol, username) {
|
||||
this._resetAssertion();
|
||||
this.provider = provider;
|
||||
this.protocol = protocol || 'default';
|
||||
this.username = username;
|
||||
@ -71,8 +59,16 @@ PeerConnectionIdp.prototype = {
|
||||
this._idp = new IdpSandbox(provider, protocol);
|
||||
},
|
||||
|
||||
// start the IdP and do some error fixup
|
||||
start: function() {
|
||||
return this._idp.start()
|
||||
.catch(e => {
|
||||
throw new this._win.DOMException(e.message, 'IdpError');
|
||||
});
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this.assertion = null;
|
||||
this._resetAssertion();
|
||||
this.provider = null;
|
||||
this.protocol = null;
|
||||
if (this._idp) {
|
||||
@ -81,30 +77,6 @@ PeerConnectionIdp.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Generate an error event of the identified type;
|
||||
* and put a little more precise information in the console.
|
||||
*
|
||||
* A little note on error handling in this class: this class reports errors
|
||||
* exclusively through the event handlers that are passed to it
|
||||
* (this._dispatchError, specifically). That means that all the functions
|
||||
* return resolved promises; promises are never rejected. This probably isn't
|
||||
* the best design, but the refactor can wait.
|
||||
*/
|
||||
reportError: function(type, message, extra) {
|
||||
let args = {
|
||||
idp: this.provider,
|
||||
protocol: this.protocol
|
||||
};
|
||||
if (extra) {
|
||||
Object.keys(extra).forEach(function(k) {
|
||||
args[k] = extra[k];
|
||||
});
|
||||
}
|
||||
this._warning('RTC identity: ' + message, null, 0);
|
||||
this._dispatchError('idp' + type + 'error', args);
|
||||
},
|
||||
|
||||
_getFingerprintsFromSdp: function(sdp) {
|
||||
let fingerprints = {};
|
||||
let m = sdp.match(PeerConnectionIdp._fingerprintPattern);
|
||||
@ -131,7 +103,7 @@ PeerConnectionIdp.prototype = {
|
||||
let mLineMatch = sdp.match(PeerConnectionIdp._mLinePattern);
|
||||
if (mLineMatch) {
|
||||
let sessionLevel = sdp.substring(0, mLineMatch.index);
|
||||
let idMatch = sessionLevel.match(PeerConnectionIdp._identityPattern);
|
||||
idMatch = sessionLevel.match(PeerConnectionIdp._identityPattern);
|
||||
}
|
||||
if (!idMatch) {
|
||||
return; // undefined === no identity
|
||||
@ -141,12 +113,12 @@ PeerConnectionIdp.prototype = {
|
||||
try {
|
||||
assertion = JSON.parse(atob(idMatch[1]));
|
||||
} catch (e) {
|
||||
this.reportError('validation',
|
||||
'invalid identity assertion: ' + e);
|
||||
throw new this._win.DOMException('invalid identity assertion: ' + e,
|
||||
'InvalidSessionDescriptionError');
|
||||
}
|
||||
if (!this._isValidAssertion(assertion)) {
|
||||
this.reportError('validation', 'assertion missing' +
|
||||
' idp/idp.domain/assertion');
|
||||
throw new this._win.DOMException('assertion missing idp/idp.domain/assertion',
|
||||
'InvalidSessionDescriptionError');
|
||||
}
|
||||
return assertion;
|
||||
},
|
||||
@ -157,15 +129,15 @@ PeerConnectionIdp.prototype = {
|
||||
* 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. This relies on the
|
||||
* invariant that the RTCPeerConnection won't connect to a peer if the
|
||||
* 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: function(sdp, origin) {
|
||||
let identity = this._getIdentityFromSdp(sdp);
|
||||
let fingerprints = this._getFingerprintsFromSdp(sdp);
|
||||
if (!identity || fingerprints.length <= 0) {
|
||||
return Promise.resolve();
|
||||
return this._win.Promise.resolve(); // undefined result = no identity
|
||||
}
|
||||
|
||||
this.setIdentityProvider(identity.idp.domain, identity.idp.protocol);
|
||||
@ -175,17 +147,21 @@ PeerConnectionIdp.prototype = {
|
||||
/**
|
||||
* Checks that the name in the identity provided by the IdP is OK.
|
||||
*
|
||||
* @param error (function) an error function to call
|
||||
* @param name (string) the name to validate
|
||||
* @throws if the name isn't valid
|
||||
*/
|
||||
_validateName: function(error, name) {
|
||||
_validateName: function(name) {
|
||||
let error = msg => {
|
||||
throw new this._win.DOMException('assertion name error: ' + msg,
|
||||
'IdpError');
|
||||
};
|
||||
|
||||
if (typeof name !== 'string') {
|
||||
return error('name not a string');
|
||||
error('name not a string');
|
||||
}
|
||||
let atIdx = name.indexOf('@');
|
||||
if (atIdx <= 0) {
|
||||
return error('missing authority in name from IdP');
|
||||
error('missing authority in name from IdP');
|
||||
}
|
||||
|
||||
// no third party assertions... for now
|
||||
@ -197,14 +173,13 @@ PeerConnectionIdp.prototype = {
|
||||
if (providerPortIdx > 0) {
|
||||
provider = provider.substring(0, providerPortIdx);
|
||||
}
|
||||
let idnService = Components.classes['@mozilla.org/network/idn-service;1'].
|
||||
getService(Components.interfaces.nsIIDNService);
|
||||
let idnService = Components.classes['@mozilla.org/network/idn-service;1']
|
||||
.getService(Components.interfaces.nsIIDNService);
|
||||
if (idnService.convertUTF8toACE(tail) !==
|
||||
idnService.convertUTF8toACE(provider)) {
|
||||
return error('name "' + identity.name +
|
||||
error('name "' + identity.name +
|
||||
'" doesn\'t match IdP: "' + this.provider + '"');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
@ -212,33 +187,38 @@ PeerConnectionIdp.prototype = {
|
||||
* the message from the IdP proxy. That way, broken IdPs aren't likely to
|
||||
* cause catastrophic damage.
|
||||
*/
|
||||
_isValidVerificationResponse: function(validation, sdpFingerprints) {
|
||||
_checkValidation: function(validation, sdpFingerprints) {
|
||||
let error = msg => {
|
||||
this.reportError('validation', 'assertion validation failure: ' + msg);
|
||||
return false;
|
||||
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') {
|
||||
return error('no payload in validation response');
|
||||
error('no payload in validation response');
|
||||
}
|
||||
|
||||
let fingerprints;
|
||||
try {
|
||||
fingerprints = JSON.parse(validation.contents).fingerprint;
|
||||
} catch (e) {
|
||||
return error('idp returned invalid JSON');
|
||||
error('invalid JSON');
|
||||
}
|
||||
|
||||
let isFingerprint = f =>
|
||||
(typeof f.digest === 'string') &&
|
||||
(typeof f.algorithm === 'string');
|
||||
if (!Array.isArray(fingerprints) || !fingerprints.every(isFingerprint)) {
|
||||
return error('fingerprints must be an array of objects' +
|
||||
' with digest and algorithm attributes');
|
||||
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));
|
||||
@ -248,25 +228,22 @@ PeerConnectionIdp.prototype = {
|
||||
return (a.digest === b.digest) && (a.algorithm === b.algorithm);
|
||||
};
|
||||
if (!isSubsetOf(fingerprints, sdpFingerprints, compareFingerprints)) {
|
||||
return error('the fingerprints in SDP aren\'t covered by the assertion');
|
||||
error('the fingerprints must be covered by the assertion');
|
||||
}
|
||||
return this._validateName(error, validation.identity);
|
||||
this._validateName(validation.identity);
|
||||
return validation;
|
||||
},
|
||||
|
||||
/**
|
||||
* Asks the IdP proxy to verify an identity assertion.
|
||||
*/
|
||||
_verifyIdentity: function(assertion, fingerprints, origin) {
|
||||
let validationPromise = this._idp.start()
|
||||
.then(idp => idp.validateAssertion(assertion, origin));
|
||||
let p = this.start()
|
||||
.then(idp => this._wrapCrossCompartmentPromise(
|
||||
idp.validateAssertion(assertion, origin)))
|
||||
.then(validation => this._checkValidation(validation, fingerprints));
|
||||
|
||||
return this._safetyNet('validation', validationPromise)
|
||||
.then(validation => {
|
||||
if (validation &&
|
||||
this._isValidVerificationResponse(validation, fingerprints)) {
|
||||
return validation;
|
||||
}
|
||||
});
|
||||
return this._applyTimeout(p);
|
||||
},
|
||||
|
||||
/**
|
||||
@ -287,13 +264,14 @@ PeerConnectionIdp.prototype = {
|
||||
|
||||
/**
|
||||
* Asks the IdP proxy for an identity assertion. Don't call this unless you
|
||||
* have checked .enabled, or you really like exceptions.
|
||||
* 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: function(fingerprint) {
|
||||
getIdentityAssertion: function(fingerprint, origin) {
|
||||
if (!this.enabled) {
|
||||
this.reportError('assertion', 'no IdP set,' +
|
||||
' call setIdentityProvider() to set one');
|
||||
return Promise.resolve();
|
||||
throw new this._win.DOMException(
|
||||
'no IdP set, call setIdentityProvider() to set one', 'InvalidStateError');
|
||||
}
|
||||
|
||||
let [algorithm, digest] = fingerprint.split(' ', 2);
|
||||
@ -303,51 +281,60 @@ PeerConnectionIdp.prototype = {
|
||||
digest: digest
|
||||
}]
|
||||
};
|
||||
let origin = Cu.getWebIDLCallerPrincipal().origin;
|
||||
|
||||
let assertionPromise = this._idp.start()
|
||||
.then(idp => idp.generateAssertion(JSON.stringify(content),
|
||||
origin, this.username));
|
||||
|
||||
return this._safetyNet('assertion', assertionPromise)
|
||||
.then(assertion => {
|
||||
if (this._isValidAssertion(assertion)) {
|
||||
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));
|
||||
} else {
|
||||
if (assertion) {
|
||||
// only report an error for an invalid assertion
|
||||
// other paths generate more specific error reports
|
||||
this.reportError('assertion', 'invalid assertion generated');
|
||||
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: function(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'));
|
||||
}
|
||||
this.assertion = null;
|
||||
}
|
||||
return this.assertion;
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Wraps a promise, adding a timeout guard on it so that it can't take longer
|
||||
* than the specified time. Returns a promise that always resolves; if there
|
||||
* is a problem the resolved value is undefined.
|
||||
* than the specified time. Returns a promise that rejects if the timeout
|
||||
* elapses before `p` resolves.
|
||||
*/
|
||||
_safetyNet: function(type, p) {
|
||||
let done = false; // ... all because Promises don't expose state
|
||||
let timeoutPromise = delay(this._timeout)
|
||||
_applyTimeout: function(p) {
|
||||
let timeout = new this._win.Promise(
|
||||
r => this._win.setTimeout(r, this._timeout))
|
||||
.then(() => {
|
||||
if (!done) {
|
||||
this.reportError(type, 'IdP timed out');
|
||||
}
|
||||
throw new this._win.DOMException('IdP timed out', 'IdpError');
|
||||
});
|
||||
let realPromise = p
|
||||
.catch(e => this.reportError(type, 'error reported by IdP: ' + e.message))
|
||||
.then(result => {
|
||||
done = true;
|
||||
return result;
|
||||
});
|
||||
// If timeoutPromise completes first, the returned value will be undefined,
|
||||
// just like when there is an error.
|
||||
return Promise.race([realPromise, timeoutPromise]);
|
||||
return this._win.Promise.race([ timeout, p ]);
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user