./ct_report/coverage/mongoose_account_api.COVER.html

1 %% @doc Provide an interface for frontends (like graphql or ctl) to manage accounts.
2 -module(mongoose_account_api).
3
4 -export([list_users/1,
5 count_users/1,
6 list_old_users/1,
7 list_old_users_for_domain/2,
8 register_user/3,
9 register_generated_user/2,
10 unregister_user/1,
11 unregister_user/2,
12 delete_old_users/1,
13 delete_old_users_for_domain/2,
14 ban_account/2,
15 ban_account/3,
16 change_password/2,
17 change_password/3,
18 check_account/1,
19 check_account/2,
20 check_password/2,
21 check_password/3,
22 check_password_hash/3,
23 check_password_hash/4,
24 num_active_users/2]).
25
26 -include("mongoose.hrl").
27
28 -type register_result() :: {ok | exists | invalid_jid | cannot_register, iolist()}.
29
30 -type unregister_result() :: {ok | not_allowed | invalid_jid, string()}.
31
32 -type change_password_result() :: {ok | empty_password | not_allowed | invalid_jid, string()}.
33
34 -type check_password_result() :: {ok | incorrect | user_does_not_exist, string()}.
35
36 -type check_password_hash_result() :: {ok | incorrect | wrong_user | wrong_method, string()}.
37
38 -type check_account_result() :: {ok | user_does_not_exist, string()}.
39
40 -type num_active_users_result() :: {ok, non_neg_integer()} | {cannot_count, string()}.
41
42 -type delete_old_users_result() :: {ok, iolist()}.
43
44 -export_type([register_result/0,
45 unregister_result/0,
46 change_password_result/0,
47 check_password_result/0,
48 check_password_hash_result/0,
49 check_account_result/0,
50 num_active_users_result/0,
51 delete_old_users_result/0]).
52
53 %% API
54
55 -spec list_users(jid:server()) -> [jid:literal_jid()].
56 list_users(Domain) ->
57 9 Users = ejabberd_auth:get_vh_registered_users(Domain),
58 9 SUsers = lists:sort(Users),
59 9 [jid:to_binary(US) || US <- SUsers].
60
61 -spec count_users(jid:server()) -> integer().
62 count_users(Domain) ->
63 2 ejabberd_auth:get_vh_registered_users_number(Domain).
64
65 -spec register_generated_user(jid:server(), binary()) -> {register_result(), jid:literal_jid()}.
66 register_generated_user(Host, Password) ->
67 2 Username = generate_username(),
68 2 JID = jid:to_binary({Username, Host}),
69 2 {register_user(Username, Host, Password), JID}.
70
71 -spec register_user(jid:user(), jid:server(), binary()) -> register_result().
72 register_user(User, Host, Password) ->
73 3485 JID = jid:make(User, Host, <<>>),
74 3485 case ejabberd_auth:try_register(JID, Password) of
75 {error, exists} ->
76 11 String =
77 io_lib:format("User ~s already registered at node ~p",
78 [jid:to_binary(JID), node()]),
79 11 {exists, String};
80 {error, invalid_jid} ->
81 1 String = io_lib:format("Invalid JID ~s@~s", [User, Host]),
82 1 {invalid_jid, String};
83 {error, Reason} ->
84
:-(
String =
85 io_lib:format("Can't register user ~s at node ~p: ~p",
86 [jid:to_binary(JID), node(), Reason]),
87
:-(
{cannot_register, String};
88 _ ->
89 3473 {ok, io_lib:format("User ~s successfully registered", [jid:to_binary(JID)])}
90 end.
91
92 -spec unregister_user(jid:user(), jid:server()) -> unregister_result().
93 unregister_user(User, Host) ->
94 3494 JID = jid:make(User, Host, <<>>),
95 3494 unregister_user(JID).
96
97 -spec unregister_user(jid:jid()) -> unregister_result().
98 unregister_user(JID) ->
99 3497 case ejabberd_auth:remove_user(JID) of
100 ok ->
101 3491 {ok, io_lib:format("User ~s successfully unregistered", [jid:to_binary(JID)])};
102 error ->
103 1 {invalid_jid, "Invalid JID"};
104 {error, not_allowed} ->
105 5 {not_allowed, "User does not exist or you are not authorised properly"}
106 end.
107
108 -spec change_password(jid:user(), jid:server(), binary()) -> change_password_result().
109 change_password(User, Host, Password) ->
110 7 JID = jid:make(User, Host, <<>>),
111 7 change_password(JID, Password).
112
113 -spec change_password(jid:jid(), binary()) -> change_password_result().
114 change_password(JID, Password) ->
115 12 Result = ejabberd_auth:set_password(JID, Password),
116 12 format_change_password(Result).
117
118 -spec check_account(jid:user(), jid:server()) -> check_account_result().
119 check_account(User, Host) ->
120 13 JID = jid:make(User, Host, <<>>),
121 13 check_account(JID).
122
123 -spec check_account(jid:jid()) -> check_account_result().
124 check_account(JID) ->
125 15 case ejabberd_auth:does_user_exist(JID) of
126 true ->
127 8 {ok, io_lib:format("User ~s exists", [jid:to_binary(JID)])};
128 false ->
129 7 {user_does_not_exist, io_lib:format("User ~s does not exist", [jid:to_binary(JID)])}
130 end.
131
132 -spec check_password(jid:user(), jid:server(), binary()) -> check_password_result().
133 check_password(User, Host, Password) ->
134 2 JID = jid:make(User, Host, <<>>),
135 2 check_password(JID, Password).
136
137 -spec check_password(jid:jid(), binary()) -> check_password_result().
138 check_password(JID, Password) ->
139 5 case ejabberd_auth:does_user_exist(JID) of
140 true ->
141 4 case ejabberd_auth:check_password(JID, Password) of
142 true ->
143 2 {ok, io_lib:format("Password '~s' for user ~s is correct",
144 [Password, jid:to_binary(JID)])};
145 false ->
146 2 {incorrect, io_lib:format("Password '~s' for user ~s is incorrect",
147 [Password, jid:to_binary(JID)])}
148 end;
149 false ->
150 1 {user_does_not_exist,
151 io_lib:format("Password ~s for user ~s is incorrect because this user does not"
152 " exist", [Password, jid:to_binary(JID)])}
153 end.
154
155 -spec check_password_hash(jid:user(), jid:server(), string(), string()) ->
156 check_password_hash_result().
157 check_password_hash(User, Host, PasswordHash, HashMethod) ->
158 3 JID = jid:make(User, Host, <<>>),
159 3 check_password_hash(JID, PasswordHash, HashMethod).
160
161 -spec check_password_hash(jid:jid(), string(), string()) -> check_password_hash_result().
162 check_password_hash(JID, PasswordHash, HashMethod) ->
163 8 AccountPass = ejabberd_auth:get_password_s(JID),
164 8 AccountPassHash = case HashMethod of
165 6 "md5" -> get_md5(AccountPass);
166 1 "sha" -> get_sha(AccountPass);
167 1 _ -> undefined
168 end,
169 8 case {AccountPass, AccountPassHash} of
170 {<<>>, _} ->
171 2 {wrong_user, "User does not exist or using SCRAM password"};
172 {_, undefined} ->
173 1 Msg = io_lib:format("Given hash method `~s` is not supported. Try `md5` or `sha`",
174 [HashMethod]),
175 1 {wrong_method, Msg};
176 {_, PasswordHash} ->
177 3 {ok, "Password hash is correct"};
178 _->
179 2 {incorrect, "Password hash is incorrect"}
180 end.
181
182 -spec num_active_users(jid:lserver(), integer()) -> num_active_users_result().
183 num_active_users(Domain, Days) ->
184 4 TimeStamp = erlang:system_time(second),
185 4 TS = TimeStamp - Days * 86400,
186 4 try
187 4 {ok, HostType} = mongoose_domain_api:get_domain_host_type(Domain),
188 3 Num = mod_last:count_active_users(HostType, Domain, TS),
189 3 {ok, Num}
190 catch
191 _:_ ->
192 1 Message = io_lib:format("Cannot count active users for domain ~s", [Domain]),
193 1 {cannot_count, Message}
194 end.
195
196 -spec ban_account(jid:user(), jid:server(), binary()) -> change_password_result().
197 ban_account(User, Host, ReasonText) ->
198 1 JID = jid:make(User, Host, <<>>),
199 1 ban_account(JID, ReasonText).
200
201 -spec ban_account(jid:jid(), binary()) -> change_password_result().
202 ban_account(JID, ReasonText) ->
203 3 Reason = mongoose_session_api:prepare_reason(ReasonText),
204 3 mongoose_session_api:kick_sessions(JID, Reason),
205 3 case set_random_password(JID, Reason) of
206 ok ->
207 2 {ok, io_lib:format("User ~s successfully banned with reason: ~s",
208 [jid:to_binary(JID), ReasonText])};
209 ErrResult ->
210 1 format_change_password(ErrResult)
211 end.
212
213 -spec delete_old_users(non_neg_integer()) -> {delete_old_users_result(), [jid:literal_jid()]}.
214 delete_old_users(Days) ->
215 2 Users = list_old_users_raw(Days),
216 2 DeletedUsers = delete_users(Users),
217 2 {{ok, format_deleted_users(DeletedUsers)}, DeletedUsers}.
218
219 -spec delete_old_users_for_domain(binary(), non_neg_integer()) ->
220 {delete_old_users_result(), [jid:literal_jid()]}.
221 delete_old_users_for_domain(Domain, Days) ->
222 2 Users = list_old_users_raw(Domain, Days),
223 2 DeletedUsers = delete_users(Users),
224 2 {{ok, format_deleted_users(DeletedUsers)}, DeletedUsers}.
225
226 -spec list_old_users(non_neg_integer()) -> {ok, [jid:literal_jid()]}.
227 list_old_users(Days) ->
228 1 Users = list_old_users_raw(Days),
229 1 {ok, lists:map(fun jid:to_binary/1, Users)}.
230
231 -spec list_old_users_for_domain(binary(), non_neg_integer()) -> {ok, [jid:literal_jid()]}.
232 list_old_users_for_domain(Domain, Days) ->
233 1 Users = list_old_users_raw(Domain, Days),
234 1 {ok, lists:map(fun jid:to_binary/1, Users)}.
235
236 %% Internal
237
238 -spec delete_users([jid:simple_bare_jid()]) -> [jid:literal_jid()].
239 delete_users(Users) ->
240 4 lists:map(fun({LUser, LServer}) ->
241 6 JID = jid:make(LUser, LServer, <<>>),
242 6 ok = ejabberd_auth:remove_user(JID),
243 6 jid:to_binary(JID)
244 end, Users).
245
246 -spec list_old_users_raw(non_neg_integer()) -> [jid:simple_bare_jid()].
247 list_old_users_raw(Days) ->
248 3 lists:append([list_old_users_raw(Domain, Days) ||
249 3 HostType <- ?ALL_HOST_TYPES,
250 18 Domain <- mongoose_domain_api:get_domains_by_host_type(HostType)]).
251
252 -spec list_old_users_raw(jid:lserver(), non_neg_integer()) -> [jid:simple_bare_jid()].
253 list_old_users_raw(Domain, Days) ->
254 21 Users = ejabberd_auth:get_vh_registered_users(Domain),
255 % Convert older time
256 21 SecOlder = Days * 24 * 60 * 60,
257 % Get current time
258 21 TimeStampNow = erlang:system_time(second),
259 % Filter old users
260 21 lists:filter(fun(User) -> is_old_user(User, TimeStampNow, SecOlder) end, Users).
261
262 -spec is_old_user(jid:simple_bare_jid(), non_neg_integer(), non_neg_integer()) -> boolean().
263 is_old_user({LUser, LServer}, TimeStampNow, SecOlder) ->
264 24 JID = jid:make(LUser, LServer, <<>>),
265 % Check if the user is logged
266 24 case ejabberd_sm:get_user_resources(JID) of
267 24 [] -> is_user_nonactive_long_enough(JID, TimeStampNow, SecOlder);
268
:-(
_ -> false
269 end.
270
271 -spec is_user_nonactive_long_enough(jid:jid(), non_neg_integer(), non_neg_integer()) -> boolean().
272 is_user_nonactive_long_enough(JID, TimeStampNow, SecOlder) ->
273 24 {LUser, LServer} = jid:to_lus(JID),
274 24 {ok, HostType} = mongoose_domain_api:get_domain_host_type(LServer),
275 24 case mod_last:get_last_info(HostType, LUser, LServer) of
276 {ok, TimeStamp, _Status} ->
277 % Get user age
278 22 Sec = TimeStampNow - TimeStamp,
279 22 case Sec < SecOlder of
280 % Younger than SecOlder
281 true ->
282 15 false;
283 %% Older
284 false ->
285 7 true
286 end;
287 not_found ->
288 2 true
289 end.
290
291 format_deleted_users(Users) ->
292 4 io_lib:format("Deleted ~p users: ~p", [length(Users), Users]).
293
294 format_change_password(ok) ->
295 7 {ok, "Password changed"};
296 format_change_password({error, empty_password}) ->
297 3 {empty_password, "Empty password"};
298 format_change_password({error, not_allowed}) ->
299 2 {not_allowed, "Password change not allowed"};
300 format_change_password({error, invalid_jid}) ->
301 1 {invalid_jid, "Invalid JID"}.
302
303 -spec set_random_password(JID, Reason) -> Result when
304 JID :: jid:jid(),
305 Reason :: binary(),
306 Result :: ok | {error, any()}.
307 set_random_password(JID, Reason) ->
308 3 NewPass = build_random_password(Reason),
309 3 ejabberd_auth:set_password(JID, NewPass).
310
311 -spec build_random_password(Reason :: binary()) -> binary().
312 build_random_password(Reason) ->
313 3 {{Year, Month, Day}, {Hour, Minute, Second}} = calendar:universal_time(),
314 3 Date = iolist_to_binary(io_lib:format("~4..0w-~2..0w-~2..0wT~2..0w:~2..0w:~2..0wZ",
315 [Year, Month, Day, Hour, Minute, Second])),
316 3 RandomString = mongoose_bin:gen_from_crypto(),
317 3 <<"BANNED_ACCOUNT--", Date/binary, "--", RandomString/binary, "--", Reason/binary>>.
318
319 -spec generate_username() -> binary().
320 generate_username() ->
321 2 mongoose_bin:join([mongoose_bin:gen_from_timestamp(),
322 mongoose_bin:gen_from_crypto()], $-).
323
324 -spec get_md5(binary()) -> string().
325 get_md5(AccountPass) ->
326 6 lists:flatten([io_lib:format("~.16B", [X])
327 6 || X <- binary_to_list(crypto:hash(md5, AccountPass))]).
328
329 -spec get_sha(binary()) -> string().
330 get_sha(AccountPass) ->
331 1 lists:flatten([io_lib:format("~.16B", [X])
332 1 || X <- binary_to_list(crypto:hash(sha, AccountPass))]).
Line Hits Source