1 |
|
%% @doc Provide an interface for frontends (like graphql or ctl) to manage sessions. |
2 |
|
-module(mongoose_session_api). |
3 |
|
|
4 |
|
-export([list_sessions/0, |
5 |
|
list_sessions/1, |
6 |
|
count_sessions/0, |
7 |
|
count_sessions/1, |
8 |
|
list_user_sessions/1, |
9 |
|
list_resources/1, |
10 |
|
list_user_resources/1, |
11 |
|
num_resources/1, |
12 |
|
get_user_resource/2, |
13 |
|
list_status_users/1, |
14 |
|
list_status_users/2, |
15 |
|
num_status_users/1, |
16 |
|
num_status_users/2, |
17 |
|
set_presence/5, |
18 |
|
kick_session/2, |
19 |
|
kick_sessions/2, |
20 |
|
prepare_reason/1]). |
21 |
|
|
22 |
|
-ignore_xref([prepare_reason/1, |
23 |
|
get_user_resources/2, |
24 |
|
list_user_sessions/1, |
25 |
|
num_resources/1]). |
26 |
|
|
27 |
|
-include("session.hrl"). |
28 |
|
-include_lib("jid/include/jid.hrl"). |
29 |
|
-include_lib("exml/include/exml.hrl"). |
30 |
|
|
31 |
|
-type status() :: binary(). |
32 |
|
-type session() :: #session{}. |
33 |
|
-type status_user_info() :: {JID :: jid:jid(), |
34 |
|
Prio :: ejabberd_sm:priority(), |
35 |
|
Status :: status()}. |
36 |
|
|
37 |
|
-type session_info() :: {USR :: jid:jid(), |
38 |
|
Conn :: atom(), |
39 |
|
Address :: {inet:ip_address(), inet:port_number()} | undefined, |
40 |
|
Prio :: ejabberd_sm:priority(), |
41 |
|
NodeS :: node(), |
42 |
|
Uptime :: integer()}. |
43 |
|
|
44 |
|
-type res_number_result() :: {ok | wrong_res_number | user_not_found, binary()}. |
45 |
|
|
46 |
|
-type kick_user_result() :: #{binary() => term()}. |
47 |
|
|
48 |
|
-type set_presence_result() :: {ok | empty_resource | user_not_found, binary()}. |
49 |
|
|
50 |
|
-export_type([res_number_result/0, |
51 |
|
set_presence_result/0, |
52 |
|
kick_user_result/0, |
53 |
|
status_user_info/0, |
54 |
|
session_info/0, |
55 |
|
status/0]). |
56 |
|
|
57 |
|
-spec list_sessions() -> {ok, [session_info()]}. |
58 |
|
list_sessions() -> |
59 |
2 |
USRIs = ejabberd_sm:get_full_session_list(), |
60 |
2 |
{ok, lists:map(fun format_user_info/1, USRIs)}. |
61 |
|
|
62 |
|
-spec list_sessions(jid:server()) -> {ok, [session_info()]} | {domain_not_found, binary()}. |
63 |
|
list_sessions(Host) -> |
64 |
7 |
case check_domain(Host) of |
65 |
|
ok -> |
66 |
5 |
USRIs = ejabberd_sm:get_vh_session_list(Host), |
67 |
5 |
{ok, lists:map(fun format_user_info/1, USRIs)}; |
68 |
|
Error -> |
69 |
2 |
Error |
70 |
|
end. |
71 |
|
|
72 |
|
-spec count_sessions() -> {ok, non_neg_integer()}. |
73 |
|
count_sessions() -> |
74 |
2 |
{ok, ejabberd_sm:get_total_sessions_number()}. |
75 |
|
|
76 |
|
-spec count_sessions(jid:server()) -> {ok, non_neg_integer()} | {domain_not_found, binary()}. |
77 |
|
count_sessions(Host) -> |
78 |
7 |
case check_domain(Host) of |
79 |
|
ok -> |
80 |
5 |
{ok, ejabberd_sm:get_vh_session_number(Host)}; |
81 |
|
Error -> |
82 |
2 |
Error |
83 |
|
end. |
84 |
|
|
85 |
|
-spec list_resources(jid:server()) -> {ok, [jid:literal_jid()]} | {domain_not_found, binary()}. |
86 |
|
list_resources(Host) -> |
87 |
3 |
case check_domain(Host) of |
88 |
|
ok -> |
89 |
3 |
Lst = ejabberd_sm:get_vh_session_list(Host), |
90 |
3 |
{ok, [jid:to_binary(USR) || #session{usr = USR} <- Lst]}; |
91 |
|
Error -> |
92 |
:-( |
Error |
93 |
|
end. |
94 |
|
|
95 |
|
-spec list_user_resources(jid:jid()) -> {ok, [jid:literal_jid()]} | {user_not_found, binary()}. |
96 |
|
list_user_resources(JID) -> |
97 |
2 |
case check_user(JID) of |
98 |
|
ok -> |
99 |
2 |
{ok, ejabberd_sm:get_user_resources(JID)}; |
100 |
|
Error -> |
101 |
:-( |
Error |
102 |
|
end. |
103 |
|
|
104 |
|
-spec list_user_sessions(jid:jid()) -> {ok, [session_info()]} | {user_not_found, binary()}. |
105 |
|
list_user_sessions(JID) -> |
106 |
7 |
case check_user(JID) of |
107 |
|
ok -> |
108 |
4 |
Resources = ejabberd_sm:get_user_resources(JID), |
109 |
4 |
{ok, lists:foldl( |
110 |
|
fun(Res, Acc) -> |
111 |
7 |
RJID = jid:replace_resource(JID, Res), |
112 |
7 |
case ejabberd_sm:get_session(RJID) of |
113 |
:-( |
offline -> Acc; |
114 |
7 |
Session -> [format_user_info(Session) | Acc] |
115 |
|
end |
116 |
|
end, |
117 |
|
[], |
118 |
|
Resources)}; |
119 |
|
Error -> |
120 |
3 |
Error |
121 |
|
end. |
122 |
|
|
123 |
|
-spec num_resources(jid:jid()) -> {ok, non_neg_integer()} | {user_not_found, binary()}. |
124 |
|
num_resources(JID) -> |
125 |
6 |
case check_user(JID) of |
126 |
|
ok -> |
127 |
3 |
{ok, length(ejabberd_sm:get_user_resources(JID))}; |
128 |
|
Error -> |
129 |
3 |
Error |
130 |
|
end. |
131 |
|
|
132 |
|
-spec get_user_resource(jid:jid(), integer()) -> res_number_result(). |
133 |
|
get_user_resource(JID, Num) -> |
134 |
14 |
case check_user(JID) of |
135 |
|
ok -> |
136 |
11 |
Resources = ejabberd_sm:get_user_resources(JID), |
137 |
11 |
case (0 < Num) and (Num =< length(Resources)) of |
138 |
|
true -> |
139 |
7 |
{ok, lists:nth(Num, Resources)}; |
140 |
|
false -> |
141 |
4 |
{wrong_res_number, |
142 |
|
iolist_to_binary(io_lib:format("Wrong resource number: ~p", [Num]))} |
143 |
|
end; |
144 |
|
Error -> |
145 |
3 |
Error |
146 |
|
end. |
147 |
|
|
148 |
|
-spec num_status_users(jid:server(), status()) -> {ok, non_neg_integer()} |
149 |
|
| {domain_not_found, binary()}. |
150 |
|
num_status_users(Host, Status) -> |
151 |
11 |
case check_domain(Host) of |
152 |
|
ok -> |
153 |
7 |
{ok, Sessions} = list_status_users(Host, Status), |
154 |
7 |
{ok, length(Sessions)}; |
155 |
|
Error -> |
156 |
4 |
Error |
157 |
|
end. |
158 |
|
|
159 |
|
-spec num_status_users(status()) -> {ok, non_neg_integer()}. |
160 |
|
num_status_users(Status) -> |
161 |
4 |
{ok, Sessions} = list_status_users(Status), |
162 |
4 |
{ok, length(Sessions)}. |
163 |
|
|
164 |
|
-spec list_status_users(jid:server(), status()) -> {ok, [status_user_info()]} |
165 |
|
| {domain_not_found, binary()}. |
166 |
|
list_status_users(Host, Status) -> |
167 |
14 |
case check_domain(Host) of |
168 |
|
ok -> |
169 |
14 |
Sessions = ejabberd_sm:get_vh_session_list(Host), |
170 |
14 |
{ok, get_status_list(Sessions, Status)}; |
171 |
|
Error -> |
172 |
:-( |
Error |
173 |
|
end. |
174 |
|
|
175 |
|
-spec list_status_users(status()) -> {ok, [status_user_info()]}. |
176 |
|
list_status_users(Status) -> |
177 |
8 |
Sessions = ejabberd_sm:get_full_session_list(), |
178 |
8 |
{ok, get_status_list(Sessions, Status)}. |
179 |
|
|
180 |
|
-spec set_presence(jid:jid(), Type :: binary(), Show :: binary(), |
181 |
|
Status :: binary(), Prio :: binary()) -> set_presence_result(). |
182 |
|
set_presence(#jid{lresource = <<>>}, _Type, _Show, _Status, _Priority) -> |
183 |
3 |
{empty_resource, <<"The resource is empty. You need to provide a full JID">>}; |
184 |
|
set_presence(JID, Type, Show, Status, Priority) -> |
185 |
12 |
case check_user(JID) of |
186 |
|
ok -> |
187 |
9 |
Pid = ejabberd_sm:get_session_pid(JID), |
188 |
9 |
USR = jid:to_binary(JID), |
189 |
9 |
US = jid:to_binary(jid:to_bare(JID)), |
190 |
|
|
191 |
9 |
Children = maybe_pres_status(Status, |
192 |
|
maybe_pres_priority(Priority, |
193 |
|
maybe_pres_show(Show, []))), |
194 |
9 |
Message = #xmlel{name = <<"presence">>, |
195 |
|
attrs = [{<<"from">>, USR}, {<<"to">>, US} | maybe_type_attr(Type)], |
196 |
|
children = Children}, |
197 |
9 |
ok = mod_presence:set_presence(Pid, Message), |
198 |
9 |
{ok, <<"Presence set successfully">>}; |
199 |
|
Error -> |
200 |
3 |
Error |
201 |
|
end. |
202 |
|
|
203 |
|
-spec kick_sessions(jid:jid(), binary()) -> {ok, [kick_user_result()]} | {user_not_found, binary()}. |
204 |
|
kick_sessions(JID, Reason) -> |
205 |
7 |
case check_user(JID) of |
206 |
|
ok -> |
207 |
5 |
{ok, lists:map( |
208 |
|
fun(Resource) -> |
209 |
7 |
FullJID = jid:replace_resource(JID, Resource), |
210 |
7 |
case kick_session_internal(FullJID, Reason) of |
211 |
|
{ok, Result} -> |
212 |
7 |
{ok, Result}; |
213 |
|
{Code, Message} -> |
214 |
:-( |
{ok, #{<<"jid">> => FullJID, |
215 |
|
<<"kicked">> => false, |
216 |
|
<<"code">> => atom_to_binary(Code), |
217 |
|
<<"message">> => Message}} |
218 |
|
end |
219 |
|
end, |
220 |
|
ejabberd_sm:get_user_resources(JID))}; |
221 |
|
Error -> |
222 |
2 |
Error |
223 |
|
end. |
224 |
|
|
225 |
|
-spec kick_session(jid:jid(), binary() | null) -> {ok, kick_user_result()} |
226 |
|
| {no_session | user_not_found, binary()}. |
227 |
|
kick_session(JID, Reason) -> |
228 |
14 |
case check_user(JID) of |
229 |
|
ok -> |
230 |
11 |
kick_session_internal(JID, Reason); |
231 |
|
Error -> |
232 |
3 |
Error |
233 |
|
end. |
234 |
|
|
235 |
|
-spec kick_session_internal(jid:jid(), binary() | null) -> {ok, kick_user_result()} |
236 |
|
| {no_session, binary()}. |
237 |
|
kick_session_internal(JID, Reason) -> |
238 |
18 |
case ejabberd_sm:terminate_session(JID, prepare_reason(Reason)) of |
239 |
|
no_session -> |
240 |
4 |
{no_session, <<"No active session">>}; |
241 |
|
ok -> |
242 |
14 |
{ok, #{<<"jid">> => JID, |
243 |
|
<<"kicked">> => true, |
244 |
|
<<"message">> => <<"Session kicked">>}} |
245 |
|
end. |
246 |
|
|
247 |
|
-spec prepare_reason(binary() | null) -> binary(). |
248 |
|
prepare_reason(Reason) when Reason == <<>>; Reason == null -> |
249 |
3 |
<<"Kicked by administrator">>; |
250 |
|
prepare_reason(Reason) when is_binary(Reason) -> |
251 |
15 |
Reason. |
252 |
|
|
253 |
|
%% Internal |
254 |
|
|
255 |
|
-spec get_status_list([session()], status()) -> [status_user_info()]. |
256 |
|
get_status_list(Sessions0, StatusRequired) -> |
257 |
22 |
Sessions = [ {catch mod_presence:get_presence(Pid), S, P} |
258 |
22 |
|| #session{sid = {_, Pid}, usr = {_, S, _}, priority = P} <- Sessions0], |
259 |
|
|
260 |
22 |
[{jid:make_noprep(User, Server, Resource), Priority, StatusText} |
261 |
22 |
|| {{User, Resource, Status, StatusText}, Server, Priority} <- Sessions, |
262 |
30 |
Status == StatusRequired]. |
263 |
|
|
264 |
|
-spec format_user_info(ejabberd_sm:session()) -> session_info(). |
265 |
|
format_user_info(#session{sid = {Microseconds, Pid}, usr = Usr, |
266 |
|
priority = Priority, info = Info}) -> |
267 |
19 |
Conn = maps:get(conn, Info, undefined), |
268 |
19 |
Address = maps:get(ip, Info, undefined), |
269 |
19 |
Node = node(Pid), |
270 |
19 |
Uptime = (erlang:system_time(microsecond) - Microseconds) div 1000000, |
271 |
19 |
{Usr, Conn, Address, Priority, Node, Uptime}. |
272 |
|
|
273 |
|
-spec maybe_type_attr(binary())-> list(). |
274 |
|
maybe_type_attr(<<"available">>) -> |
275 |
6 |
[]; |
276 |
|
maybe_type_attr(Type) -> |
277 |
3 |
[{<<"type">>, Type}]. |
278 |
|
|
279 |
|
-spec maybe_pres_show(binary(), list()) -> list(). |
280 |
|
maybe_pres_show(Show, Children) when Show =:= <<>>; |
281 |
|
Show =:= <<"online">> -> |
282 |
6 |
Children; |
283 |
|
maybe_pres_show(Show, Children) -> |
284 |
3 |
[#xmlel{name = <<"show">>, |
285 |
|
children = [#xmlcdata{content = Show}]} | Children]. |
286 |
|
|
287 |
|
-spec maybe_pres_priority(binary(), list()) -> list(). |
288 |
|
maybe_pres_priority(<<>>, Children) -> |
289 |
6 |
Children; |
290 |
|
maybe_pres_priority(Prio, Children) -> |
291 |
3 |
[#xmlel{name = <<"priority">>, |
292 |
|
children = [#xmlcdata{content = Prio}]} | Children]. |
293 |
|
|
294 |
|
-spec maybe_pres_status(binary(), list()) -> list(). |
295 |
|
maybe_pres_status(<<>>, Children) -> |
296 |
3 |
Children; |
297 |
|
maybe_pres_status(Status, Children) -> |
298 |
6 |
[#xmlel{name = <<"status">>, |
299 |
|
children = [#xmlcdata{content = Status}]} | Children]. |
300 |
|
|
301 |
|
-spec check_domain(jid:server()) -> ok | {domain_not_found, binary()}. |
302 |
|
check_domain(Domain) -> |
303 |
42 |
case mongoose_domain_api:get_domain_host_type(Domain) of |
304 |
34 |
{ok, _} -> ok; |
305 |
8 |
{error, not_found} -> {domain_not_found, <<"Domain not found">>} |
306 |
|
end. |
307 |
|
|
308 |
|
-spec check_user(jid:jid()) -> ok | {user_not_found, binary()}. |
309 |
|
check_user(JID = #jid{lserver = LServer}) -> |
310 |
62 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
311 |
|
{ok, HostType} -> |
312 |
62 |
case ejabberd_auth:does_user_exist(HostType, JID, stored) of |
313 |
45 |
true -> ok; |
314 |
17 |
false -> {user_not_found, <<"Given user does not exist">>} |
315 |
|
end; |
316 |
|
{error, not_found} -> |
317 |
:-( |
{user_not_found, <<"User's domain does not exist">>} |
318 |
|
end. |