nick_j_white
nick_j_white

Reputation: 622

How to get list of MultiUserChat groups on an XMPP (ejabberd) server which a user is a member of?

I am running an ejabberd XMPP server with a requirement for all MultiUserChat rooms to be configured as private by default. Only an admin user will be able to create multi user chat groups, and users will only be permitted to join if membership is granted by the admin.

ejabberd.yml configuration of MUC as per below:

#Access Rules
access_rules:
  local:
    allow: local
  c2s:
    deny: blocked
    allow: all
  announce:
    allow: admin
  configure:
    allow: admin
  muc_create:
    allow: admin
  pubsub_createnode:
    allow: admin
  trusted_network:
    allow: loopback
  register:
    allow:
        user: admin
        
#Modules
modules:
  mod_muc:
    access:
      - allow
    access_admin:
      - allow: admin
    access_create: muc_create
    access_persistent: muc_create
    access_mam:
      - allow
    default_room_options:
      allow_change_subj: false
      allow_private_messages: false
      allow_private_messages_from_visitors: nobody
      allow_query_users: false
      allow_subscription: false
      allow_user_invites: false
      allow_visitor_nickchange: false
      allow_visitor_status: false
      anonymous: false
      captcha_protected: false
      logging: false
      mam: false
      members_by_default: true
      members_only: true
      moderated: true
      password_protected: false
      persistent: true
      presence_broadcast:
        - moderator
        - participant
        - visitor
      public: false
      public_list: false

The backend application uses Smack v4.4.6 client for the admin user to connect to the server and grant membership of existing users to MUC channels as required. Demo of the admin client granting room membership:

package com.example.xmpp;

import java.io.IOException;
import java.net.InetAddress;

import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.muc.MultiUserChat;
import org.jivesoftware.smackx.muc.MultiUserChatManager;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPException;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.jid.parts.Resourcepart;

public class AdminClient {
    
    public static void main(String[] args){

        String username = "admin";
        String password = "password";
        String resource = "local";
        
        String host = "example.com";
        int port = 5222;
        String domain = "example.com";
        
        String channelName = "exampleChannel";
        
        try {
        
            XMPPTCPConnectionConfiguration.Builder builder = XMPPTCPConnectionConfiguration.builder()
                    .setHostAddress(InetAddress.getByName(host))
                    .setPort(port)
                    .setUsernameAndPassword(username, password)
                    .setXmppDomain(JidCreate.domainBareFrom(domain))
                    .setCompressionEnabled(true)
                    .setSecurityMode(ConnectionConfiguration.SecurityMode.required)
                    ;
            
            XMPPTCPConnectionConfiguration conf = builder.build();
            XMPPTCPConnection connection = new XMPPTCPConnection(conf);
            connection.connect().login();
            
            //MUC Manager
            MultiUserChatManager manager = MultiUserChatManager.getInstanceFor(connection);
            
            //Create MUC room object
            EntityBareJid jid = JidCreate.entityBareFrom(channelName);  //Room JID
            Resourcepart resourcePart = Resourcepart.from(resource);    //Admin user resource part
            MultiUserChat muc = manager.getMultiUserChat(jid);          //MUC object for room
            
            //Add member to room
            String memberUsername = "user1";
            EntityBareJid memberJid = JidCreate.entityBareFrom(memberUsername);
            muc.grantMembership(memberJid);
            
            //Disconnect
            connection.disconnect();
            
        } catch(IOException | XMPPException | SmackException | InterruptedException e) {
            e.printStackTrace();
        }
    
    }

}

The client application will also be built using Smack 4.4.6 for android, and has a requirement to monitor which rooms the user has been granted membership of. Currently I have no way of knowing what multi user chat rooms are available on the server, nor whether membership has been granted (without actively attempting to join).

For now, I have implemented a REST API service to return a list of channel names the backend application is storing as available to the user which is called when the app is started. To avoid having to regularly call this API, I also have firebase notifications sent out when a new membership is granted so that the affected client knows to add this to their list and join. However, I would prefer to achieve this by querying the XMPPServer directly if possible.

Are there methods available to Smack client to:

  1. Pull a list of all multi user chat rooms (including private as all are configured this way)
  2. Pull a list of all room membership allocations for the currently connected user?

(Strictly speaking 1. is not required if 2. can be fulfilled by itself)

Upvotes: 0

Views: 108

Answers (2)

Taras Filatov
Taras Filatov

Reputation: 123

We were also lacking this functionality for Ethora project and we ended up writing our own plugin to address this.

Code below might help you with some ideas in case you decide to go down the same route:

 join_room({#presence{sub_els = [{xmlel, <<"x">>, [{<<"xmlns">>, <<"http://jabber.org/protocol/muc">>}], []}],
  from = From, to = To} = Pkt, C2SState}) ->
%%  ?INFO_MSG("From: ~p~nTo: ~p~n", [From, To]),
  JidFrom = list_to_binary([From#jid.luser, "@", From#jid.lserver]),
  LServer = From#jid.lserver,
  SelectSql = "
    SELECT * FROM user_rooms WHERE
      user ='" ++ binary_to_list(ejabberd_sql:escape(JidFrom)) ++ "' AND
      name='" ++ binary_to_list(ejabberd_sql:escape(To#jid.luser)) ++ "' AND
      host='" ++ binary_to_list(ejabberd_sql:escape(To#jid.lserver)) ++ "'",
  case ejabberd_sql:sql_query(LServer, SelectSql) of
    {selected, _, []} -> ok,
      Sql = "
      INSERT INTO user_rooms SET
        user='" ++ binary_to_list(ejabberd_sql:escape(JidFrom)) ++ "',
        name='" ++ binary_to_list(ejabberd_sql:escape(To#jid.luser)) ++ "',
        host='" ++ binary_to_list(ejabberd_sql:escape(To#jid.lserver)) ++ "',
        user_count=0",
      ejabberd_sql:sql_query(LServer, Sql),
      UsersCountSql = "SELECT COUNT(*) FROM user_rooms WHERE
      name='" ++ binary_to_list(ejabberd_sql:escape(To#jid.luser)) ++ "' AND
      host='" ++ binary_to_list(ejabberd_sql:escape(To#jid.lserver)) ++ "'",
      {selected, _, [[Count]]} = ejabberd_sql:sql_query(LServer, UsersCountSql),
      Sql2 = "UPDATE user_rooms SET
        user_count =" ++ binary_to_list(Count) ++ "
        WHERE name='" ++ binary_to_list(ejabberd_sql:escape(To#jid.luser)) ++ "' AND
        host='" ++ binary_to_list(ejabberd_sql:escape(To#jid.lserver)) ++ "'",
      ejabberd_sql:sql_query(LServer, Sql2);
    {selected, _, _} -> ok
  end,
  {Pkt, C2SState};
join_room(Acc) ->
  Acc.

DB table:

CREATE TABLE `user_rooms` (
  `user` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `name` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `host` text COLLATE utf8mb4_unicode_ci NOT NULL,
  `user_count` int(10) UNSIGNED NOT NULL DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

ALTER TABLE `user_rooms`
  ADD KEY `user` (`user`),
  ADD KEY `name` (`name`(768)),
  ADD KEY `host` (`host`(768));

Upvotes: 0

Badlop
Badlop

Reputation: 4120

Given a room JID, it is possible to know the list of members. However, given a user JID, it is not possible to know the list of rooms it is member of. I couldn't find any such a thing in https://xmpp.org/extensions/xep-0045.html

Then I started looking at MucSub which is an experimental addition to the MUC protocol, implemented in ejabberd's mod_muc. And found a tricky solution...

If your users (or the admin) add as member and also add as subscriber, then the users (or the admin) could request the list of subscribed rooms for a given user, that you know is equivalent to the list of rooms where he is member.

Upvotes: 0

Related Questions