• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

processone / ejabberd / 1212

18 Nov 2025 12:37PM UTC coverage: 33.784% (+0.003%) from 33.781%
1212

push

github

badlop
mod_conversejs: Improve link to conversejs in WebAdmin (#4495)

Until now, the WebAdmin menu included a link to the first request handler
with mod_conversejs that the admin configured in ejabberd.yml
That link included the authentication credentials hashed as URI arguments
if using HTTPS. Then process/2 extracted those arguments and passed them
as autologin options to Converse.

From now, mod_conversejs automatically adds a request_handler nested in
webadmin subpath. The webadmin menu links to that converse URI; this allows
to access the HTTP auth credentials, no need to explicitly pass them.
process/2 extracts this HTTP auth and passes autologin options to Converse.
Now scram password storage is supported too.

This minimum configuration allows WebAdmin to access Converse:

listen:
  -
    port: 5443
    module: ejabberd_http
    tls: true
    request_handlers:
      /admin: ejabberd_web_admin
      /ws: ejabberd_http_ws
modules:
  mod_conversejs:
    conversejs_resources: "/home/conversejs/12.0.0/dist"

0 of 12 new or added lines in 1 file covered. (0.0%)

11290 existing lines in 174 files now uncovered.

15515 of 45924 relevant lines covered (33.78%)

1277.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

53.09
/src/mod_roster_sql.erl
1
%%%-------------------------------------------------------------------
2
%%% File    : mod_roster_sql.erl
3
%%% Author  : Evgeny Khramtsov <ekhramtsov@process-one.net>
4
%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
5
%%%
6
%%%
7
%%% ejabberd, Copyright (C) 2002-2025   ProcessOne
8
%%%
9
%%% This program is free software; you can redistribute it and/or
10
%%% modify it under the terms of the GNU General Public License as
11
%%% published by the Free Software Foundation; either version 2 of the
12
%%% License, or (at your option) any later version.
13
%%%
14
%%% This program is distributed in the hope that it will be useful,
15
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
16
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17
%%% General Public License for more details.
18
%%%
19
%%% You should have received a copy of the GNU General Public License along
20
%%% with this program; if not, write to the Free Software Foundation, Inc.,
21
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22
%%%
23
%%%----------------------------------------------------------------------
24

25
-module(mod_roster_sql).
26

27

28
-behaviour(mod_roster).
29

30
%% API
31
-export([init/2, read_roster_version/2, write_roster_version/4,
32
         get_roster/2, get_roster_item/3, roster_subscribe/4,
33
         read_subscription_and_groups/3, remove_user/2,
34
         update_roster/4, del_roster/3, transaction/2,
35
         process_rosteritems/5,
36
         import/3, export/1, raw_to_record/2]).
37
-export([sql_schemas/0]).
38

39
-include("mod_roster.hrl").
40
-include("ejabberd_sql_pt.hrl").
41
-include("logger.hrl").
42
-include_lib("xmpp/include/jid.hrl").
43

44
%%%===================================================================
45
%%% API
46
%%%===================================================================
47
init(Host, _Opts) ->
UNCOV
48
    ejabberd_sql_schema:update_schema(Host, ?MODULE, sql_schemas()),
7✔
UNCOV
49
    ok.
7✔
50

51
sql_schemas() ->
UNCOV
52
    [#sql_schema{
7✔
53
        version = 1,
54
        tables =
55
            [#sql_table{
56
                name = <<"rosterusers">>,
57
                columns =
58
                    [#sql_column{name = <<"username">>, type = text},
59
                     #sql_column{name = <<"server_host">>, type = text},
60
                     #sql_column{name = <<"jid">>, type = text},
61
                     #sql_column{name = <<"nick">>, type = text},
62
                     #sql_column{name = <<"subscription">>, type = {char, 1}},
63
                     #sql_column{name = <<"ask">>, type = {char, 1}},
64
                     #sql_column{name = <<"askmessage">>, type = text},
65
                     #sql_column{name = <<"server">>, type = {char, 1}},
66
                     #sql_column{name = <<"subscribe">>, type = text},
67
                     #sql_column{name = <<"type">>, type = text},
68
                     #sql_column{name = <<"created_at">>, type = timestamp,
69
                                 default = true}],
70
                indices = [#sql_index{
71
                              columns = [<<"server_host">>, <<"username">>,
72
                                         <<"jid">>],
73
                              unique = true},
74
                           #sql_index{
75
                              columns = [<<"server_host">>, <<"jid">>]}]},
76
             #sql_table{
77
                name = <<"rostergroups">>,
78
                columns =
79
                    [#sql_column{name = <<"username">>, type = text},
80
                     #sql_column{name = <<"server_host">>, type = text},
81
                     #sql_column{name = <<"jid">>, type = text},
82
                     #sql_column{name = <<"grp">>, type = text}],
83
                indices = [#sql_index{
84
                              columns = [<<"server_host">>, <<"username">>,
85
                                         <<"jid">>]}]},
86
             #sql_table{
87
                name = <<"roster_version">>,
88
                columns =
89
                    [#sql_column{name = <<"username">>, type = text},
90
                     #sql_column{name = <<"server_host">>, type = text},
91
                     #sql_column{name = <<"version">>, type = text}],
92
                indices = [#sql_index{
93
                              columns = [<<"server_host">>, <<"username">>],
94
                              unique = true}]}]}].
95

96
read_roster_version(LUser, LServer) ->
UNCOV
97
    case ejabberd_sql:sql_query(
2,779✔
98
           LServer,
UNCOV
99
           ?SQL("select @(version)s from roster_version"
2,779✔
100
                " where username = %(LUser)s and %(LServer)H")) of
UNCOV
101
        {selected, [{Version}]} -> {ok, Version};
2,779✔
102
        {selected, []} -> error;
×
103
        _ -> {error, db_failure}
×
104
    end.
105

106
write_roster_version(LUser, LServer, InTransaction, Ver) ->
UNCOV
107
    if InTransaction ->
1,253✔
UNCOV
108
            set_roster_version(LUser, LServer, Ver);
1,253✔
109
       true ->
110
            transaction(
×
111
              LServer,
112
              fun () -> set_roster_version(LUser, LServer, Ver) end)
×
113
    end.
114

115
get_roster(LUser, LServer) ->
UNCOV
116
    case ejabberd_sql:sql_query(
2,814✔
117
           LServer,
UNCOV
118
           ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s, "
2,814✔
119
                "@(ask)s, @(askmessage)s, @(server)s, @(subscribe)s, "
120
                "@(type)s from rosterusers "
121
                "where username=%(LUser)s and %(LServer)H")) of
122
        {selected, Items} when is_list(Items) ->
UNCOV
123
            JIDGroups = case get_roster_jid_groups(LServer, LUser) of
2,814✔
124
                            {selected, JGrps} when is_list(JGrps) ->
UNCOV
125
                                JGrps;
2,814✔
126
                            _ ->
127
                                []
×
128
                        end,
UNCOV
129
            GroupsDict = lists:foldl(fun({J, G}, Acc) ->
2,814✔
UNCOV
130
                                             Gs = maps:get(J, Acc, []),
49✔
UNCOV
131
                                             maps:put(J, [G | Gs], Acc)
49✔
132
                                     end,
133
                                     maps:new(), JIDGroups),
UNCOV
134
            {ok, lists:flatmap(
2,814✔
135
                   fun(I) ->
UNCOV
136
                           case raw_to_record(LServer, I) of
1,855✔
137
                               %% Bad JID in database:
138
                               error -> [];
×
139
                               R ->
UNCOV
140
                                   SJID = jid:encode(R#roster.jid),
1,855✔
UNCOV
141
                                   Groups = maps:get(SJID, GroupsDict, []),
1,855✔
UNCOV
142
                                   [R#roster{groups = Groups}]
1,855✔
143
                           end
144
                   end, Items)};
145
        _ ->
146
            error
×
147
    end.
148

149
roster_subscribe(_LUser, _LServer, _LJID, Item) ->
UNCOV
150
    ItemVals = record_to_row(Item),
938✔
UNCOV
151
    roster_subscribe(ItemVals).
938✔
152

153
transaction(LServer, F) ->
UNCOV
154
    ejabberd_sql:sql_transaction(LServer, F).
3,409✔
155

156
get_roster_item(LUser, LServer, LJID) ->
UNCOV
157
    SJID = jid:encode(LJID),
2,975✔
UNCOV
158
    case get_roster_by_jid(LServer, LUser, SJID) of
2,975✔
159
        {selected, [I]} ->
UNCOV
160
            case raw_to_record(LServer, I) of
2,058✔
161
                error ->
162
                    error;
×
163
                R ->
UNCOV
164
                    Groups = case get_roster_groups(LServer, LUser, SJID) of
2,058✔
165
                                 {selected, JGrps} when is_list(JGrps) ->
UNCOV
166
                                     [JGrp || {JGrp} <- JGrps];
2,058✔
167
                                 _ -> []
×
168
                             end,
UNCOV
169
                    {ok, R#roster{groups = Groups}}
2,058✔
170
            end;
171
        {selected, []} ->
UNCOV
172
            error
917✔
173
    end.
174

175
remove_user(LUser, LServer) ->
UNCOV
176
    transaction(
14✔
177
      LServer,
178
      fun () ->
UNCOV
179
              ejabberd_sql:sql_query_t(
14✔
UNCOV
180
                ?SQL("delete from rosterusers"
14✔
181
                     " where username=%(LUser)s and %(LServer)H")),
UNCOV
182
              ejabberd_sql:sql_query_t(
14✔
UNCOV
183
                ?SQL("delete from rostergroups"
14✔
184
                     " where username=%(LUser)s and %(LServer)H"))
185
      end),
UNCOV
186
    ok.
14✔
187

188
update_roster(LUser, LServer, LJID, Item) ->
UNCOV
189
    SJID = jid:encode(LJID),
231✔
UNCOV
190
    ItemVals = record_to_row(Item),
231✔
UNCOV
191
    ItemGroups = Item#roster.groups,
231✔
UNCOV
192
    roster_subscribe(ItemVals),
231✔
UNCOV
193
    ejabberd_sql:sql_query_t(
231✔
UNCOV
194
      ?SQL("delete from rostergroups"
231✔
195
           " where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")),
UNCOV
196
    lists:foreach(
231✔
197
      fun(ItemGroup) ->
UNCOV
198
              ejabberd_sql:sql_query_t(
21✔
UNCOV
199
                ?SQL_INSERT(
21✔
200
                   "rostergroups",
201
                   ["username=%(LUser)s",
202
                    "server_host=%(LServer)s",
203
                    "jid=%(SJID)s",
204
                    "grp=%(ItemGroup)s"]))
205
      end,
206
      ItemGroups).
207

208
del_roster(LUser, LServer, LJID) ->
UNCOV
209
    SJID = jid:encode(LJID),
602✔
UNCOV
210
    ejabberd_sql:sql_query_t(
602✔
UNCOV
211
      ?SQL("delete from rosterusers"
602✔
212
           " where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")),
UNCOV
213
    ejabberd_sql:sql_query_t(
602✔
UNCOV
214
      ?SQL("delete from rostergroups"
602✔
215
           " where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
216

217
read_subscription_and_groups(LUser, LServer, LJID) ->
218
    SJID = jid:encode(LJID),
×
219
    case get_subscription(LServer, LUser, SJID) of
×
220
        {selected, [{SSubscription, SAsk}]} ->
221
            Subscription = decode_subscription(LUser, LServer, SSubscription),
×
222
            Ask = decode_ask(LUser, LServer, SAsk),
×
223
            Groups = case get_rostergroup_by_jid(LServer, LUser, SJID) of
×
224
                         {selected, JGrps} when is_list(JGrps) ->
225
                             [JGrp || {JGrp} <- JGrps];
×
226
                         _ -> []
×
227
                     end,
228
            {ok, {Subscription, Ask, Groups}};
×
229
        _ ->
230
            error
×
231
    end.
232

233
export(_Server) ->
234
    [{roster,
×
235
      fun(Host, #roster{usj = {_LUser, LServer, _LJID}} = R)
236
            when LServer == Host ->
237
              ItemVals = record_to_row(R),
×
238
              ItemGroups = R#roster.groups,
×
239
              update_roster_sql(ItemVals, ItemGroups);
×
240
        (_Host, _R) ->
241
              []
×
242
      end},
243
     {roster_version,
244
      fun(Host, #roster_version{us = {LUser, LServer}, version = Ver})
245
            when LServer == Host ->
246
              [?SQL("delete from roster_version"
×
247
                    " where username=%(LUser)s and %(LServer)H;"),
248
               ?SQL_INSERT(
×
249
                  "roster_version",
250
                  ["username=%(LUser)s",
251
                   "server_host=%(LServer)s",
252
                   "version=%(Ver)s"])];
253
         (_Host, _R) ->
254
              []
×
255
      end}].
256

257
import(_, _, _) ->
258
    ok.
×
259

260
%%%===================================================================
261
%%% Internal functions
262
%%%===================================================================
263
set_roster_version(LUser, LServer, Version) ->
UNCOV
264
    ?SQL_UPSERT_T(
1,253✔
265
       "roster_version",
266
       ["!username=%(LUser)s",
267
        "!server_host=%(LServer)s",
268
        "version=%(Version)s"]).
269

270
get_roster_jid_groups(LServer, LUser) ->
UNCOV
271
    ejabberd_sql:sql_query(
2,814✔
272
      LServer,
UNCOV
273
      ?SQL("select @(jid)s, @(grp)s from rostergroups where "
2,814✔
274
           "username=%(LUser)s and %(LServer)H")).
275

276
get_roster_groups(LServer, LUser, SJID) ->
UNCOV
277
    ejabberd_sql:sql_query_t(
2,058✔
UNCOV
278
      ?SQL("select @(grp)s from rostergroups"
2,058✔
279
           " where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
280

281
roster_subscribe({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage}) ->
UNCOV
282
    ?SQL_UPSERT_T(
1,169✔
283
       "rosterusers",
284
       ["!username=%(LUser)s",
285
        "!server_host=%(LServer)s",
286
        "!jid=%(SJID)s",
287
        "nick=%(Name)s",
288
        "subscription=%(SSubscription)s",
289
        "ask=%(SAsk)s",
290
        "askmessage=%(AskMessage)s",
291
        "server='N'",
292
        "subscribe=''",
293
        "type='item'"]).
294

295
get_roster_by_jid(LServer, LUser, SJID) ->
UNCOV
296
    ejabberd_sql:sql_query_t(
2,975✔
UNCOV
297
      ?SQL("select @(username)s, @(jid)s, @(nick)s, @(subscription)s,"
2,975✔
298
           " @(ask)s, @(askmessage)s, @(server)s, @(subscribe)s,"
299
           " @(type)s from rosterusers"
300
           " where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
301

302
get_rostergroup_by_jid(LServer, LUser, SJID) ->
303
    ejabberd_sql:sql_query(
×
304
      LServer,
305
      ?SQL("select @(grp)s from rostergroups"
×
306
           " where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
307

308
get_subscription(LServer, LUser, SJID) ->
309
    ejabberd_sql:sql_query(
×
310
      LServer,
311
      ?SQL("select @(subscription)s, @(ask)s from rosterusers "
×
312
           "where username=%(LUser)s and %(LServer)H and jid=%(SJID)s")).
313

314
update_roster_sql({LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage},
315
                  ItemGroups) ->
316
    [?SQL("delete from rosterusers where"
×
317
          " username=%(LUser)s and %(LServer)H and jid=%(SJID)s;"),
318
     ?SQL_INSERT(
×
319
        "rosterusers",
320
        ["username=%(LUser)s",
321
         "server_host=%(LServer)s",
322
         "jid=%(SJID)s",
323
         "nick=%(Name)s",
324
         "subscription=%(SSubscription)s",
325
         "ask=%(SAsk)s",
326
         "askmessage=%(AskMessage)s",
327
         "server='N'",
328
         "subscribe=''",
329
         "type='item'"]),
330
     ?SQL("delete from rostergroups where"
×
331
          " username=%(LUser)s and %(LServer)H and jid=%(SJID)s;")]
332
      ++
×
333
      [?SQL_INSERT(
×
334
          "rostergroups",
335
          ["username=%(LUser)s",
336
           "server_host=%(LServer)s",
337
           "jid=%(SJID)s",
338
           "grp=%(ItemGroup)s"])
339
       || ItemGroup <- ItemGroups].
×
340

341
raw_to_record(LServer,
342
              [User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
343
               SServer, SSubscribe, SType]) ->
344
    raw_to_record(LServer,
×
345
                  {User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
346
                   SServer, SSubscribe, SType});
347
raw_to_record(LServer,
348
              {User, SJID, Nick, SSubscription, SAsk, SAskMessage,
349
               SServer, SSubscribe, SType}) ->
UNCOV
350
    raw_to_record(LServer,
3,913✔
351
                  {User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
352
                   SServer, SSubscribe, SType});
353
raw_to_record(LServer,
354
              {User, LServer, SJID, Nick, SSubscription, SAsk, SAskMessage,
355
               _SServer, _SSubscribe, _SType}) ->
UNCOV
356
    try jid:decode(SJID) of
3,913✔
357
      JID ->
UNCOV
358
          LJID = jid:tolower(JID),
3,913✔
UNCOV
359
          Subscription = decode_subscription(User, LServer, SSubscription),
3,913✔
UNCOV
360
          Ask = decode_ask(User, LServer, SAsk),
3,913✔
UNCOV
361
          #roster{usj = {User, LServer, LJID},
3,913✔
362
                  us = {User, LServer}, jid = LJID, name = Nick,
363
                  subscription = Subscription, ask = Ask,
364
                  askmessage = SAskMessage}
365
    catch _:{bad_jid, _} ->
366
            ?ERROR_MSG("~ts", [format_row_error(User, LServer, {jid, SJID})]),
×
367
            error
×
368
    end.
369

370
record_to_row(
371
  #roster{us = {LUser, LServer},
372
          jid = JID, name = Name, subscription = Subscription,
373
          ask = Ask, askmessage = AskMessage}) ->
UNCOV
374
    SJID = jid:encode(jid:tolower(JID)),
1,169✔
UNCOV
375
    SSubscription = case Subscription of
1,169✔
UNCOV
376
                      both -> <<"B">>;
238✔
UNCOV
377
                      to -> <<"T">>;
91✔
UNCOV
378
                      from -> <<"F">>;
161✔
UNCOV
379
                      none -> <<"N">>
679✔
380
                    end,
UNCOV
381
    SAsk = case Ask of
1,169✔
382
             subscribe -> <<"S">>;
×
383
             unsubscribe -> <<"U">>;
×
UNCOV
384
             both -> <<"B">>;
35✔
UNCOV
385
             out -> <<"O">>;
231✔
UNCOV
386
             in -> <<"I">>;
182✔
UNCOV
387
             none -> <<"N">>
721✔
388
           end,
UNCOV
389
    {LUser, LServer, SJID, Name, SSubscription, SAsk, AskMessage}.
1,169✔
390

391
decode_subscription(User, Server, S) ->
UNCOV
392
    case S of
3,913✔
UNCOV
393
        <<"B">> -> both;
518✔
UNCOV
394
        <<"T">> -> to;
294✔
UNCOV
395
        <<"F">> -> from;
406✔
UNCOV
396
        <<"N">> -> none;
2,695✔
397
        <<"">> -> none;
×
398
        _ ->
399
            ?ERROR_MSG("~ts", [format_row_error(User, Server, {subscription, S})]),
×
400
            none
×
401
    end.
402

403
decode_ask(User, Server, A) ->
UNCOV
404
    case A of
3,913✔
405
        <<"S">> -> subscribe;
×
406
        <<"U">> -> unsubscribe;
×
UNCOV
407
        <<"B">> -> both;
126✔
UNCOV
408
        <<"O">> -> out;
840✔
UNCOV
409
        <<"I">> -> in;
826✔
UNCOV
410
        <<"N">> -> none;
2,121✔
411
        <<"">> -> none;
×
412
        _ ->
413
            ?ERROR_MSG("~ts", [format_row_error(User, Server, {ask, A})]),
×
414
            none
×
415
    end.
416

417
format_row_error(User, Server, Why) ->
418
    [case Why of
×
419
         {jid, JID} -> ["Malformed 'jid' field with value '", JID, "'"];
×
420
         {subscription, Sub} -> ["Malformed 'subscription' field with value '", Sub, "'"];
×
421
         {ask, Ask} -> ["Malformed 'ask' field with value '", Ask, "'"]
×
422
     end,
423
     " detected for ", User, "@", Server, " in table 'rosterusers'"].
424

425
process_rosteritems(ActionS, SubsS, AsksS, UsersS, ContactsS) ->
426
    process_rosteritems_sql(ActionS, list_to_atom(SubsS), list_to_atom(AsksS),
×
427
        list_to_binary(UsersS), list_to_binary(ContactsS)).
428

429
process_rosteritems_sql(ActionS, Subscription, Ask, SLocalJID, SJID) ->
430
    [LUser, LServer] = binary:split(SLocalJID, <<"@">>),
×
431
    SSubscription = case Subscription of
×
432
                      any -> <<"_">>;
×
433
                      both -> <<"B">>;
×
434
                      to -> <<"T">>;
×
435
                      from -> <<"F">>;
×
436
                      none -> <<"N">>
×
437
                    end,
438
    SAsk = case Ask of
×
439
             any -> <<"_">>;
×
440
             subscribe -> <<"S">>;
×
441
             unsubscribe -> <<"U">>;
×
442
             both -> <<"B">>;
×
443
             out -> <<"O">>;
×
444
             in -> <<"I">>;
×
445
             none -> <<"N">>
×
446
           end,
447
    {selected, List} = ejabberd_sql:sql_query(
×
448
      LServer,
449
      ?SQL("select @(username)s, @(jid)s from rosterusers "
×
450
           "where username LIKE %(LUser)s"
451
           " and %(LServer)H"
452
           " and jid LIKE %(SJID)s"
453
           " and subscription LIKE %(SSubscription)s"
454
           " and ask LIKE %(SAsk)s")),
455
    case ActionS of
×
456
        "delete" -> [mod_roster:del_roster(User, LServer, jid:tolower(jid:decode(Contact))) || {User, Contact} <- List];
×
457
        "list" -> ok
×
458
    end,
459
    List.
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc