Authorizing Creation/Entrance of a Conference in Jitsi

23 / May / 2016 by Siddharth Jain 6 comments

In this blog, we are going to discuss how we can provide authorization for creating or entering a room in Jitsi. While using Jitsi at organisation level there must be some security so that conferences are created or entered after an authorization.

About Jitsi :

Jitsi is a free & open source video conferencing application which allow user to create there room and other user can enter the room with hitting same url with same room name.

Jitsi consist of different module like

Lib-jitsi-meet: The Module works on mainly UI part of Jitsi.

Jicofo(Jitsi Conference Focus) : The Module Works on Mainly Room & Member Management in Jitsi

JVB(Jitsi-Videobridge: The Module Works on relaying the video in conferences.

Jicoco(Jitsi Common Component): The Module works on common component of Jitsi.

All Module Work together to make Jitsi working .

Why authorization is required in Jitsi:

We know that Jitsi is video conferencing application so whenever you create a room in Jitsi other user can only join this room if he/she has the url of that room. but it may also possible that un-authorized user can hit the same url and join in the same room, to overcome this kind of behaviour we are adding a key in the url to check whether the user accessing the url is an authorized user or user has an access to that room or not ,if user is not a authorized user then they will not be allowed to enter the room till they access url with an authorized key.

So lets start working on how to add Authorization while entering a room in Jitsi

Will be achieving this by prefixing a key to the room name & separating it with “=” .

For Example

Url Before Key: http://Domain/roomname

Url After Key: http://Domain/key=roomname

Jitsi-homePage

Where admin123 is key & TTND is roomName.

Step 1 :Let’s find out where the call is going in Jitsi when you enter room name in Jitsi homepage & click “Go” Button with help of which you can create/enter a room.

So when you hit “Go” Button the Call is internally going to one of the module of Jitsi that is “lib-jitsi-meet” and create a XMPP Packet(org.xmpp.packet.IQ) and send it to JICOFO.

In “lib-jitsi-meet” we have a JS File named as moderator.js at /lib-jitsi-meet/modules/xmpp/moderator.js which has a function called moderator which has one parameter named roomName,so this roomName is what we have given in text area on home page.

Now In this roomName, Key is attached to it so we can split on “=” and get the key from the room name. Once we get the key in moderator function we can add that key in property map which will be send to JICOFO in the form of XMPP Packet(org.xmpp.packet.IQ) & if Client Key is Not Passed it will prompt an alert saying “Please enter the Client Key & Room Name in the format <clientKey>=<roomName>”

 function Moderator(roomName, xmpp, emitter, settings, options) {

    if (roomName.split("=").length == 2) {
        var clientKey = roomName.split("=")[0];
        this.roomName = roomName;
        this.clientKey = clientKey;
        this.xmppService = xmpp;
        this.getNextTimeout = createExpBackoffTimer(1000);
        this.getNextErrorTimeout = createExpBackoffTimer(1000);
        // External authentication stuff
        this.externalAuthEnabled = false;
        this.settings = settings;
        this.options = options;

        // Sip gateway can be enabled by configuring Jigasi host in config.js or
        // it will be enabled automatically if focus detects the component through
        // service discovery.
        this.sipGatewayEnabled = this.options.connection.hosts & amp; & amp;
        this.options.connection.hosts.call_control !== undefined;

        this.eventEmitter = emitter;

        this.connection = this.xmppService.connection;
        this.focusUserJid;
        //FIXME:
        // Message listener that talks to POPUP window
        function listener(event) {
            if (event.data & amp; & amp; event.data.sessionId) {
                if (event.origin !== window.location.origin) {
                    logger.warn("Ignoring sessionId from different origin: " +
                        event.origin);
                    return;
                }
                settings.setSessionId(event.data.sessionId);
                // After popup is closed we will authenticate
            }
        }
        // Register
        if (window.addEventListener) {
            window.addEventListener("message", listener, false);
        } else {
            window.attachEvent("onmessage", listener);
        }
    } else {
        alert('Please enter the Client Key &amp; Room Name in the format <clientKey>=<roomName>');
    }
}

Step 2: Lets add a Key in property Map,

To add a key in Property Map we have to do the changes in createConferenceIq function in moderator.js , In the propertyMap we are adding a property named clientKey.

Moderator.prototype.createConferenceIq = function () {

// Generate create conference IQ

var elem = $iq({to: this.getFocusComponent(), type: 'set'});

// Session Id used for authentication

var sessionId = this.settings.getSessionId();

var machineUID = this.settings.getUserId();

logger.info(

"Session ID: " + sessionId + " machine UID: " + machineUID);

elem.c('conference', {

xmlns: 'http://jitsi.org/protocol/focus',

room: this.roomName,

'machine-uid': machineUID

});

if (sessionId) {

elem.attrs({ 'session-id': sessionId});

}

if (this.options.connection.hosts !== undefined

&& this.options.connection.hosts.bridge !== undefined) {

elem.c(

'property', {

name: 'bridge',

value: this.options.connection.hosts.bridge

}).up();

}

if (this.options.connection.enforcedBridge !== undefined) {

elem.c(

'property', {

name: 'enforcedBridge',

value: this.options.connection.enforcedBridge

}).up();

}

// Tell the focus we have Jigasi configured

if (this.options.connection.hosts !== undefined &&

this.options.connection.hosts.call_control !== undefined) {

elem.c(

'property', {

name: 'call_control',

value: this.options.connection.hosts.call_control

}).up();

}

if (this.options.conference.channelLastN !== undefined) {

elem.c(

'property', {

name: 'channelLastN',

value: this.options.conference.channelLastN

}).up();

}

if (this.options.conference.adaptiveLastN !== undefined) {

elem.c(

'property', {

name: 'adaptiveLastN',

value: this.options.conference.adaptiveLastN

}).up();

}

if (this.options.conference.disableAdaptiveSimulcast !== undefined ||

this.options.conference.disableSimulcast) {

// disableSimulcast implies disableAdaptiveSimulcast.

var value = this.options.conference.disableSimulcast ? true :

this.options.conference.disableAdaptiveSimulcast;

elem.c(

'property', {

name: 'disableAdaptiveSimulcast',

value: value

}).up();

}

// TODO: re-enable once rtx is stable

//if (this.options.conference.disableRtx !== undefined) {

elem.c(

'property', {

name: 'disableRtx',

//value: this.options.conference.disableRtx

value: true

}).up();

//}

if (this.options.conference.openSctp !== undefined) {

elem.c(

'property', {

name: 'openSctp',

value: this.options.conference.openSctp

}).up();

}

if (this.options.conference.startAudioMuted !== undefined)

{

elem.c(

'property', {

name: 'startAudioMuted',

value: this.options.conference.startAudioMuted

}).up();

}

if (this.options.conference.startVideoMuted !== undefined)

{

elem.c(

'property', {

name: 'startVideoMuted',

value: this.options.conference.startVideoMuted

}).up();

}

elem.c(

'property', {

name: 'simulcastMode',

value: 'rewriting'

}).up();

//elem.up();

elem.c(

'property', {

name: 'clientKey',

value: this.clientKey

}).up();

elem.up();

return elem;

};

After “lib-jitsi-meet” call goes to “JICOFO” module, In Jicofo we have a Function handleIQSet() in FocusComponent class where the call comes to Jicofo Which Handles an XMPP Packet(org.xmpp.packet.IQ) which represents a request & returns an XMPP Packet(org.xmpp.packet.IQ) which represents the response to the specified request.

Step 3: Let’s create a property file on the server which will be having Client Key & Client Name in this format ClientKey=ClientName .

I am creating the file at this location /root/Jitsi/Keys/ClientKeys.properties

# Client Key & Client Names

# Example

# client name = client key

admin123=admin

Step 4 : Let’s retrive the authorization Key from the XMPP Packet(org.xmpp.packet.IQ) which is coming as request in handleIQSet() function of focusComponent class of JICOFO & Restrict the user to create/enter room if it is not authorized key .

Now in handleIQset() when we get packet as instanceof ConferenceIq then from this ConferenceIq we can fetch the map & clientKey.

String clientKey = conferenceIq.getPropertiesMap().get(“clientKey”);

if Client key is empty then error is thrown saying “Client Name / Client Key is not Supplied. Please Retry”.

if Client key is not empty then will match client key with the keys in property file on the server.

if Client Key matches the Key in Property file then will allow him/her to create/join the room.

if Client Key is not matched he will not be allowed to create/join room and thrown a error saying “Client Name / Client Key pair mismatch. Please Retry” .

@Override
	public IQ handleIQSet(IQ iq) throws Exception {
		try {
			org.jivesoftware.smack.packet.IQ smackIq = IQUtils.convert(iq);
			String client = null;
			if (smackIq instanceof ConferenceIq) {

				ConferenceIq conferenceIq = (ConferenceIq) smackIq;
				String clientKey = conferenceIq.getPropertiesMap().get("clientKey");

				if (clientKey != null && !clientKey.isEmpty()) {

					Properties prop = new Properties();
					String propFileName ="/root/Jitsi/Keys/ClientKeys.properties";

					InputStream inputStream = new FileInputStream(propFileName);

					if (inputStream != null) {
						prop.load(inputStream);
					}

					if (prop.isEmpty()) {
						throw new FileNotFoundException(
								"property file '" + propFileName + "' does not contain any keys");
					}
					client = prop.getProperty(clientKey);

					if (client != null && !client.isEmpty()) {

						ClientAttributes clientAttributes = makeClientObject(client);

						if(clientAttributes != null){
							boolean roomExists = focusManager.getConference(conferenceIq.getRoom()) != null;

							if (!roomExists && authAuthority != null) {
								Map<String, String> propertyMap = conferenceIq.getPropertiesMap();
								clientAttributes.setMachineUID(conferenceIq.getMachineUID());
								clientAttributes.setClientKey(clientKey);
								clientAttributes.setRoomName(conferenceIq.getRoom());
								String sessionId = clientAuthenticationImpl.authenticateUser(clientAttributes, propertyMap);

								conferenceIq.setSessionId(sessionId);
							}
						}

					} else {
						throw new ClientKeyException("Client Name / Client Key pair mismatch. Please Retry");
					}
				} else {
					throw new ClientKeyException("Client Name / Client Key is not Supplied. Please Retry");
				}

				org.jivesoftware.smack.packet.IQ response = handleConferenceIq((ConferenceIq) smackIq);

				return response != null ? IQUtils.convert(response) : null;

			} else if (smackIq instanceof ShutdownIQ) {
				ShutdownIQ gracefulShutdownIQ = (ShutdownIQ) smackIq;

				if (!gracefulShutdownIQ.isGracefulShutdown()) {
					return IQUtils.convert(org.jivesoftware.smack.packet.IQ.createErrorResponse(smackIq,
							new XMPPError(XMPPError.Condition.bad_request)));
				}

				String from = gracefulShutdownIQ.getFrom();
				String bareFrom = org.jivesoftware.smack.util.StringUtils.parseBareAddress(from);

				if (StringUtils.isNullOrEmpty(shutdownAllowedJid) || !shutdownAllowedJid.equals(bareFrom)) {
					// Forbidden
					XMPPError forbiddenError = new XMPPError(XMPPError.Condition.forbidden);

					logger.warn("Client : " + client + "Rejected shutdown request from: " + from);

					return IQUtils
							.convert(org.jivesoftware.smack.packet.IQ.createErrorResponse(smackIq, forbiddenError));
				}

				logger.info("Client : " + client + "Accepted shutdown request from: " + from);

				focusManager.enableGracefulShutdownMode();

				return IQUtils.convert(org.jivesoftware.smack.packet.IQ.createResultIQ(smackIq));
			} else if (smackIq instanceof LogoutIq) {
				logger.info("Client : " + client + "Logout IQ received: " + iq.toXML());

				if (authAuthority == null) {
					return null;
				}

				org.jivesoftware.smack.packet.IQ smackResult = authAuthority.processLogoutIq((LogoutIq) smackIq);

				return smackResult != null ? IQUtils.convert(smackResult) : null;
			} else {
				return super.handleIQSet(iq);
			}

		} catch (Exception e) {
			logger.error(e, e);
			throw e;
		}
	}

Thank You, Hope you liked it .

Keep Jitsing , Cheers !!!

FOUND THIS USEFUL? SHARE IT

comments (6)

  1. chengji

    Thanks for sharing your idea ! Is there any versatile ways instead of hard code the Client Key & Client Name in a property file ? What if I would like to change the file from time to time ?

    Regards !

    Chengji Su

    Reply
    1. Siddharth Jain Post author

      Hi Chengji

      The Client key & Client name are retrieved from property files on run time whenever you hit URL with client key it will check for a match in property file,we have created one admin module ,you may create same which reads the property file and show it to admin on UI and admin can add/delete/change the client key or client name and save it, so by this way you can achieve changing of Client Key & Client Name on run time.

      Hope That helps

      Thanks
      Siddharth

      Reply
  2. Nitesh

    Hi,

    Good work. I am trying do some thing similar but using rest API.

    I am not able to find lib-jitsi-meet directory on my server.

    Thanks!

    Reply
    1. Siddharth Jain Post author

      Hi Nitesh,
      Thanks,while implementing this authorization you need your jitsi server up & running with source code on your server which include module like Jicofo,JVB & lib-jitsi-meet.

      Working with debian installation will not help in this scenario .

      Here is GitHub url for lib-jitsi-meet github.com/jitsi/lib-jitsi-meet

      I hope this may help you

      Reply
  3. Vivek Kumar Tamta

    Great job on writing an article on authorization the secure access to the conference in Jitsi. Just following the mentioned steps any one can create a conference room with access to only authorized people.

    Reply
  4. Ankit AroraAnkit Arora

    Nice work of securing the open-source Jitsi environment. I suppose this would give an edge to the enterprises looking to bring up Jitsi as Software as a Service.

    Reply

Leave a comment -