WebRTC extension FF fix

Thanks to updates in modern FFs handling of WebRTC
the webrtcadapter wrapper had to be updated.
To ensure this would be picked up, also added
WebRTC REST integration test cases.
The tests only run if the extension is enabled,
which is still OFF by default.

See Issue #1134 and #1083
This commit is contained in:
Christian Frichot
2015-07-08 15:13:21 +08:00
parent 526cd42170
commit 13593990e5
7 changed files with 679 additions and 157 deletions

View File

@@ -1,190 +1,327 @@
var RTCPeerConnection = null; /*
* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree.
*/
/* More information about these options at jshint.com/docs/options */
/* jshint browser: true, camelcase: true, curly: true, devel: true,
eqeqeq: true, forin: false, globalstrict: true, node: true,
quotmark: single, undef: true, unused: strict */
/* global mozRTCIceCandidate, mozRTCPeerConnection, Promise,
mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack */
/* exported trace,requestUserMedia */
'use strict';
var getUserMedia = null; var getUserMedia = null;
var attachMediaStream = null; var attachMediaStream = null;
var reattachMediaStream = null; var reattachMediaStream = null;
var webrtcDetectedBrowser = null; var webrtcDetectedBrowser = null;
var webrtcDetectedVersion = null; var webrtcDetectedVersion = null;
var webrtcMinimumVersion = null;
function maybeFixConfiguration(pcConfig) { function trace(text) {
if (pcConfig === null) { // This function is used for logging.
return; if (text[text.length - 1] === '\n') {
text = text.substring(0, text.length - 1);
} }
for (var i = 0; i < pcConfig.iceServers.length; i++) { if (window.performance) {
if (pcConfig.iceServers[i].hasOwnProperty('urls')){ var now = (window.performance.now() / 1000).toFixed(3);
if (pcConfig.iceServers[i]['urls'].length > 0) { beef.debug(now + ': ' + text);
// In FF - we just take the FIRST STUN Server } else {
pcConfig.iceServers[i]['url'] = pcConfig.iceServers[i]['urls'][0]; beef.debug(text);
} else {
pcConfig.iceServers[i]['url'] = pcConfig.iceServers[i]['urls'];
}
delete pcConfig.iceServers[i]['urls'];
}
} }
} }
if (navigator.mozGetUserMedia) { if (navigator.mozGetUserMedia) {
webrtcDetectedBrowser = "firefox"; webrtcDetectedBrowser = 'firefox';
// the detected firefox version.
webrtcDetectedVersion = webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10); parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
// the minimum firefox version still supported by adapter.
webrtcMinimumVersion = 31;
// The RTCPeerConnection object. // The RTCPeerConnection object.
var RTCPeerConnection = function(pcConfig, pcConstraints) { window.RTCPeerConnection = function(pcConfig, pcConstraints) {
// .urls is not supported in FF yet. if (webrtcDetectedVersion < 38) {
maybeFixConfiguration(pcConfig); // .urls is not supported in FF < 38.
return new mozRTCPeerConnection(pcConfig, pcConstraints); // create RTCIceServers with a single url.
} if (pcConfig && pcConfig.iceServers) {
var newIceServers = [];
// The RTCSessionDescription object. for (var i = 0; i < pcConfig.iceServers.length; i++) {
RTCSessionDescription = mozRTCSessionDescription; var server = pcConfig.iceServers[i];
if (server.hasOwnProperty('urls')) {
// The RTCIceCandidate object. for (var j = 0; j < server.urls.length; j++) {
RTCIceCandidate = mozRTCIceCandidate; var newServer = {
url: server.urls[j]
// Get UserMedia (only difference is the prefix). };
// Code from Adam Barth. if (server.urls[j].indexOf('turn') === 0) {
getUserMedia = navigator.mozGetUserMedia.bind(navigator); newServer.username = server.username;
navigator.getUserMedia = getUserMedia; newServer.credential = server.credential;
}
// Creates iceServer from the url for FF. newIceServers.push(newServer);
createIceServer = function(url, username, password) { }
var iceServer = null; } else {
var url_parts = url.split(':'); newIceServers.push(pcConfig.iceServers[i]);
if (url_parts[0].indexOf('stun') === 0) { }
// Create iceServer with stun url.
iceServer = { 'url': url };
} else if (url_parts[0].indexOf('turn') === 0) {
if (webrtcDetectedVersion < 27) {
// Create iceServer with turn url.
// Ignore the transport parameter from TURN url for FF version <=27.
var turn_url_parts = url.split("?");
// Return null for createIceServer if transport=tcp.
if (turn_url_parts.length === 1 ||
turn_url_parts[1].indexOf('transport=udp') === 0) {
iceServer = {'url': turn_url_parts[0],
'credential': password,
'username': username};
} }
} else { pcConfig.iceServers = newIceServers;
// FF 27 and above supports transport parameters in TURN url,
// So passing in the full url to create iceServer.
iceServer = {'url': url,
'credential': password,
'username': username};
} }
} }
return iceServer; return new mozRTCPeerConnection(pcConfig, pcConstraints);
}; };
createIceServers = function(urls, username, password) { // The RTCSessionDescription object.
var iceServers = []; window.RTCSessionDescription = mozRTCSessionDescription;
// Use .url for FireFox.
for (i = 0; i < urls.length; i++) {
var iceServer = createIceServer(urls[i],
username,
password);
if (iceServer !== null) {
iceServers.push(iceServer);
}
}
return iceServers;
}
// The RTCIceCandidate object.
window.RTCIceCandidate = mozRTCIceCandidate;
// getUserMedia constraints shim.
getUserMedia = (webrtcDetectedVersion < 38) ?
function(c, onSuccess, onError) {
var constraintsToFF37 = function(c) {
if (typeof c !== 'object' || c.require) {
return c;
}
var require = [];
Object.keys(c).forEach(function(key) {
var r = c[key] = (typeof c[key] === 'object') ?
c[key] : {ideal: c[key]};
if (r.exact !== undefined) {
r.min = r.max = r.exact;
delete r.exact;
}
if (r.min !== undefined || r.max !== undefined) {
require.push(key);
}
if (r.ideal !== undefined) {
c.advanced = c.advanced || [];
var oc = {};
oc[key] = {min: r.ideal, max: r.ideal};
c.advanced.push(oc);
delete r.ideal;
if (!Object.keys(r).length) {
delete c[key];
}
}
});
if (require.length) {
c.require = require;
}
return c;
};
beef.debug('spec: ' + JSON.stringify(c));
c.audio = constraintsToFF37(c.audio);
c.video = constraintsToFF37(c.video);
beef.debug('ff37: ' + JSON.stringify(c));
return navigator.mozGetUserMedia(c, onSuccess, onError);
} : navigator.mozGetUserMedia.bind(navigator);
navigator.getUserMedia = getUserMedia;
// Shim for mediaDevices on older versions.
if (!navigator.mediaDevices) {
navigator.mediaDevices = {getUserMedia: requestUserMedia,
addEventListener: function() { },
removeEventListener: function() { }
};
}
navigator.mediaDevices.enumerateDevices =
navigator.mediaDevices.enumerateDevices || function() {
return new Promise(function(resolve) {
var infos = [
{kind: 'audioinput', deviceId: 'default', label:'', groupId:''},
{kind: 'videoinput', deviceId: 'default', label:'', groupId:''}
];
resolve(infos);
});
};
if (webrtcDetectedVersion < 41) {
// Work around http://bugzil.la/1169665
var orgEnumerateDevices =
navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
navigator.mediaDevices.enumerateDevices = function() {
return orgEnumerateDevices().catch(function(e) {
if (e.name === 'NotFoundError') {
return [];
}
throw e;
});
};
}
// Attach a media stream to an element. // Attach a media stream to an element.
attachMediaStream = function(element, stream) { attachMediaStream = function(element, stream) {
beef.debug("Attaching media stream"); beef.debug('Attaching media stream');
element.mozSrcObject = stream; element.mozSrcObject = stream;
element.play();
}; };
reattachMediaStream = function(to, from) { reattachMediaStream = function(to, from) {
beef.debug("Reattaching media stream"); beef.debug('Reattaching media stream');
to.mozSrcObject = from.mozSrcObject; to.mozSrcObject = from.mozSrcObject;
to.play();
}; };
// Fake get{Video,Audio}Tracks
if (!MediaStream.prototype.getVideoTracks) {
MediaStream.prototype.getVideoTracks = function() {
return [];
};
}
if (!MediaStream.prototype.getAudioTracks) {
MediaStream.prototype.getAudioTracks = function() {
return [];
};
}
} else if (navigator.webkitGetUserMedia) { } else if (navigator.webkitGetUserMedia) {
webrtcDetectedBrowser = "chrome"; webrtcDetectedBrowser = 'chrome';
// Temporary fix until crbug/374263 is fixed.
// Setting Chrome version to 999, if version is unavailable.
var result = navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./);
if (result !== null) {
webrtcDetectedVersion = parseInt(result[2], 10);
} else {
webrtcDetectedVersion = 999;
}
// Creates iceServer from the url for Chrome M33 and earlier. // the detected chrome version.
createIceServer = function(url, username, password) { webrtcDetectedVersion =
var iceServer = null; parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10);
var url_parts = url.split(':');
if (url_parts[0].indexOf('stun') === 0) {
// Create iceServer with stun url.
iceServer = { 'url': url };
} else if (url_parts[0].indexOf('turn') === 0) {
// Chrome M28 & above uses below TURN format.
iceServer = {'url': url,
'credential': password,
'username': username};
}
return iceServer;
};
// Creates iceServers from the urls for Chrome M34 and above. // the minimum chrome version still supported by adapter.
createIceServers = function(urls, username, password) { webrtcMinimumVersion = 38;
var iceServers = [];
if (webrtcDetectedVersion >= 34) {
// .urls is supported since Chrome M34.
iceServers = {'urls': urls,
'credential': password,
'username': username };
} else {
for (i = 0; i < urls.length; i++) {
var iceServer = createIceServer(urls[i],
username,
password);
if (iceServer !== null) {
iceServers.push(iceServer);
}
}
}
return iceServers;
};
// The RTCPeerConnection object. // The RTCPeerConnection object.
var RTCPeerConnection = function(pcConfig, pcConstraints) { window.RTCPeerConnection = function(pcConfig, pcConstraints) {
// .urls is supported since Chrome M34. var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints);
if (webrtcDetectedVersion < 34) { var origGetStats = pc.getStats.bind(pc);
maybeFixConfiguration(pcConfig); pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line
} // If selector is a function then we are in the old style stats so just
return new webkitRTCPeerConnection(pcConfig, pcConstraints); // pass back the original getStats format to avoid breaking old users.
} if (typeof selector === 'function') {
return origGetStats(selector, successCallback);
}
// Get UserMedia (only difference is the prefix). var fixChromeStats = function(response) {
// Code from Adam Barth. var standardReport = {};
getUserMedia = navigator.webkitGetUserMedia.bind(navigator); var reports = response.result();
reports.forEach(function(report) {
var standardStats = {
id: report.id,
timestamp: report.timestamp,
type: report.type
};
report.names().forEach(function(name) {
standardStats[name] = report.stat(name);
});
standardReport[standardStats.id] = standardStats;
});
return standardReport;
};
var successCallbackWrapper = function(response) {
successCallback(fixChromeStats(response));
};
return origGetStats(successCallbackWrapper, selector);
};
return pc;
};
// add promise support
['createOffer', 'createAnswer'].forEach(function(method) {
var nativeMethod = webkitRTCPeerConnection.prototype[method];
webkitRTCPeerConnection.prototype[method] = function() {
var self = this;
if (arguments.length < 1 || (arguments.length === 1 &&
typeof(arguments[0]) === 'object')) {
var opts = arguments.length === 1 ? arguments[0] : undefined;
return new Promise(function(resolve, reject) {
nativeMethod.apply(self, [resolve, reject, opts]);
});
} else {
return nativeMethod.apply(this, arguments);
}
};
});
['setLocalDescription', 'setRemoteDescription',
'addIceCandidate'].forEach(function(method) {
var nativeMethod = webkitRTCPeerConnection.prototype[method];
webkitRTCPeerConnection.prototype[method] = function() {
var args = arguments;
var self = this;
return new Promise(function(resolve, reject) {
nativeMethod.apply(self, [args[0],
function() {
resolve();
if (args.length >= 2) {
args[1].apply(null, []);
}
},
function(err) {
reject(err);
if (args.length >= 3) {
args[2].apply(null, [err]);
}
}]
);
});
};
});
// getUserMedia constraints shim.
getUserMedia = function(c, onSuccess, onError) {
var constraintsToChrome = function(c) {
if (typeof c !== 'object' || c.mandatory || c.optional) {
return c;
}
var cc = {};
Object.keys(c).forEach(function(key) {
if (key === 'require' || key === 'advanced') {
return;
}
var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
if (r.exact !== undefined && typeof r.exact === 'number') {
r.min = r.max = r.exact;
}
var oldname = function(prefix, name) {
if (prefix) {
return prefix + name.charAt(0).toUpperCase() + name.slice(1);
}
return (name === 'deviceId') ? 'sourceId' : name;
};
if (r.ideal !== undefined) {
cc.optional = cc.optional || [];
var oc = {};
if (typeof r.ideal === 'number') {
oc[oldname('min', key)] = r.ideal;
cc.optional.push(oc);
oc = {};
oc[oldname('max', key)] = r.ideal;
cc.optional.push(oc);
} else {
oc[oldname('', key)] = r.ideal;
cc.optional.push(oc);
}
}
if (r.exact !== undefined && typeof r.exact !== 'number') {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname('', key)] = r.exact;
} else {
['min', 'max'].forEach(function(mix) {
if (r[mix] !== undefined) {
cc.mandatory = cc.mandatory || {};
cc.mandatory[oldname(mix, key)] = r[mix];
}
});
}
});
if (c.advanced) {
cc.optional = (cc.optional || []).concat(c.advanced);
}
return cc;
};
beef.debug('spec: ' + JSON.stringify(c)); // whitespace for alignment
c.audio = constraintsToChrome(c.audio);
c.video = constraintsToChrome(c.video);
beef.debug('chrome: ' + JSON.stringify(c));
return navigator.webkitGetUserMedia(c, onSuccess, onError);
};
navigator.getUserMedia = getUserMedia; navigator.getUserMedia = getUserMedia;
// Attach a media stream to an element. // Attach a media stream to an element.
attachMediaStream = function(element, stream) { attachMediaStream = function(element, stream) {
if (typeof element.srcObject !== 'undefined') { if (typeof element.srcObject !== 'undefined') {
element.srcObject = stream; element.srcObject = stream;
} else if (typeof element.mozSrcObject !== 'undefined') {
element.mozSrcObject = stream;
} else if (typeof element.src !== 'undefined') { } else if (typeof element.src !== 'undefined') {
element.src = URL.createObjectURL(stream); element.src = URL.createObjectURL(stream);
} else { } else {
@@ -195,6 +332,78 @@ if (navigator.mozGetUserMedia) {
reattachMediaStream = function(to, from) { reattachMediaStream = function(to, from) {
to.src = from.src; to.src = from.src;
}; };
if (!navigator.mediaDevices) {
navigator.mediaDevices = {getUserMedia: requestUserMedia,
enumerateDevices: function() {
return new Promise(function(resolve) {
var kinds = {audio: 'audioinput', video: 'videoinput'};
return MediaStreamTrack.getSources(function(devices) {
resolve(devices.map(function(device) {
return {label: device.label,
kind: kinds[device.kind],
deviceId: device.id,
groupId: ''};
}));
});
});
}};
// in case someone wants to listen for the devicechange event.
navigator.mediaDevices.addEventListener = function() { };
navigator.mediaDevices.removeEventListener = function() { };
}
} else if (navigator.mediaDevices && navigator.userAgent.match(
/Edge\/(\d+).(\d+)$/)) {
webrtcDetectedBrowser = 'edge';
webrtcDetectedVersion =
parseInt(navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)[2], 10);
// the minimum version still supported by adapter.
webrtcMinimumVersion = 12;
attachMediaStream = function(element, stream) {
element.srcObject = stream;
};
reattachMediaStream = function(to, from) {
to.srcObject = from.srcObject;
};
} else { } else {
beef.debug("Browser does not appear to be WebRTC-capable"); // console.log('Browser does not appear to be WebRTC-capable');
}
// Returns the result of getUserMedia as a Promise.
function requestUserMedia(constraints) {
return new Promise(function(resolve, reject) {
getUserMedia(constraints, resolve, reject);
});
}
if (typeof module !== 'undefined') {
module.exports = {
RTCPeerConnection: window.RTCPeerConnection,
getUserMedia: getUserMedia,
attachMediaStream: attachMediaStream,
reattachMediaStream: reattachMediaStream,
webrtcDetectedBrowser: webrtcDetectedBrowser,
webrtcDetectedVersion: webrtcDetectedVersion,
webrtcMinimumVersion: webrtcMinimumVersion
//requestUserMedia: not exposed on purpose.
//trace: not exposed on purpose.
};
} else if ((typeof require === 'function') && (typeof define === 'function')) {
// Expose objects and functions when RequireJS is doing the loading.
define([], function() {
return {
RTCPeerConnection: window.RTCPeerConnection,
getUserMedia: getUserMedia,
attachMediaStream: attachMediaStream,
reattachMediaStream: reattachMediaStream,
webrtcDetectedBrowser: webrtcDetectedBrowser,
webrtcDetectedVersion: webrtcDetectedVersion,
webrtcMinimumVersion: webrtcMinimumVersion
//requestUserMedia: not exposed on purpose.
//trace: not exposed on purpose.
};
});
} }

View File

@@ -67,10 +67,12 @@ Beefwebrtc.prototype.initialize = function() {
// Initialise the pcConfig hash with the provided stunservers // Initialise the pcConfig hash with the provided stunservers
var stuns = JSON.parse(this.stunservers); var stuns = JSON.parse(this.stunservers);
this.pcConfig = {"iceServers": [{"urls":stuns}]}; this.pcConfig = {"iceServers": [{"urls":stuns, "username":"user",
"credential":"pass"}]};
// We're not getting the browsers to request their own TURN servers, we're specifying them through BeEF // We're not getting the browsers to request their own TURN servers, we're specifying them through BeEF
this.forceTurn(this.turnjson); // this.forceTurn(this.turnjson);
this.turnDone = true;
// Caller is always ready to create peerConnection. // Caller is always ready to create peerConnection.
this.signalingReady = this.initiator; this.signalingReady = this.initiator;
@@ -450,6 +452,18 @@ Beefwebrtc.prototype.onCreateSessionDescriptionError = function(error) {
if (localverbose === true) {beef.debug('Failed to create session description: ' + error.toString());} if (localverbose === true) {beef.debug('Failed to create session description: ' + error.toString());}
} }
// If the browser successfully sets a remote description
Beefwebrtc.prototype.onSetRemoteDescriptionSuccess = function() {
var localverbose = false;
for (var k in beefrtcs) {
if (beefrtcs[k].verbose === true) {
localverbose = true;
}
}
if (localverbose === true) {beef.debug('Set remote session description successfully');}
}
// Check for messages - which includes signaling from a calling peer - this gets kicked off in maybeStart() // Check for messages - which includes signaling from a calling peer - this gets kicked off in maybeStart()
Beefwebrtc.prototype.calleeStart = function() { Beefwebrtc.prototype.calleeStart = function() {
// Callee starts to process cached offer and other messages. // Callee starts to process cached offer and other messages.
@@ -467,11 +481,55 @@ Beefwebrtc.prototype.processSignalingMessage = function(message) {
if (message.type === 'offer') { if (message.type === 'offer') {
if (this.verbose) {beef.debug("Processing signalling message: OFFER");} if (this.verbose) {beef.debug("Processing signalling message: OFFER");}
this.setRemote(message); if (navigator.mozGetUserMedia) { // Mozilla shim fuckn shit - since the new
this.doAnswer(); // version of FF - which no longer works
if (this.verbose) {beef.debug("Moz shim here");}
globalrtc[this.peerid].setRemoteDescription(
new RTCSessionDescription(message),
function() {
// globalrtc[this.peerid].createAnswer(function(answer) {
// globalrtc[this.peerid].setLocalDescription(
var peerid = null;
for (var k in beefrtcs) {
if (beefrtcs[k].allgood === false) {
peerid = beefrtcs[k].peerid;
}
}
globalrtc[peerid].createAnswer(function(answer) {
globalrtc[peerid].setLocalDescription(
new RTCSessionDescription(answer),
function() {
beefrtcs[peerid].sendSignalMsg(answer);
},function(error) {
beef.debug("setLocalDescription error: " + error);
});
},function(error) {
beef.debug("createAnswer error: " +error);
});
},function(error) {
beef.debug("setRemoteDescription error: " + error);
});
} else {
this.setRemote(message);
this.doAnswer();
}
} else if (message.type === 'answer') { } else if (message.type === 'answer') {
if (this.verbose) {beef.debug("Processing signalling message: ANSWER");} if (this.verbose) {beef.debug("Processing signalling message: ANSWER");}
this.setRemote(message); if (navigator.mozGetUserMedia) { // terrible moz shim - as for the offer
if (this.verbose) {beef.debug("Moz shim here");}
globalrtc[this.peerid].setRemoteDescription(
new RTCSessionDescription(message),
function() {},
function(error) {
beef.debug("setRemoteDescription error: " + error);
});
} else {
this.setRemote(message);
}
} else if (message.type === 'candidate') { } else if (message.type === 'candidate') {
if (this.verbose) {beef.debug("Processing signalling message: CANDIDATE");} if (this.verbose) {beef.debug("Processing signalling message: CANDIDATE");}
var candidate = new RTCIceCandidate({sdpMLineIndex: message.label, var candidate = new RTCIceCandidate({sdpMLineIndex: message.label,
@@ -486,11 +544,11 @@ Beefwebrtc.prototype.processSignalingMessage = function(message) {
// Used to set the RTC remote session // Used to set the RTC remote session
Beefwebrtc.prototype.setRemote = function(message) { Beefwebrtc.prototype.setRemote = function(message) {
globalrtc[this.peerid].setRemoteDescription(new RTCSessionDescription(message), globalrtc[this.peerid].setRemoteDescription(new RTCSessionDescription(message),
onSetRemoteDescriptionSuccess, this.onSetSessionDescriptionError); this.onSetRemoteDescriptionSuccess, this.onSetSessionDescriptionError);
function onSetRemoteDescriptionSuccess() { // function onSetRemoteDescriptionSuccess() {
if (this.verbose) {beef.debug("Set remote session description success.");} // if (this.verbose) {beef.debug("Set remote session description success.");}
} // }
} }
// As part of the processSignalingMessage function, we check for 'offers' from peers. If there's an offer, we answer, as below // As part of the processSignalingMessage function, we check for 'offers' from peers. If there's an offer, we answer, as below
@@ -585,4 +643,4 @@ beef.webrtc = {
} }
} }
} }
beef.regCmp('beef.webrtc'); beef.regCmp('beef.webrtc');

View File

@@ -9,6 +9,6 @@ beef:
name: 'WebRTC' name: 'WebRTC'
enable: false enable: false
authors: ["xntrik"] authors: ["xntrik"]
stunservers: '["stun:stun.l.google.com:19302","stun:stun1.l.google.com:19302"]' stunservers: '["stun:stun.l.google.com:19302","stun:stun1.l.google.com:19302","turn:numb.viagenie.ca:3478"]'
# stunservers: '["stun:stun.l.google.com:19302"]' # stunservers: '["stun:stun.l.google.com:19302"]'
turnservers: '{"username": "someone%40somewhere.com", "password": "somepass", "uris": ["turn:numb.viagenie.ca:3478?transport=udp","turn:numb.viagenie.ca:3478?transport=tcp"]}' turnservers: '{"username": "someone%40somewhere.com", "password": "somepass", "uris": ["turn:numb.viagenie.ca:3478?transport=udp","turn:numb.viagenie.ca:3478?transport=tcp"]}'

View File

@@ -152,7 +152,7 @@ module BeEF
# +++ Example with curl +++ # +++ Example with curl +++
# curl -H "Content-type: application/json; charset=UTF-8" -v # curl -H "Content-type: application/json; charset=UTF-8" -v
# -X POST -d '{"from":1,"to":2,"message":"Just a plain message"}' # -X POST -d '{"from":1,"to":2,"message":"Just a plain message"}'
# http://127.0.0.1:3000/api/webrtc/go\?token\=df67654b03d030d97018f85f0284247d7f49c348 # http://127.0.0.1:3000/api/webrtc/msg\?token\=df67654b03d030d97018f85f0284247d7f49c348
# #
# Available client-side "message" options and handling: # Available client-side "message" options and handling:
# !gostealth - will put the <to> browser into a stealth mode # !gostealth - will put the <to> browser into a stealth mode

View File

@@ -23,3 +23,4 @@ RESTAPI_NETWORK = "http://" + ATTACK_DOMAIN + ":3000/api/network"
RESTAPI_DNS = "http://" + ATTACK_DOMAIN + ":3000/api/dns" RESTAPI_DNS = "http://" + ATTACK_DOMAIN + ":3000/api/dns"
RESTAPI_SENG = "http://" + ATTACK_DOMAIN + ":3000/api/seng" RESTAPI_SENG = "http://" + ATTACK_DOMAIN + ":3000/api/seng"
RESTAPI_ADMIN = "http://" + ATTACK_DOMAIN + ":3000/api/admin" RESTAPI_ADMIN = "http://" + ATTACK_DOMAIN + ":3000/api/admin"
RESTAPI_WEBRTC = "http://" + ATTACK_DOMAIN + ":3000/api/webrtc"

View File

@@ -0,0 +1,252 @@
#
# Copyright (c) 2006-2015 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - http://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
require 'test/unit'
require 'rest-client'
require 'json'
require '../common/test_constants'
require '../common/beef_test'
class TC_WebRTCRest < Test::Unit::TestCase
class << self
# Login to API before performing any tests - and fetch config too
def startup
json = {:username => BEEF_USER, :password => BEEF_PASSWD}.to_json
@@headers = {:content_type => :json, :accept => :json}
response = RestClient.post("#{RESTAPI_ADMIN}/login",
json,
@@headers)
result = JSON.parse(response.body)
@@token = result['token']
$root_dir = '../../'
$:.unshift($root_dir)
require 'core/loader'
BeEF::Core::Configuration.new(File.join($root_dir, 'config.yaml'))
BeEF::Core::Configuration.instance.load_extensions_config
@@config = BeEF::Core::Configuration.instance
@@activated = @@config.get('beef.extension.webrtc.enable') || false
@@victim1 = BeefTest.new_victim
@@victim2 = BeefTest.new_victim
puts "WebRTC Tests: Sleeping for 8 - waiting for 2 browsers to get hooked"
sleep 8.0
# Fetch last online browsers' ids
rest_response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {
:token => @@token}}
result = JSON.parse(rest_response.body)
browsers = result["hooked-browsers"]["online"]
browsers.each_with_index do |elem, index|
if index == browsers.length - 1
@@victim2id = browsers["#{index}"]["id"].to_s
end
if index == browsers.length - 2
@@victim1id = browsers["#{index}"]["id"].to_s
end
end
end
def shutdown
$root_dir = nil
@@victim1.driver.browser.close
@@victim2.driver.browser.close
end
end
def test_1_webrtc_check_for_two_hooked_browsers
return if not @@activated
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
browsers = result["hooked-browsers"]["online"]
assert_not_nil browsers
assert_operator browsers.length, :>=, 2
end
def test_2_webrtc_establishing_p2p
return if not @@activated
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.post("#{RESTAPI_WEBRTC}/go?token=#{@@token}",
{:from => @@victim1id, :to => @@victim2id, :verbose => "true"}.to_json,
@@headers)
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
assert_equal true, result["success"]
sleep 20.0
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_LOGS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
loghitcount = 0
result["logs"].reverse.each {|l|
# Using free-space matching mode /x below to wrap regex.
# therefore need to escape spaces I want to check, hence the \
regex = Regexp.new(/Browser:(#{@@victim1id}|#{@@victim2id})\ received\
message\ from\ Browser:(#{@@victim1id}|#{@@victim2id})
:\ ICE\ Status:\ connected/x)
loghitcount += 1 if (not regex.match(l["event"]).nil?) and
(l["type"].to_s.eql?("WebRTC"))
}
assert_equal 2, loghitcount
end
def test_3_webrtc_send_msg # assumes test 2 has run
return if not @@activated
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.post("#{RESTAPI_WEBRTC}/msg?token=#{@@token}",
{:from => @@victim1id, :to => @@victim2id,
:message => "RTC test message"}.to_json,
@@headers)
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
assert_equal true, result["success"]
sleep 10.0
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_LOGS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
assert_block do
result["logs"].reverse.each {|l|
# Using free-space matching mode /x below to wrap regex.
# therefore need to escape spaces I want to check, hence the \
regex = Regexp.new(/Browser:(#{@@victim1id}|#{@@victim2id})\ received\
message\ from\ Browser:
(#{@@victim1id}|#{@@victim2id})
:\ RTC\ test\ message/x)
return true if (not regex.match(l["event"]).nil?) and
(l["type"].to_s.eql?("WebRTC"))
}
end
end
def test_4_webrtc_stealthmode # assumes test 2 has run
return if not @@activated
# Test our two browsers are still online
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
online = result["hooked-browsers"]["online"]
assert_block do
online.each {|hb|
return true if hb[1]["id"].eql?(@@victim1id)
}
end
assert_block do
online.each {|hb|
return true if hb[1]["id"].eql?(@@victim2id)
}
end
# Command one of the browsers to go stealth
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.post("#{RESTAPI_WEBRTC}/msg?token=#{@@token}",
{:from => @@victim1id, :to => @@victim2id,
:message => "!gostealth"}.to_json,
@@headers)
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
assert_equal true, result["success"]
sleep 40.0 #Wait until that browser is offline.
# Test that the browser is now offline
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
offline = result["hooked-browsers"]["offline"]
assert_block do
offline.each {|hb|
return true if hb[1]["id"].eql?(@@victim2id)
}
end
# Test that we can bring it back online (which implies comms are still ok)
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.post("#{RESTAPI_WEBRTC}/msg?token=#{@@token}",
{:from => @@victim1id, :to => @@victim2id,
:message => "!endstealth"}.to_json,
@@headers)
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
assert_equal true, result["success"]
sleep 10.0 # Wait until browser comes back
# Test that the browser is now online
rest_response = nil
assert_nothing_raised do
rest_response = RestClient.get "#{RESTAPI_HOOKS}", {:params => {
:token => @@token}}
end
check_rest_response(rest_response)
result = JSON.parse(rest_response.body)
online = result["hooked-browsers"]["online"]
assert_block do
online.each {|hb|
return true if hb[1]["id"].eql?(@@victim2id)
}
end
end
private
# Standard assertions for verifying response from RESTful API
def check_rest_response(response)
assert_not_nil(response.body)
assert_equal(200, response.code)
end
end

View File

@@ -19,6 +19,7 @@ require './tc_jools' # Basic tests for jools
require './tc_network_rest' # Basic tests for Network extension RESTful API interface require './tc_network_rest' # Basic tests for Network extension RESTful API interface
#require './tc_dns_rest' # Basic tests for DNS RESTful API interface #require './tc_dns_rest' # Basic tests for DNS RESTful API interface
require './tc_social_engineering_rest' # Basic tests for social engineering RESTful API interface require './tc_social_engineering_rest' # Basic tests for social engineering RESTful API interface
require './tc_webrtc_rest' # Basic tests for WebRTC extension
class TS_BeefIntegrationTests class TS_BeefIntegrationTests
def self.suite def self.suite
@@ -31,6 +32,7 @@ class TS_BeefIntegrationTests
suite << TC_NetworkRest.suite suite << TC_NetworkRest.suite
#suite << TC_DnsRest.suite #suite << TC_DnsRest.suite
suite << TC_SocialEngineeringRest.suite suite << TC_SocialEngineeringRest.suite
suite << TC_WebRTCRest.suite
return suite return suite
end end