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

esl / MongooseIM / 20028424826

08 Dec 2025 12:39PM UTC coverage: 85.69% (-0.04%) from 85.728%
20028424826

push

github

web-flow
Merge pull request #4586 from esl/tomasz/MIM-2364-rerun-flaky-tests

[CI] Enable "Rerun Failed Tests" for Small Tests

29048 of 33899 relevant lines covered (85.69%)

50156.8 hits per line

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

74.31
/src/pubsub/node_flat.erl
1
%%% ====================================================================
2
%%% ``The contents of this file are subject to the Erlang Public License,
3
%%% Version 1.1, (the "License"); you may not use this file except in
4
%%% compliance with the License. You should have received a copy of the
5
%%% Erlang Public License along with this software. If not, it can be
6
%%% retrieved via the world wide web at http://www.erlang.org/.
7
%%%
8
%%%
9
%%% Software distributed under the License is distributed on an "AS IS"
10
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11
%%% the License for the specific language governing rights and limitations
12
%%% under the License.
13
%%%
14
%%%
15
%%% The Initial Developer of the Original Code is ProcessOne.
16
%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne
17
%%% All Rights Reserved.''
18
%%% This software is copyright 2006-2015, ProcessOne.
19
%%%
20
%%% @copyright 2006-2015 ProcessOne
21
%%% @author Christophe Romain <christophe.romain@process-one.net>
22
%%%   [http://www.process-one.net/]
23
%%% @end
24
%%% ====================================================================
25

26
%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin.
27
%%% <p>It is used as a default for all unknown PubSub node type.  It can serve
28
%%% as a developer basis and reference to build its own custom pubsub node
29
%%% types.</p>
30
%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p>
31

32
-module(node_flat).
33
-behaviour(gen_pubsub_node).
34
-author('christophe.romain@process-one.net').
35

36
-include("pubsub.hrl").
37
-include("jlib.hrl").
38
-include("mongoose.hrl").
39

40
-export([based_on/0, init/3, terminate/2, options/0, features/0,
41
         create_node_permission/6, create_node/2, delete_node/1,
42
         purge_node/2, subscribe_node/8, unsubscribe_node/4,
43
         publish_item/9, delete_item/4, remove_extra_items/3,
44
         get_entity_affiliations/2, get_node_affiliations/1,
45
         get_affiliation/2, set_affiliation/3,
46
         get_entity_subscriptions/2, get_node_subscriptions/1,
47
         get_subscriptions/2, set_subscriptions/4,
48
         get_pending_nodes/2,
49
         get_items_if_authorised/3, get_items/3, get_item/7,
50
         get_item/2, set_item/1, get_item_name/3, node_to_path/1,
51
         path_to_node/1, can_fetch_item/2, is_subscribed/1,
52
         should_delete_when_owner_removed/0, remove_user/2]).
53

54
-define(MOD_PUBSUB_DB_BACKEND, mod_pubsub_db_backend).
55
-ignore_xref([
56
    {?MOD_PUBSUB_DB_BACKEND, create_node, 2},
57
    {?MOD_PUBSUB_DB_BACKEND, del_item, 2},
58
    {?MOD_PUBSUB_DB_BACKEND, get_states, 2},
59
    {?MOD_PUBSUB_DB_BACKEND, remove_items, 3},
60
    {?MOD_PUBSUB_DB_BACKEND, get_state, 2},
61
    {?MOD_PUBSUB_DB_BACKEND, del_node, 1},
62
    {?MOD_PUBSUB_DB_BACKEND, get_affiliation, 2},
63
    {?MOD_PUBSUB_DB_BACKEND, get_states_by_bare, 1},
64
    {?MOD_PUBSUB_DB_BACKEND, get_states_by_bare_and_full, 1},
65
    {?MOD_PUBSUB_DB_BACKEND, get_item, 2},
66
    {?MOD_PUBSUB_DB_BACKEND, get_node_entity_subscriptions, 2},
67
    {?MOD_PUBSUB_DB_BACKEND, delete_subscription, 2},
68
    {?MOD_PUBSUB_DB_BACKEND, add_subscription, 5},
69
    {?MOD_PUBSUB_DB_BACKEND, del_items, 2},
70
    {?MOD_PUBSUB_DB_BACKEND, get_states, 1},
71
    {?MOD_PUBSUB_DB_BACKEND, get_states_by_lus, 1},
72
    {?MOD_PUBSUB_DB_BACKEND, get_items, 2},
73
    {?MOD_PUBSUB_DB_BACKEND, delete_subscription, 3},
74
    {?MOD_PUBSUB_DB_BACKEND, delete_all_subscriptions, 2},
75
    {?MOD_PUBSUB_DB_BACKEND, set_item, 1},
76
    {?MOD_PUBSUB_DB_BACKEND, set_affiliation, 3},
77
    {?MOD_PUBSUB_DB_BACKEND, delete_node, 1},
78
    {?MOD_PUBSUB_DB_BACKEND, find_nodes_by_affiliated_user, 1},
79
    {?MOD_PUBSUB_DB_BACKEND, dirty, 2},
80
    {?MOD_PUBSUB_DB_BACKEND, delete_user_subscriptions, 1},
81
    {?MOD_PUBSUB_DB_BACKEND, remove_all_items, 1},
82
    {?MOD_PUBSUB_DB_BACKEND, get_node_subscriptions, 1},
83
    {?MOD_PUBSUB_DB_BACKEND, get_idxs_of_own_nodes_with_pending_subs, 1},
84
    {?MOD_PUBSUB_DB_BACKEND, add_item, 3},
85
    {?MOD_PUBSUB_DB_BACKEND, update_subscription, 4},
86
    can_fetch_item/2, is_subscribed/1, set_subscriptions/4
87
]).
88

89
based_on() ->  none.
×
90

91
init(_Host, _ServerHost, _Opts) ->
92
    ok.
385✔
93

94
terminate(_Host, _ServerHost) ->
95
    ok.
385✔
96

97
options() ->
98
    [{deliver_payloads, true},
1,156✔
99
        {notify_config, false},
100
        {notify_delete, false},
101
        {notify_retract, false},
102
        {purge_offline, false},
103
        {persist_items, true},
104
        {max_items, ?MAXITEMS},
105
        {subscribe, true},
106
        {access_model, open},
107
        {roster_groups_allowed, []},
108
        {publish_model, publishers},
109
        {notification_type, headline},
110
        {max_payload_size, ?MAX_PAYLOAD_SIZE},
111
        {send_last_published_item, never},
112
        {deliver_notifications, true},
113
        {presence_based_delivery, false}].
114

115
features() ->
116
    [<<"create-nodes">>,
5,672✔
117
        <<"auto-create">>,
118
        <<"access-authorize">>,
119
        <<"delete-nodes">>,
120
        <<"delete-items">>,
121
        <<"get-pending">>,
122
        <<"instant-nodes">>,
123
        <<"manage-subscriptions">>,
124
        <<"modify-affiliations">>,
125
        <<"outcast-affiliation">>,
126
        <<"persistent-items">>,
127
        <<"publish">>,
128
        <<"publish-only-affiliation">>,
129
        <<"purge-nodes">>,
130
        <<"retract-items">>,
131
        <<"retrieve-affiliations">>,
132
        <<"retrieve-items">>,
133
        <<"retrieve-subscriptions">>,
134
        <<"subscribe">>,
135
        <<"subscription-notifications">>,
136
        <<"subscription-options">>
137
    ].
138

139

140
%% @doc Checks if the current user has the permission to create the requested node
141
%% <p>In flat node, any unused node name is allowed. The access parameter is also
142
%% checked. This parameter depends on the value of the
143
%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p>
144
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) ->
145
    Allowed = case jid:to_lower(Owner) of
812✔
146
        {<<"">>, Host, <<"">>} ->
147
            true; % pubsub service always allowed
×
148
        _ ->
149
            {ok, HostType} = mongoose_domain_api:get_domain_host_type(ServerHost),
812✔
150
            acl:match_rule(HostType, ServerHost, Access, Owner) =:= allow
812✔
151
    end,
152
    {result, Allowed}.
812✔
153

154
create_node(Nidx, Owner) ->
155
    mod_pubsub_db_backend:create_node(Nidx, jid:to_lower(Owner)),
1,622✔
156
    {result, {default, broadcast}}.
1,608✔
157

158
delete_node(Nodes) ->
159
    Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
1,080✔
160
            lists:map(fun (S) -> {J, S} end, Ss)
1,654✔
161
    end,
162
    Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
1,080✔
163
                    {ok, States} = mod_pubsub_db_backend:del_node(Nidx),
1,087✔
164
                    {PubsubNode, lists:flatmap(Tr, States)}
1,059✔
165
            end, Nodes),
166
    {result, {default, broadcast, Reply}}.
1,052✔
167

168
%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p>
169
%% <p>The mechanism works as follow:
170
%% <ul>
171
%% <li>The main PubSub module prepares the subscription and passes the
172
%% result of the preparation as a record.</li>
173
%% <li>This function gets the prepared record and several other parameters and
174
%% can decide to:<ul>
175
%%  <li>reject the subscription;</li>
176
%%  <li>allow it as is, letting the main module perform the database
177
%%  persistance;</li>
178
%%  <li>allow it, modifying the record. The main module will store the
179
%%  modified record;</li>
180
%%  <li>allow it, but perform the needed persistance operations.</li></ul>
181
%% </li></ul></p>
182
%% <p>The selected behaviour depends on the return parameter:
183
%%  <ul>
184
%%   <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No
185
%%   subscription will actually be performed.</li>
186
%%   <li><tt>true</tt>: Subscribe operation is allowed, based on the
187
%%   unmodified record passed in parameter <tt>SubscribeResult</tt>. If this
188
%%   parameter contains an error, no subscription will be performed.</li>
189
%%   <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but
190
%%   the {@link mod_pubsub:pubsubState()} record returned replaces the value
191
%%   passed in parameter <tt>SubscribeResult</tt>.</li>
192
%%   <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the
193
%%   {@link mod_pubsub:pubsubState()} will be considered as already stored and
194
%%   no further persistance operation will be performed. This case is used,
195
%%   when the plugin module is doing the persistance by itself or when it want
196
%%   to completly disable persistance.</li></ul>
197
%% </p>
198
%% <p>In the default plugin module, the record is unchanged.</p>
199
subscribe_node(Nidx, Sender, Subscriber, AccessModel,
200
            SendLast, PresenceSubscription, RosterGroup, Options) ->
201
    SenderMatchesSubscriber = jid:are_bare_equal(Sender, Subscriber),
634✔
202
    {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, Subscriber),
634✔
203
    {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, Subscriber),
634✔
204
    Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
634✔
205
    PendingSubscription = lists:any(fun
634✔
206
                ({pending, _, _}) -> true;
×
207
                (_) -> false
×
208
            end,
209
            Subscriptions),
210
    case authorize_subscription(SenderMatchesSubscriber, Affiliation, PendingSubscription,
634✔
211
                               AccessModel, PresenceSubscription, RosterGroup, Whitelisted) of
212
        ok ->
213
            {NewSub, SubId} = case Subscriptions of
620✔
214
                [{subscribed, Id, _}|_] ->
215
                    {subscribed, Id};
×
216
                [] ->
217
                    Id = make_subid(),
620✔
218
                    Sub = access_model_to_subscription(AccessModel),
620✔
219
                    mod_pubsub_db_backend:add_subscription(Nidx, Subscriber, Sub, Id, Options),
620✔
220
                    {Sub, Id}
620✔
221
            end,
222
            case {NewSub, SendLast} of
620✔
223
                {subscribed, never} ->
224
                    {result, {default, subscribed, SubId}};
502✔
225
                {subscribed, _} ->
226
                    {result, {default, subscribed, SubId, send_last}};
55✔
227
                {_, _} ->
228
                    {result, {default, pending, SubId}}
63✔
229
            end;
230
        {error, _} = Err ->
231
            Err
14✔
232
    end.
233

234
-spec access_model_to_subscription(accessModel()) -> pending | subscribed.
235
access_model_to_subscription(authorize) -> pending;
63✔
236
access_model_to_subscription(_) -> subscribed.
557✔
237

238
-spec authorize_subscription(SenderMatchesSubscriber :: boolean(),
239
                            Affiliation :: affiliation(),
240
                            PendingSubscription :: boolean(),
241
                            AccessModel :: accessModel(),
242
                            PresenceSubscription :: boolean(),
243
                            RosterGroup :: boolean(),
244
                            Whitelisted :: boolean()) -> ok | {error, exml:element()}.
245
authorize_subscription(false, _Affiliation, _PendingSubscription, _AccessModel,
246
                       _PresenceSubscription, _RosterGroup, _Whitelisted) ->
247
    {error, ?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"invalid-jid">>)};
×
248
authorize_subscription(_SenderMatchesSubscriber, Affiliation, _PendingSubscription, _AccessModel,
249
                       _PresenceSubscription, _RosterGroup, _Whitelisted)
250
  when (Affiliation == outcast) or (Affiliation == publish_only) ->
251
    {error, mongoose_xmpp_errors:forbidden()};
×
252
authorize_subscription(_SenderMatchesSubscriber, _Affiliation, true, _AccessModel,
253
                       _PresenceSubscription, _RosterGroup, _Whitelisted) ->
254
    {error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"pending-subscription">>)};
×
255
authorize_subscription(_SenderMatchesSubscriber, Affiliation, _PendingSubscription, presence,
256
                       false, _RosterGroup, _Whitelisted) when Affiliation /= owner ->
257
    {error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"presence-subscription-required">>)};
×
258
authorize_subscription(_SenderMatchesSubscriber, Affiliation, _PendingSubscription, roster,
259
                       _PresenceSubscription, false, _Whitelisted) when Affiliation /= owner ->
260
    {error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"not-in-roster-group">>)};
×
261
authorize_subscription(_SenderMatchesSubscriber, Affiliation, _PendingSubscription, whitelist,
262
                       _PresenceSubscription, _RosterGroup, false) when Affiliation /= owner ->
263
    {error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_allowed()), <<"closed-node">>)};
14✔
264
authorize_subscription(_SenderMatchesSubscriber, _Affiliation, _PendingSubscription, _AccessModel,
265
                       _PresenceSubscription, _RosterGroup, _Whitelisted) ->
266
    ok.
620✔
267

268
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
269
unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
270
    SenderMatchesSubscriber = jid:are_bare_equal(Subscriber, Sender),
49✔
271
    {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, Subscriber),
49✔
272
    SubIdExists = case SubId of
49✔
273
                      <<>> -> false;
35✔
274
                      Binary when is_binary(Binary) -> true;
×
275
                      _ -> false
14✔
276
                  end,
277
    case authenticate_unsubscribe(SenderMatchesSubscriber, Subscriptions, SubIdExists, SubId) of
49✔
278
        sub_id_exists ->
279
            case lists:keyfind(SubId, 2, Subscriptions) of
×
280
                false ->
281
                    {error,
×
282
                     ?ERR_EXTENDED((mongoose_xmpp_errors:unexpected_request_cancel()),
283
                                   <<"not-subscribed">>)};
284
                _S ->
285
                    mod_pubsub_db_backend:delete_subscription(Nidx, Subscriber, SubId),
×
286
                    {result, default}
×
287
            end;
288
        remove_all_subs ->
289
            mod_pubsub_db_backend:delete_all_subscriptions(Nidx, Subscriber),
14✔
290
            {result, default};
14✔
291
        remove_only_sub ->
292
            mod_pubsub_db_backend:delete_all_subscriptions(Nidx, Subscriber),
35✔
293
            {result, default}
35✔
294
    end.
295

296
authenticate_unsubscribe(false, _Subscriptions, _SubIdExists, _SubId) ->
297
    {error, mongoose_xmpp_errors:forbidden()};
×
298
authenticate_unsubscribe(_SenderMatchesSubscriber, [], _SubIdExists, _SubId) ->
299
    %% Requesting entity is not a subscriber
300
    {error, ?ERR_EXTENDED((mongoose_xmpp_errors:unexpected_request_cancel()), <<"not-subscribed">>)};
×
301
authenticate_unsubscribe(_SenderMatchesSubscriber, _Subscriptions, true, _SubId) ->
302
    %% Subid supplied, so use that.
303
    sub_id_exists;
×
304
authenticate_unsubscribe(_SenderMatchesSubscriber, _Subscriptions, _SubIdExists, all) ->
305
    %% Asking to remove all subscriptions to the given node
306
    remove_all_subs;
14✔
307
authenticate_unsubscribe(_SenderMatchesSubscriber, [_], _SubIdExists, _SubId) ->
308
    %% No subid supplied, but there's only one matching subscription
309
    remove_only_sub;
35✔
310
authenticate_unsubscribe(_SenderMatchesSubscriber, _Subscriptions, _SubIdExists, _SubId) ->
311
    {error, ?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"subid-required">>)}.
×
312

313
%% @doc <p>Publishes the item passed as parameter.</p>
314
%% <p>The mechanism works as follow:
315
%% <ul>
316
%% <li>The main PubSub module prepares the item to publish and passes the
317
%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li>
318
%% <li>This function gets the prepared record and several other parameters and can decide to:<ul>
319
%%  <li>reject the publication;</li>
320
%%  <li>allow the publication as is, letting the main module perform the database persistance;</li>
321
%%  <li>allow the publication, modifying the record.
322
%%      The main module will store the modified record;</li>
323
%%  <li>allow it, but perform the needed persistance operations.</li></ul>
324
%% </li></ul></p>
325
%% <p>The selected behaviour depends on the return parameter:
326
%%  <ul>
327
%%   <li><tt>{error, Reason}</tt>: an iq error result will be return. No
328
%%   publication is actually performed.</li>
329
%%   <li><tt>true</tt>: Publication operation is allowed, based on the
330
%%   unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt>
331
%%   parameter contains an error, no subscription will actually be
332
%%   performed.</li>
333
%%   <li><tt>{true, Item}</tt>: Publication operation is allowed, but the
334
%%   {@link mod_pubsub:pubsubItem()} record returned replaces the value passed
335
%%   in parameter <tt>Item</tt>. The persistance will be performed by the main
336
%%   module.</li>
337
%%   <li><tt>{true, done}</tt>: Publication operation is allowed, but the
338
%%   {@link mod_pubsub:pubsubItem()} will be considered as already stored and
339
%%   no further persistance operation will be performed. This case is used,
340
%%   when the plugin module is doing the persistance by itself or when it want
341
%%   to completly disable persistance.</li></ul>
342
%% </p>
343
%% <p>In the default plugin module, the record is unchanged.</p>
344
publish_item(_ServerHost, Nidx, Publisher, PublishModel, MaxItems, ItemId, ItemPublisher,
345
             Payload, _PublishOptions) ->
346
    %% vvvvvvvvvvvv
347
    BarePublisher = jid:to_bare(Publisher),
923✔
348
    SubKey = jid:to_lower(Publisher),
923✔
349
    GenKey = jid:to_lower(BarePublisher),
923✔
350
    {ok, GenState} = mod_pubsub_db_backend:get_state(Nidx, GenKey),
923✔
351
    SubState = case Publisher#jid.lresource of
923✔
352
                   <<>> ->
353
                       GenState;
×
354
                   _ ->
355
                       {ok, SubState0} = mod_pubsub_db_backend:get_state(Nidx, SubKey),
923✔
356
                       SubState0
923✔
357
               end,
358
    {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, GenKey),
923✔
359
    Subscribed = case PublishModel of
923✔
360
        subscribers -> is_subscribed(GenState#pubsub_state.subscriptions) orelse
×
361
                       is_subscribed(SubState#pubsub_state.subscriptions);
×
362
        _ -> undefined
923✔
363
    end,
364
    %% ^^^^^^^^^^^^ TODO: Whole this block may be refactored when we migrate pubsub_item
365
    %%                    as GenState won't be needed anymore.
366
    Allowed = (PublishModel == open) or
923✔
367
              (PublishModel == publishers) and
368
              ( (Affiliation == owner) or
369
                (Affiliation == publisher) or
370
                (Affiliation == publish_only) ) or
371
              (Subscribed == true),
372
    case Allowed of
923✔
373
        false  ->
374
            {error, mongoose_xmpp_errors:forbidden()};
42✔
375
        true ->
376
            case MaxItems > 0 of
881✔
377
               true ->
378
                   Now = os:system_time(microsecond),
860✔
379
                   Item = make_pubsub_item(Nidx, ItemId, Now, SubKey, GenKey,
860✔
380
                                           Payload, Publisher, ItemPublisher),
381
                   Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
860✔
382
                   {result, {_NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
860✔
383
                   mod_pubsub_db_backend:add_item(Nidx, GenKey, Item),
860✔
384
                   mod_pubsub_db_backend:remove_items(Nidx, GenKey, OI),
860✔
385
                   {result, {default, broadcast, OI}};
860✔
386
               false ->
387
                   {result, {default, broadcast, []}}
21✔
388
            end
389
    end.
390

391
make_pubsub_item(Nidx, ItemId, Now, SubKey, GenKey, Payload, Publisher, ItemPublisher) ->
392
    PubId = {Now, SubKey},
860✔
393
    case get_item(Nidx, ItemId) of
860✔
394
        {result, OldItem} ->
395
            OldItem#pubsub_item{modification = PubId,
14✔
396
                                payload = Payload};
397
        _ ->
398
            Publisher0 = case ItemPublisher of
846✔
399
                             true -> Publisher;
28✔
400
                             false -> undefined
818✔
401
                         end,
402
            #pubsub_item{itemid = {ItemId, Nidx},
846✔
403
                         creation = {Now, GenKey},
404
                         modification = PubId,
405
                         publisher = Publisher0,
406
                         payload = Payload}
407
    end.
408

409
%% @doc <p>This function is used to remove extra items, most notably when the
410
%% maximum number of items has been reached.</p>
411
%% <p>This function is used internally by the core PubSub module, as no
412
%% permission check is performed.</p>
413
%% <p>In the default plugin module, the oldest items are removed, but other
414
%% rules can be used.</p>
415
%% <p>If another PubSub plugin wants to delegate the item removal (and if the
416
%% plugin is using the default pubsub storage), it can implements this function like this:
417
%% ```remove_extra_items(Nidx, MaxItems, ItemIds) ->
418
%%           node_default:remove_extra_items(Nidx, MaxItems, ItemIds).'''</p>
419
remove_extra_items(_Nidx, unlimited, ItemIds) ->
420
    {result, {ItemIds, []}};
×
421
remove_extra_items(Nidx, MaxItems, ItemIds) ->
422
    NewItems = lists:sublist(ItemIds, MaxItems),
860✔
423
    OldItems = lists:nthtail(length(NewItems), ItemIds),
860✔
424
    del_items(Nidx, OldItems),
860✔
425
    {result, {NewItems, OldItems}}.
860✔
426

427
%% @doc <p>Triggers item deletion.</p>
428
%% <p>Default plugin: The user performing the deletion must be the node owner
429
%% or a publisher, or PublishModel being open.</p>
430
delete_item(Nidx, Publisher, PublishModel, ItemId) ->
431
    GenKey = jid:to_bare(jid:to_lower(Publisher)),
77✔
432
    {ok, GenState} = mod_pubsub_db_backend:get_state(Nidx, GenKey),
77✔
433
    #pubsub_state{affiliation = Affiliation, items = Items} = GenState,
77✔
434
    Allowed = get_permition(Affiliation, PublishModel, Nidx, ItemId, GenKey),
77✔
435
    case Allowed of
77✔
436
        false ->
437
            {error, mongoose_xmpp_errors:forbidden()};
14✔
438
        true ->
439
            case lists:member(ItemId, Items) of
63✔
440
                true ->
441
                    del_item(Nidx, ItemId),
63✔
442
                    mod_pubsub_db_backend:remove_items(Nidx, GenKey, [ItemId]),
63✔
443
                    {result, {default, broadcast}};
63✔
444
                false ->
445
                    delete_foreign_item(Nidx, ItemId, Affiliation)
×
446
            end
447
    end.
448

449
get_permition(publisher, _PublishModel, _Nidx, _ItemId, _GenKey) -> true;
×
450
get_permition(owner, _PublishModel, _Nidx, _ItemId, _GenKey) -> true;
35✔
451
get_permition(_Affiliation, open, _Nidx, _ItemId, _GenKey) -> true;
14✔
452
get_permition(_Affiliation, _PublishModel, Nidx, ItemId, GenKey) ->
453
    case get_item(Nidx, ItemId) of
28✔
454
        {result, #pubsub_item{creation = {_, GenKey}}} -> true;
14✔
455
        _ -> false
14✔
456
    end.
457

458
%% Delete an item that does not belong to the user
459
%% TODO: Whole function should be moved to DB layer but we need to migrate pubsub_item first
460
delete_foreign_item(Nidx, ItemId, owner) ->
461
    {ok, States} = mod_pubsub_db_backend:get_states(Nidx),
×
462
    lists:foldl(fun
×
463
                    (#pubsub_state{stateid = {User, _}, items = PI}, Res) ->
464
                        case lists:member(ItemId, PI) of
×
465
                            true ->
466
                                del_item(Nidx, ItemId),
×
467
                                mod_pubsub_db_backend:remove_items(Nidx, User, [ItemId]),
×
468
                                {result, {default, broadcast}};
×
469
                            false ->
470
                                Res
×
471
                        end;
472
                    (_, Res) ->
473
                        Res
×
474
                end,
475
                {error, mongoose_xmpp_errors:item_not_found()}, States);
476
delete_foreign_item(_Nidx, _ItemId, _Affiliation) ->
477
    {error, mongoose_xmpp_errors:item_not_found()}.
×
478

479
purge_node(Nidx, Owner) ->
480
    case mod_pubsub_db_backend:get_affiliation(Nidx, jid:to_lower(Owner)) of
56✔
481
        {ok, owner} ->
482
            {ok, States} = mod_pubsub_db_backend:get_states(Nidx),
28✔
483
            lists:foreach(fun
28✔
484
                    (#pubsub_state{items = []}) ->
485
                        ok;
×
486
                    (#pubsub_state{items = Items}) ->
487
                        del_items(Nidx, Items)
28✔
488
                end,
489
                States),
490
            mod_pubsub_db_backend:remove_all_items(Nidx),
28✔
491
            {result, {default, broadcast}};
28✔
492
        _ ->
493
            {error, mongoose_xmpp_errors:forbidden()}
28✔
494
    end.
495

496
get_entity_affiliations(Host, #jid{} = Owner) ->
497
    get_entity_affiliations(Host, jid:to_lower(Owner));
×
498
get_entity_affiliations(Host, LOwner) ->
499
    {ok, States} = mod_pubsub_db_backend:get_states_by_bare(LOwner),
2,562✔
500
    HT = mod_pubsub:host_to_host_type(Host),
2,562✔
501
    NodeTree = mod_pubsub:tree(HT),
2,562✔
502
    Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
2,562✔
503
                                case gen_pubsub_nodetree:get_node(NodeTree, N) of
717✔
504
                                    #pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc];
605✔
505
                                    _ -> Acc
112✔
506
                                end
507
                        end,
508
                        [], States),
509
    {result, Reply}.
2,562✔
510

511
get_node_affiliations(Nidx) ->
512
    {ok, States} = mod_pubsub_db_backend:get_states(Nidx),
42✔
513
    Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end,
42✔
514
    {result, lists:map(Tr, States)}.
42✔
515

516
get_affiliation(Nidx, Owner) ->
517
    {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, jid:to_lower(Owner)),
1,339✔
518
    {result, Affiliation}.
1,339✔
519

520
set_affiliation(Nidx, JID, Affiliation) ->
521
    mod_pubsub_db_backend:set_affiliation(Nidx, JID, Affiliation).
352✔
522

523
get_entity_subscriptions(Host, Owner) ->
524
    LOwner = jid:to_lower(Owner),
3,518✔
525
    States = case Owner#jid.lresource of
3,518✔
526
                 <<>> ->
527
                     {ok, States0} = mod_pubsub_db_backend:get_states_by_lus(LOwner),
90✔
528
                     States0;
90✔
529
                 _ ->
530
                     {ok, States0} = mod_pubsub_db_backend:get_states_by_bare_and_full(LOwner),
3,428✔
531
                     States0
3,428✔
532
             end,
533
    HT = mod_pubsub:host_to_host_type(Host),
3,518✔
534
    NodeTree = mod_pubsub:tree(HT),
3,518✔
535
    Reply = lists:foldl(fun (PubSubState, Acc) ->
3,518✔
536
                                get_entity_subscriptions_loop(NodeTree, PubSubState, Acc)
561✔
537
                        end,
538
                        [], States),
539
    {result, Reply}.
3,518✔
540

541
get_entity_subscriptions_loop(NodeTree, #pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
542
    case gen_pubsub_nodetree:get_node(NodeTree, N) of
561✔
543
        #pubsub_node{} = Node ->
544
            lists:foldl(fun ({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, J} | Acc2] end, Acc, Ss);
561✔
545
        _ ->
546
            Acc
×
547
    end.
548

549
get_node_subscriptions(Nidx) ->
550
    {ok, Subscriptions} = mod_pubsub_db_backend:get_node_subscriptions(Nidx),
4,125✔
551
    {result, Subscriptions}.
4,095✔
552

553
get_subscriptions(Nidx, #jid{} = Owner) ->
554
    get_subscriptions(Nidx, jid:to_lower(Owner));
35✔
555
get_subscriptions(Nidx, LOwner) ->
556
    {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LOwner),
161✔
557
    {result, Subscriptions}.
161✔
558

559
set_subscriptions(Nidx, #jid{} = Owner, Subscription, SubId) ->
560
    set_subscriptions(Nidx, jid:to_lower(Owner), Subscription, SubId);
35✔
561
set_subscriptions(Nidx, LOwner, Subscription, SubId) ->
562
    {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LOwner),
91✔
563
    case {SubId, Subscriptions} of
91✔
564
        {_, []} ->
565
            case Subscription of
42✔
566
                none ->
567
                    {error,
×
568
                        ?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"not-subscribed">>)};
569
                _ ->
570
                    NewSubId = make_subid(),
42✔
571
                    mod_pubsub_db_backend:add_subscription(Nidx, LOwner, Subscription, NewSubId, [])
42✔
572
            end;
573
        {<<>>, [{_, SID, _}]} ->
574
            case Subscription of
14✔
575
                none -> mod_pubsub_db_backend:delete_subscription(Nidx, LOwner, SID);
14✔
576
                _ -> mod_pubsub_db_backend:update_subscription(Nidx, LOwner, Subscription, SID)
×
577
            end;
578
        {<<>>, [_ | _]} ->
579
            {error,
×
580
                ?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"subid-required">>)};
581
        _ ->
582
            case Subscription of
35✔
583
                none -> mod_pubsub_db_backend:delete_subscription(Nidx, LOwner, SubId);
14✔
584
                _ -> mod_pubsub_db_backend:update_subscription(Nidx, LOwner, Subscription, SubId)
21✔
585
            end
586
    end.
587

588
%% @doc <p>Returns a list of Owner's nodes on Host with pending
589
%% subscriptions.</p>
590
get_pending_nodes(Host, Owner) ->
591
    LOwner = jid:to_lower(Owner),
14✔
592
    {ok, Nidxs} = mod_pubsub_db_backend:get_idxs_of_own_nodes_with_pending_subs(LOwner),
14✔
593
    HT = mod_pubsub:host_to_host_type(Host),
14✔
594
    NodeTree = mod_pubsub:tree(HT),
14✔
595
    {result,
14✔
596
     lists:foldl(fun(N, Acc) ->
597
                         case gen_pubsub_nodetree:get_node(NodeTree, N) of
20✔
598
                             #pubsub_node{nodeid = {_, Node}} -> [Node | Acc];
20✔
599
                             _ -> Acc
×
600
                         end
601
                 end, [], Nidxs)}.
602

603
%% @doc Returns the list of stored items for a given node.
604
%% <p>For the default PubSub module, items are stored in Mnesia database.</p>
605
%% <p>We can consider that the pubsub_item table have been created by the main
606
%% mod_pubsub module.</p>
607
%% <p>PubSub plugins can store the items where they wants (for example in a
608
%% relational database), or they can even decide not to persist any items.</p>
609
get_items(Nidx, _From, Opts) ->
610
    %% TODO add tests and implementation supporting RSM
611
    %% when looking for node's items.
612
    %% Currently the RSM attribute is ignored
613
    case mod_pubsub_db_backend:get_items(Nidx, Opts) of
377✔
614
        {ok, Result} ->
615
            {result, Result};
377✔
616
        {error, item_not_found} ->
617
            {error, mongoose_xmpp_errors:item_not_found()}
×
618
    end.
619

620
get_items_if_authorised(Nidx, JID, #{access_model := AccessModel,
621
                                     presence_permission := PresenceSubscription,
622
                                     roster_permission := RosterGroup,
623
                                     rsm := RSM} = Opts) ->
624
    LJID = jid:to_lower(JID),
287✔
625
    {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, LJID),
287✔
626
    {ok, BareSubscriptions}
287✔
627
    = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, jid:to_bare(LJID)),
628
    {ok, FullSubscriptions}
287✔
629
    = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LJID),
630
    Whitelisted = can_fetch_item(Affiliation, BareSubscriptions) orelse
287✔
631
                  can_fetch_item(Affiliation, FullSubscriptions),
175✔
632
    case authorize_get_item(Affiliation, AccessModel, PresenceSubscription,
287✔
633
                            RosterGroup, Whitelisted) of
634
        ok ->
635
            LowLevelOpts = #{rsm => RSM,
287✔
636
                             max_items => maps:get(max_items, Opts, undefined),
637
                             item_ids => maps:get(item_ids, Opts, undefined)},
638
            get_items(Nidx, JID, LowLevelOpts);
287✔
639
        {error, _} = Err -> Err
×
640
    end.
641

642
%% @doc <p>Returns an item (one item list), given its reference.</p>
643

644
get_item(Nidx, ItemId) ->
645
    case mod_pubsub_db_backend:get_item(Nidx, ItemId) of
916✔
646
        {ok, Item} ->
647
            {result, Item};
56✔
648
        {error, Error} ->
649
            {error, Error}
860✔
650
    end.
651

652
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
653
    {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, JID),
×
654
    {ok, Subscriptions}
×
655
    = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, jid:to_bare(JID)),
656
    Whitelisted = can_fetch_item(Affiliation, Subscriptions),
×
657
    case authorize_get_item(Affiliation, AccessModel, PresenceSubscription,
×
658
                           RosterGroup, Whitelisted) of
659
        ok -> get_item(Nidx, ItemId);
×
660
        {error, _} = Err -> Err
×
661
    end.
662

663
authorize_get_item(Affiliation, _AccessModel, _PresenceSubscription, _RosterGroup, _Whitelisted)
664
  when (Affiliation == outcast) or (Affiliation == publish_only) ->
665
    {error, mongoose_xmpp_errors:forbidden()};
×
666
authorize_get_item(_Affiliation, presence, false, _RosterGroup, _Whitelisted) ->
667
    {error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"presence-subscription-required">>)};
×
668
authorize_get_item(_Affiliation, roster, _PresenceSubscription, false, _Whitelisted) ->
669
    {error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"not-in-roster-group">>)};
×
670
authorize_get_item(_Affiliation, whitelist, _PresenceSubscription, _RosterGroup, false) ->
671
    {error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_allowed()), <<"closed-node">>)};
×
672
authorize_get_item(_Affiliation, authorize, _PresenceSubscription, _RosterGroup, false) ->
673
    {error, mongoose_xmpp_errors:forbidden()};
×
674
authorize_get_item(_Affiliation, _AccessModel, _PresenceSubscription, _RosterGroup, _Whitelisted) ->
675
    ok.
287✔
676

677
%% @doc <p>Write an item into database.</p>
678
set_item(Item) when is_record(Item, pubsub_item) ->
679
    mod_pubsub_db_backend:set_item(Item).
×
680
%set_item(_) -> {error, mongoose_xmpp_errors:internal_server_error()}.
681

682
%% @doc <p>Delete an item from database.</p>
683
del_item(Nidx, ItemId) ->
684
    mod_pubsub_db_backend:del_item(Nidx, ItemId).
63✔
685

686
del_items(Nidx, ItemIds) ->
687
    mod_pubsub_db_backend:del_items(Nidx, ItemIds).
888✔
688

689
get_item_name(_Host, _Node, Id) ->
690
    Id.
×
691

692
%% @doc <p>Return the path of the node. In flat it's just node id.</p>
693
node_to_path(Node) ->
694
    [(Node)].
969✔
695

696
path_to_node(Path) ->
697
    case Path of
×
698
        % default slot
699
        [Node] -> iolist_to_binary(Node);
×
700
        % handle old possible entries, used when migrating database content to new format
701
        [Node | _] when is_binary(Node) ->
702
            mongoose_bin:join([<<"">> | Path], <<"/">>);
×
703
        % default case (used by PEP for example)
704
        _ -> iolist_to_binary(Path)
×
705
    end.
706

707
should_delete_when_owner_removed() -> false.
64✔
708

709
can_fetch_item(owner, _) -> true;
112✔
710
can_fetch_item(member, _) -> true;
×
711
can_fetch_item(publisher, _) -> true;
×
712
can_fetch_item(publish_only, _) -> false;
×
713
can_fetch_item(outcast, _) -> false;
×
714
can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
350✔
715
%can_fetch_item(_Affiliation, _Subscription) -> false.
716

717
is_subscribed(Subscriptions) ->
718
    lists:any(fun
350✔
719
            ({subscribed, _SubId, _}) -> true;
28✔
720
            (_) -> false
×
721
        end,
722
        Subscriptions).
723

724
make_subid() ->
725
    mongoose_bin:gen_from_timestamp().
662✔
726

727
remove_user(LUser, LServer) ->
728
    mod_pubsub_db_backend:dirty(fun() ->
672✔
729
                          LJID = {LUser, LServer, <<>>},
672✔
730
                          mod_pubsub_db_backend:delete_user_subscriptions(LJID),
672✔
731
                          NodesAndAffs = mod_pubsub_db_backend:find_nodes_by_affiliated_user(LJID),
672✔
732
                          %% We don't broadcast node deletion notifications to subscribers because:
733
                          %% * PEP nodes do not broadcast anything upon deletion
734
                          %% * Push nodes do not have (should not have) any subscribers
735
                          %% * Remaining nodes are not deleted when the owner leaves
736
                          lists:foreach(
672✔
737
                            fun(NodeAffPair) ->
738
                                    remove_user_by_affiliation_and_node(NodeAffPair, LJID)
112✔
739
                            end, NodesAndAffs)
740
                  end, #{}).
741

742
-spec remove_user_by_affiliation_and_node(NodeAffPair :: {mod_pubsub:pubsubNode(),
743
                                                          mod_pubsub:affiliation()},
744
                                          LJID :: jid:ljid()) ->
745
    any().
746
remove_user_by_affiliation_and_node({#pubsub_node{ id = Nidx } = Node, owner}, LJID) ->
747
    case mod_pubsub:plugin_call(mod_pubsub:plugin(Node#pubsub_node.type),
96✔
748
                                should_delete_when_owner_removed, []) of
749
        {result, true} ->
750
            % Oh my, we do have a mess in the API, don't we?
751
            % delete_node removes actual node structure
752
            % del_node removes node's items and states (affs and subs)
753
            mod_pubsub_db_backend:delete_node(Node),
32✔
754
            mod_pubsub_db_backend:del_node(Nidx);
32✔
755
        _ ->
756
            mod_pubsub_db_backend:set_affiliation(Nidx, LJID, none)
64✔
757
    end;
758
remove_user_by_affiliation_and_node({#pubsub_node{ id = Nidx }, _}, LJID) ->
759
    mod_pubsub_db_backend:set_affiliation(Nidx, LJID, none).
16✔
760

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

© 2025 Coveralls, Inc