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

processone / ejabberd / 1173

28 Oct 2025 11:02AM UTC coverage: 33.768% (-0.02%) from 33.79%
1173

push

github

badlop
CHANGELOG.md: Update to 25.10

15513 of 45940 relevant lines covered (33.77%)

1078.01 hits per line

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

67.63
/src/mod_privacy_sql.erl
1
%%%-------------------------------------------------------------------
2
%%% File    : mod_privacy_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_privacy_sql).
26

27

28
-behaviour(mod_privacy).
29

30
%% API
31
-export([init/2, set_default/3, unset_default/2, set_lists/1,
32
         set_list/4, get_lists/2, get_list/3, remove_lists/2,
33
         remove_list/3, import/1, export/1]).
34

35
-export([item_to_raw/1, raw_to_item/1]).
36

37
-export([sql_schemas/0]).
38

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

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

51
sql_schemas() ->
52
    [#sql_schema{
6✔
53
        version = 1,
54
        tables =
55
            [#sql_table{
56
                name = <<"privacy_default_list">>,
57
                columns =
58
                    [#sql_column{name = <<"username">>, type = text},
59
                     #sql_column{name = <<"server_host">>, type = text},
60
                     #sql_column{name = <<"name">>, type = text}],
61
                indices = [#sql_index{
62
                              columns = [<<"server_host">>, <<"username">>],
63
                              unique = true}]},
64
             #sql_table{
65
                name = <<"privacy_list">>,
66
                columns =
67
                    [#sql_column{name = <<"username">>, type = text},
68
                     #sql_column{name = <<"server_host">>, type = text},
69
                     #sql_column{name = <<"name">>, type = text},
70
                     #sql_column{name = <<"id">>, type = bigserial},
71
                     #sql_column{name = <<"created_at">>, type = timestamp,
72
                                 default = true}],
73
                indices = [#sql_index{
74
                              columns = [<<"id">>],
75
                              unique = true},
76
                           #sql_index{
77
                              columns = [<<"server_host">>, <<"username">>,
78
                                         <<"name">>],
79
                              unique = true}]},
80
             #sql_table{
81
                name = <<"privacy_list_data">>,
82
                columns =
83
                    [#sql_column{name = <<"id">>, type = bigint,
84
                                 opts = [#sql_references{
85
                                            table = <<"privacy_list">>,
86
                                            column = <<"id">>}]},
87
                     #sql_column{name = <<"t">>, type = {char, 1}},
88
                     #sql_column{name = <<"value">>, type = text},
89
                     #sql_column{name = <<"action">>, type = {char, 1}},
90
                     #sql_column{name = <<"ord">>, type = numeric},
91
                     #sql_column{name = <<"match_all">>, type = boolean},
92
                     #sql_column{name = <<"match_iq">>, type = boolean},
93
                     #sql_column{name = <<"match_message">>, type = boolean},
94
                     #sql_column{name = <<"match_presence_in">>, type = boolean},
95
                     #sql_column{name = <<"match_presence_out">>, type = boolean}],
96
                indices = [#sql_index{columns = [<<"id">>]}]}]}].
97

98
unset_default(LUser, LServer) ->
99
    case unset_default_privacy_list(LUser, LServer) of
6✔
100
        ok ->
101
            ok;
6✔
102
        _Err ->
103
            {error, db_failure}
×
104
    end.
105

106
set_default(LUser, LServer, Name) ->
107
    F = fun () ->
54✔
108
                case get_privacy_list_names_t(LUser, LServer) of
54✔
109
                    {selected, []} ->
110
                        {error, notfound};
6✔
111
                    {selected, Names} ->
112
                        case lists:member({Name}, Names) of
48✔
113
                            true ->
114
                                set_default_privacy_list(LUser, LServer, Name);
48✔
115
                            false ->
116
                                {error, notfound}
×
117
                        end
118
                end
119
        end,
120
    transaction(LServer, F).
54✔
121

122
remove_list(LUser, LServer, Name) ->
123
    F = fun () ->
18✔
124
                case get_default_privacy_list_t(LUser, LServer) of
18✔
125
                    {selected, []} ->
126
                        remove_privacy_list_t(LUser, LServer, Name);
12✔
127
                    {selected, [{Default}]} ->
128
                        if Name == Default ->
6✔
129
                                {error, conflict};
6✔
130
                           true ->
131
                                remove_privacy_list_t(LUser, LServer, Name)
×
132
                        end
133
                end
134
        end,
135
    transaction(LServer, F).
18✔
136

137
set_lists(#privacy{us = {LUser, LServer},
138
                   default = Default,
139
                   lists = Lists}) ->
140
    F = fun() ->
×
141
                lists:foreach(
×
142
                  fun({Name, List}) ->
143
                          add_privacy_list(LUser, LServer, Name),
×
144
                          {selected, [{I}]} =
×
145
                              get_privacy_list_id_t(LUser, LServer, Name),
146
                          RItems = lists:map(fun item_to_raw/1, List),
×
147
                          set_privacy_list(I, RItems),
×
148
                          if is_binary(Default) ->
×
149
                                  set_default_privacy_list(
×
150
                                    LUser, LServer, Default);
151
                             true ->
152
                                  ok
×
153
                          end
154
                  end, Lists)
155
        end,
156
    transaction(LServer, F).
×
157

158
set_list(LUser, LServer, Name, List) ->
159
    RItems = lists:map(fun item_to_raw/1, List),
462✔
160
    F = fun() ->
462✔
161
        {ID, New} = case get_privacy_list_id_t(LUser, LServer, Name) of
462✔
162
                        {selected, []} ->
163
                            add_privacy_list(LUser, LServer, Name),
444✔
164
                            {selected, [{I}]} =
444✔
165
                            get_privacy_list_id_t(LUser, LServer, Name),
166
                            {I, true};
444✔
167
                        {selected, [{I}]} -> {I, false}
18✔
168
                    end,
169
        case New of
462✔
170
            false ->
171
                set_privacy_list(ID, RItems);
18✔
172
            _ ->
173
                set_privacy_list_new(ID, RItems)
444✔
174
        end
175
        end,
176
    transaction(LServer, F).
462✔
177

178
get_list(LUser, LServer, default) ->
179
    case get_default_privacy_list(LUser, LServer) of
462✔
180
        {selected, []} ->
181
            error;
396✔
182
        {selected, [{Default}]} ->
183
            get_list(LUser, LServer, Default);
66✔
184
        _Err ->
185
            {error, db_failure}
×
186
    end;
187
get_list(LUser, LServer, Name) ->
188
    case get_privacy_list_data(LUser, LServer, Name) of
468✔
189
        {selected, []} ->
190
            error;
30✔
191
        {selected, RItems} ->
192
            {ok, {Name, lists:flatmap(fun raw_to_item/1, RItems)}};
438✔
193
        _Err ->
194
            {error, db_failure}
×
195
    end.
196

197
get_lists(LUser, LServer) ->
198
    case get_default_privacy_list(LUser, LServer) of
252✔
199
        {selected, Selected} ->
200
            Default = case Selected of
252✔
201
                          [] -> none;
204✔
202
                          [{DefName}] -> DefName
48✔
203
                      end,
204
            case get_privacy_list_names(LUser, LServer) of
252✔
205
                {selected, Names} ->
206
                    case lists:foldl(
252✔
207
                           fun(_, {error, _} = Err) ->
208
                                   Err;
×
209
                              ({Name}, Acc) ->
210
                                   case get_privacy_list_data(LUser, LServer, Name) of
444✔
211
                                       {selected, RItems} ->
212
                                           Items = lists:flatmap(
444✔
213
                                                     fun raw_to_item/1,
214
                                                     RItems),
215
                                           [{Name, Items}|Acc];
444✔
216
                                       _Err ->
217
                                           {error, db_failure}
×
218
                                   end
219
                           end, [], Names) of
220
                        {error, Reason} ->
221
                            {error, Reason};
×
222
                        Lists ->
223
                            {ok, #privacy{default = Default,
252✔
224
                                          us = {LUser, LServer},
225
                                          lists = Lists}}
226
                    end;
227
                _Err ->
228
                    {error, db_failure}
×
229
            end;
230
        _Err ->
231
            {error, db_failure}
×
232
    end.
233

234
remove_lists(LUser, LServer) ->
235
    case del_privacy_lists(LUser, LServer) of
240✔
236
        ok ->
237
            ok;
240✔
238
        _Err ->
239
            {error, db_failure}
×
240
    end.
241

242
export(Server) ->
243
    SqlType = ejabberd_option:sql_type(Server),
×
244
    case catch ejabberd_sql:sql_query(jid:nameprep(Server),
×
245
                                 [<<"select id from privacy_list order by "
246
                                    "id desc limit 1;">>]) of
247
        {selected, [<<"id">>], [[I]]} ->
248
            put(id, binary_to_integer(I));
×
249
        _ ->
250
            put(id, 0)
×
251
    end,
252
    [{privacy,
×
253
      fun(Host, #privacy{us = {LUser, LServer}, lists = Lists,
254
                         default = Default})
255
            when LServer == Host ->
256
              if Default /= none ->
257
                      [?SQL("delete from privacy_default_list where"
×
258
                            " username=%(LUser)s and %(LServer)H;"),
259
                       ?SQL_INSERT(
×
260
                          "privacy_default_list",
261
                          ["username=%(LUser)s",
262
                           "server_host=%(LServer)s",
263
                           "name=%(Default)s"])];
264
                 true ->
265
                      []
×
266
              end ++
×
267
                  lists:flatmap(
268
                    fun({Name, List}) ->
269
                            RItems = lists:map(fun item_to_raw/1, List),
×
270
                            ID = get_id(),
×
271
                            [?SQL("delete from privacy_list where"
×
272
                                  " username=%(LUser)s and %(LServer)H and"
273
                                  " name=%(Name)s;"),
274
                             ?SQL_INSERT(
×
275
                                "privacy_list",
276
                                ["username=%(LUser)s",
277
                                 "server_host=%(LServer)s",
278
                                 "name=%(Name)s",
279
                                 "id=%(ID)d"]),
280
                             ?SQL("delete from privacy_list_data where"
×
281
                                  " id=%(ID)d;")] ++
×
282
                            case SqlType of
283
                                pgsql ->
284
                                [?SQL("insert into privacy_list_data(id, t, "
×
285
                                      "value, action, ord, match_all, match_iq, "
286
                                      "match_message, match_presence_in, "
287
                                      "match_presence_out) "
288
                                      "values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
289
                                      " %(Order)d, CAST(%(MatchAll)b as boolean), CAST(%(MatchIQ)b as boolean),"
290
                                      " CAST(%(MatchMessage)b as boolean), CAST(%(MatchPresenceIn)b as boolean),"
291
                                      " CAST(%(MatchPresenceOut)b as boolean));")
292
                                 || {SType, SValue, SAction, Order,
293
                                     MatchAll, MatchIQ,
294
                                     MatchMessage, MatchPresenceIn,
295
                                     MatchPresenceOut} <- RItems];
×
296
                                _ ->
297
                                [?SQL("insert into privacy_list_data(id, t, "
×
298
                                      "value, action, ord, match_all, match_iq, "
299
                                      "match_message, match_presence_in, "
300
                                      "match_presence_out) "
301
                                      "values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
302
                                      " %(Order)d, %(MatchAll)b, %(MatchIQ)b,"
303
                                      " %(MatchMessage)b, %(MatchPresenceIn)b,"
304
                                      " %(MatchPresenceOut)b);")
305
                                 || {SType, SValue, SAction, Order,
306
                                     MatchAll, MatchIQ,
307
                                     MatchMessage, MatchPresenceIn,
308
                                     MatchPresenceOut} <- RItems]
×
309
                            end
310
                    end,
311
                    Lists);
312
         (_Host, _R) ->
313
              []
×
314
      end}].
315

316
get_id() ->
317
    ID = get(id),
×
318
    put(id, ID + 1),
×
319
    ID + 1.
×
320

321
import(_) ->
322
    ok.
×
323

324
%%%===================================================================
325
%%% Internal functions
326
%%%===================================================================
327
transaction(LServer, F) ->
328
    case ejabberd_sql:sql_transaction(LServer, F) of
534✔
329
        {atomic, Res} -> Res;
534✔
330
        {aborted, _Reason} -> {error, db_failure}
×
331
    end.
332

333
raw_to_item({SType, SValue, SAction, Order, MatchAll,
334
             MatchIQ, MatchMessage, MatchPresenceIn,
335
             MatchPresenceOut} = Row) ->
336
    try
942✔
337
        {Type, Value} = case SType of
942✔
338
                            <<"n">> -> {none, none};
186✔
339
                            <<"j">> ->
340
                                JID = jid:decode(SValue),
336✔
341
                                {jid, jid:tolower(JID)};
336✔
342
                            <<"g">> -> {group, SValue};
84✔
343
                            <<"s">> ->
344
                                case SValue of
336✔
345
                                    <<"none">> -> {subscription, none};
84✔
346
                                    <<"both">> -> {subscription, both};
84✔
347
                                    <<"from">> -> {subscription, from};
84✔
348
                                    <<"to">> -> {subscription, to}
84✔
349
                                end
350
                        end,
351
        Action = case SAction of
942✔
352
                     <<"a">> -> allow;
24✔
353
                     <<"d">> -> deny
918✔
354
                 end,
355
        [#listitem{type = Type, value = Value, action = Action,
942✔
356
                   order = Order, match_all = MatchAll, match_iq = MatchIQ,
357
                   match_message = MatchMessage,
358
                   match_presence_in = MatchPresenceIn,
359
                   match_presence_out = MatchPresenceOut}]
360
    catch _:_ ->
361
            ?WARNING_MSG("Failed to parse row: ~p", [Row]),
×
362
            []
×
363
    end.
364

365
item_to_raw(#listitem{type = Type, value = Value,
366
                      action = Action, order = Order, match_all = MatchAll,
367
                      match_iq = MatchIQ, match_message = MatchMessage,
368
                      match_presence_in = MatchPresenceIn,
369
                      match_presence_out = MatchPresenceOut}) ->
370
    {SType, SValue} = case Type of
486✔
371
                        none -> {<<"n">>, <<"">>};
96✔
372
                        jid -> {<<"j">>, jid:encode(Value)};
180✔
373
                        group -> {<<"g">>, Value};
42✔
374
                        subscription ->
375
                            case Value of
168✔
376
                              none -> {<<"s">>, <<"none">>};
42✔
377
                              both -> {<<"s">>, <<"both">>};
42✔
378
                              from -> {<<"s">>, <<"from">>};
42✔
379
                              to -> {<<"s">>, <<"to">>}
42✔
380
                            end
381
                      end,
382
    SAction = case Action of
486✔
383
                allow -> <<"a">>;
12✔
384
                deny -> <<"d">>
474✔
385
              end,
386
    {SType, SValue, SAction, Order, MatchAll, MatchIQ,
486✔
387
     MatchMessage, MatchPresenceIn, MatchPresenceOut}.
388

389
get_default_privacy_list(LUser, LServer) ->
390
    ejabberd_sql:sql_query(
714✔
391
      LServer,
392
      ?SQL("select @(name)s from privacy_default_list "
714✔
393
           "where username=%(LUser)s and %(LServer)H")).
394

395
get_default_privacy_list_t(LUser, LServer) ->
396
    ejabberd_sql:sql_query_t(
18✔
397
      ?SQL("select @(name)s from privacy_default_list "
18✔
398
           "where username=%(LUser)s and %(LServer)H")).
399

400
get_privacy_list_names(LUser, LServer) ->
401
    ejabberd_sql:sql_query(
252✔
402
      LServer,
403
      ?SQL("select @(name)s from privacy_list"
252✔
404
           " where username=%(LUser)s and %(LServer)H")).
405

406
get_privacy_list_names_t(LUser, LServer) ->
407
    ejabberd_sql:sql_query_t(
54✔
408
      ?SQL("select @(name)s from privacy_list"
54✔
409
           " where username=%(LUser)s and %(LServer)H")).
410

411
get_privacy_list_id_t(LUser, LServer, Name) ->
412
    ejabberd_sql:sql_query_t(
906✔
413
      ?SQL("select @(id)d from privacy_list"
906✔
414
           " where username=%(LUser)s and %(LServer)H and name=%(Name)s")).
415

416
get_privacy_list_data(LUser, LServer, Name) ->
417
    ejabberd_sql:sql_query(
912✔
418
      LServer,
419
      ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
912✔
420
           "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
421
           "@(match_presence_out)b from privacy_list_data "
422
           "where id ="
423
           " (select id from privacy_list"
424
           " where username=%(LUser)s and %(LServer)H and name=%(Name)s) "
425
           "order by ord")).
426

427
set_default_privacy_list(LUser, LServer, Name) ->
428
    ?SQL_UPSERT_T(
48✔
429
       "privacy_default_list",
430
       ["!username=%(LUser)s",
431
        "!server_host=%(LServer)s",
432
        "name=%(Name)s"]).
433

434
unset_default_privacy_list(LUser, LServer) ->
435
    case ejabberd_sql:sql_query(
6✔
436
           LServer,
437
           ?SQL("delete from privacy_default_list"
6✔
438
                " where username=%(LUser)s and %(LServer)H")) of
439
        {updated, _} -> ok;
6✔
440
        Err -> Err
×
441
    end.
442

443
remove_privacy_list_t(LUser, LServer, Name) ->
444
    case ejabberd_sql:sql_query_t(
12✔
445
           ?SQL("delete from privacy_list where"
12✔
446
                " username=%(LUser)s and %(LServer)H and name=%(Name)s")) of
447
        {updated, 0} -> {error, notfound};
6✔
448
        {updated, _} -> ok;
6✔
449
        Err -> Err
×
450
    end.
451

452
add_privacy_list(LUser, LServer, Name) ->
453
    ejabberd_sql:sql_query_t(
444✔
454
      ?SQL_INSERT(
444✔
455
         "privacy_list",
456
         ["username=%(LUser)s",
457
          "server_host=%(LServer)s",
458
          "name=%(Name)s"])).
459

460
set_privacy_list_new(ID, RItems) ->
461
    lists:foreach(
444✔
462
        fun({SType, SValue, SAction, Order, MatchAll, MatchIQ,
463
             MatchMessage, MatchPresenceIn, MatchPresenceOut}) ->
464
            ejabberd_sql:sql_query_t(
486✔
465
                ?SQL("insert into privacy_list_data(id, t, "
652✔
466
                     "value, action, ord, match_all, match_iq, "
467
                     "match_message, match_presence_in, match_presence_out) "
468
                     "values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
469
                     " %(Order)d, %(MatchAll)b, %(MatchIQ)b,"
470
                     " %(MatchMessage)b, %(MatchPresenceIn)b,"
471
                     " %(MatchPresenceOut)b)"))
472
        end,
473
        RItems).
474

475
calculate_difference(List1, List2) ->
476
    Set1 = gb_sets:from_list(List1),
18✔
477
    Set2 = gb_sets:from_list(List2),
18✔
478
    {gb_sets:to_list(gb_sets:subtract(Set1, Set2)),
18✔
479
     gb_sets:to_list(gb_sets:subtract(Set2, Set1))}.
480

481
set_privacy_list(ID, RItems) ->
482
    case ejabberd_sql:sql_query_t(
18✔
483
        ?SQL("select @(t)s, @(value)s, @(action)s, @(ord)d, @(match_all)b, "
28✔
484
             "@(match_iq)b, @(match_message)b, @(match_presence_in)b, "
485
             "@(match_presence_out)b from privacy_list_data "
486
             "where id=%(ID)d")) of
487
        {selected, ExistingItems} ->
488
            {ToAdd2, ToDelete2} = calculate_difference(RItems, ExistingItems),
18✔
489
            ToAdd3 = if
18✔
490
                         ToDelete2 /= [] ->
491
                             ejabberd_sql:sql_query_t(
18✔
492
                                 ?SQL("delete from privacy_list_data where id=%(ID)d")),
28✔
493
                             RItems;
18✔
494
                         true ->
495
                             ToAdd2
×
496
                     end,
497
            lists:foreach(
18✔
498
                fun({SType, SValue, SAction, Order, MatchAll, MatchIQ,
499
                     MatchMessage, MatchPresenceIn, MatchPresenceOut}) ->
500
                    ejabberd_sql:sql_query_t(
×
501
                        ?SQL("insert into privacy_list_data(id, t, "
×
502
                             "value, action, ord, match_all, match_iq, "
503
                             "match_message, match_presence_in, match_presence_out) "
504
                             "values (%(ID)d, %(SType)s, %(SValue)s, %(SAction)s,"
505
                             " %(Order)d, %(MatchAll)b, %(MatchIQ)b,"
506
                             " %(MatchMessage)b, %(MatchPresenceIn)b,"
507
                             " %(MatchPresenceOut)b)"))
508
                end,
509
                ToAdd3);
510
        Err ->
511
            Err
×
512
    end.
513

514
del_privacy_lists(LUser, LServer) ->
515
    case ejabberd_sql:sql_query(
240✔
516
           LServer,
517
           ?SQL("delete from privacy_list where username=%(LUser)s and %(LServer)H")) of
240✔
518
        {updated, _} ->
519
            case ejabberd_sql:sql_query(
240✔
520
                   LServer,
521
                   ?SQL("delete from privacy_default_list "
240✔
522
                        "where username=%(LUser)s and %(LServer)H")) of
523
                {updated, _} -> ok;
240✔
524
                Err -> Err
×
525
            end;
526
        Err ->
527
            Err
×
528
    end.
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