./ct_report/coverage/node_flat.COVER.html

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.
93
94 terminate(_Host, _ServerHost) ->
95
:-(
ok.
96
97 options() ->
98
:-(
[{deliver_payloads, true},
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">>,
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
146 {<<"">>, Host, <<"">>} ->
147
:-(
true; % pubsub service always allowed
148 _ ->
149
:-(
{ok, HostType} = mongoose_domain_api:get_domain_host_type(ServerHost),
150
:-(
acl:match_rule(HostType, ServerHost, Access, Owner) =:= allow
151 end,
152
:-(
{result, Allowed}.
153
154 create_node(Nidx, Owner) ->
155
:-(
mod_pubsub_db_backend:create_node(Nidx, jid:to_lower(Owner)),
156
:-(
{result, {default, broadcast}}.
157
158 delete_node(Nodes) ->
159
:-(
Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
160
:-(
lists:map(fun (S) -> {J, S} end, Ss)
161 end,
162
:-(
Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
163
:-(
{ok, States} = mod_pubsub_db_backend:del_node(Nidx),
164
:-(
{PubsubNode, lists:flatmap(Tr, States)}
165 end, Nodes),
166
:-(
{result, {default, broadcast, Reply}}.
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),
202
:-(
{ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, Subscriber),
203
:-(
{ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, Subscriber),
204
:-(
Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
205
:-(
PendingSubscription = lists:any(fun
206
:-(
({pending, _, _}) -> true;
207
:-(
(_) -> false
208 end,
209 Subscriptions),
210
:-(
case authorize_subscription(SenderMatchesSubscriber, Affiliation, PendingSubscription,
211 AccessModel, PresenceSubscription, RosterGroup, Whitelisted) of
212 ok ->
213
:-(
{NewSub, SubId} = case Subscriptions of
214 [{subscribed, Id, _}|_] ->
215
:-(
{subscribed, Id};
216 [] ->
217
:-(
Id = make_subid(),
218
:-(
Sub = access_model_to_subscription(AccessModel),
219
:-(
mod_pubsub_db_backend:add_subscription(Nidx, Subscriber, Sub, Id, Options),
220
:-(
{Sub, Id}
221 end,
222
:-(
case {NewSub, SendLast} of
223 {subscribed, never} ->
224
:-(
{result, {default, subscribed, SubId}};
225 {subscribed, _} ->
226
:-(
{result, {default, subscribed, SubId, send_last}};
227 {_, _} ->
228
:-(
{result, {default, pending, SubId}}
229 end;
230 {error, _} = Err ->
231
:-(
Err
232 end.
233
234 -spec access_model_to_subscription(accessModel()) -> pending | subscribed.
235
:-(
access_model_to_subscription(authorize) -> pending;
236
:-(
access_model_to_subscription(_) -> subscribed.
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">>)};
264 authorize_subscription(_SenderMatchesSubscriber, _Affiliation, _PendingSubscription, _AccessModel,
265 _PresenceSubscription, _RosterGroup, _Whitelisted) ->
266
:-(
ok.
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),
271
:-(
{ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, Subscriber),
272
:-(
SubIdExists = case SubId of
273
:-(
<<>> -> false;
274
:-(
Binary when is_binary(Binary) -> true;
275
:-(
_ -> false
276 end,
277
:-(
case authenticate_unsubscribe(SenderMatchesSubscriber, Subscriptions, SubIdExists, SubId) of
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),
290
:-(
{result, default};
291 remove_only_sub ->
292
:-(
mod_pubsub_db_backend:delete_all_subscriptions(Nidx, Subscriber),
293
:-(
{result, default}
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;
307 authenticate_unsubscribe(_SenderMatchesSubscriber, [_], _SubIdExists, _SubId) ->
308 %% No subid supplied, but there's only one matching subscription
309
:-(
remove_only_sub;
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),
348
:-(
SubKey = jid:to_lower(Publisher),
349
:-(
GenKey = jid:to_lower(BarePublisher),
350
:-(
{ok, GenState} = mod_pubsub_db_backend:get_state(Nidx, GenKey),
351
:-(
SubState = case Publisher#jid.lresource of
352 <<>> ->
353
:-(
GenState;
354 _ ->
355
:-(
{ok, SubState0} = mod_pubsub_db_backend:get_state(Nidx, SubKey),
356
:-(
SubState0
357 end,
358
:-(
{ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, GenKey),
359
:-(
Subscribed = case PublishModel of
360
:-(
subscribers -> is_subscribed(GenState#pubsub_state.subscriptions) orelse
361
:-(
is_subscribed(SubState#pubsub_state.subscriptions);
362
:-(
_ -> undefined
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
367 (PublishModel == publishers) and
368 ( (Affiliation == owner) or
369 (Affiliation == publisher) or
370 (Affiliation == publish_only) ) or
371 (Subscribed == true),
372
:-(
case Allowed of
373 false ->
374
:-(
{error, mongoose_xmpp_errors:forbidden()};
375 true ->
376
:-(
case MaxItems > 0 of
377 true ->
378
:-(
Now = os:system_time(microsecond),
379
:-(
Item = make_pubsub_item(Nidx, ItemId, Now, SubKey, GenKey,
380 Payload, Publisher, ItemPublisher),
381
:-(
Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
382
:-(
{result, {_NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
383
:-(
mod_pubsub_db_backend:add_item(Nidx, GenKey, Item),
384
:-(
mod_pubsub_db_backend:remove_items(Nidx, GenKey, OI),
385
:-(
{result, {default, broadcast, OI}};
386 false ->
387
:-(
{result, {default, broadcast, []}}
388 end
389 end.
390
391 make_pubsub_item(Nidx, ItemId, Now, SubKey, GenKey, Payload, Publisher, ItemPublisher) ->
392
:-(
PubId = {Now, SubKey},
393
:-(
case get_item(Nidx, ItemId) of
394 {result, OldItem} ->
395
:-(
OldItem#pubsub_item{modification = PubId,
396 payload = Payload};
397 _ ->
398
:-(
Publisher0 = case ItemPublisher of
399
:-(
true -> Publisher;
400
:-(
false -> undefined
401 end,
402
:-(
#pubsub_item{itemid = {ItemId, Nidx},
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),
423
:-(
OldItems = lists:nthtail(length(NewItems), ItemIds),
424
:-(
del_items(Nidx, OldItems),
425
:-(
{result, {NewItems, OldItems}}.
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)),
432
:-(
{ok, GenState} = mod_pubsub_db_backend:get_state(Nidx, GenKey),
433
:-(
#pubsub_state{affiliation = Affiliation, items = Items} = GenState,
434
:-(
Allowed = get_permition(Affiliation, PublishModel, Nidx, ItemId, GenKey),
435
:-(
case Allowed of
436 false ->
437
:-(
{error, mongoose_xmpp_errors:forbidden()};
438 true ->
439
:-(
case lists:member(ItemId, Items) of
440 true ->
441
:-(
del_item(Nidx, ItemId),
442
:-(
mod_pubsub_db_backend:remove_items(Nidx, GenKey, [ItemId]),
443
:-(
{result, {default, broadcast}};
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;
451
:-(
get_permition(_Affiliation, open, _Nidx, _ItemId, _GenKey) -> true;
452 get_permition(_Affiliation, _PublishModel, Nidx, ItemId, GenKey) ->
453
:-(
case get_item(Nidx, ItemId) of
454
:-(
{result, #pubsub_item{creation = {_, GenKey}}} -> true;
455
:-(
_ -> false
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
481 {ok, owner} ->
482
:-(
{ok, States} = mod_pubsub_db_backend:get_states(Nidx),
483
:-(
lists:foreach(fun
484 (#pubsub_state{items = []}) ->
485
:-(
ok;
486 (#pubsub_state{items = Items}) ->
487
:-(
del_items(Nidx, Items)
488 end,
489 States),
490
:-(
mod_pubsub_db_backend:remove_all_items(Nidx),
491
:-(
{result, {default, broadcast}};
492 _ ->
493
:-(
{error, mongoose_xmpp_errors:forbidden()}
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),
500
:-(
NodeTree = mod_pubsub:tree(Host),
501
:-(
Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
502
:-(
case gen_pubsub_nodetree:get_node(NodeTree, N) of
503
:-(
#pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc];
504
:-(
_ -> Acc
505 end
506 end,
507 [], States),
508
:-(
{result, Reply}.
509
510 get_node_affiliations(Nidx) ->
511
:-(
{ok, States} = mod_pubsub_db_backend:get_states(Nidx),
512
:-(
Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end,
513
:-(
{result, lists:map(Tr, States)}.
514
515 get_affiliation(Nidx, Owner) ->
516
:-(
{ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, jid:to_lower(Owner)),
517
:-(
{result, Affiliation}.
518
519 set_affiliation(Nidx, JID, Affiliation) ->
520
:-(
mod_pubsub_db_backend:set_affiliation(Nidx, JID, Affiliation).
521
522 get_entity_subscriptions(Host, Owner) ->
523
:-(
LOwner = jid:to_lower(Owner),
524
:-(
States = case Owner#jid.lresource of
525 <<>> ->
526
:-(
{ok, States0} = mod_pubsub_db_backend:get_states_by_lus(LOwner),
527
:-(
States0;
528 _ ->
529
:-(
{ok, States0} = mod_pubsub_db_backend:get_states_by_bare_and_full(LOwner),
530
:-(
States0
531 end,
532
:-(
NodeTree = mod_pubsub:tree(Host),
533
:-(
Reply = lists:foldl(fun (PubSubState, Acc) ->
534
:-(
get_entity_subscriptions_loop(NodeTree, PubSubState, Acc)
535 end,
536 [], States),
537
:-(
{result, Reply}.
538
539 get_entity_subscriptions_loop(NodeTree, #pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
540
:-(
case gen_pubsub_nodetree:get_node(NodeTree, N) of
541 #pubsub_node{} = Node ->
542
:-(
lists:foldl(fun ({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, J} | Acc2] end, Acc, Ss);
543 _ ->
544
:-(
Acc
545 end.
546
547 get_node_subscriptions(Nidx) ->
548
:-(
{ok, Subscriptions} = mod_pubsub_db_backend:get_node_subscriptions(Nidx),
549
:-(
{result, Subscriptions}.
550
551 get_subscriptions(Nidx, #jid{} = Owner) ->
552
:-(
get_subscriptions(Nidx, jid:to_lower(Owner));
553 get_subscriptions(Nidx, LOwner) ->
554
:-(
{ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LOwner),
555
:-(
{result, Subscriptions}.
556
557 set_subscriptions(Nidx, #jid{} = Owner, Subscription, SubId) ->
558
:-(
set_subscriptions(Nidx, jid:to_lower(Owner), Subscription, SubId);
559 set_subscriptions(Nidx, LOwner, Subscription, SubId) ->
560
:-(
{ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LOwner),
561
:-(
case {SubId, Subscriptions} of
562 {_, []} ->
563
:-(
case Subscription of
564 none ->
565
:-(
{error,
566 ?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"not-subscribed">>)};
567 _ ->
568
:-(
NewSubId = make_subid(),
569
:-(
mod_pubsub_db_backend:add_subscription(Nidx, LOwner, Subscription, NewSubId, [])
570 end;
571 {<<>>, [{_, SID, _}]} ->
572
:-(
case Subscription of
573
:-(
none -> mod_pubsub_db_backend:delete_subscription(Nidx, LOwner, SID);
574
:-(
_ -> mod_pubsub_db_backend:update_subscription(Nidx, LOwner, Subscription, SID)
575 end;
576 {<<>>, [_ | _]} ->
577
:-(
{error,
578 ?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"subid-required">>)};
579 _ ->
580
:-(
case Subscription of
581
:-(
none -> mod_pubsub_db_backend:delete_subscription(Nidx, LOwner, SubId);
582
:-(
_ -> mod_pubsub_db_backend:update_subscription(Nidx, LOwner, Subscription, SubId)
583 end
584 end.
585
586 %% @doc <p>Returns a list of Owner's nodes on Host with pending
587 %% subscriptions.</p>
588 get_pending_nodes(Host, Owner) ->
589
:-(
LOwner = jid:to_lower(Owner),
590
:-(
{ok, Nidxs} = mod_pubsub_db_backend:get_idxs_of_own_nodes_with_pending_subs(LOwner),
591
:-(
NodeTree = mod_pubsub:tree(Host),
592
:-(
{result,
593 lists:foldl(fun(N, Acc) ->
594
:-(
case gen_pubsub_nodetree:get_node(NodeTree, N) of
595
:-(
#pubsub_node{nodeid = {_, Node}} -> [Node | Acc];
596
:-(
_ -> Acc
597 end
598 end, [], Nidxs)}.
599
600 %% @doc Returns the list of stored items for a given node.
601 %% <p>For the default PubSub module, items are stored in Mnesia database.</p>
602 %% <p>We can consider that the pubsub_item table have been created by the main
603 %% mod_pubsub module.</p>
604 %% <p>PubSub plugins can store the items where they wants (for example in a
605 %% relational database), or they can even decide not to persist any items.</p>
606 get_items(Nidx, _From, Opts) ->
607 %% TODO add tests and implementation supporting RSM
608 %% when looking for node's items.
609 %% Currently the RSM attribute is ignored
610
:-(
case mod_pubsub_db_backend:get_items(Nidx, Opts) of
611 {ok, Result} ->
612
:-(
{result, Result};
613 {error, item_not_found} ->
614
:-(
{error, mongoose_xmpp_errors:item_not_found()}
615 end.
616
617 get_items_if_authorised(Nidx, JID, #{access_model := AccessModel,
618 presence_permission := PresenceSubscription,
619 roster_permission := RosterGroup,
620 rsm := RSM} = Opts) ->
621
:-(
LJID = jid:to_lower(JID),
622
:-(
{ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, LJID),
623
:-(
{ok, BareSubscriptions}
624 = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, jid:to_bare(LJID)),
625
:-(
{ok, FullSubscriptions}
626 = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LJID),
627
:-(
Whitelisted = can_fetch_item(Affiliation, BareSubscriptions) orelse
628
:-(
can_fetch_item(Affiliation, FullSubscriptions),
629
:-(
case authorize_get_item(Affiliation, AccessModel, PresenceSubscription,
630 RosterGroup, Whitelisted) of
631 ok ->
632
:-(
LowLevelOpts = #{rsm => RSM,
633 max_items => maps:get(max_items, Opts, undefined),
634 item_ids => maps:get(item_ids, Opts, undefined)},
635
:-(
get_items(Nidx, JID, LowLevelOpts);
636
:-(
{error, _} = Err -> Err
637 end.
638
639 %% @doc <p>Returns an item (one item list), given its reference.</p>
640
641 get_item(Nidx, ItemId) ->
642
:-(
case mod_pubsub_db_backend:get_item(Nidx, ItemId) of
643 {ok, Item} ->
644
:-(
{result, Item};
645 {error, Error} ->
646
:-(
{error, Error}
647 end.
648
649 get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) ->
650
:-(
{ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, JID),
651
:-(
{ok, Subscriptions}
652 = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, jid:to_bare(JID)),
653
:-(
Whitelisted = can_fetch_item(Affiliation, Subscriptions),
654
:-(
case authorize_get_item(Affiliation, AccessModel, PresenceSubscription,
655 RosterGroup, Whitelisted) of
656
:-(
ok -> get_item(Nidx, ItemId);
657
:-(
{error, _} = Err -> Err
658 end.
659
660 authorize_get_item(Affiliation, _AccessModel, _PresenceSubscription, _RosterGroup, _Whitelisted)
661 when (Affiliation == outcast) or (Affiliation == publish_only) ->
662
:-(
{error, mongoose_xmpp_errors:forbidden()};
663 authorize_get_item(_Affiliation, presence, false, _RosterGroup, _Whitelisted) ->
664
:-(
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"presence-subscription-required">>)};
665 authorize_get_item(_Affiliation, roster, _PresenceSubscription, false, _Whitelisted) ->
666
:-(
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"not-in-roster-group">>)};
667 authorize_get_item(_Affiliation, whitelist, _PresenceSubscription, _RosterGroup, false) ->
668
:-(
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_allowed()), <<"closed-node">>)};
669 authorize_get_item(_Affiliation, authorize, _PresenceSubscription, _RosterGroup, false) ->
670
:-(
{error, mongoose_xmpp_errors:forbidden()};
671 authorize_get_item(_Affiliation, _AccessModel, _PresenceSubscription, _RosterGroup, _Whitelisted) ->
672
:-(
ok.
673
674 %% @doc <p>Write an item into database.</p>
675 set_item(Item) when is_record(Item, pubsub_item) ->
676
:-(
mod_pubsub_db_backend:set_item(Item).
677 %set_item(_) -> {error, mongoose_xmpp_errors:internal_server_error()}.
678
679 %% @doc <p>Delete an item from database.</p>
680 del_item(Nidx, ItemId) ->
681
:-(
mod_pubsub_db_backend:del_item(Nidx, ItemId).
682
683 del_items(Nidx, ItemIds) ->
684
:-(
mod_pubsub_db_backend:del_items(Nidx, ItemIds).
685
686 get_item_name(_Host, _Node, Id) ->
687
:-(
Id.
688
689 %% @doc <p>Return the path of the node. In flat it's just node id.</p>
690 node_to_path(Node) ->
691
:-(
[(Node)].
692
693 path_to_node(Path) ->
694
:-(
case Path of
695 % default slot
696
:-(
[Node] -> iolist_to_binary(Node);
697 % handle old possible entries, used when migrating database content to new format
698 [Node | _] when is_binary(Node) ->
699
:-(
mongoose_bin:join([<<"">> | Path], <<"/">>);
700 % default case (used by PEP for example)
701
:-(
_ -> iolist_to_binary(Path)
702 end.
703
704
:-(
should_delete_when_owner_removed() -> false.
705
706
:-(
can_fetch_item(owner, _) -> true;
707
:-(
can_fetch_item(member, _) -> true;
708
:-(
can_fetch_item(publisher, _) -> true;
709
:-(
can_fetch_item(publish_only, _) -> false;
710
:-(
can_fetch_item(outcast, _) -> false;
711
:-(
can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
712 %can_fetch_item(_Affiliation, _Subscription) -> false.
713
714 is_subscribed(Subscriptions) ->
715
:-(
lists:any(fun
716
:-(
({subscribed, _SubId, _}) -> true;
717
:-(
(_) -> false
718 end,
719 Subscriptions).
720
721 make_subid() ->
722
:-(
mongoose_bin:gen_from_timestamp().
723
724 remove_user(LUser, LServer) ->
725
:-(
mod_pubsub_db_backend:dirty(fun() ->
726
:-(
LJID = {LUser, LServer, <<>>},
727
:-(
mod_pubsub_db_backend:delete_user_subscriptions(LJID),
728
:-(
NodesAndAffs = mod_pubsub_db_backend:find_nodes_by_affiliated_user(LJID),
729 %% We don't broadcast node deletion notifications to subscribers because:
730 %% * PEP nodes do not broadcast anything upon deletion
731 %% * Push nodes do not have (should not have) any subscribers
732 %% * Remaining nodes are not deleted when the owner leaves
733
:-(
lists:foreach(
734 fun(NodeAffPair) ->
735
:-(
remove_user_by_affiliation_and_node(NodeAffPair, LJID)
736 end, NodesAndAffs)
737 end, #{}).
738
739 -spec remove_user_by_affiliation_and_node(NodeAffPair :: {mod_pubsub:pubsubNode(),
740 mod_pubsub:affiliation()},
741 LJID :: jid:ljid()) ->
742 any().
743 remove_user_by_affiliation_and_node({#pubsub_node{ id = Nidx } = Node, owner}, LJID) ->
744
:-(
case mod_pubsub:plugin_call(mod_pubsub:plugin(Node#pubsub_node.type),
745 should_delete_when_owner_removed, []) of
746 {result, true} ->
747 % Oh my, we do have a mess in the API, don't we?
748 % delete_node removes actual node structure
749 % del_node removes node's items and states (affs and subs)
750
:-(
mod_pubsub_db_backend:delete_node(Node),
751
:-(
mod_pubsub_db_backend:del_node(Nidx);
752 _ ->
753
:-(
mod_pubsub_db_backend:set_affiliation(Nidx, LJID, none)
754 end;
755 remove_user_by_affiliation_and_node({#pubsub_node{ id = Nidx }, _}, LJID) ->
756
:-(
mod_pubsub_db_backend:set_affiliation(Nidx, LJID, none).
757
Line Hits Source