mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-11 16:32:59 +00:00
Bug 884573 - Identity assertion generation and verification for WebRTC. r=abr
This commit is contained in:
parent
2bc58720fc
commit
693f8fdc57
@ -330,6 +330,10 @@ const kEventConstructors = {
|
||||
return new RTCPeerConnectionIceEvent(aName, aProps);
|
||||
},
|
||||
},
|
||||
RTCPeerConnectionIdentityEvent: { create: function (aName, aProps) {
|
||||
return new RTCPeerConnectionIdentityEvent(aName, aProps);
|
||||
},
|
||||
},
|
||||
ScrollAreaEvent: { create: function (aName, aProps) {
|
||||
var e = document.createEvent("scrollareaevent");
|
||||
e.initScrollAreaEvent(aName, aProps.bubbles, aProps.cancelable,
|
||||
|
@ -47,7 +47,7 @@ IdpChannel.prototype = {
|
||||
return callback(new Error("IdP channel already open"));
|
||||
}
|
||||
|
||||
var ready = this._sandboxReady.bind(this, callback);
|
||||
let ready = this._sandboxReady.bind(this, callback);
|
||||
this.sandbox = new Sandbox(this.source, ready);
|
||||
},
|
||||
|
||||
@ -190,6 +190,9 @@ IdpProxy.prototype = {
|
||||
* automatically to the message so that the callback is only invoked for the
|
||||
* response to the message.
|
||||
*
|
||||
* This enqueues the message to send if the IdP hasn't signaled that it is
|
||||
* "READY", and sends the message when it is.
|
||||
*
|
||||
* The caller is responsible for ensuring that a response is received. If the
|
||||
* IdP doesn't respond, the callback simply isn't invoked.
|
||||
*/
|
||||
@ -212,7 +215,7 @@ IdpProxy.prototype = {
|
||||
if (!message) {
|
||||
return;
|
||||
}
|
||||
if (message.type === "READY") {
|
||||
if (!this.ready && message.type === "READY") {
|
||||
this.ready = true;
|
||||
this.pending.forEach(function(p) {
|
||||
this.send(p.message, p.callback);
|
||||
@ -238,18 +241,23 @@ IdpProxy.prototype = {
|
||||
return;
|
||||
}
|
||||
|
||||
// dump a message of type "ERROR" in response to all outstanding
|
||||
// messages to the IdP
|
||||
let error = { type: "ERROR" };
|
||||
Object.keys(this.tracking).forEach(function(k) {
|
||||
this.tracking[k](error);
|
||||
}, this);
|
||||
this.pending.forEach(function(p) {
|
||||
p.callback(error);
|
||||
}, this);
|
||||
// clear out before letting others know in case they do something bad
|
||||
let trackingCopy = this.tracking;
|
||||
let pendingCopy = this.pending;
|
||||
|
||||
this.channel.close();
|
||||
this._reset();
|
||||
|
||||
// dump a message of type "ERROR" in response to all outstanding
|
||||
// messages to the IdP
|
||||
let error = { type: "ERROR", message: "IdP closed" };
|
||||
Object.keys(trackingCopy).forEach(function(k) {
|
||||
this.trackingCopy[k](error);
|
||||
}, this);
|
||||
pendingCopy.forEach(function(p) {
|
||||
p.callback(error);
|
||||
}, this);
|
||||
|
||||
},
|
||||
|
||||
toString: function() {
|
||||
|
@ -8,7 +8,10 @@
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PopupNotifications.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PeerConnectionIdp",
|
||||
"resource://gre/modules/media/PeerConnectionIdp.jsm");
|
||||
|
||||
const PC_CONTRACT = "@mozilla.org/dom/peerconnection;1";
|
||||
const WEBRTC_GLOBAL_CONTRACT = "@mozilla.org/dom/webrtcglobalinformation1";
|
||||
@ -17,6 +20,7 @@ const PC_ICE_CONTRACT = "@mozilla.org/dom/rtcicecandidate;1";
|
||||
const PC_SESSION_CONTRACT = "@mozilla.org/dom/rtcsessiondescription;1";
|
||||
const PC_MANAGER_CONTRACT = "@mozilla.org/dom/peerconnectionmanager;1";
|
||||
const PC_STATS_CONTRACT = "@mozilla.org/dom/rtcstatsreport;1";
|
||||
const PC_IDENTITY_CONTRACT = "@mozilla.org/dom/rtcidentityassertion;1";
|
||||
|
||||
const PC_CID = Components.ID("{00e0e20d-1494-4776-8e0e-0f0acbea3c79}");
|
||||
const WEBRTC_GLOBAL_CID = Components.ID("{f6063d11-f467-49ad-9765-e7923050dc08}");
|
||||
@ -25,6 +29,7 @@ const PC_ICE_CID = Components.ID("{02b9970c-433d-4cc2-923d-f7028ac66073}");
|
||||
const PC_SESSION_CID = Components.ID("{1775081b-b62d-4954-8ffe-a067bbf508a7}");
|
||||
const PC_MANAGER_CID = Components.ID("{7293e901-2be3-4c02-b4bd-cbef6fc24f78}");
|
||||
const PC_STATS_CID = Components.ID("{7fe6e18b-0da3-4056-bf3b-440ef3809e06}");
|
||||
const PC_IDENTITY_CID = Components.ID("{1abc7499-3c54-43e0-bd60-686e2703f072}");
|
||||
|
||||
// Global list of PeerConnection objects, so they can be cleaned up when
|
||||
// a page is torn down. (Maps inner window ID to an array of PC objects).
|
||||
@ -100,7 +105,7 @@ GlobalPCList.prototype = {
|
||||
topic == "network:offline-about-to-go-offline") {
|
||||
// Delete all peerconnections on shutdown - mostly synchronously (we
|
||||
// need them to be done deleting transports and streams before we
|
||||
// return)! All socket operations must be queued to STS thread
|
||||
// return)! All socket operations must be queued to STS thread
|
||||
// before we return to here.
|
||||
// Also kill them if "Work Offline" is selected - more can be created
|
||||
// while offline, but attempts to connect them should fail.
|
||||
@ -111,7 +116,7 @@ GlobalPCList.prototype = {
|
||||
}
|
||||
else if (topic == "network:offline-status-changed") {
|
||||
if (data == "offline") {
|
||||
// this._list shold be empty here
|
||||
// this._list shold be empty here
|
||||
this._networkdown = true;
|
||||
} else if (data == "online") {
|
||||
this._networkdown = false;
|
||||
@ -278,6 +283,22 @@ RTCStatsReport.prototype = {
|
||||
get mozPcid() { return this._pcid; }
|
||||
};
|
||||
|
||||
function RTCIdentityAssertion() {}
|
||||
RTCIdentityAssertion.prototype = {
|
||||
classDescription: "RTCIdentityAssertion",
|
||||
classID: PC_IDENTITY_CID,
|
||||
contractID: PC_IDENTITY_CONTRACT,
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
|
||||
Ci.nsIDOMGlobalPropertyInitializer]),
|
||||
|
||||
init: function(win) { this._win = win; },
|
||||
|
||||
__init: function(idp, name) {
|
||||
this.idp = idp;
|
||||
this.name = name;
|
||||
}
|
||||
};
|
||||
|
||||
function RTCPeerConnection() {
|
||||
this._queue = [];
|
||||
|
||||
@ -298,14 +319,15 @@ function RTCPeerConnection() {
|
||||
this._localType = null;
|
||||
this._remoteType = null;
|
||||
this._trickleIce = false;
|
||||
this._peerIdentity = null;
|
||||
|
||||
/**
|
||||
* Everytime we get a request from content, we put it in the queue. If
|
||||
* there are no pending operations though, we will execute it immediately.
|
||||
* In PeerConnectionObserver, whenever we are notified that an operation
|
||||
* has finished, we will check the queue for the next operation and execute
|
||||
* if neccesary. The _pending flag indicates whether an operation is currently
|
||||
* in progress.
|
||||
* Everytime we get a request from content, we put it in the queue. If there
|
||||
* are no pending operations though, we will execute it immediately. In
|
||||
* PeerConnectionObserver, whenever we are notified that an operation has
|
||||
* finished, we will check the queue for the next operation and execute if
|
||||
* neccesary. The _pending flag indicates whether an operation is currently in
|
||||
* progress.
|
||||
*/
|
||||
this._pending = false;
|
||||
|
||||
@ -343,15 +365,16 @@ RTCPeerConnection.prototype = {
|
||||
this.makeGetterSetterEH("onconnection");
|
||||
this.makeGetterSetterEH("onclosedconnection");
|
||||
this.makeGetterSetterEH("oniceconnectionstatechange");
|
||||
this.makeGetterSetterEH("onidentityresult");
|
||||
|
||||
this._pc = new this._win.PeerConnectionImpl();
|
||||
|
||||
this.__DOM_IMPL__._innerObject = this;
|
||||
this._observer = new this._win.PeerConnectionObserver(this.__DOM_IMPL__);
|
||||
this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
||||
|
||||
// Add a reference to the PeerConnection to global list (before init).
|
||||
this._winID = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
||||
_globalPCList.addPC(this);
|
||||
|
||||
this._queueOrRun({
|
||||
@ -363,11 +386,12 @@ RTCPeerConnection.prototype = {
|
||||
},
|
||||
|
||||
_initialize: function(rtcConfig) {
|
||||
this._getPC().initialize(this._observer, this._win, rtcConfig,
|
||||
Services.tm.currentThread);
|
||||
this._impl.initialize(this._observer, this._win, rtcConfig,
|
||||
Services.tm.currentThread);
|
||||
this._initIdp();
|
||||
},
|
||||
|
||||
_getPC: function() {
|
||||
get _impl() {
|
||||
if (!this._pc) {
|
||||
throw new this._win.DOMError("",
|
||||
"RTCPeerConnection is gone (did you enter Offline mode?)");
|
||||
@ -375,11 +399,19 @@ RTCPeerConnection.prototype = {
|
||||
return this._pc;
|
||||
},
|
||||
|
||||
_initIdp: function() {
|
||||
let prefName = "media.peerconnection.identity.timeout";
|
||||
let idpTimeout = Services.prefs.getIntPref(prefName);
|
||||
let warningFunc = this.reportWarning.bind(this);
|
||||
this._localIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc);
|
||||
this._remoteIdp = new PeerConnectionIdp(this._win, idpTimeout, warningFunc);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a function to the queue or run it immediately if the queue is empty.
|
||||
* Argument is an object with the func, args and wait properties; wait should
|
||||
* be set to true if the function has a success/error callback that will
|
||||
* call _executeNext, false if it doesn't have a callback.
|
||||
* be set to true if the function has a success/error callback that will call
|
||||
* _executeNext, false if it doesn't have a callback.
|
||||
*/
|
||||
_queueOrRun: function(obj) {
|
||||
this._checkClosed();
|
||||
@ -468,8 +500,8 @@ RTCPeerConnection.prototype = {
|
||||
* }
|
||||
*
|
||||
* WebIDL normalizes the top structure for us, but the mandatory constraints
|
||||
* member comes in as a raw object so we can detect unknown constraints.
|
||||
* We compare its members against ones we support, and fail if not found.
|
||||
* member comes in as a raw object so we can detect unknown constraints. We
|
||||
* compare its members against ones we support, and fail if not found.
|
||||
*/
|
||||
_mustValidateConstraints: function(constraints, errorMsg) {
|
||||
if (constraints.mandatory) {
|
||||
@ -587,7 +619,7 @@ RTCPeerConnection.prototype = {
|
||||
_createOffer: function(onSuccess, onError, constraints) {
|
||||
this._onCreateOfferSuccess = onSuccess;
|
||||
this._onCreateOfferFailure = onError;
|
||||
this._getPC().createOffer(constraints);
|
||||
this._impl.createOffer(constraints);
|
||||
},
|
||||
|
||||
_createAnswer: function(onSuccess, onError, constraints, provisional) {
|
||||
@ -610,7 +642,7 @@ RTCPeerConnection.prototype = {
|
||||
|
||||
// TODO: Implement provisional answer.
|
||||
|
||||
this._getPC().createAnswer(constraints);
|
||||
this._impl.createAnswer(constraints);
|
||||
},
|
||||
|
||||
createAnswer: function(onSuccess, onError, constraints, provisional) {
|
||||
@ -658,7 +690,7 @@ RTCPeerConnection.prototype = {
|
||||
_setLocalDescription: function(type, sdp, onSuccess, onError) {
|
||||
this._onSetLocalDescriptionSuccess = onSuccess;
|
||||
this._onSetLocalDescriptionFailure = onError;
|
||||
this._getPC().setLocalDescription(type, sdp);
|
||||
this._impl.setLocalDescription(type, sdp);
|
||||
},
|
||||
|
||||
setRemoteDescription: function(desc, onSuccess, onError) {
|
||||
@ -677,6 +709,16 @@ RTCPeerConnection.prototype = {
|
||||
"Invalid type " + desc.type + " provided to setRemoteDescription");
|
||||
}
|
||||
|
||||
try {
|
||||
let showUX = this._showIdentityUx.bind(this);
|
||||
this._remoteIdp.verifyIdentityFromSDP(desc.sdp, showUX);
|
||||
} catch (e) {
|
||||
this.reportWarning(e.message, e.fileName, e.lineNumber);
|
||||
// only happens if processing the SDP for identity doesn't work
|
||||
this._showIdentityUx(null);
|
||||
// let _setRemoteDescription do the error reporting
|
||||
}
|
||||
|
||||
this._queueOrRun({
|
||||
func: this._setRemoteDescription,
|
||||
args: [type, desc.sdp, onSuccess, onError],
|
||||
@ -685,10 +727,88 @@ RTCPeerConnection.prototype = {
|
||||
});
|
||||
},
|
||||
|
||||
_showIdentityUx: function(message) {
|
||||
let browser = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell)
|
||||
.chromeEventHandler
|
||||
.ownerDocument
|
||||
.defaultView
|
||||
.gBrowser;
|
||||
let notificationBox = browser.getNotificationBox();
|
||||
|
||||
if (message) {
|
||||
this._peerIdentity = new this._win.RTCIdentityAssertion(
|
||||
this._remoteIdp.provider, message.identity.name);
|
||||
|
||||
// TODO (:mt) - ultimately, these notifications need to go away
|
||||
// for one, they are highly spoofable, which is very bad
|
||||
// but they are also ugly and unnecessary
|
||||
// Bug 942372 should provide a better approach
|
||||
notificationBox.appendNotification(
|
||||
"Identity of your WebRTC peer is " + this._peerIdentity.name,
|
||||
"webrtc-auth",
|
||||
null,
|
||||
notificationBox.PRIORITY_INFO_HIGH,
|
||||
[]
|
||||
);
|
||||
|
||||
let args = { peerIdentity: this._peerIdentity };
|
||||
let ev = new this._win.RTCPeerConnectionIdentityEvent("identityresult",
|
||||
args);
|
||||
this.dispatchEvent(ev);
|
||||
} else {
|
||||
notificationBox.appendNotification(
|
||||
"Identity of your WebRTC peer is not verified",
|
||||
"webrtc-auth",
|
||||
null,
|
||||
notificationBox.PRIORITY_WARNING_HIGH,
|
||||
[]
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
_setRemoteDescription: function(type, sdp, onSuccess, onError) {
|
||||
this._onSetRemoteDescriptionSuccess = onSuccess;
|
||||
this._onSetRemoteDescriptionFailure = onError;
|
||||
this._getPC().setRemoteDescription(type, sdp);
|
||||
this._impl.setRemoteDescription(type, sdp);
|
||||
},
|
||||
|
||||
setIdentityProvider: function(provider, protocol, username) {
|
||||
this._checkClosed();
|
||||
this._localIdp.setIdentityProvider(provider, protocol, username);
|
||||
},
|
||||
|
||||
// we're going off spec with the error callback here.
|
||||
getIdentityAssertion: function(errorCallback) {
|
||||
this._checkClosed();
|
||||
if (typeof errorCallback !== "function") {
|
||||
if (errorCallback) {
|
||||
let message ="getIdentityAssertion argument must be a function";
|
||||
throw new this._win.DOMError("", message);
|
||||
}
|
||||
errorCallback = function() {
|
||||
this.reportWarning("getIdentityAssertion: no error callback set");
|
||||
}.bind(this);
|
||||
}
|
||||
|
||||
function gotAssertion(assertion) {
|
||||
if (assertion) {
|
||||
let args = { assertion: assertion };
|
||||
let ev = new this._win.RTCPeerConnectionIdentityEvent("identityresult", args);
|
||||
this.dispatchEvent(ev);
|
||||
} else {
|
||||
errorCallback("IdP did not produce an assertion");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
this._localIdp.getIdentityAssertion(this._impl.fingerprint,
|
||||
gotAssertion.bind(this));
|
||||
}
|
||||
catch (e) {
|
||||
errorCallback("Could not get identity assertion: " + e.message);
|
||||
}
|
||||
},
|
||||
|
||||
updateIce: function(config, constraints) {
|
||||
@ -707,9 +827,9 @@ RTCPeerConnection.prototype = {
|
||||
},
|
||||
|
||||
_addIceCandidate: function(cand) {
|
||||
this._getPC().addIceCandidate(cand.candidate, cand.sdpMid || "",
|
||||
(cand.sdpMLineIndex === null)? 0 :
|
||||
cand.sdpMLineIndex + 1);
|
||||
this._impl.addIceCandidate(cand.candidate, cand.sdpMid || "",
|
||||
(cand.sdpMLineIndex === null) ? 0 :
|
||||
cand.sdpMLineIndex + 1);
|
||||
},
|
||||
|
||||
addStream: function(stream, constraints) {
|
||||
@ -727,11 +847,11 @@ RTCPeerConnection.prototype = {
|
||||
},
|
||||
|
||||
_addStream: function(stream, constraints) {
|
||||
this._getPC().addStream(stream, constraints);
|
||||
this._impl.addStream(stream, constraints);
|
||||
},
|
||||
|
||||
removeStream: function(stream) {
|
||||
//Bug 844295: Not implementing this functionality.
|
||||
// Bug 844295: Not implementing this functionality.
|
||||
throw new this._win.DOMError("", "removeStream not yet implemented");
|
||||
},
|
||||
|
||||
@ -746,32 +866,36 @@ RTCPeerConnection.prototype = {
|
||||
},
|
||||
|
||||
_close: function() {
|
||||
this._getPC().close();
|
||||
this._localIdp.close();
|
||||
this._remoteIdp.close();
|
||||
this._impl.close();
|
||||
},
|
||||
|
||||
getLocalStreams: function() {
|
||||
this._checkClosed();
|
||||
return this._getPC().getLocalStreams();
|
||||
return this._impl.getLocalStreams();
|
||||
},
|
||||
|
||||
getRemoteStreams: function() {
|
||||
this._checkClosed();
|
||||
return this._getPC().getRemoteStreams();
|
||||
return this._impl.getRemoteStreams();
|
||||
},
|
||||
|
||||
get localDescription() {
|
||||
this._checkClosed();
|
||||
let sdp = this._getPC().localDescription;
|
||||
let sdp = this._impl.localDescription;
|
||||
if (sdp.length == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
sdp = this._localIdp.wrapSdp(sdp);
|
||||
return new this._win.mozRTCSessionDescription({ type: this._localType,
|
||||
sdp: sdp });
|
||||
},
|
||||
|
||||
get remoteDescription() {
|
||||
this._checkClosed();
|
||||
let sdp = this._getPC().remoteDescription;
|
||||
let sdp = this._impl.remoteDescription;
|
||||
if (sdp.length == 0) {
|
||||
return null;
|
||||
}
|
||||
@ -779,13 +903,14 @@ RTCPeerConnection.prototype = {
|
||||
sdp: sdp });
|
||||
},
|
||||
|
||||
get peerIdentity() { return this._peerIdentity; },
|
||||
get iceGatheringState() { return this._iceGatheringState; },
|
||||
get iceConnectionState() { return this._iceConnectionState; },
|
||||
|
||||
get signalingState() {
|
||||
// checking for our local pc closed indication
|
||||
// before invoking the pc methods.
|
||||
if(this._closed) {
|
||||
if (this._closed) {
|
||||
return "closed";
|
||||
}
|
||||
return {
|
||||
@ -796,7 +921,7 @@ RTCPeerConnection.prototype = {
|
||||
"SignalingHaveLocalPranswer": "have-local-pranswer",
|
||||
"SignalingHaveRemotePranswer": "have-remote-pranswer",
|
||||
"SignalingClosed": "closed"
|
||||
}[this._getPC().signalingState];
|
||||
}[this._impl.signalingState];
|
||||
},
|
||||
|
||||
changeIceGatheringState: function(state) {
|
||||
@ -828,7 +953,7 @@ RTCPeerConnection.prototype = {
|
||||
this._onGetStatsSuccess = onSuccess;
|
||||
this._onGetStatsFailure = onError;
|
||||
|
||||
this._getPC().getStats(selector, internal);
|
||||
this._impl.getStats(selector, internal);
|
||||
},
|
||||
|
||||
getLogging: function(pattern, onSuccess, onError) {
|
||||
@ -843,7 +968,7 @@ RTCPeerConnection.prototype = {
|
||||
this._onGetLoggingSuccess = onSuccess;
|
||||
this._onGetLoggingFailure = onError;
|
||||
|
||||
this._getPC().getLogging(pattern);
|
||||
this._impl.getLogging(pattern);
|
||||
},
|
||||
|
||||
createDataChannel: function(label, dict) {
|
||||
@ -856,7 +981,8 @@ RTCPeerConnection.prototype = {
|
||||
this.reportWarning("Deprecated RTCDataChannelInit dictionary entry maxRetransmitNum used!", null, 0);
|
||||
}
|
||||
if (dict.outOfOrderAllowed != undefined) {
|
||||
dict.ordered = !dict.outOfOrderAllowed; // the meaning is swapped with the name change
|
||||
dict.ordered = !dict.outOfOrderAllowed; // the meaning is swapped with
|
||||
// the name change
|
||||
this.reportWarning("Deprecated RTCDataChannelInit dictionary entry outOfOrderAllowed used!", null, 0);
|
||||
}
|
||||
if (dict.preset != undefined) {
|
||||
@ -891,7 +1017,7 @@ RTCPeerConnection.prototype = {
|
||||
}
|
||||
|
||||
// Synchronous since it doesn't block.
|
||||
let channel = this._getPC().createDataChannel(
|
||||
let channel = this._impl.createDataChannel(
|
||||
label, protocol, type, !dict.ordered, dict.maxRetransmitTime,
|
||||
dict.maxRetransmits, dict.negotiated ? true : false,
|
||||
dict.id != undefined ? dict.id : 0xFFFF
|
||||
@ -911,7 +1037,7 @@ RTCPeerConnection.prototype = {
|
||||
},
|
||||
|
||||
_connectDataConnection: function(localport, remoteport, numstreams) {
|
||||
this._getPC().connectDataConnection(localport, remoteport, numstreams);
|
||||
this._impl.connectDataConnection(localport, remoteport, numstreams);
|
||||
}
|
||||
};
|
||||
|
||||
@ -970,10 +1096,14 @@ PeerConnectionObserver.prototype = {
|
||||
},
|
||||
|
||||
onCreateOfferSuccess: function(sdp) {
|
||||
this.callCB(this._dompc._onCreateOfferSuccess,
|
||||
new this._dompc._win.mozRTCSessionDescription({ type: "offer",
|
||||
sdp: sdp }));
|
||||
this._dompc._executeNext();
|
||||
let pc = this._dompc;
|
||||
let fp = pc._impl.fingerprint;
|
||||
pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) {
|
||||
this.callCB(pc._onCreateOfferSuccess,
|
||||
new pc._win.mozRTCSessionDescription({ type: "offer",
|
||||
sdp: sdp }));
|
||||
pc._executeNext();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
onCreateOfferError: function(code, message) {
|
||||
@ -982,10 +1112,14 @@ PeerConnectionObserver.prototype = {
|
||||
},
|
||||
|
||||
onCreateAnswerSuccess: function(sdp) {
|
||||
this.callCB (this._dompc._onCreateAnswerSuccess,
|
||||
new this._dompc._win.mozRTCSessionDescription({ type: "answer",
|
||||
sdp: sdp }));
|
||||
this._dompc._executeNext();
|
||||
let pc = this._dompc;
|
||||
let fp = pc._impl.fingerprint;
|
||||
pc._localIdp.appendIdentityToSDP(sdp, fp, function(sdp, assertion) {
|
||||
this.callCB (pc._onCreateAnswerSuccess,
|
||||
new pc._win.mozRTCSessionDescription({ type: "answer",
|
||||
sdp: sdp }));
|
||||
pc._executeNext();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
onCreateAnswerError: function(code, message) {
|
||||
@ -1229,6 +1363,7 @@ this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
|
||||
RTCSessionDescription,
|
||||
RTCPeerConnection,
|
||||
RTCStatsReport,
|
||||
RTCIdentityAssertion,
|
||||
PeerConnectionObserver,
|
||||
WebrtcGlobalInformation]
|
||||
);
|
||||
|
@ -5,6 +5,7 @@ component {02b9970c-433d-4cc2-923d-f7028ac66073} PeerConnection.js
|
||||
component {1775081b-b62d-4954-8ffe-a067bbf508a7} PeerConnection.js
|
||||
component {7293e901-2be3-4c02-b4bd-cbef6fc24f78} PeerConnection.js
|
||||
component {7fe6e18b-0da3-4056-bf3b-440ef3809e06} PeerConnection.js
|
||||
component {1abc7499-3c54-43e0-bd60-686e2703f072} PeerConnection.js
|
||||
|
||||
contract @mozilla.org/dom/peerconnection;1 {00e0e20d-1494-4776-8e0e-0f0acbea3c79}
|
||||
contract @mozilla.org/dom/webrtcglobalinformation;1 {f6063d11-f467-49ad-9765-e7923050dc08}
|
||||
@ -13,3 +14,4 @@ contract @mozilla.org/dom/rtcicecandidate;1 {02b9970c-433d-4cc2-923d-f7028ac6607
|
||||
contract @mozilla.org/dom/rtcsessiondescription;1 {1775081b-b62d-4954-8ffe-a067bbf508a7}
|
||||
contract @mozilla.org/dom/peerconnectionmanager;1 {7293e901-2be3-4c02-b4bd-cbef6fc24f78}
|
||||
contract @mozilla.org/dom/rtcstatsreport;1 {7fe6e18b-0da3-4056-bf3b-440ef3809e06}
|
||||
contract @mozilla.org/dom/rtcidentityassertion;1 {1abc7499-3c54-43e0-bd60-686e2703f072}
|
||||
|
340
dom/media/PeerConnectionIdp.jsm
Normal file
340
dom/media/PeerConnectionIdp.jsm
Normal file
@ -0,0 +1,340 @@
|
||||
/* 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"];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "IdpProxy",
|
||||
"resource://gre/modules/media/IdpProxy.jsm");
|
||||
|
||||
/**
|
||||
* Creates an IdP helper.
|
||||
*
|
||||
* @param window (object) the window object to use for miscellaneous goodies
|
||||
* @param timeout (int) the timeout in milliseconds
|
||||
* @param warningFunc (function) somewhere to dump warning messages
|
||||
*/
|
||||
function PeerConnectionIdp(window, timeout, warningFunc) {
|
||||
this._win = window;
|
||||
this._timeout = timeout || 5000;
|
||||
this._warning = warningFunc;
|
||||
|
||||
this.assertion = null;
|
||||
this.provider = null;
|
||||
}
|
||||
|
||||
(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 = {
|
||||
setIdentityProvider: function(
|
||||
provider, protocol, username) {
|
||||
this.provider = provider;
|
||||
this._idpchannel = new IdpProxy(provider, protocol, username);
|
||||
},
|
||||
|
||||
close: function() {
|
||||
this.assertion = null;
|
||||
this.provider = null;
|
||||
if (this._idpchannel) {
|
||||
this._idpchannel.close();
|
||||
this._idpchannel = null;
|
||||
}
|
||||
},
|
||||
|
||||
_getFingerprintFromSdp: function(sdp) {
|
||||
let sections = sdp.split(PeerConnectionIdp._mLinePattern);
|
||||
let attributes = sections.map(function(sect) {
|
||||
let m = sect.match(PeerConnectionIdp._fingerprintPattern);
|
||||
if (m) {
|
||||
let remainder = sect.substring(m.index + m[0].length);
|
||||
if (!remainder.match(PeerConnectionIdp._fingerprintPattern)) {
|
||||
return { algorithm: m[1], digest: m[2] };
|
||||
}
|
||||
this._warning("RTC identity: two fingerprint values in same media " +
|
||||
"section are not supported", null, 0);
|
||||
// we have to return non-falsy here so that a media section doesn't
|
||||
// accidentally fall back to the session-level stuff (which is bad)
|
||||
return "error";
|
||||
}
|
||||
// return undefined unless there is exactly one match
|
||||
}, this);
|
||||
|
||||
let sessionLevel = attributes.shift();
|
||||
attributes = attributes.map(function(sectionLevel) {
|
||||
return sectionLevel || sessionLevel;
|
||||
});
|
||||
|
||||
let first = attributes.shift();
|
||||
function sameAsFirst(attr) {
|
||||
return typeof attr === "object" &&
|
||||
first.algorithm === attr.algorithm &&
|
||||
first.digest === attr.digest;
|
||||
}
|
||||
|
||||
if (typeof first === "object" && attributes.every(sameAsFirst)) {
|
||||
return first;
|
||||
}
|
||||
// undefined!
|
||||
},
|
||||
|
||||
_getIdentityFromSdp: function(sdp) {
|
||||
// we only pull from the session level right now
|
||||
// TODO allow for per-m=-section identity
|
||||
let mLineMatch = sdp.match(PeerConnectionIdp._mLinePattern);
|
||||
let sessionLevel = sdp.substring(0, mLineMatch.index);
|
||||
let idMatch = sessionLevel.match(PeerConnectionIdp._identityPattern);
|
||||
if (idMatch) {
|
||||
let assertion = {};
|
||||
try {
|
||||
assertion = JSON.parse(atob(idMatch[1]));
|
||||
} catch (e) {
|
||||
this._warning("RTC identity: invalid identity assertion: " + e, null, 0);
|
||||
} // for JSON.parse
|
||||
if (typeof assertion.idp === "object" &&
|
||||
typeof assertion.idp.domain === "string" &&
|
||||
typeof assertion.assertion === "string") {
|
||||
return assertion;
|
||||
}
|
||||
this._warning("RTC identity: assertion missing idp/idp.domain/assertion",
|
||||
null, 0);
|
||||
}
|
||||
// undefined!
|
||||
},
|
||||
|
||||
/**
|
||||
* Queues a task to verify 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.
|
||||
*/
|
||||
verifyIdentityFromSDP: function(sdp, callback) {
|
||||
let identity = this._getIdentityFromSdp(sdp);
|
||||
let fingerprint = this._getFingerprintFromSdp(sdp);
|
||||
// it's safe to use the fingerprint from the SDP here,
|
||||
// only because we ensure that there is only one
|
||||
if (!fingerprint || !identity) {
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
if (!this._idpchannel) {
|
||||
this.setIdentityProvider(identity.idp.domain, identity.idp.protocol);
|
||||
}
|
||||
|
||||
this._verifyIdentity(identity.assertion, fingerprint, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks that the name in the identity provided by the IdP is OK.
|
||||
*
|
||||
* @param name (string) the name to validate
|
||||
* @returns (string) an error message, iff the name isn't good
|
||||
*/
|
||||
_validateName: function(name) {
|
||||
if (typeof name !== "string") {
|
||||
return "name not a string";
|
||||
}
|
||||
let atIdx = name.indexOf("@");
|
||||
if (atIdx > 0) {
|
||||
// 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);
|
||||
}
|
||||
// this really isn't correct for IDN names
|
||||
// Bug 958741 will fix that
|
||||
if (tail.toLowerCase() !== provider.toLowerCase()) {
|
||||
return "name '" + identity.name +
|
||||
"' doesn't match IdP: '" + this.provider + "'";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return "missing authority in name from IdP";
|
||||
},
|
||||
|
||||
// we are very defensive here when handling the message from the IdP
|
||||
// proxy so that broken IdPs can only do as little harm as possible.
|
||||
_checkVerifyResponse: function(
|
||||
message, fingerprint) {
|
||||
let warn = function(message) {
|
||||
this._warning("RTC identity: VERIFY error: " + message, null, 0);
|
||||
}.bind(this);
|
||||
|
||||
try {
|
||||
let contents = JSON.parse(message.contents);
|
||||
if (typeof contents.fingerprint !== "object" ||
|
||||
typeof message.identity !== "object") {
|
||||
warn("fingerprint or identity not objects");
|
||||
} else if (contents.fingerprint.digest !== fingerprint.digest ||
|
||||
contents.fingerprint.algorithm !== fingerprint.algorithm) {
|
||||
warn("fingerprint does not match");
|
||||
} else {
|
||||
let error = this._validateName(message.identity.name);
|
||||
if (error) {
|
||||
warn(error);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
warn("invalid JSON in content");
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Asks the IdP proxy to verify an identity.
|
||||
*/
|
||||
_verifyIdentity: function(
|
||||
assertion, fingerprint, callback) {
|
||||
function onVerification(message) {
|
||||
if (!message) {
|
||||
this._warning("RTC identity: verification failure", null, 0);
|
||||
callback(null);
|
||||
return;
|
||||
}
|
||||
if (this._checkVerifyResponse(message, fingerprint)) {
|
||||
callback(message);
|
||||
} else {
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
|
||||
this._sendToIdp("VERIFY", assertion, onVerification.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Asks the IdP proxy for an identity assertion and, on success, enriches the
|
||||
* given SDP with an a=identity line and calls callback with the new SDP as
|
||||
* parameter. If no IdP is configured the original SDP (without a=identity
|
||||
* line) is passed to the callback.
|
||||
*/
|
||||
appendIdentityToSDP: function(
|
||||
sdp, fingerprint, callback) {
|
||||
if (!this._idpchannel) {
|
||||
callback(sdp);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.assertion) {
|
||||
callback(this.wrapSdp(sdp));
|
||||
return;
|
||||
}
|
||||
|
||||
function onAssertion(assertion) {
|
||||
if (!assertion) {
|
||||
this._warning("RTC identity: assertion generation failure", null, 0);
|
||||
callback(sdp);
|
||||
return;
|
||||
}
|
||||
|
||||
this.assertion = btoa(JSON.stringify(assertion));
|
||||
callback(this.wrapSdp(sdp));
|
||||
}
|
||||
|
||||
this._getIdentityAssertion(fingerprint, onAssertion.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Inserts an identity assertion into the given SDP.
|
||||
*/
|
||||
wrapSdp: function(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);
|
||||
},
|
||||
|
||||
getIdentityAssertion: function(
|
||||
fingerprint, callback) {
|
||||
if (!this._idpchannel) {
|
||||
throw new Error("IdP not set");
|
||||
}
|
||||
|
||||
this._getIdentityAssertion(fingerprint, callback);
|
||||
},
|
||||
|
||||
_getIdentityAssertion: function(
|
||||
fingerprint, callback) {
|
||||
let [algorithm, digest] = fingerprint.split(" ");
|
||||
let message = {
|
||||
fingerprint: {
|
||||
algorithm: algorithm,
|
||||
digest: digest
|
||||
}
|
||||
};
|
||||
this._sendToIdp("SIGN", JSON.stringify(message), callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* Packages a message and sends it to the IdP.
|
||||
*/
|
||||
_sendToIdp: function(type, message, callback) {
|
||||
let origin = this._win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||
.getInterface(Ci.nsIWebNavigation)
|
||||
.QueryInterface(Ci.nsIDocShell).chromeEventHandler
|
||||
.ownerDocument
|
||||
.defaultView
|
||||
.gBrowser
|
||||
.currentURI
|
||||
.prePath;
|
||||
|
||||
this._idpchannel.send({
|
||||
type: type,
|
||||
message: message,
|
||||
origin: origin
|
||||
}, this._wrapCallback(callback));
|
||||
},
|
||||
|
||||
/**
|
||||
* Wraps a callback, adding a timeout and ensuring that the callback doesn't
|
||||
* receive any message other than one where the IdP generated a "SUCCESS"
|
||||
* response.
|
||||
*/
|
||||
_wrapCallback: function(callback) {
|
||||
let timeout = this._win.setTimeout(function() {
|
||||
this._warning("RTC identity: IdP timeout for " + this._idpchannel + " " +
|
||||
(this._idpchannel.ready ? "[ready]" : "[not ready]"), null, 0);
|
||||
timeout = null;
|
||||
callback(null);
|
||||
}.bind(this), this._timeout);
|
||||
|
||||
return function(message) {
|
||||
if (!timeout) {
|
||||
return;
|
||||
}
|
||||
this._win.clearTimeout(timeout);
|
||||
timeout = null;
|
||||
var content = null;
|
||||
if (message.type === "SUCCESS") {
|
||||
content = message.message;
|
||||
} else {
|
||||
this._warning("RTC Identity: received response of type '" +
|
||||
message.type + "' from IdP: " + message.message, null, 0);
|
||||
}
|
||||
callback(content);
|
||||
}.bind(this);
|
||||
}
|
||||
};
|
||||
|
||||
this.PeerConnectionIdp = PeerConnectionIdp;
|
@ -44,6 +44,7 @@ JS_MODULES_PATH = 'modules/media'
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'IdpProxy.jsm',
|
||||
'PeerConnectionIdp.jsm',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_B2G']:
|
||||
|
@ -52,4 +52,7 @@ skip-if = os == 'mac'
|
||||
[test_peerConnection_setRemoteOfferInHaveLocalOffer.html]
|
||||
[test_peerConnection_throwInCallbacks.html]
|
||||
[test_peerConnection_toJSON.html]
|
||||
[test_setIdentityProvider.html]
|
||||
[test_setIdentityProviderWithErrors.html]
|
||||
[test_getIdentityAssertion.html]
|
||||
[test_idpproxy.html]
|
||||
|
@ -435,6 +435,11 @@ function PCT_createOffer(peer, onSuccess) {
|
||||
});
|
||||
};
|
||||
|
||||
PeerConnectionTest.prototype.setIdentityProvider =
|
||||
function(peer, provider, protocol, identity) {
|
||||
peer.setIdentityProvider(provider, protocol, identity);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the local description for the specified peer connection instance
|
||||
* and automatically handles the failure case.
|
||||
@ -670,7 +675,7 @@ DataChannelTest.prototype = Object.create(PeerConnectionTest.prototype, {
|
||||
});
|
||||
} else {
|
||||
check_next_test();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -1109,6 +1114,10 @@ PeerConnectionWrapper.prototype = {
|
||||
return this._pc.iceConnectionState;
|
||||
},
|
||||
|
||||
setIdentityProvider: function(provider, protocol, identity) {
|
||||
this._pc.setIdentityProvider(provider, protocol, identity);
|
||||
},
|
||||
|
||||
/**
|
||||
* Callback when we get media from either side. Also an appropriate
|
||||
* HTML media element will be created.
|
||||
|
85
dom/media/tests/mochitest/test_getIdentityAssertion.html
Normal file
85
dom/media/tests/mochitest/test_getIdentityAssertion.html
Normal file
@ -0,0 +1,85 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="head.js"></script>
|
||||
<script type="application/javascript" src="pc.js"></script>
|
||||
<script type="application/javascript" src="templates.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
createHTML({
|
||||
title: "getIdentityAssertion Tests"
|
||||
});
|
||||
|
||||
var test;
|
||||
function theTest() {
|
||||
test = new PeerConnectionTest();
|
||||
test.setMediaConstraints([{audio: true}], [{audio: true}]);
|
||||
test.chain.append([
|
||||
[
|
||||
"GET_IDENTITY_ASSERTION_FAILS_WITHOUT_PROVIDER",
|
||||
function(test) {
|
||||
test.pcLocal._pc.getIdentityAssertion(function(err) {
|
||||
ok(err, "getIdentityAssertion must fail without provider");
|
||||
test.next();
|
||||
});
|
||||
},
|
||||
],
|
||||
[
|
||||
"GET_IDENTITY_ASSERTION_FIRES_EVENTUALLY_AND_SUBSEQUENTLY",
|
||||
function(test) {
|
||||
var fired = 0;
|
||||
test.setIdentityProvider(test.pcLocal, 'example.com', 'idp.html', 'nobody');
|
||||
test.pcLocal._pc.onidentityresult = function() {
|
||||
fired++;
|
||||
if (fired == 1) {
|
||||
ok(true, "identityresult fired");
|
||||
} else if (fired == 2) {
|
||||
ok(true, "identityresult fired 2x");
|
||||
test.next();
|
||||
}
|
||||
};
|
||||
test.pcLocal._pc.getIdentityAssertion();
|
||||
test.pcLocal._pc.getIdentityAssertion();
|
||||
}
|
||||
],
|
||||
[
|
||||
"GET_IDENTITY_ASSERTION_FAILS",
|
||||
function(test) {
|
||||
test.setIdentityProvider(test.pcLocal, 'example.com', 'idp.html#error');
|
||||
test.pcLocal._pc.onidentityresult = function(e) {
|
||||
ok(false, "Should not get an identity result");
|
||||
test.next();
|
||||
};
|
||||
test.pcLocal._pc.getIdentityAssertion(function(err) {
|
||||
ok(err, "Got error callback from getIdentityAssertion");
|
||||
test.next();
|
||||
});
|
||||
}
|
||||
],
|
||||
[
|
||||
"GET_IDENTITY_ASSERTION_IDP_NOT_READY",
|
||||
function(test) {
|
||||
test.setIdentityProvider(test.pcLocal, 'example.com', 'idp.html#error:ready');
|
||||
test.pcLocal._pc.onidentityresult = function(e) {
|
||||
ok(false, "Should not get an identity result");
|
||||
test.next();
|
||||
};
|
||||
test.pcLocal._pc.getIdentityAssertion(function(err) {
|
||||
ok(err, "Got error callback from getIdentityAssertion");
|
||||
test.next();
|
||||
});
|
||||
}
|
||||
]
|
||||
]);
|
||||
test.run();
|
||||
}
|
||||
runTest(theTest);
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
95
dom/media/tests/mochitest/test_setIdentityProvider.html
Normal file
95
dom/media/tests/mochitest/test_setIdentityProvider.html
Normal file
@ -0,0 +1,95 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="head.js"></script>
|
||||
<script type="application/javascript" src="pc.js"></script>
|
||||
<script type="application/javascript" src="templates.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
createHTML({
|
||||
title: "setIdentityProvider leads to peerIdentity and assertions in SDP"
|
||||
});
|
||||
|
||||
var test;
|
||||
function theTest() {
|
||||
test = new PeerConnectionTest();
|
||||
test.setMediaConstraints([{audio: true}], [{audio: true}]);
|
||||
test.setIdentityProvider(test.pcLocal, 'test1.example.com', 'idp.html', 'nobody');
|
||||
test.setIdentityProvider(test.pcRemote, 'test2.example.com', 'idp.html', 'nobody');
|
||||
test.chain.append([
|
||||
[
|
||||
"PEER_IDENTITY_IS_SET_CORRECTLY",
|
||||
function(test) {
|
||||
var outstanding = 0;
|
||||
// we have to wait for the identity result in order to get the actual
|
||||
// identity information, since the call will complete before the identity
|
||||
// provider has a chance to finish verifying... that's OK, but it makes
|
||||
// testing more difficult
|
||||
function checkOrSetupCheck(pc, pfx, idp, name) {
|
||||
if (pc.peerIdentity) {
|
||||
is(pc.peerIdentity.idp, idp, pfx + "IdP is correct");
|
||||
is(pc.peerIdentity.name, name + "@" + idp, pfx + "identity is correct");
|
||||
} else {
|
||||
++outstanding;
|
||||
pc.onidentityresult = function checkIdentity(e) {
|
||||
console.log("Got result", e, e.assertion, e.peerIdentity);
|
||||
if (e.assertion) {
|
||||
return;
|
||||
}
|
||||
ok(e.peerIdentity, pfx + "identity result contains identity");
|
||||
is(e.peerIdentity, e.target.peerIdentity, pfx + "identity result is on PC");
|
||||
is(e.peerIdentity.idp, idp, pfx + "IdP is correct");
|
||||
is(e.peerIdentity.name, name + "@" + idp, pfx + "identity is correct");
|
||||
--outstanding;
|
||||
if (outstanding <= 0) {
|
||||
test.next();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
checkOrSetupCheck(test.pcLocal._pc, "local: ", "test2.example.com", "someone");
|
||||
checkOrSetupCheck(test.pcRemote._pc, "remote: ", "test1.example.com", "someone");
|
||||
if (outstanding <= 0) {
|
||||
test.next();
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"OFFERS_AND_ANSWERS_INCLUDE_IDENTITY",
|
||||
function(test) {
|
||||
ok(test.pcLocal._last_offer.sdp.contains("a=identity"), "a=identity is in the offer SDP");
|
||||
ok(test.pcRemote._last_answer.sdp.contains("a=identity"), "a=identity is in the answer SDP");
|
||||
test.next();
|
||||
}
|
||||
],
|
||||
[
|
||||
"DESCRIPTIONS_CONTAIN_IDENTITY",
|
||||
function(test) {
|
||||
ok(test.pcLocal.localDescription.sdp.contains("a=identity"),
|
||||
"a=identity is in the local copy of the offer");
|
||||
ok(test.pcRemote.localDescription.sdp.contains("a=identity"),
|
||||
"a=identity is in the remote copy of the offer");
|
||||
ok(test.pcLocal.remoteDescription.sdp.contains("a=identity"),
|
||||
"a=identity is in the local copy of the answer");
|
||||
ok(test.pcRemote.remoteDescription.sdp.contains("a=identity"),
|
||||
"a=identity is in the remote copy of the answer");
|
||||
test.next();
|
||||
}
|
||||
]
|
||||
]);
|
||||
test.run();
|
||||
}
|
||||
runTest(theTest);
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,49 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
|
||||
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="application/javascript" src="head.js"></script>
|
||||
<script type="application/javascript" src="pc.js"></script>
|
||||
<script type="application/javascript" src="templates.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="test">
|
||||
<script type="application/javascript">
|
||||
createHTML({
|
||||
title: "Identity Provider returning errors is handled correctly"
|
||||
});
|
||||
|
||||
var test;
|
||||
runTest(function () {
|
||||
test = new PeerConnectionTest();
|
||||
test.setMediaConstraints([{audio: true}], [{audio: true}]);
|
||||
// first example generates an error
|
||||
test.setIdentityProvider(test.pcLocal, 'example.com', 'idp.html#error', 'nobody');
|
||||
// first doesn't even get a ready message from the IdP - results in a timeout
|
||||
test.setIdentityProvider(test.pcRemote, 'example.com', 'idp.html#error:ready', 'nobody');
|
||||
test.chain.append([
|
||||
[
|
||||
"PEER_IDENTITY_IS_EMPTY",
|
||||
function(test) {
|
||||
ok(!test.pcLocal._pc.peerIdentity, "local peerIdentity is not set");
|
||||
ok(!test.pcRemote._pc.peerIdentity, "remote peerIdentity is not set");
|
||||
test.next();
|
||||
}
|
||||
],
|
||||
[
|
||||
"OFFERS_AND_ANSWERS_DONT_INCLUDE_IDENTITY",
|
||||
function(test) {
|
||||
ok(!test.pcLocal._last_offer.sdp.contains("a=identity"), "a=identity not contained in the offer SDP");
|
||||
ok(!test.pcRemote._last_answer.sdp.contains("a=identity"), "a=identity not contained in the answer SDP");
|
||||
test.next();
|
||||
}
|
||||
],
|
||||
]);
|
||||
test.run();
|
||||
});
|
||||
|
||||
</script>
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
@ -442,6 +442,7 @@ var interfaceNamesInGlobalScope =
|
||||
"RGBColor",
|
||||
{name: "RTCDataChannelEvent", pref: "media.peerconnection.enabled"},
|
||||
{name: "RTCPeerConnectionIceEvent", pref: "media.peerconnection.enabled"},
|
||||
{name: "RTCPeerConnectionIdentityEvent", pref: "media.peerconnection.identity.enabled"},
|
||||
{name: "RTCStatsReport", pref: "media.peerconnection.enabled"},
|
||||
"Screen",
|
||||
"ScriptProcessorNode",
|
||||
|
16
dom/webidl/RTCIdentityAssertion.webidl
Normal file
16
dom/webidl/RTCIdentityAssertion.webidl
Normal file
@ -0,0 +1,16 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/.
|
||||
*
|
||||
* The origin of this IDL file is
|
||||
* http://www.w3.org/TR/2013/WD-webrtc-20130910/#idl-def-RTCIdentityAssertion
|
||||
*/
|
||||
|
||||
[Pref="media.peerconnection.identity.enabled",
|
||||
JSImplementation="@mozilla.org/dom/rtcidentityassertion;1",
|
||||
Constructor(DOMString idp, DOMString name)]
|
||||
interface RTCIdentityAssertion {
|
||||
attribute DOMString idp;
|
||||
attribute DOMString name;
|
||||
};
|
@ -87,6 +87,12 @@ interface RTCDataChannel;
|
||||
optional object? constraints)]
|
||||
// moz-prefixed until sufficiently standardized.
|
||||
interface mozRTCPeerConnection : EventTarget {
|
||||
[Pref="media.peerconnection.identity.enabled"]
|
||||
void setIdentityProvider (DOMString provider,
|
||||
optional DOMString protocol,
|
||||
optional DOMString username);
|
||||
[Pref="media.peerconnection.identity.enabled"]
|
||||
void getIdentityAssertion(optional RTCPeerConnectionErrorCallback failureCallback);
|
||||
void createOffer (RTCSessionDescriptionCallback successCallback,
|
||||
RTCPeerConnectionErrorCallback failureCallback,
|
||||
optional MediaConstraints constraints);
|
||||
@ -109,6 +115,9 @@ interface mozRTCPeerConnection : EventTarget {
|
||||
optional RTCPeerConnectionErrorCallback failureCallback);
|
||||
readonly attribute RTCIceGatheringState iceGatheringState;
|
||||
readonly attribute RTCIceConnectionState iceConnectionState;
|
||||
[Pref="media.peerconnection.identity.enabled"]
|
||||
readonly attribute RTCIdentityAssertion? peerIdentity;
|
||||
|
||||
sequence<MediaStream> getLocalStreams ();
|
||||
sequence<MediaStream> getRemoteStreams ();
|
||||
MediaStream? getStreamById (DOMString streamId);
|
||||
@ -137,6 +146,8 @@ interface mozRTCPeerConnection : EventTarget {
|
||||
attribute EventHandler ondatachannel;
|
||||
attribute EventHandler onconnection;
|
||||
attribute EventHandler onclosedconnection;
|
||||
[Pref="media.peerconnection.identity.enabled"]
|
||||
attribute EventHandler onidentityresult;
|
||||
};
|
||||
|
||||
callback RTCLogCallback = void (sequence<DOMString> logMessages);
|
||||
@ -151,6 +162,3 @@ interface WebrtcGlobalInformation {
|
||||
RTCLogCallback callback,
|
||||
RTCPeerConnectionErrorCallback errorCallback);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
21
dom/webidl/RTCPeerConnectionIdentityEvent.webidl
Normal file
21
dom/webidl/RTCPeerConnectionIdentityEvent.webidl
Normal file
@ -0,0 +1,21 @@
|
||||
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/.
|
||||
*
|
||||
* Proposed only, not in spec yet:
|
||||
* http://lists.w3.org/Archives/Public/public-webrtc/2013Dec/0104.html
|
||||
*/
|
||||
|
||||
dictionary RTCPeerConnectionIdentityEventInit : EventInit {
|
||||
RTCIdentityAssertion? peerIdentity = null;
|
||||
DOMString? assertion = null;
|
||||
};
|
||||
|
||||
[Pref="media.peerconnection.identity.enabled",
|
||||
Constructor(DOMString type,
|
||||
optional RTCPeerConnectionIdentityEventInit eventInitDict)]
|
||||
interface RTCPeerConnectionIdentityEvent : Event {
|
||||
readonly attribute RTCIdentityAssertion? peerIdentity;
|
||||
readonly attribute DOMString? assertion;
|
||||
};
|
@ -270,6 +270,7 @@ WEBIDL_FILES = [
|
||||
'RGBColor.webidl',
|
||||
'RTCConfiguration.webidl',
|
||||
'RTCIceCandidate.webidl',
|
||||
'RTCIdentityAssertion.webidl',
|
||||
'RTCPeerConnection.webidl',
|
||||
'RTCSessionDescription.webidl',
|
||||
'RTCStatsReport.webidl',
|
||||
@ -570,6 +571,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
|
||||
'MozStkCommandEvent.webidl',
|
||||
'RTCDataChannelEvent.webidl',
|
||||
'RTCPeerConnectionIceEvent.webidl',
|
||||
'RTCPeerConnectionIdentityEvent.webidl',
|
||||
'TrackEvent.webidl',
|
||||
'UserProximityEvent.webidl',
|
||||
'USSDReceivedEvent.webidl',
|
||||
|
@ -243,6 +243,11 @@ pref("media.navigator.permission.disabled", false);
|
||||
pref("media.peerconnection.default_iceservers", "[{\"url\": \"stun:stun.services.mozilla.com\"}]");
|
||||
pref("media.peerconnection.trickle_ice", true);
|
||||
pref("media.peerconnection.use_document_iceservers", true);
|
||||
// Do not enable identity before ensuring that the UX cannot be spoofed
|
||||
// see Bug 884573 for details
|
||||
// Do not enable identity before fixing domain comparison: see Bug 958741
|
||||
pref("media.peerconnection.identity.enabled", false);
|
||||
pref("media.peerconnection.identity.timeout", 5000);
|
||||
// These values (aec, agc, and noice) are from media/webrtc/trunk/webrtc/common_types.h
|
||||
// kXxxUnchanged = 0, kXxxDefault = 1, and higher values are specific to each
|
||||
// setting (for Xxx = Ec, Agc, or Ns). Defaults are all set to kXxxDefault here.
|
||||
|
Loading…
Reference in New Issue
Block a user