Reputation: 622
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:
(Strictly speaking 1. is not required if 2. can be fulfilled by itself)
Upvotes: 0
Views: 108
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
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