1 |
|
%%============================================================================== |
2 |
|
%% Copyright 2016 Erlang Solutions Ltd. |
3 |
|
%% |
4 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
5 |
|
%% you may not use this file except in compliance with the License. |
6 |
|
%% You may obtain a copy of the License at |
7 |
|
%% |
8 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
9 |
|
%% |
10 |
|
%% Unless required by applicable law or agreed to in writing, software |
11 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
12 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 |
|
%% See the License for the specific language governing permissions and |
14 |
|
%% limitations under the License. |
15 |
|
%% |
16 |
|
%% Author: Joseph Yiasemides <joseph.yiasemides@erlang-solutions.com> |
17 |
|
%% Description: Administration commands for MUC Light |
18 |
|
%%============================================================================== |
19 |
|
|
20 |
|
-module(mod_muc_light_commands). |
21 |
|
|
22 |
|
-behaviour(gen_mod). |
23 |
|
-behaviour(mongoose_module_metrics). |
24 |
|
|
25 |
|
-export([start/2, stop/1, supported_features/0]). |
26 |
|
|
27 |
|
-export([create_unique_room/4]). |
28 |
|
-export([create_identifiable_room/5]). |
29 |
|
-export([send_message/4]). |
30 |
|
-export([invite_to_room/4]). |
31 |
|
-export([change_affiliation/5]). |
32 |
|
-export([delete_room/3]). |
33 |
|
-export([change_room_config/5]). |
34 |
|
|
35 |
|
-ignore_xref([delete_room/3, invite_to_room/4, send_message/4]). |
36 |
|
|
37 |
|
-include("mod_muc_light.hrl"). |
38 |
|
-include("mongoose.hrl"). |
39 |
|
-include("jlib.hrl"). |
40 |
|
|
41 |
|
|
42 |
|
%%-------------------------------------------------------------------- |
43 |
|
%% `gen_mod' callbacks |
44 |
|
%%-------------------------------------------------------------------- |
45 |
|
|
46 |
|
start(_, _) -> |
47 |
308 |
mongoose_commands:register(commands()). |
48 |
|
|
49 |
|
stop(_) -> |
50 |
308 |
mongoose_commands:unregister(commands()). |
51 |
|
|
52 |
|
-spec supported_features() -> [atom()]. |
53 |
|
supported_features() -> |
54 |
142 |
[dynamic_domains]. |
55 |
|
|
56 |
|
%%-------------------------------------------------------------------- |
57 |
|
%% Interface descriptions |
58 |
|
%%-------------------------------------------------------------------- |
59 |
|
|
60 |
|
commands() -> |
61 |
|
|
62 |
616 |
[ |
63 |
|
[{name, create_muc_light_room}, |
64 |
|
{category, <<"muc-lights">>}, |
65 |
|
{desc, <<"Create a MUC Light room with unique username part in JID.">>}, |
66 |
|
{module, ?MODULE}, |
67 |
|
{function, create_unique_room}, |
68 |
|
{action, create}, |
69 |
|
{identifiers, [domain]}, |
70 |
|
{args, |
71 |
|
[ |
72 |
|
%% The parent `domain' under which MUC Light is |
73 |
|
%% configured. |
74 |
|
{domain, binary}, |
75 |
|
{name, binary}, |
76 |
|
{owner, binary}, |
77 |
|
{subject, binary} |
78 |
|
]}, |
79 |
|
{result, {name, binary}}], |
80 |
|
|
81 |
|
[{name, create_identifiable_muc_light_room}, |
82 |
|
{category, <<"muc-lights">>}, |
83 |
|
{desc, <<"Create a MUC Light room with user-provided username part in JID">>}, |
84 |
|
{module, ?MODULE}, |
85 |
|
{function, create_identifiable_room}, |
86 |
|
{action, update}, |
87 |
|
{identifiers, [domain]}, |
88 |
|
{args, |
89 |
|
[{domain, binary}, |
90 |
|
{id, binary}, |
91 |
|
{name, binary}, |
92 |
|
{owner, binary}, |
93 |
|
{subject, binary} |
94 |
|
]}, |
95 |
|
{result, {id, binary}}], |
96 |
|
|
97 |
|
[{name, change_muc_light_room_configuration}, |
98 |
|
{category, <<"muc-lights">>}, |
99 |
|
{subcategory, <<"config">>}, |
100 |
|
{desc, <<"Change configuration of MUC Light room.">>}, |
101 |
|
{module, ?MODULE}, |
102 |
|
{function, change_room_config}, |
103 |
|
{action, update}, |
104 |
|
{identifiers, [domain]}, |
105 |
|
{args, |
106 |
|
[ |
107 |
|
{domain, binary}, |
108 |
|
{id, binary}, |
109 |
|
{name, binary}, |
110 |
|
{user, binary}, |
111 |
|
{subject, binary} |
112 |
|
]}, |
113 |
|
{result, ok}], |
114 |
|
|
115 |
|
[{name, invite_to_room}, |
116 |
|
{category, <<"muc-lights">>}, |
117 |
|
{subcategory, <<"participants">>}, |
118 |
|
{desc, <<"Invite to a MUC Light room.">>}, |
119 |
|
{module, ?MODULE}, |
120 |
|
{function, invite_to_room}, |
121 |
|
{action, create}, |
122 |
|
{identifiers, [domain, name]}, |
123 |
|
{args, |
124 |
|
[{domain, binary}, |
125 |
|
{name, binary}, |
126 |
|
{sender, binary}, |
127 |
|
{recipient, binary} |
128 |
|
]}, |
129 |
|
{result, ok}], |
130 |
|
|
131 |
|
[{name, send_message_to_muc_light_room}, |
132 |
|
{category, <<"muc-lights">>}, |
133 |
|
{subcategory, <<"messages">>}, |
134 |
|
{desc, <<"Send a message to a MUC Light room.">>}, |
135 |
|
{module, ?MODULE}, |
136 |
|
{function, send_message}, |
137 |
|
{action, create}, |
138 |
|
{identifiers, [domain, name]}, |
139 |
|
{args, |
140 |
|
[{domain, binary}, |
141 |
|
{name, binary}, |
142 |
|
{from, binary}, |
143 |
|
{body, binary} |
144 |
|
]}, |
145 |
|
{result, ok}], |
146 |
|
|
147 |
|
[{name, delete_room}, |
148 |
|
{category, <<"muc-lights">>}, |
149 |
|
{subcategory, <<"management">>}, |
150 |
|
{desc, <<"Delete a MUC Light room.">>}, |
151 |
|
{module, ?MODULE}, |
152 |
|
{function, delete_room}, |
153 |
|
{action, delete}, |
154 |
|
{identifiers, [domain, name, owner]}, |
155 |
|
{args, |
156 |
|
[{domain, binary}, |
157 |
|
{name, binary}, |
158 |
|
{owner, binary}]}, |
159 |
|
{result, ok}] |
160 |
|
]. |
161 |
|
|
162 |
|
|
163 |
|
%%-------------------------------------------------------------------- |
164 |
|
%% Internal procedures |
165 |
|
%%-------------------------------------------------------------------- |
166 |
|
|
167 |
|
create_unique_room(Domain, RoomName, Creator, Subject) -> |
168 |
19 |
create_room(Domain, <<>>, RoomName, Creator, Subject). |
169 |
|
|
170 |
|
create_identifiable_room(Domain, Identifier, RoomName, Creator, Subject) -> |
171 |
7 |
create_room(Domain, Identifier, RoomName, Creator, Subject). |
172 |
|
|
173 |
|
invite_to_room(Domain, RoomName, Sender, Recipient0) -> |
174 |
1 |
Recipient1 = jid:binary_to_bare(Recipient0), |
175 |
1 |
case muc_light_room_name_to_jid_and_aff(jid:from_binary(Sender), RoomName, Domain) of |
176 |
|
{ok, R, _Aff} -> |
177 |
1 |
S = jid:binary_to_bare(Sender), |
178 |
1 |
Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, |
179 |
|
[affiliate(jid:to_binary(Recipient1), <<"member">>)]), |
180 |
1 |
ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R), |
181 |
|
<<"set">>, [Changes])); |
182 |
|
{error, given_user_does_not_occupy_any_room} -> |
183 |
:-( |
{error, forbidden, "given user does not occupy any room"}; |
184 |
|
{error, not_found} -> |
185 |
:-( |
{error, not_found, "room does not exist"} |
186 |
|
end. |
187 |
|
|
188 |
|
change_affiliation(Domain, RoomID, Sender, Recipient0, Affiliation) -> |
189 |
15 |
Recipient1 = jid:binary_to_bare(Recipient0), |
190 |
15 |
LServer = jid:nameprep(Domain), |
191 |
15 |
HostType = mod_muc_light_utils:server_host_to_host_type(LServer), |
192 |
15 |
MUCLightDomain = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer), |
193 |
15 |
R = jid:make(RoomID, MUCLightDomain, <<>>), |
194 |
15 |
S = jid:binary_to_bare(Sender), |
195 |
15 |
Changes = query(?NS_MUC_LIGHT_AFFILIATIONS, |
196 |
|
[affiliate(jid:to_binary(Recipient1), Affiliation)]), |
197 |
15 |
ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R), |
198 |
|
<<"set">>, [Changes])). |
199 |
|
|
200 |
|
change_room_config(Domain, RoomID, RoomName, User, Subject) -> |
201 |
3 |
LServer = jid:nameprep(Domain), |
202 |
3 |
HostType = mod_muc_light_utils:server_host_to_host_type(LServer), |
203 |
3 |
MUCLightDomain = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer), |
204 |
3 |
UserUS = jid:binary_to_bare(User), |
205 |
3 |
ConfigReq = #config{ raw_config = |
206 |
|
[{<<"roomname">>, RoomName}, {<<"subject">>, Subject}]}, |
207 |
3 |
Acc = mongoose_acc:new(#{location => ?LOCATION, lserver => LServer, host_type => HostType}), |
208 |
3 |
case mod_muc_light:change_room_config(UserUS, RoomID, MUCLightDomain, ConfigReq, Acc) of |
209 |
|
{ok, _RoomJID, _} -> |
210 |
2 |
ok; |
211 |
|
{error, Reason} -> |
212 |
1 |
{error, internal, Reason} |
213 |
|
end. |
214 |
|
|
215 |
|
send_message(Domain, RoomName, Sender, Message) -> |
216 |
1 |
Body = #xmlel{name = <<"body">>, |
217 |
|
children = [ #xmlcdata{ content = Message } ] |
218 |
|
}, |
219 |
1 |
Stanza = #xmlel{name = <<"message">>, |
220 |
|
attrs = [{<<"type">>, <<"groupchat">>}], |
221 |
|
children = [ Body ] |
222 |
|
}, |
223 |
1 |
S = jid:binary_to_bare(Sender), |
224 |
1 |
case get_user_rooms(jid:to_lus(S), Domain) of |
225 |
|
[] -> |
226 |
:-( |
{error, denied, "given user does not occupy any room"}; |
227 |
|
RoomJIDs when is_list(RoomJIDs) -> |
228 |
1 |
FindFun = find_room_and_user_aff_by_room_name(RoomName, jid:to_lus(S)), |
229 |
1 |
{ok, {RU, RS}, _Aff} = lists:foldl(FindFun, none, RoomJIDs), |
230 |
1 |
true = is_subdomain(RS, Domain), |
231 |
1 |
R = jid:make(RU, RS, <<>>), |
232 |
1 |
ejabberd_router:route(S, R, Stanza) |
233 |
|
end. |
234 |
|
|
235 |
|
-spec delete_room(DomainName :: binary(), RoomName :: binary(), |
236 |
|
Owner :: binary()) -> |
237 |
|
ok | {error, atom(), term()}. |
238 |
|
delete_room(DomainName, RoomName, Owner) -> |
239 |
4 |
OwnerJID = jid:binary_to_bare(Owner), |
240 |
4 |
case muc_light_room_name_to_jid_and_aff(OwnerJID, RoomName, DomainName) of |
241 |
1 |
{ok, RoomJID, owner} -> mod_muc_light:delete_room(jid:to_lus(RoomJID)); |
242 |
1 |
{ok, _, _} -> {error, denied, "you can not delete this room"}; |
243 |
1 |
{error, given_user_does_not_occupy_any_room} -> {error, denied, "given user does not occupy this room"}; |
244 |
1 |
{error, not_found} -> {error, not_found, "room does not exist"} |
245 |
|
end. |
246 |
|
|
247 |
|
%%-------------------------------------------------------------------- |
248 |
|
%% Ancillary |
249 |
|
%%-------------------------------------------------------------------- |
250 |
|
|
251 |
|
create_room(Domain, RoomId, RoomTitle, Creator, Subject) -> |
252 |
26 |
LServer = jid:nameprep(Domain), |
253 |
26 |
HostType = mod_muc_light_utils:server_host_to_host_type(LServer), |
254 |
26 |
MUCLightDomain = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer), |
255 |
26 |
CreatorJid = jid:from_binary(Creator), |
256 |
26 |
MUCServiceJID = jid:make(RoomId, MUCLightDomain, <<>>), |
257 |
26 |
Config = make_room_config(RoomTitle, Subject), |
258 |
26 |
case mod_muc_light:try_to_create_room(CreatorJid, MUCServiceJID, Config) of |
259 |
|
{ok, RoomJid, _} -> |
260 |
25 |
jid:to_binary(RoomJid); |
261 |
|
{error, exists} -> |
262 |
1 |
{error, denied, "Room already exists"}; |
263 |
|
{error, Reason} -> |
264 |
:-( |
{error, internal, Reason} |
265 |
|
end. |
266 |
|
|
267 |
|
make_room_config(Name, Subject) -> |
268 |
26 |
#create{raw_config = [{<<"roomname">>, Name}, |
269 |
|
{<<"subject">>, Subject}] |
270 |
|
}. |
271 |
|
|
272 |
|
-spec muc_light_room_name_to_jid_and_aff(UserJID :: jid:jid(), |
273 |
|
RoomName :: binary(), |
274 |
|
Domain :: jid:lserver()) -> |
275 |
|
{ok, jid:jid(), aff()} | {error, given_user_does_not_occupy_any_room} | {error, not_found}. |
276 |
|
muc_light_room_name_to_jid_and_aff(UserJID, RoomName, Domain) -> |
277 |
5 |
UserUS = jid:to_lus(UserJID), |
278 |
5 |
case get_user_rooms(UserUS, Domain) of |
279 |
|
[] -> |
280 |
1 |
{error, given_user_does_not_occupy_any_room}; |
281 |
|
RoomUSs when is_list(RoomUSs) -> |
282 |
4 |
FindFun = find_room_and_user_aff_by_room_name(RoomName, UserUS), |
283 |
4 |
case lists:foldl(FindFun, none, RoomUSs) of |
284 |
|
{ok, {RU, RS}, UserAff} -> |
285 |
3 |
true = is_subdomain(RS, Domain), |
286 |
3 |
{ok, jid:make(RU, RS, <<>>), UserAff}; |
287 |
|
none -> |
288 |
1 |
{error, not_found} |
289 |
|
end |
290 |
|
end. |
291 |
|
|
292 |
|
-spec get_user_rooms(UserUS :: jid:simple_bare_jid(), Domain :: jid:lserver()) -> |
293 |
|
[jid:simple_bare_jid()]. |
294 |
|
get_user_rooms({_, UserS} = UserUS, Domain) -> |
295 |
6 |
HostType = mod_muc_light_utils:server_host_to_host_type(UserS), |
296 |
6 |
mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, Domain). |
297 |
|
|
298 |
|
-spec get_room_name_and_user_aff(RoomUS :: jid:simple_bare_jid(), |
299 |
|
UserUS :: jid:simple_bare_jid()) -> |
300 |
|
{ok, RoomName :: binary(), UserAff :: aff()} | {error, not_exists}. |
301 |
|
get_room_name_and_user_aff(RoomUS, {_, UserS} = UserUS) -> |
302 |
5 |
HostType = mod_muc_light_utils:server_host_to_host_type(UserS), |
303 |
5 |
case mod_muc_light_db_backend:get_info(HostType, RoomUS) of |
304 |
|
{ok, Cfg, Affs, _} -> |
305 |
5 |
{roomname, RoomName} = lists:keyfind(roomname, 1, Cfg), |
306 |
5 |
{_, UserAff} = lists:keyfind(UserUS, 1, Affs), |
307 |
5 |
{ok, RoomName, UserAff}; |
308 |
|
Error -> |
309 |
:-( |
Error |
310 |
|
end. |
311 |
|
|
312 |
|
-type find_room_acc() :: {ok, RoomUS :: jid:simple_bare_jid(), UserAff :: aff()} | none. |
313 |
|
|
314 |
|
-spec find_room_and_user_aff_by_room_name(RoomName :: binary(), |
315 |
|
UserUS :: jid:simple_bare_jid()) -> |
316 |
|
fun((RoomUS :: jid:simple_bare_jid(), find_room_acc()) -> find_room_acc()). |
317 |
|
find_room_and_user_aff_by_room_name(RoomName, UserUS) -> |
318 |
5 |
fun (RoomUS, none) -> |
319 |
5 |
case get_room_name_and_user_aff(RoomUS, UserUS) of |
320 |
|
{ok, RoomName, UserAff} -> |
321 |
4 |
{ok, RoomUS, UserAff}; |
322 |
|
_ -> |
323 |
1 |
none |
324 |
|
end; |
325 |
|
(_, Acc) when Acc =/= none -> |
326 |
:-( |
Acc |
327 |
|
end. |
328 |
|
|
329 |
|
is_subdomain(Child, Parent) -> |
330 |
|
%% Example input Child = <<"muclight.localhost">> and Parent = |
331 |
|
%% <<"localhost">> |
332 |
4 |
case binary:match(Child, Parent) of |
333 |
:-( |
nomatch -> false; |
334 |
4 |
{_, _} -> true |
335 |
|
end. |
336 |
|
|
337 |
|
iq(To, From, Type, Children) -> |
338 |
16 |
UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard), |
339 |
16 |
#xmlel{name = <<"iq">>, |
340 |
|
attrs = [{<<"from">>, From}, |
341 |
|
{<<"to">>, To}, |
342 |
|
{<<"type">>, Type}, |
343 |
|
{<<"id">>, UUID}], |
344 |
|
children = Children |
345 |
|
}. |
346 |
|
|
347 |
|
query(NS, Children) when is_binary(NS), is_list(Children) -> |
348 |
16 |
#xmlel{name = <<"query">>, |
349 |
|
attrs = [{<<"xmlns">>, NS}], |
350 |
|
children = Children |
351 |
|
}. |
352 |
|
|
353 |
|
affiliate(JID, Kind) when is_binary(JID), is_binary(Kind) -> |
354 |
16 |
#xmlel{name = <<"user">>, |
355 |
|
attrs = [{<<"affiliation">>, Kind}], |
356 |
|
children = [ #xmlcdata{ content = JID } ] |
357 |
|
}. |
358 |
|
|