Merge branch 'master' into multiple-tracks
This commit is contained in:
commit
73317c920a
|
@ -32,3 +32,9 @@ Description: Prosody configuration for Jitsi Meet
|
|||
.
|
||||
This package contains configuration for Prosody to be used with
|
||||
Jitsi Meet.
|
||||
|
||||
Package: jitsi-meet-tokens
|
||||
Architecture: all
|
||||
Depends: ${misc:Depends}, prosody | prosody-trunk, jitsi-meet-prosody
|
||||
Description: Prosody token authentication plugin for Jitsi Meet
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
Token authentication plugin for Jitsi Meet
|
||||
----------------------------
|
||||
|
||||
Jitsi Meet is a WebRTC video conferencing application. This package contains
|
||||
Prosody plugin which enables token authentication in Jitsi Meet installation.
|
||||
|
||||
-- Pawel Domas <pawel.domas@jitsi.org> Mon, 2 Nov 2015 14:45:00 -0600
|
|
@ -0,0 +1,10 @@
|
|||
#!/bin/sh -e
|
||||
|
||||
# Source debconf library.
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
db_input critical jitsi-meet-tokens/appid || true
|
||||
db_go
|
||||
|
||||
db_input critical jitsi-meet-tokens/appsecret || true
|
||||
db_go
|
|
@ -0,0 +1 @@
|
|||
prosody-plugins/ /usr/share/jitsi-meet/
|
|
@ -0,0 +1,95 @@
|
|||
#!/bin/bash
|
||||
# postinst script for jitsi-meet-tokens
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <postinst> `configure' <most-recently-configured-version>
|
||||
# * <old-postinst> `abort-upgrade' <new version>
|
||||
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
|
||||
# <new-version>
|
||||
# * <postinst> `abort-remove'
|
||||
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
|
||||
# <failed-install-package> <version> `removing'
|
||||
# <conflicting-package> <version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
|
||||
case "$1" in
|
||||
configure)
|
||||
|
||||
if [ -f "/etc/jitsi/videobridge/config" ] ; then
|
||||
. /etc/jitsi/videobridge/config
|
||||
fi
|
||||
|
||||
if [ -f "/etc/jitsi/jicofo/config" ] ; then
|
||||
. /etc/jitsi/jicofo/config
|
||||
fi
|
||||
|
||||
# loading debconf
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
db_get jitsi-meet-tokens/appid
|
||||
if [ "$RET" = "false" ] ; then
|
||||
echo "Application ID is mandatory"
|
||||
exit 1
|
||||
fi
|
||||
APP_ID=$RET
|
||||
|
||||
db_get jitsi-meet-tokens/appsecret
|
||||
if [ "$RET" = "false" ] ; then
|
||||
echo "Application secret is mandatory"
|
||||
fi
|
||||
APP_SECRET=$RET
|
||||
|
||||
# We can adjust Prosody config only if there is Jvb or Jicofo domain configured
|
||||
PROSODY_HOST_CONFIG="/etc/prosody/conf.avail/$JVB_HOSTNAME.cfg.lua"
|
||||
if [ ! -f "$PROSODY_HOST_CONFIG" ] ; then
|
||||
PROSODY_HOST_CONFIG="/etc/prosody/conf.avail/$JICOFO_HOSTNAME.cfg.lua"
|
||||
fi
|
||||
|
||||
# Store config filename for purge
|
||||
db_set jitsi-meet-prosody/prosody_config $PROSODY_HOST_CONFIG
|
||||
|
||||
db_stop
|
||||
|
||||
if [ -f "$PROSODY_HOST_CONFIG" ] ; then
|
||||
if grep -q "plugin_paths" "$PROSODY_HOST_CONFIG"; then
|
||||
# enable tokens in prosody host config
|
||||
sed -i 's/--plugin_paths/plugin_paths/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/authentication = "anonymous"/authentication = "token"/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ --allow_unencrypted_plain_auth/ allow_unencrypted_plain_auth/g' $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ --app_id=example_app_id/ app_id=$APP_ID/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ --app_secret=example_app_secret/ app_secret=$APP_SECRET/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ --modules_enabled = { "token_verification" }/ modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody reload
|
||||
fi
|
||||
else
|
||||
echo "Failed apply auto-config to $PROSODY_HOST_CONFIG which most likely comes from not supported version of jitsi-meet"
|
||||
fi
|
||||
else
|
||||
echo "Prosody config not found at $PROSODY_HOST_CONFIG - unable to auto-configure token authentication"
|
||||
fi
|
||||
|
||||
;;
|
||||
|
||||
abort-upgrade|abort-remove|abort-deconfigure)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postinst called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,74 @@
|
|||
#!/bin/sh
|
||||
# postrm script for jitsi-meet-tokens
|
||||
#
|
||||
# see: dh_installdeb(1)
|
||||
|
||||
set -e
|
||||
|
||||
# summary of how this script can be called:
|
||||
# * <postrm> `remove'
|
||||
# * <postrm> `purge'
|
||||
# * <old-postrm> `upgrade' <new-version>
|
||||
# * <new-postrm> `failed-upgrade' <old-version>
|
||||
# * <new-postrm> `abort-install'
|
||||
# * <new-postrm> `abort-install' <old-version>
|
||||
# * <new-postrm> `abort-upgrade' <old-version>
|
||||
# * <disappearer's-postrm> `disappear' <overwriter>
|
||||
# <overwriter-version>
|
||||
# for details, see http://www.debian.org/doc/debian-policy/ or
|
||||
# the debian-policy package
|
||||
|
||||
# Load debconf
|
||||
. /usr/share/debconf/confmodule
|
||||
|
||||
|
||||
case "$1" in
|
||||
remove)
|
||||
|
||||
db_get jitsi-meet-prosody/prosody_config
|
||||
PROSODY_HOST_CONFIG=$RET
|
||||
|
||||
if [ -f "$PROSODY_HOST_CONFIG" ] ; then
|
||||
|
||||
db_get jitsi-meet-tokens/appid
|
||||
APP_ID=$RET
|
||||
|
||||
db_get jitsi-meet-tokens/appsecret
|
||||
APP_SECRET=$RET
|
||||
|
||||
# Revert prosody config
|
||||
sed -i 's/plugin_paths/--plugin_paths/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/authentication = "token"/authentication = "anonymous"/g' $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ allow_unencrypted_plain_auth/ --allow_unencrypted_plain_auth/g' $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ app_id=$APP_ID/ --app_id=example_app_id/g" $PROSODY_HOST_CONFIG
|
||||
sed -i "s/ app_secret=$APP_SECRET/ --app_secret=example_app_secret/g" $PROSODY_HOST_CONFIG
|
||||
sed -i 's/ modules_enabled = { "token_verification" }/ --modules_enabled = { "token_verification" }/g' $PROSODY_HOST_CONFIG
|
||||
|
||||
if [ -x "/etc/init.d/prosody" ]; then
|
||||
invoke-rc.d prosody reload
|
||||
fi
|
||||
fi
|
||||
|
||||
db_stop
|
||||
;;
|
||||
|
||||
purge)
|
||||
;;
|
||||
|
||||
upgrade|failed-upgrade|abort-install|abort-upgrade|disappear)
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "postrm called with unknown argument \`$1'" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# dh_installdeb will replace this with shell code automatically
|
||||
# generated by other debhelper scripts.
|
||||
|
||||
#DEBHELPER#
|
||||
|
||||
db_stop
|
||||
|
||||
exit 0
|
|
@ -0,0 +1,14 @@
|
|||
Template: jitsi-meet-tokens/appid
|
||||
Type: string
|
||||
_Description: The application ID to be used by token authentication plugin:
|
||||
Application ID:
|
||||
|
||||
Template: jitsi-meet-tokens/appsecret
|
||||
Type: password
|
||||
_Description: The application secret to be used by token authentication plugin:
|
||||
Application secret:
|
||||
|
||||
Template: jitsi-meet-prosody/prosody_config
|
||||
Type: string
|
||||
_Description: The location of Jitsi Meet Prosody config file
|
||||
Jitsi-meet Prosody config file location:
|
|
@ -1,6 +1,14 @@
|
|||
-- Plugins path gets uncommented during jitsi-meet-tokens package install - that's where token plugin is located
|
||||
--plugin_paths = { "/usr/share/jitsi-meet/prosody-plugins/" }
|
||||
|
||||
VirtualHost "jitmeet.example.com"
|
||||
-- enabled = false -- Remove this line to enable this host
|
||||
authentication = "anonymous"
|
||||
-- Three properties below get uncommented by jitsi-meet-tokens package config
|
||||
-- and authentication above is switched to "token"
|
||||
--allow_unencrypted_plain_auth = true;
|
||||
--app_id=example_app_id
|
||||
--app_secret=example_app_secret
|
||||
-- Assign this host a certificate for TLS, otherwise it would use the one
|
||||
-- set in the global section (if any).
|
||||
-- Note that old-style SSL on port 5223 only supports one certificate, and will always
|
||||
|
@ -17,13 +25,14 @@ VirtualHost "jitmeet.example.com"
|
|||
}
|
||||
|
||||
Component "conference.jitmeet.example.com" "muc"
|
||||
--modules_enabled = { "token_verification" }
|
||||
admins = { "focusUser@auth.jitmeet.example.com" }
|
||||
|
||||
Component "jitsi-videobridge.jitmeet.example.com"
|
||||
component_secret = "jitmeetSecret"
|
||||
|
||||
VirtualHost "auth.jitmeet.example.com"
|
||||
authentication = "internal_plain"
|
||||
authentication = "internal_plain"
|
||||
|
||||
Component "focus.jitmeet.example.com"
|
||||
component_secret = "focusSecret"
|
||||
|
|
|
@ -194,6 +194,7 @@
|
|||
"password": "password",
|
||||
"userPassword": "user password",
|
||||
"token": "token",
|
||||
"tokenAuthFailed": "Failed to authenticate with XMPP server: invalid token",
|
||||
"displayNameRequired": "Please enter your display name:",
|
||||
"extensionRequired": "Extension required:",
|
||||
"firefoxExtensionPrompt": "You need to install a Firefox extension in order to use screen sharing. Please try again after you <a href='__url__'>get it from here</a>!"
|
||||
|
|
|
@ -9,12 +9,12 @@ var RTCBrowserType = require("./RTCBrowserType");
|
|||
* @param stream original WebRTC stream object to which 'onended' handling
|
||||
* will be added.
|
||||
*/
|
||||
function implementOnEndedHandling(stream) {
|
||||
function implementOnEndedHandling(localStream) {
|
||||
var stream = localStream.getOriginalStream();
|
||||
var originalStop = stream.stop;
|
||||
stream.stop = function () {
|
||||
originalStop.apply(stream);
|
||||
if (!stream.ended) {
|
||||
stream.ended = true;
|
||||
if (localStream.isActive()) {
|
||||
stream.onended();
|
||||
}
|
||||
};
|
||||
|
@ -39,11 +39,14 @@ function LocalStream(stream, type, eventEmitter, videoType, isGUMStream) {
|
|||
};
|
||||
}
|
||||
|
||||
this.stream.onended = function () {
|
||||
self.streamEnded();
|
||||
};
|
||||
APP.RTC.addMediaStreamInactiveHandler(
|
||||
this.stream,
|
||||
function () {
|
||||
self.streamEnded();
|
||||
});
|
||||
|
||||
if (RTCBrowserType.isFirefox()) {
|
||||
implementOnEndedHandling(this.stream);
|
||||
implementOnEndedHandling(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,7 +81,7 @@ LocalStream.prototype.setMute = function (mute)
|
|||
} else {
|
||||
if (mute) {
|
||||
APP.xmpp.removeStream(this.stream);
|
||||
this.stream.stop();
|
||||
APP.RTC.stopMediaStream(this.stream);
|
||||
this.eventEmitter.emit(eventType, true);
|
||||
} else {
|
||||
var self = this;
|
||||
|
@ -106,7 +109,7 @@ LocalStream.prototype.isMuted = function () {
|
|||
if (this.isAudioStream()) {
|
||||
tracks = this.stream.getAudioTracks();
|
||||
} else {
|
||||
if (this.stream.ended)
|
||||
if (!this.isActive())
|
||||
return true;
|
||||
tracks = this.stream.getVideoTracks();
|
||||
}
|
||||
|
@ -121,4 +124,17 @@ LocalStream.prototype.getId = function () {
|
|||
return this.stream.getTracks()[0].id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the MediaStream is avtive/not ended.
|
||||
* When there is no check for active we don't have information and so
|
||||
* will return that stream is active (in case of FF).
|
||||
* @returns {boolean} whether MediaStream is active.
|
||||
*/
|
||||
LocalStream.prototype.isActive = function () {
|
||||
if((typeof this.stream.active !== "undefined"))
|
||||
return this.stream.active;
|
||||
else
|
||||
return true;
|
||||
};
|
||||
|
||||
module.exports = LocalStream;
|
||||
|
|
|
@ -53,7 +53,6 @@ var RTC = {
|
|||
audio: true,
|
||||
video: true
|
||||
},
|
||||
localStreams: [],
|
||||
remoteStreams: {},
|
||||
localAudio: null,
|
||||
localVideo: null,
|
||||
|
@ -74,10 +73,6 @@ var RTC = {
|
|||
|
||||
var localStream =
|
||||
new LocalStream(stream, type, eventEmitter, videoType, isGUMStream);
|
||||
//in firefox we have only one stream object
|
||||
if(this.localStreams.length === 0 ||
|
||||
this.localStreams[0].getOriginalStream() != stream)
|
||||
this.localStreams.push(localStream);
|
||||
if(isMuted === true)
|
||||
localStream.setMute(true);
|
||||
|
||||
|
@ -93,14 +88,6 @@ var RTC = {
|
|||
eventEmitter.emit(eventType, localStream, isMuted);
|
||||
return localStream;
|
||||
},
|
||||
removeLocalStream: function (stream) {
|
||||
for(var i = 0; i < this.localStreams.length; i++) {
|
||||
if(this.localStreams[i].getOriginalStream() === stream) {
|
||||
delete this.localStreams[i];
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
createRemoteStream: function (data, ssrc) {
|
||||
var jid = data.peerjid || APP.xmpp.myJid();
|
||||
|
||||
|
@ -214,16 +201,6 @@ var RTC = {
|
|||
}
|
||||
return false;
|
||||
},
|
||||
switchVideoStreams: function (newStream) {
|
||||
this.localVideo.stream = newStream;
|
||||
|
||||
this.localStreams = [];
|
||||
|
||||
//in firefox we have only one stream object
|
||||
if (this.localAudio.getOriginalStream() != newStream)
|
||||
this.localStreams.push(this.localAudio);
|
||||
this.localStreams.push(this.localVideo);
|
||||
},
|
||||
changeLocalVideo: function (stream, isUsingScreenStream, callback) {
|
||||
var oldStream = this.localVideo.getOriginalStream();
|
||||
var type = (isUsingScreenStream ? "screen" : "camera");
|
||||
|
@ -244,10 +221,8 @@ var RTC = {
|
|||
var videoStream = this.rtcUtils.createStream(stream, true);
|
||||
this.localVideo =
|
||||
this.createLocalStream(videoStream, "video", true, type);
|
||||
// Stop the stream to trigger onended event for old stream
|
||||
oldStream.stop();
|
||||
|
||||
this.switchVideoStreams(videoStream);
|
||||
// Stop the stream
|
||||
this.stopMediaStream(oldStream);
|
||||
|
||||
APP.xmpp.switchStreams(videoStream, oldStream,localCallback);
|
||||
},
|
||||
|
@ -255,8 +230,8 @@ var RTC = {
|
|||
var oldStream = this.localAudio.getOriginalStream();
|
||||
var newStream = this.rtcUtils.createStream(stream);
|
||||
this.localAudio = this.createLocalStream(newStream, "audio", true);
|
||||
// Stop the stream to trigger onended event for old stream
|
||||
oldStream.stop();
|
||||
// Stop the stream
|
||||
this.stopMediaStream(oldStream);
|
||||
APP.xmpp.switchStreams(newStream, oldStream, callback, true);
|
||||
},
|
||||
isVideoMuted: function (jid) {
|
||||
|
@ -298,6 +273,60 @@ var RTC = {
|
|||
if(devices.video === true || devices.video === false)
|
||||
this.devices.video = devices.video;
|
||||
eventEmitter.emit(RTCEvents.AVAILABLE_DEVICES_CHANGED, this.devices);
|
||||
},
|
||||
/**
|
||||
* A method to handle stopping of the stream.
|
||||
* One point to handle the differences in various implementations.
|
||||
* @param mediaStream MediaStream object to stop.
|
||||
*/
|
||||
stopMediaStream: function (mediaStream) {
|
||||
mediaStream.getTracks().forEach(function (track) {
|
||||
// stop() not supported with IE
|
||||
if (track.stop) {
|
||||
track.stop();
|
||||
}
|
||||
});
|
||||
|
||||
// leave stop for implementation still using it
|
||||
if (mediaStream.stop) {
|
||||
mediaStream.stop();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Adds onended/inactive handler to a MediaStream.
|
||||
* @param mediaStream a MediaStream to attach onended/inactive handler
|
||||
* @param handler the handler
|
||||
*/
|
||||
addMediaStreamInactiveHandler: function (mediaStream, handler) {
|
||||
if (mediaStream.addEventListener) {
|
||||
// chrome
|
||||
if(typeof mediaStream.active !== "undefined")
|
||||
mediaStream.inactive = handler;
|
||||
else
|
||||
mediaStream.onended = handler;
|
||||
} else {
|
||||
// themasys
|
||||
mediaStream.attachEvent('ended', function () {
|
||||
handler(mediaStream);
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Removes onended/inactive handler.
|
||||
* @param mediaStream the MediaStream to remove the handler from.
|
||||
* @param handler the handler to remove.
|
||||
*/
|
||||
removeMediaStreamInactiveHandler: function (mediaStream, handler) {
|
||||
if (mediaStream.removeEventListener) {
|
||||
// chrome
|
||||
if(typeof mediaStream.active !== "undefined")
|
||||
mediaStream.inactive = null;
|
||||
else
|
||||
mediaStream.onended = null;
|
||||
} else {
|
||||
// themasys
|
||||
mediaStream.detachEvent('ended', handler);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -315,7 +315,11 @@ function registerListeners() {
|
|||
});
|
||||
APP.xmpp.addListener(XMPPEvents.PROMPT_FOR_LOGIN, function (callback) {
|
||||
// FIXME: re-use LoginDialog which supports retries
|
||||
UI.showLoginPopup(callback);
|
||||
if (config.token) {
|
||||
messageHandler.showError("dialog.error", "dialog.tokenAuthFailed");
|
||||
} else {
|
||||
UI.showLoginPopup(callback);
|
||||
}
|
||||
});
|
||||
|
||||
APP.xmpp.addListener(XMPPEvents.FOCUS_DISCONNECTED, function (focusComponent, retrySec) {
|
||||
|
|
|
@ -208,13 +208,14 @@ LocalVideo.prototype.changeVideo = function (stream, isMuted) {
|
|||
APP.RTC.attachMediaStream(localVideoSelector, stream.getOriginalStream());
|
||||
|
||||
// Add stream ended handler
|
||||
stream.getOriginalStream().onended = function () {
|
||||
APP.RTC.addMediaStreamInactiveHandler(
|
||||
stream.getOriginalStream(), function () {
|
||||
// We have to re-select after attach when Temasys plugin is used,
|
||||
// because <video> element is replaced with <object>
|
||||
localVideo = $('#' + localVideo.id)[0];
|
||||
localVideoContainer.removeChild(localVideo);
|
||||
self.VideoLayout.updateRemovedVideo(APP.xmpp.myResource());
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
LocalVideo.prototype.joined = function (jid) {
|
||||
|
|
|
@ -229,12 +229,12 @@ RemoteVideo.prototype.addRemoteStreamElement = function (stream) {
|
|||
APP.RTC.attachMediaStream(sel, stream);
|
||||
}
|
||||
|
||||
stream.onended = function () {
|
||||
console.log('stream ended', this);
|
||||
APP.RTC.addMediaStreamInactiveHandler(
|
||||
stream, function () {
|
||||
console.log('stream ended', this);
|
||||
|
||||
self.removeRemoteStreamElement(stream, isVideo, newElementId);
|
||||
|
||||
};
|
||||
self.removeRemoteStreamElement(stream, isVideo, newElementId);
|
||||
});
|
||||
|
||||
// Add click handler.
|
||||
var onClickHandler = function (event) {
|
||||
|
|
|
@ -54,12 +54,8 @@ function onEndedHandler(stream) {
|
|||
if (!switchInProgress && isUsingScreenStream) {
|
||||
APP.desktopsharing.toggleScreenSharing();
|
||||
}
|
||||
//FIXME: to be verified
|
||||
if (stream.removeEventListener) {
|
||||
stream.removeEventListener('ended', onEndedHandler);
|
||||
} else {
|
||||
stream.detachEvent('ended', onEndedHandler);
|
||||
}
|
||||
|
||||
APP.RTC.removeMediaStreamInactiveHandler(stream, onEndedHandler);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
@ -113,16 +109,8 @@ module.exports = {
|
|||
isUsingScreenStream = true;
|
||||
// Hook 'ended' event to restore camera
|
||||
// when screen stream stops
|
||||
//FIXME: to be verified
|
||||
if (stream.addEventListener) {
|
||||
stream.addEventListener('ended', function () {
|
||||
onEndedHandler(stream);
|
||||
});
|
||||
} else {
|
||||
stream.attachEvent('ended', function () {
|
||||
onEndedHandler(stream);
|
||||
});
|
||||
}
|
||||
APP.RTC.addMediaStreamInactiveHandler(
|
||||
stream, onEndedHandler);
|
||||
newStreamCreated(stream);
|
||||
},
|
||||
getDesktopStreamFailed);
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
/**
|
||||
* Generates random hex number within the range [min, max]
|
||||
* @param max the maximum value for the generated number
|
||||
* @param min the minimum value for the generated number
|
||||
* @returns random hex number
|
||||
*/
|
||||
function rangeRandomHex(min, max)
|
||||
{
|
||||
return Math.floor(Math.random() * (max - min) + min).toString(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exported interface.
|
||||
*/
|
||||
var RandomUtil = {
|
||||
/**
|
||||
* Generates hex number with length 4
|
||||
*/
|
||||
random4digitsHex: function() {
|
||||
return rangeRandomHex(4096, 65535);
|
||||
},
|
||||
/**
|
||||
* Generates hex number with length 8
|
||||
*/
|
||||
random8digitsHex: function() {
|
||||
return rangeRandomHex(268435456, 4294967295);
|
||||
},
|
||||
/**
|
||||
* Generates hex number with length 12
|
||||
*/
|
||||
random12digitsHex: function() {
|
||||
return rangeRandomHex(17592186044416, 281474976710655);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = RandomUtil;
|
|
@ -20,7 +20,6 @@ function JingleSessionPC(me, sid, connection, service, eventEmitter) {
|
|||
this.state = null;
|
||||
this.localSDP = null;
|
||||
this.remoteSDP = null;
|
||||
this.relayedStreams = [];
|
||||
this.pc_constraints = null;
|
||||
|
||||
this.usetrickle = true;
|
||||
|
@ -169,13 +168,12 @@ JingleSessionPC.prototype.doInitialize = function () {
|
|||
this.peerconnection.onnegotiationneeded = function (event) {
|
||||
self.eventEmitter.emit(XMPPEvents.PEERCONNECTION_READY, self);
|
||||
};
|
||||
// add any local and relayed stream
|
||||
APP.RTC.localStreams.forEach(function(stream) {
|
||||
self.peerconnection.addStream(stream.getOriginalStream());
|
||||
});
|
||||
this.relayedStreams.forEach(function(stream) {
|
||||
self.peerconnection.addStream(stream);
|
||||
});
|
||||
if (APP.RTC.localAudio) {
|
||||
self.peerconnection.addStream(APP.RTC.localAudio.getOriginalStream());
|
||||
}
|
||||
if (APP.RTC.localVideo) {
|
||||
self.peerconnection.addStream(APP.RTC.localVideo.getOriginalStream());
|
||||
}
|
||||
};
|
||||
|
||||
function onIceConnectionStateChange(sid, session) {
|
||||
|
@ -1173,20 +1171,56 @@ JingleSessionPC.prototype._modifySources = function (successCallback, queueCallb
|
|||
* @param newStream new stream that will be used as video of this session.
|
||||
* @param oldStream old video stream of this session.
|
||||
* @param successCallback callback executed after successful stream switch.
|
||||
* @param isAudio whether the streams are audio (if true) or video (if false).
|
||||
*/
|
||||
JingleSessionPC.prototype.switchStreams = function (newStream, oldStream, successCallback) {
|
||||
|
||||
JingleSessionPC.prototype.switchStreams =
|
||||
function (newStream, oldStream, successCallback, isAudio) {
|
||||
var self = this;
|
||||
|
||||
var sender, newTrack;
|
||||
var senderKind = isAudio ? 'audio' : 'video';
|
||||
// Remember SDP to figure out added/removed SSRCs
|
||||
var oldSdp = null;
|
||||
|
||||
if (self.peerconnection) {
|
||||
if (self.peerconnection.localDescription) {
|
||||
oldSdp = new SDP(self.peerconnection.localDescription.sdp);
|
||||
}
|
||||
self.peerconnection.removeStream(oldStream, true);
|
||||
if (newStream)
|
||||
self.peerconnection.addStream(newStream);
|
||||
if (RTCBrowserType.getBrowserType() ===
|
||||
RTCBrowserType.RTC_BROWSER_FIREFOX) {
|
||||
// On Firefox we don't replace MediaStreams as this messes up the
|
||||
// m-lines (which can't be removed in Plan Unified) and brings a lot
|
||||
// of complications. Instead, we use the RTPSender and replace just
|
||||
// the track.
|
||||
|
||||
// Find the right sender (for audio or video)
|
||||
self.peerconnection.peerconnection.getSenders().some(function (s) {
|
||||
if (s.track && s.track.kind === senderKind) {
|
||||
sender = s;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (sender) {
|
||||
// We assume that our streams have a single track, either audio
|
||||
// or video.
|
||||
newTrack = isAudio ? newStream.getAudioTracks()[0] :
|
||||
newStream.getVideoTracks()[0];
|
||||
sender.replaceTrack(newTrack)
|
||||
.then(function() {
|
||||
console.log("Replaced a track, isAudio=" + isAudio);
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.log("Failed to replace a track: " + err);
|
||||
});
|
||||
} else {
|
||||
console.log("Cannot switch tracks: no RTPSender.");
|
||||
}
|
||||
} else {
|
||||
self.peerconnection.removeStream(oldStream, true);
|
||||
if (newStream) {
|
||||
self.peerconnection.addStream(newStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Conference is not active
|
||||
|
@ -1430,6 +1464,7 @@ JingleSessionPC.prototype.setLocalDescription = function () {
|
|||
var self = this;
|
||||
var newssrcs = [];
|
||||
var session = transform.parse(this.peerconnection.localDescription.sdp);
|
||||
var i;
|
||||
session.media.forEach(function (media) {
|
||||
|
||||
if (media.ssrcs && media.ssrcs.length > 0) {
|
||||
|
@ -1458,10 +1493,28 @@ JingleSessionPC.prototype.setLocalDescription = function () {
|
|||
|
||||
// Bind us as local SSRCs owner
|
||||
if (newssrcs.length > 0) {
|
||||
for (var i = 1; i <= newssrcs.length; i ++) {
|
||||
var ssrc = newssrcs[i-1].ssrc;
|
||||
|
||||
if (config.advertiseSSRCsInPresence) {
|
||||
// This is only for backward compatibility with clients which
|
||||
// don't support getting sources from Jingle (i.e. jirecon).
|
||||
this.connection.emuc.clearPresenceMedia();
|
||||
}
|
||||
|
||||
for (i = 0; i < newssrcs.length; i++) {
|
||||
var ssrc = newssrcs[i].ssrc;
|
||||
var myJid = self.connection.emuc.myroomjid;
|
||||
self.ssrcOwners[ssrc] = myJid;
|
||||
|
||||
if (config.advertiseSSRCsInPresence) {
|
||||
// This is only for backward compatibility with clients which
|
||||
// don't support getting sources from Jingle (i.e. jirecon).
|
||||
this.connection.emuc.addMediaToPresence(
|
||||
i+1, newssrcs[i].type, ssrc, newssrcs[i].direction);
|
||||
}
|
||||
}
|
||||
|
||||
if (config.advertiseSSRCsInPresence) {
|
||||
this.connection.emuc.sendPresence();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -258,21 +258,7 @@ TraceablePeerConnection.prototype.addStream = function (stream) {
|
|||
TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) {
|
||||
this.trace('removeStream', stream.id);
|
||||
if(stopStreams) {
|
||||
stream.getAudioTracks().forEach(function (track) {
|
||||
// stop() not supported with IE
|
||||
if (track.stop) {
|
||||
track.stop();
|
||||
}
|
||||
});
|
||||
stream.getVideoTracks().forEach(function (track) {
|
||||
// stop() not supported with IE
|
||||
if (track.stop) {
|
||||
track.stop();
|
||||
}
|
||||
});
|
||||
if (stream.stop) {
|
||||
stream.stop();
|
||||
}
|
||||
RTC.stopMediaStream(stream);
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -529,6 +529,31 @@ module.exports = function(XMPP, eventEmitter) {
|
|||
.c('current').t(this.presMap['prezicurrent']).up().up();
|
||||
}
|
||||
|
||||
// This is only for backward compatibility with clients which
|
||||
// don't support getting sources from Jingle (i.e. jirecon).
|
||||
if (this.presMap['medians']) {
|
||||
pres.c('media', {xmlns: this.presMap['medians']});
|
||||
var sourceNumber = 0;
|
||||
Object.keys(this.presMap).forEach(function (key) {
|
||||
if (key.indexOf('source') >= 0) {
|
||||
sourceNumber++;
|
||||
}
|
||||
});
|
||||
if (sourceNumber > 0) {
|
||||
for (var i = 1; i <= sourceNumber / 3; i++) {
|
||||
pres.c('source',
|
||||
{
|
||||
type: this.presMap['source' + i + '_type'],
|
||||
ssrc: this.presMap['source' + i + '_ssrc'],
|
||||
direction: this.presMap['source' + i + '_direction']
|
||||
|| 'sendrecv'
|
||||
}
|
||||
).up();
|
||||
}
|
||||
}
|
||||
pres.up();
|
||||
}
|
||||
|
||||
if(this.presMap["startMuted"] !== undefined)
|
||||
{
|
||||
pres.c("startmuted", {audio: this.presMap["startMuted"].audio,
|
||||
|
@ -537,12 +562,36 @@ module.exports = function(XMPP, eventEmitter) {
|
|||
delete this.presMap["startMuted"];
|
||||
}
|
||||
|
||||
if (config.token) {
|
||||
pres.c('token', { xmlns: 'http://jitsi.org/jitmeet/auth-token'}).t(config.token).up();
|
||||
}
|
||||
|
||||
pres.up();
|
||||
this.connection.send(pres);
|
||||
},
|
||||
addDisplayNameToPresence: function (displayName) {
|
||||
this.presMap['displayName'] = displayName;
|
||||
},
|
||||
// This is only for backward compatibility with clients which
|
||||
// don't support getting sources from Jingle (i.e. jirecon).
|
||||
addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
|
||||
if (!this.presMap['medians'])
|
||||
this.presMap['medians'] = 'http://estos.de/ns/mjs';
|
||||
|
||||
this.presMap['source' + sourceNumber + '_type'] = mtype;
|
||||
this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
|
||||
this.presMap['source' + sourceNumber + '_direction'] = direction;
|
||||
},
|
||||
// This is only for backward compatibility with clients which
|
||||
// don't support getting sources from Jingle (i.e. jirecon).
|
||||
clearPresenceMedia: function () {
|
||||
var self = this;
|
||||
Object.keys(this.presMap).forEach(function (key) {
|
||||
if (key.indexOf('source') != -1) {
|
||||
delete self.presMap[key];
|
||||
}
|
||||
});
|
||||
},
|
||||
addDevicesToPresence: function (devices) {
|
||||
this.presMap['devices'] = devices;
|
||||
},
|
||||
|
|
|
@ -11,11 +11,22 @@ var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
|
|||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
var retry = require('retry');
|
||||
var RandomUtil = require("../util/RandomUtil");
|
||||
|
||||
var eventEmitter = new EventEmitter();
|
||||
var connection = null;
|
||||
var authenticatedUser = false;
|
||||
|
||||
/**
|
||||
* Utility method that generates user name based on random hex values.
|
||||
* Eg. 12345678-1234-1234-12345678
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateUserName() {
|
||||
return RandomUtil.random8digitsHex() + "-" + RandomUtil.random4digitsHex() + "-" +
|
||||
RandomUtil.random4digitsHex() + "-" + RandomUtil.random8digitsHex();
|
||||
}
|
||||
|
||||
function connect(jid, password) {
|
||||
|
||||
var faultTolerantConnect = retry.operation({
|
||||
|
@ -296,12 +307,21 @@ var XMPP = {
|
|||
configDomain = config.hosts.domain;
|
||||
}
|
||||
var jid = configDomain || window.location.hostname;
|
||||
connect(jid, null);
|
||||
var password = null;
|
||||
if (config.token) {
|
||||
password = config.token;
|
||||
if (config.id) {
|
||||
jid = config.id + "@" + jid;
|
||||
} else {
|
||||
jid = generateUserName() + "@" + jid;
|
||||
}
|
||||
}
|
||||
connect(jid, password);
|
||||
},
|
||||
createConnection: function () {
|
||||
var bosh = config.bosh || '/http-bind';
|
||||
|
||||
return new Strophe.Connection(bosh);
|
||||
// adds the room name used to the bosh connection
|
||||
return new Strophe.Connection(bosh + '?ROOM=' + APP.UI.getRoomNode());
|
||||
},
|
||||
getStatusString: function (status) {
|
||||
return Strophe.getStatusString(status);
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
-- Token authentication
|
||||
-- Copyright (C) 2015 Atlassian
|
||||
|
||||
local usermanager = require "core.usermanager";
|
||||
local new_sasl = require "util.sasl".new;
|
||||
|
||||
local log = module._log;
|
||||
local host = module.host;
|
||||
|
||||
local token_util = module:require "token/util";
|
||||
|
||||
-- define auth provider
|
||||
local provider = {};
|
||||
|
||||
--do
|
||||
-- local list;
|
||||
-- for mechanism in pairs(new_sasl(module.host):mechanisms()) do
|
||||
-- list = (not(list) and mechanism) or (list..", "..mechanism);
|
||||
-- end
|
||||
-- if not list then
|
||||
-- module:log("error", "No mechanisms");
|
||||
-- else
|
||||
-- module:log("error", "Mechanisms: %s", list);
|
||||
-- end
|
||||
--end
|
||||
|
||||
|
||||
local appId = module:get_option_string("app_id");
|
||||
local appSecret = module:get_option_string("app_secret");
|
||||
local tokenLifetime = module:get_option_number("token_lifetime");
|
||||
|
||||
function provider.test_password(username, password)
|
||||
local result, msg = token_util.verify_password(password, appId, appSecret, tokenLifetime);
|
||||
if result == true then
|
||||
return true;
|
||||
else
|
||||
log("error", "Token auth failed for user %s, reason: %s",username, msg);
|
||||
return nil, msg;
|
||||
end
|
||||
end
|
||||
|
||||
function provider.get_password(username)
|
||||
return nil;
|
||||
end
|
||||
|
||||
function provider.set_password(username, password)
|
||||
return nil, "Set password not supported";
|
||||
end
|
||||
|
||||
function provider.user_exists(username)
|
||||
return nil;
|
||||
end
|
||||
|
||||
function provider.users()
|
||||
return next, hosts[module.host].sessions, nil;
|
||||
end
|
||||
|
||||
function provider.create_user(username, password)
|
||||
return nil;
|
||||
end
|
||||
|
||||
function provider.delete_user(username)
|
||||
return nil;
|
||||
end
|
||||
|
||||
function provider.get_sasl_handler()
|
||||
local testpass_authentication_profile = {
|
||||
plain_test = function(sasl, username, password, realm)
|
||||
return usermanager.test_password(username, realm, password), true;
|
||||
end
|
||||
};
|
||||
return new_sasl(host, testpass_authentication_profile);
|
||||
end
|
||||
|
||||
module:provides("auth", provider);
|
|
@ -0,0 +1,52 @@
|
|||
-- Token authentication
|
||||
-- Copyright (C) 2015 Atlassian
|
||||
|
||||
local log = module._log;
|
||||
local host = module.host;
|
||||
local st = require "util.stanza";
|
||||
local token_util = module:require("token/util");
|
||||
local is_admin = require "core.usermanager".is_admin;
|
||||
|
||||
|
||||
local parentHostName = string.gmatch(tostring(host), "%w+.(%w.+)")();
|
||||
if parentHostName == nil then
|
||||
log("error", "Failed to start - unable to get parent hostname");
|
||||
return;
|
||||
end
|
||||
|
||||
local parentCtx = module:context(parentHostName);
|
||||
if parentCtx == nil then
|
||||
log("error", "Failed to start - unable to get parent context for host: %s", tostring(parentHostName));
|
||||
return;
|
||||
end
|
||||
|
||||
local appId = parentCtx:get_option_string("app_id");
|
||||
local appSecret = parentCtx:get_option_string("app_secret");
|
||||
local tokenLifetime = parentCtx:get_option_string("token_lifetime");
|
||||
|
||||
log("debug", "%s - starting MUC token verifier app_id: %s app_secret: %s token-lifetime: %s",
|
||||
tostring(host), tostring(appId), tostring(appSecret), tostring(tokenLifetime));
|
||||
|
||||
local function handle_pre_create(event)
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local token = stanza:get_child("token", "http://jitsi.org/jitmeet/auth-token");
|
||||
-- token not required for admin users
|
||||
local user_jid = stanza.attr.from;
|
||||
if is_admin(user_jid) then
|
||||
log("debug", "Token not required from admin user: %s", user_jid);
|
||||
return nil;
|
||||
end
|
||||
log("debug", "Will verify token for user: %s ", user_jid);
|
||||
if token ~= nil then
|
||||
token = token[1];
|
||||
end
|
||||
local result, msg = token_util.verify_password(token, appId, appSecret, tokenLifetime);
|
||||
if result ~= true then
|
||||
log("debug", "Token verification failed: %s", msg);
|
||||
origin.send(st.error_reply(stanza, "cancel", "not-allowed", msg));
|
||||
return true;
|
||||
end
|
||||
end
|
||||
|
||||
module:hook("muc-room-pre-create", handle_pre_create);
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
-- Token authentication
|
||||
-- Copyright (C) 2015 Atlassian
|
||||
|
||||
local hashes = require "util.hashes";
|
||||
|
||||
local _M = {};
|
||||
|
||||
local function calc_hash(password, appId, appSecret)
|
||||
local hash, room, ts = string.match(password, "(%w+)_(%w+)_(%d+)");
|
||||
if hash ~= nil and room ~= nil and ts ~= nil then
|
||||
log("debug", "Hash: '%s' room: '%s', ts: '%s'", hash, room, ts);
|
||||
local toHash = room .. ts .. appId .. appSecret;
|
||||
log("debug", "to be hashed: '%s'", toHash);
|
||||
local hash = hashes.sha256(toHash, true);
|
||||
log("debug", "hash: '%s'", hash);
|
||||
return hash;
|
||||
else
|
||||
log("error", "Invalid password format: '%s'", password);
|
||||
return nil;
|
||||
end
|
||||
end
|
||||
|
||||
local function extract_hash(password)
|
||||
local hash, room, ts = string.match(password, "(%w+)_(%w+)_(%d+)");
|
||||
return hash;
|
||||
end
|
||||
|
||||
local function extract_ts(password)
|
||||
local hash, room, ts = string.match(password, "(%w+)_(%w+)_(%d+)");
|
||||
return ts;
|
||||
end
|
||||
|
||||
local function get_utc_timestamp()
|
||||
return os.time(os.date("!*t")) * 1000;
|
||||
end
|
||||
|
||||
local function verify_timestamp(ts, tokenLifetime)
|
||||
return get_utc_timestamp() - ts <= tokenLifetime;
|
||||
end
|
||||
|
||||
local function verify_password_impl(password, appId, appSecret, tokenLifetime)
|
||||
|
||||
if password == nil then
|
||||
return nil, "password is missing";
|
||||
end
|
||||
|
||||
if tokenLifetime == nil then
|
||||
tokenLifetime = 24 * 60 * 60 * 1000;
|
||||
end
|
||||
|
||||
local ts = extract_ts(password);
|
||||
if ts == nil then
|
||||
return nil, "timestamp not found in the password";
|
||||
end
|
||||
local os_ts = get_utc_timestamp();
|
||||
log("debug", "System TS: '%s' user TS: %s", tostring(os_ts), tostring(ts));
|
||||
local isValid = verify_timestamp(ts, tokenLifetime);
|
||||
if not isValid then
|
||||
return nil, "token expired";
|
||||
end
|
||||
|
||||
local realHash = calc_hash(password, appId, appSecret);
|
||||
local givenhash = extract_hash(password);
|
||||
log("debug", "Compare '%s' to '%s'", tostring(realHash), tostring(givenhash));
|
||||
if realHash == givenhash then
|
||||
return true;
|
||||
else
|
||||
return nil, "invalid hash";
|
||||
end
|
||||
end
|
||||
|
||||
function _M.verify_password(password, appId, appSecret, tokenLifetime)
|
||||
return verify_password_impl(password, appId, appSecret, tokenLifetime);
|
||||
end
|
||||
|
||||
return _M;
|
Loading…
Reference in New Issue