Adds functionality for authentication with external system.

This commit is contained in:
paweldomas 2014-12-16 14:54:13 +01:00
parent cc38c2641b
commit f4004656a3
11 changed files with 256 additions and 59 deletions

92
app.js
View File

@ -2,11 +2,12 @@
/* application specific logic */ /* application specific logic */
var connection = null; var connection = null;
var authenticatedUser = false; var authenticatedUser = false;
var authenticationWindow = null;
var activecall = null; var activecall = null;
var RTC = null; var RTC = null;
var nickname = null; var nickname = null;
var sharedKey = ''; var sharedKey = '';
var focusJid = null; var focusMucJid = null;
var roomUrl = null; var roomUrl = null;
var roomName = null; var roomName = null;
var ssrc2jid = {}; var ssrc2jid = {};
@ -164,6 +165,8 @@ function connect(jid, password) {
} }
document.getElementById('connect').disabled = true; document.getElementById('connect').disabled = true;
console.info("My Jabber ID: " + connection.jid);
if(password) if(password)
authenticatedUser = true; authenticatedUser = true;
maybeDoJoin(); maybeDoJoin();
@ -755,6 +758,10 @@ $(document).bind('joined.muc', function (event, jid, info) {
// Once we've joined the muc show the toolbar // Once we've joined the muc show the toolbar
ToolbarToggler.showToolbar(); ToolbarToggler.showToolbar();
// Show authenticate button if needed
Toolbar.showAuthenticateButton(
Moderator.isExternalAuthEnabled() && !Moderator.isModerator());
var displayName = !config.displayJids var displayName = !config.displayJids
? info.displayName : Strophe.getResourceFromJid(jid); ? info.displayName : Strophe.getResourceFromJid(jid);
@ -767,10 +774,8 @@ $(document).bind('entered.muc', function (event, jid, info, pres) {
console.log('entered', jid, info); console.log('entered', jid, info);
if (info.isFocus) if (info.isFocus)
{ {
focusJid = jid; focusMucJid = jid;
console.info("Ignore focus: " + jid +", real JID: " + info.jid); console.info("Ignore focus: " + jid +", real JID: " + info.jid);
// We don't want this notification for the focus.
// messageHandler.notify('Focus', 'connected', 'connected');
return; return;
} }
@ -944,6 +949,44 @@ $(document).bind('kicked.muc', function (event, jid) {
} }
}); });
$(document).bind('role.changed.muc', function (event, jid, member, pres) {
console.info("Role changed for " + jid + ", new role: " + member.role);
VideoLayout.showModeratorIndicator();
if (member.role === 'moderator') {
var displayName = member.displayName;
if (!displayName) {
displayName = 'Somebody';
}
messageHandler.notify(
displayName,
'connected',
'Moderator rights granted to ' + displayName + '!');
}
}
);
$(document).bind('local.role.changed.muc', function (event, jid, info, pres) {
console.info("My role changed, new role: " + info.role);
var isModerator = Moderator.isModerator();
VideoLayout.showModeratorIndicator();
Toolbar.showAuthenticateButton(
Moderator.isExternalAuthEnabled() && !isModerator);
if (isModerator) {
if (authenticationWindow) {
authenticationWindow.close();
authenticationWindow = null;
}
messageHandler.notify(
'Me', 'connected', 'Moderator rights granted !');
}
}
);
$(document).bind('passwordrequired.muc', function (event, jid) { $(document).bind('passwordrequired.muc', function (event, jid) {
console.log('on password required', jid); console.log('on password required', jid);
@ -996,6 +1039,44 @@ $(document).bind('passwordrequired.main', function (event) {
); );
}); });
$(document).bind('auth_required.moderator', function () {
// extract room name from 'room@muc.server.net'
var room = roomName.substr(0, roomName.indexOf('@'));
messageHandler.openDialog(
'Stop',
'Authentication is required to create room:<br/>' + room,
true,
{
Authenticate: 'authNow',
Close: 'close'
},
function (onSubmitEvent, submitValue) {
console.info('On submit: ' + submitValue, submitValue);
if (submitValue === 'authNow') {
authenticateClicked();
} else {
Toolbar.showAuthenticateButton(true);
}
}
);
});
function authenticateClicked() {
// Get authentication URL
Moderator.getAuthUrl(function (url) {
// Open popup with authentication URL
authenticationWindow = messageHandler.openCenteredPopup(
url, 500, 400,
function () {
// On popup closed - retry room allocation
Moderator.allocateConferenceFocus(
roomName, doJoinAfterFocus);
authenticationWindow = null;
});
});
};
/** /**
* Checks if video identified by given src is desktop stream. * Checks if video identified by given src is desktop stream.
* @param videoSrc eg. * @param videoSrc eg.
@ -1474,6 +1555,9 @@ $(window).bind('beforeunload', function () {
}); });
function disposeConference(onUnload) { function disposeConference(onUnload) {
Toolbar.showAuthenticateButton(false);
var handler = getConferenceHandler(); var handler = getConferenceHandler();
if (handler && handler.peerconnection) { if (handler && handler.peerconnection) {
// FIXME: probably removing streams is not required and close() should // FIXME: probably removing streams is not required and close() should

View File

@ -42,6 +42,9 @@
.icon-recEnable:before { .icon-recEnable:before {
content: "\e614"; content: "\e614";
} }
.icon-authenticate:before {
content: "\e1ae";
}
.icon-kick1:before { .icon-kick1:before {
content: "\e60f"; content: "\e60f";
} }

View File

@ -184,6 +184,12 @@
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" content="Start / stop camera" onclick='toggleVideo();'> <a class="button" data-container="body" data-toggle="popover" data-placement="bottom" shortcut="toggleVideoPopover" content="Start / stop camera" onclick='toggleVideo();'>
<i id="video" class="icon-camera"></i> <i id="video" class="icon-camera"></i>
</a> </a>
<span id="authentication" style="display: none">
<div class="header_button_separator"></div>
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Authenticate" onclick='authenticateClicked();'>
<i id="authButton" class="icon-avatar"></i>
</a>
</span>
<span id="recording" style="display: none"> <span id="recording" style="display: none">
<div class="header_button_separator"></div> <div class="header_button_separator"></div>
<a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Record" onclick='toggleRecording();'> <a class="button" data-container="body" data-toggle="popover" data-placement="bottom" content="Record" onclick='toggleRecording();'>

View File

@ -80,6 +80,32 @@ var messageHandler = (function(my) {
myPrompt.on('impromptu:statechanged', stateChangedFunction); myPrompt.on('impromptu:statechanged', stateChangedFunction);
}; };
/**
* Opens new popup window for given <tt>url</tt> centered over current
* window.
*
* @param url the URL to be displayed in the popup window
* @param w the width of the popup window
* @param h the height of the popup window
* @param onPopupClosed optional callback function called when popup window
* has been closed.
*/
my.openCenteredPopup = function (url, w, h, onPopupClosed) {
var l = window.screenX + (window.innerWidth / 2) - (w / 2);
var t = window.screenY + (window.innerHeight / 2) - (h / 2);
var popup = window.open(
url, '_blank',
'top=' + t + ', left=' + l + ', width=' + w + ', height=' + h + '');
if (onPopupClosed) {
var pollTimer = window.setInterval(function () {
if (popup.closed !== false) {
window.clearInterval(pollTimer);
onPopupClosed();
}
}, 200);
}
};
/** /**
* Shows a dialog prompting the user to send an error report. * Shows a dialog prompting the user to send an error report.
* *

View File

@ -1,4 +1,4 @@
/* global $, $iq, config, connection, focusJid, forceMuted, messageHandler, /* global $, $iq, config, connection, focusMucJid, forceMuted, messageHandler,
setAudioMuted, Strophe, toggleAudio */ setAudioMuted, Strophe, toggleAudio */
/** /**
* Moderate connection plugin. * Moderate connection plugin.
@ -17,7 +17,7 @@ Strophe.addConnectionPlugin('moderate', {
}, },
setMute: function (jid, mute) { setMute: function (jid, mute) {
console.info("set mute", mute); console.info("set mute", mute);
var iqToFocus = $iq({to: focusJid, type: 'set'}) var iqToFocus = $iq({to: focusMucJid, type: 'set'})
.c('mute', { .c('mute', {
xmlns: 'http://jitsi.org/jitmeet/audio', xmlns: 'http://jitsi.org/jitmeet/audio',
jid: jid jid: jid
@ -40,7 +40,7 @@ Strophe.addConnectionPlugin('moderate', {
}, },
onMute: function (iq) { onMute: function (iq) {
var from = iq.getAttribute('from'); var from = iq.getAttribute('from');
if (from !== focusJid) { if (from !== focusMucJid) {
console.warn("Ignored mute from non focus peer"); console.warn("Ignored mute from non focus peer");
return false; return false;
} }

View File

@ -9,11 +9,21 @@ var Moderator = (function (my) {
var focusUserJid; var focusUserJid;
var getNextTimeout = Util.createExpBackoffTimer(1000); var getNextTimeout = Util.createExpBackoffTimer(1000);
var getNextErrorTimeout = Util.createExpBackoffTimer(1000); var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
// External authentication stuff
var externalAuthEnabled = false;
my.isModerator = function () { my.isModerator = function () {
return connection.emuc.isModerator(); return connection.emuc.isModerator();
}; };
my.isPeerModerator = function (peerJid) {
return connection.emuc.getMemberRole(peerJid) === 'moderator';
};
my.isExternalAuthEnabled = function () {
return externalAuthEnabled;
};
my.onModeratorStatusChanged = function (isModerator) { my.onModeratorStatusChanged = function (isModerator) {
Toolbar.showSipCallButton(isModerator); Toolbar.showSipCallButton(isModerator);
@ -27,25 +37,12 @@ var Moderator = (function (my) {
if (isModerator && config.etherpad_base) { if (isModerator && config.etherpad_base) {
Etherpad.init(); Etherpad.init();
} }
$(document).trigger('local.role.moderator', [isModerator]);
}; };
my.init = function () { my.init = function () {
$(document).bind(
'role.changed.muc',
function (event, jid, info, pres) {
console.info(
"Role changed for " + jid + ", new role: " + info.role);
VideoLayout.showModeratorIndicator();
}
);
$(document).bind( $(document).bind(
'local.role.changed.muc', 'local.role.changed.muc',
function (event, jid, info, pres) { function (event, jid, info, pres) {
console.info("My role changed, new role: " + info.role);
VideoLayout.showModeratorIndicator();
Moderator.onModeratorStatusChanged(Moderator.isModerator()); Moderator.onModeratorStatusChanged(Moderator.isModerator());
} }
); );
@ -141,6 +138,19 @@ var Moderator = (function (my) {
return elem; return elem;
}; };
my.parseConfigOptions = function (resultIq) {
Moderator.setFocusUserJid(
$(resultIq).find('conference').attr('focusjid'));
var extAuthParam
= $(resultIq).find('>conference>property[name=\'externalAuth\']');
if (extAuthParam.length) {
externalAuthEnabled = extAuthParam.attr('value') === 'true';
}
console.info("External authentication enabled: " + externalAuthEnabled);
};
// FIXME: we need to show the fact that we're waiting for the focus // FIXME: we need to show the fact that we're waiting for the focus
// to the user(or that focus is not available) // to the user(or that focus is not available)
my.allocateConferenceFocus = function (roomName, callback) { my.allocateConferenceFocus = function (roomName, callback) {
@ -155,8 +165,9 @@ var Moderator = (function (my) {
// Reset both timers // Reset both timers
getNextTimeout(true); getNextTimeout(true);
getNextErrorTimeout(true); getNextErrorTimeout(true);
Moderator.setFocusUserJid( // Setup config options
$(result).find('conference').attr('focusjid')); Moderator.parseConfigOptions(result);
// Exec callback
callback(); callback();
} else { } else {
var waitMs = getNextTimeout(); var waitMs = getNextTimeout();
@ -171,6 +182,12 @@ var Moderator = (function (my) {
} }
}, },
function (error) { function (error) {
// Not authorized to create new room
if ($(error).find('>error>not-authorized').length) {
console.warn("Unauthorized to start the conference");
$(document).trigger('auth_required.moderator');
return;
}
var waitMs = getNextErrorTimeout(); var waitMs = getNextErrorTimeout();
console.error("Focus error, retry after " + waitMs, error); console.error("Focus error, retry after " + waitMs, error);
// Show message // Show message
@ -188,6 +205,30 @@ var Moderator = (function (my) {
); );
}; };
my.getAuthUrl = function (urlCallback) {
var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
iq.c('auth-url', {
xmlns: 'http://jitsi.org/protocol/focus',
room: roomName
});
connection.sendIQ(
iq,
function (result) {
var url = $(result).find('auth-url').attr('url');
if (url) {
console.info("Got auth url: " + url);
urlCallback(url);
} else {
console.error(
"Failed to get auth url fro mthe focus", result);
}
},
function (error) {
console.error("Get auth url error", error);
}
);
};
return my; return my;
}(Moderator || {})); }(Moderator || {}));

6
muc.js
View File

@ -503,5 +503,11 @@ Strophe.addConnectionPlugin('emuc', {
}, },
isModerator: function() { isModerator: function() {
return this.role === 'moderator'; return this.role === 'moderator';
},
getMemberRole: function(peerJid) {
if (this.members[peerJid]) {
return this.members[peerJid].role;
}
return null;
} }
}); });

View File

@ -1,4 +1,4 @@
/* global $, $iq, config, connection, focusJid, messageHandler, Moderator, /* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
Toolbar, Util */ Toolbar, Util */
var Recording = (function (my) { var Recording = (function (my) {
var recordingToken = null; var recordingToken = null;
@ -13,7 +13,7 @@ var Recording = (function (my) {
// with the new recording state, according to the IQ. // with the new recording state, according to the IQ.
my.setRecording = function (state, token, callback) { my.setRecording = function (state, token, callback) {
var self = this; var self = this;
var elem = $iq({to: focusJid, type: 'set'}); var elem = $iq({to: focusMucJid, type: 'set'});
elem.c('conference', { elem.c('conference', {
xmlns: 'http://jitsi.org/protocol/colibri' xmlns: 'http://jitsi.org/protocol/colibri'
}); });

View File

@ -1,4 +1,4 @@
/* global ssrc2jid */ /* global focusMucJid, ssrc2jid */
/* jshint -W117 */ /* jshint -W117 */
/** /**
* Calculates packet lost percent using the number of lost packets and the * Calculates packet lost percent using the number of lost packets and the
@ -324,7 +324,7 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) {
}; };
StatsCollector.prototype.logStats = function () { StatsCollector.prototype.logStats = function () {
if (!focusJid) { if (!focusMucJid) {
return; return;
} }
@ -337,7 +337,7 @@ StatsCollector.prototype.logStats = function () {
content = Base64.encode(content); content = Base64.encode(content);
// XEP-0337-ish // XEP-0337-ish
var message = $msg({to: focusJid, type: 'normal'}); var message = $msg({to: focusMucJid, type: 'normal'});
message.c('log', { xmlns: 'urn:xmpp:eventlog', message.c('log', { xmlns: 'urn:xmpp:eventlog',
id: 'PeerConnectionStats'}); id: 'PeerConnectionStats'});
message.c('message').t(content).up(); message.c('message').t(content).up();

View File

@ -221,6 +221,19 @@ var Toolbar = (function (my) {
buttonClick("#lockIcon", "icon-security icon-security-locked"); buttonClick("#lockIcon", "icon-security icon-security-locked");
}; };
/**
* Shows or hides authentication button
* @param show <tt>true</tt> to show or <tt>false</tt> to hide
*/
my.showAuthenticateButton = function (show) {
if (show) {
$('#authentication').css({display: "inline"});
}
else {
$('#authentication').css({display: "none"});
}
};
// Shows or hides the 'recording' button. // Shows or hides the 'recording' button.
my.showRecordingButton = function (show) { my.showRecordingButton = function (show) {
if (!config.enableRecording) { if (!config.enableRecording) {

View File

@ -485,7 +485,8 @@ var VideoLayout = (function (my) {
if ($('#' + videoSpanId).length > 0) { if ($('#' + videoSpanId).length > 0) {
// If there's been a focus change, make sure we add focus related // If there's been a focus change, make sure we add focus related
// interface!! // interface!!
if (Moderator.isModerator() && $('#remote_popupmenu_' + resourceJid).length <= 0) { if (Moderator.isModerator() && !Moderator.isPeerModerator(peerJid)
&& $('#remote_popupmenu_' + resourceJid).length <= 0) {
addRemoteVideoMenu(peerJid, addRemoteVideoMenu(peerJid,
document.getElementById(videoSpanId)); document.getElementById(videoSpanId));
} }
@ -905,38 +906,42 @@ var VideoLayout = (function (my) {
{ {
createModeratorIndicatorElement(indicatorSpan[0]); createModeratorIndicatorElement(indicatorSpan[0]);
} }
} else {
Object.keys(connection.emuc.members).forEach(function (jid) {
var member = connection.emuc.members[jid];
if (member.role === 'moderator') {
var moderatorId
= 'participant_' + Strophe.getResourceFromJid(jid);
var moderatorContainer
= document.getElementById(moderatorId);
if (Strophe.getResourceFromJid(jid) === 'focus') {
// Skip server side focus
return;
}
if (!moderatorContainer) {
console.error("No moderator container for " + jid);
return;
}
var indicatorSpan
= $('#' + moderatorId + ' .focusindicator');
if (!indicatorSpan || indicatorSpan.length === 0) {
indicatorSpan = document.createElement('span');
indicatorSpan.className = 'focusindicator';
moderatorContainer.appendChild(indicatorSpan);
createModeratorIndicatorElement(indicatorSpan);
}
}
});
} }
Object.keys(connection.emuc.members).forEach(function (jid) {
var member = connection.emuc.members[jid];
if (member.role === 'moderator') {
var moderatorId
= 'participant_' + Strophe.getResourceFromJid(jid);
var moderatorContainer
= document.getElementById(moderatorId);
if (Strophe.getResourceFromJid(jid) === 'focus') {
// Skip server side focus
return;
}
if (!moderatorContainer) {
console.error("No moderator container for " + jid);
return;
}
var menuSpan = $('#' + moderatorId + '>span.remotevideomenu');
if (menuSpan.length) {
removeRemoteVideoMenu(moderatorId);
}
var indicatorSpan
= $('#' + moderatorId + ' .focusindicator');
if (!indicatorSpan || indicatorSpan.length === 0) {
indicatorSpan = document.createElement('span');
indicatorSpan.className = 'focusindicator';
moderatorContainer.appendChild(indicatorSpan);
createModeratorIndicatorElement(indicatorSpan);
}
}
});
}; };
/** /**
@ -1399,6 +1404,19 @@ var VideoLayout = (function (my) {
popupmenuElement.appendChild(paddingSpan); popupmenuElement.appendChild(paddingSpan);
} }
/**
* Removes remote video menu element from video element identified by
* given <tt>videoElementId</tt>.
*
* @param videoElementId the id of local or remote video element.
*/
function removeRemoteVideoMenu(videoElementId) {
var menuSpan = $('#' + videoElementId + '>span.remotevideomenu');
if (menuSpan.length) {
menuSpan.remove();
}
}
/** /**
* On contact list item clicked. * On contact list item clicked.
*/ */