Merge branch 'master' into multiple-tracks

This commit is contained in:
paweldomas 2015-11-04 15:09:51 -06:00
commit 73317c920a
24 changed files with 700 additions and 97 deletions

6
debian/control vendored
View File

@ -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

View File

@ -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

10
debian/jitsi-meet-tokens.config vendored Normal file
View File

@ -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
debian/jitsi-meet-tokens.docs vendored Normal file
View File

1
debian/jitsi-meet-tokens.install vendored Normal file
View File

@ -0,0 +1 @@
prosody-plugins/ /usr/share/jitsi-meet/

95
debian/jitsi-meet-tokens.postinst vendored Normal file
View File

@ -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

74
debian/jitsi-meet-tokens.postrm vendored Normal file
View File

@ -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

14
debian/jitsi-meet-tokens.templates vendored Normal file
View File

@ -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:

View File

@ -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"

View File

@ -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>!"

View File

@ -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;

View File

@ -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);
}
}
};

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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();
}
}
};

View File

@ -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 {

View File

@ -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;
},

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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;