Ejabberd - get user from multi user chat message using Smack XMPP client or enforce nickname

147 views Asked by At

I am running an ejabberd server with a series of locked down multi user chats (members only, registration required, no subject change or PMs permitted etc) One requirement is to strictly identify which users (from their user Id/account Jid when registering with the server) are present in each room, and which user has sent a message.

How this is achieved is not important, it can either be: a) By getting the userId from message.getFrom() b) By getting the nick/resource part from the message sender, and enforcing what nick a user can choose

In direct messages, the Jid of a sender will look like:

<userId>@<domain>/<resourcepart>

so I can take the userId (LocalPart) and not worry too much what nickname was chosen.

This is not possible in multi user chats however, since the Jid will appear as:

<roomName>@conference.<domain>/<resourcepart>

The userId of the sender is not present, so I have to rely on the nickname, but this can be set to anything by the users (and changed at any point in the chat)

Is there a way to enforce how a nick is set? (i.e. set to the same value as userId) or otherwise extract the userId from a multi user chat message?

3

There are 3 answers

3
Flow On BEST ANSWER

As I wrote, you need a non-anonymous room. The real XMPP address (JID) of a room occupant will then be part of the participant's presence (XEP-0045 § 7.2.3). You can obtain the presence of a occupant via MultiUserChat.getOccupantPresence​(EntityFullJid user). From this Presence you want to extra the MUCUser information via MUCUser.from(presence). From which you extra the MUCIitem which should allow to retrieve the real JID via MUCItem.getJid()1.

1: Note that the javadoc if this method seems to be misleading, it should contain the real JID of the user and not the MUC JID.

1
Badlop On

There is a room option that allows all room occupants to view the real Jabber ID of other occupants. By default only room moderators can view those real Jabber ID.

An alternative would be to customize the source code to only accept a room join if the nick is identical to the username in the JID, and don't accept any nick change afterwards.

0
nick_j_white On

The answer given above by Flow works well for users who are still present in the room. However, for historic messages where the user has left the room, the Presence will not be available.

For users without a Presence, the message stanza will contain an address node, e.g.:

<message 
    xmlns='jabber:client' 
    xml:lang='en' 
    to='[email protected]/12345'
    from='[email protected]/johnny' 
    id='purple44d872cb' type='groupchat'>
    <addresses xmlns='http://jabber.org/protocol/address'>
        <address xmlns='http://jabber.org/protocol/address' type='ofrom' jid='[email protected]/12345'/>
    </addresses>
    <delay xmlns='urn:xmpp:delay' stamp='2023-01-27T10:08:59.594+00:00' from='[email protected]'/>
    <body>me</body>
</message>

To extract this in smack I've called the message toXML() method to get the stanza (required upgrade to smack v4.4.x), then used an XML parser to extract the jid attribute, i.e.:

import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jxmpp.jid.EntityFullJid;
import org.jxmpp.jid.impl.JidCreate;

public static EntityFullJid getUserJIDFromMessage(MultiUserChat muc, Message message) {
    EntityFullJid jid = null;
    
    //1. Extract JID from presence
    try {
        EntityFullJid channelJid = JidCreate.entityFullFrom(message.getFrom());
        jid = extractJidFromPresence(muc, channelJid);
        if (jid != null) return jid;
    } catch (Exception e) {}
    
    //2. If presence unavailable, parse the stanza
    Document messageDoc = Jsoup.parse(message.toXML().toString());
    for (Element address: messageDoc.select("addresses").select("address")) {
        if (address.attr("type").equals("ofrom")) {
            try {
                jid = JidCreate.entityFullFrom(address.attr("jid"));
                return jid;
            } catch (Exception e) {}
        }
    } return null;
}

private static EntityFullJid extractJidFromPresence(MultiUserChat muc, EntityFullJid channelJid) {
    EntityFullJid jid = null;
    try {
        MUCUser mucUser = MUCUser.from(muc.getOccupantPresence(channelJid));
        jid = (EntityFullJid) mucUser.getItem().getJid();
        return jid;
    } catch (Exception e) {}
    return jid;
}