./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 58 ok.
93
94 terminate(_Host, _ServerHost) ->
95 58 ok.
96
97 options() ->
98 163 [{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 787 [<<"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 115 Allowed = case jid:to_lower(Owner) of
146 {<<"">>, Host, <<"">>} ->
147
:-(
true; % pubsub service always allowed
148 _ ->
149 115 {ok, HostType} = mongoose_domain_api:get_domain_host_type(ServerHost),
150 115 acl:match_rule(HostType, ServerHost, Access, Owner) =:= allow
151 end,
152 115 {result, Allowed}.
153
154 create_node(Nidx, Owner) ->
155 229 mod_pubsub_db_backend:create_node(Nidx, jid:to_lower(Owner)),
156 229 {result, {default, broadcast}}.
157
158 delete_node(Nodes) ->
159 151 Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
160 238 lists:map(fun (S) -> {J, S} end, Ss)
161 end,
162 151 Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
163 152 {ok, States} = mod_pubsub_db_backend:del_node(Nidx),
164 152 {PubsubNode, lists:flatmap(Tr, States)}
165 end, Nodes),
166 151 {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 90 SenderMatchesSubscriber = jid:are_bare_equal(Sender, Subscriber),
202 90 {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, Subscriber),
203 90 {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, Subscriber),
204 90 Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
205 90 PendingSubscription = lists:any(fun
206
:-(
({pending, _, _}) -> true;
207
:-(
(_) -> false
208 end,
209 Subscriptions),
210 90 case authorize_subscription(SenderMatchesSubscriber, Affiliation, PendingSubscription,
211 AccessModel, PresenceSubscription, RosterGroup, Whitelisted) of
212 ok ->
213 88 {NewSub, SubId} = case Subscriptions of
214 [{subscribed, Id, _}|_] ->
215
:-(
{subscribed, Id};
216 [] ->
217 88 Id = make_subid(),
218 88 Sub = access_model_to_subscription(AccessModel),
219 88 mod_pubsub_db_backend:add_subscription(Nidx, Subscriber, Sub, Id, Options),
220 88 {Sub, Id}
221 end,
222 88 case {NewSub, SendLast} of
223 {subscribed, never} ->
224 71 {result, {default, subscribed, SubId}};
225 {subscribed, _} ->
226 8 {result, {default, subscribed, SubId, send_last}};
227 {_, _} ->
228 9 {result, {default, pending, SubId}}
229 end;
230 {error, _} = Err ->
231 2 Err
232 end.
233
234 -spec access_model_to_subscription(accessModel()) -> pending | subscribed.
235 9 access_model_to_subscription(authorize) -> pending;
236 79 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 2 {error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_allowed()), <<"closed-node">>)};
264 authorize_subscription(_SenderMatchesSubscriber, _Affiliation, _PendingSubscription, _AccessModel,
265 _PresenceSubscription, _RosterGroup, _Whitelisted) ->
266 88 ok.
267
268 %% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
269 unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
270 7 SenderMatchesSubscriber = jid:are_bare_equal(Subscriber, Sender),
271 7 {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, Subscriber),
272 7 SubIdExists = case SubId of
273 5 <<>> -> false;
274
:-(
Binary when is_binary(Binary) -> true;
275 2 _ -> false
276 end,
277 7 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 2 mod_pubsub_db_backend:delete_all_subscriptions(Nidx, Subscriber),
290 2 {result, default};
291 remove_only_sub ->
292 5 mod_pubsub_db_backend:delete_all_subscriptions(Nidx, Subscriber),
293 5 {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 2 remove_all_subs;
307 authenticate_unsubscribe(_SenderMatchesSubscriber, [_], _SubIdExists, _SubId) ->
308 %% No subid supplied, but there's only one matching subscription
309 5 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 128 BarePublisher = jid:to_bare(Publisher),
348 128 SubKey = jid:to_lower(Publisher),
349 128 GenKey = jid:to_lower(BarePublisher),
350 128 {ok, GenState} = mod_pubsub_db_backend:get_state(Nidx, GenKey),
351 128 SubState = case Publisher#jid.lresource of
352 <<>> ->
353
:-(
GenState;
354 _ ->
355 128 {ok, SubState0} = mod_pubsub_db_backend:get_state(Nidx, SubKey),
356 128 SubState0
357 end,
358 128 {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, GenKey),
359 128 Subscribed = case PublishModel of
360
:-(
subscribers -> is_subscribed(GenState#pubsub_state.subscriptions) orelse
361
:-(
is_subscribed(SubState#pubsub_state.subscriptions);
362 128 _ -> 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 128 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 128 case Allowed of
373 false ->
374 6 {error, mongoose_xmpp_errors:forbidden()};
375 true ->
376 122 case MaxItems > 0 of
377 true ->
378 119 Now = os:system_time(microsecond),
379 119 Item = make_pubsub_item(Nidx, ItemId, Now, SubKey, GenKey,
380 Payload, Publisher, ItemPublisher),
381 119 Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
382 119 {result, {_NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
383 119 mod_pubsub_db_backend:add_item(Nidx, GenKey, Item),
384 119 mod_pubsub_db_backend:remove_items(Nidx, GenKey, OI),
385 119 {result, {default, broadcast, OI}};
386 false ->
387 3 {result, {default, broadcast, []}}
388 end
389 end.
390
391 make_pubsub_item(Nidx, ItemId, Now, SubKey, GenKey, Payload, Publisher, ItemPublisher) ->
392 119 PubId = {Now, SubKey},
393 119 case get_item(Nidx, ItemId) of
394 {result, OldItem} ->
395 2 OldItem#pubsub_item{modification = PubId,
396 payload = Payload};
397 _ ->
398 117 Publisher0 = case ItemPublisher of
399 4 true -> Publisher;
400 113 false -> undefined
401 end,
402 117 #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 119 NewItems = lists:sublist(ItemIds, MaxItems),
423 119 OldItems = lists:nthtail(length(NewItems), ItemIds),
424 119 del_items(Nidx, OldItems),
425 119 {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 10 GenKey = jid:to_bare(jid:to_lower(Publisher)),
432 10 {ok, GenState} = mod_pubsub_db_backend:get_state(Nidx, GenKey),
433 10 #pubsub_state{affiliation = Affiliation, items = Items} = GenState,
434 10 Allowed = get_permition(Affiliation, PublishModel, Nidx, ItemId, GenKey),
435 10 case Allowed of
436 false ->
437 2 {error, mongoose_xmpp_errors:forbidden()};
438 true ->
439 8 case lists:member(ItemId, Items) of
440 true ->
441 8 del_item(Nidx, ItemId),
442 8 mod_pubsub_db_backend:remove_items(Nidx, GenKey, [ItemId]),
443 8 {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 4 get_permition(owner, _PublishModel, _Nidx, _ItemId, _GenKey) -> true;
451 2 get_permition(_Affiliation, open, _Nidx, _ItemId, _GenKey) -> true;
452 get_permition(_Affiliation, _PublishModel, Nidx, ItemId, GenKey) ->
453 4 case get_item(Nidx, ItemId) of
454 2 {result, #pubsub_item{creation = {_, GenKey}}} -> true;
455 2 _ -> 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 8 case mod_pubsub_db_backend:get_affiliation(Nidx, jid:to_lower(Owner)) of
481 {ok, owner} ->
482 4 {ok, States} = mod_pubsub_db_backend:get_states(Nidx),
483 4 lists:foreach(fun
484 (#pubsub_state{items = []}) ->
485
:-(
ok;
486 (#pubsub_state{items = Items}) ->
487 4 del_items(Nidx, Items)
488 end,
489 States),
490 4 mod_pubsub_db_backend:remove_all_items(Nidx),
491 4 {result, {default, broadcast}};
492 _ ->
493 4 {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 366 {ok, States} = mod_pubsub_db_backend:get_states_by_bare(LOwner),
500 366 HT = mod_pubsub:host_to_host_type(Host),
501 366 NodeTree = mod_pubsub:tree(HT),
502 366 Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
503 103 case gen_pubsub_nodetree:get_node(NodeTree, N) of
504 89 #pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc];
505 14 _ -> Acc
506 end
507 end,
508 [], States),
509 366 {result, Reply}.
510
511 get_node_affiliations(Nidx) ->
512 6 {ok, States} = mod_pubsub_db_backend:get_states(Nidx),
513 6 Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end,
514 6 {result, lists:map(Tr, States)}.
515
516 get_affiliation(Nidx, Owner) ->
517 186 {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, jid:to_lower(Owner)),
518 186 {result, Affiliation}.
519
520 set_affiliation(Nidx, JID, Affiliation) ->
521 54 mod_pubsub_db_backend:set_affiliation(Nidx, JID, Affiliation).
522
523 get_entity_subscriptions(Host, Owner) ->
524 509 LOwner = jid:to_lower(Owner),
525 509 States = case Owner#jid.lresource of
526 <<>> ->
527 12 {ok, States0} = mod_pubsub_db_backend:get_states_by_lus(LOwner),
528 12 States0;
529 _ ->
530 497 {ok, States0} = mod_pubsub_db_backend:get_states_by_bare_and_full(LOwner),
531 497 States0
532 end,
533 509 HT = mod_pubsub:host_to_host_type(Host),
534 509 NodeTree = mod_pubsub:tree(HT),
535 509 Reply = lists:foldl(fun (PubSubState, Acc) ->
536 88 get_entity_subscriptions_loop(NodeTree, PubSubState, Acc)
537 end,
538 [], States),
539 509 {result, Reply}.
540
541 get_entity_subscriptions_loop(NodeTree, #pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
542 88 case gen_pubsub_nodetree:get_node(NodeTree, N) of
543 #pubsub_node{} = Node ->
544 88 lists:foldl(fun ({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, J} | Acc2] end, Acc, Ss);
545 _ ->
546
:-(
Acc
547 end.
548
549 get_node_subscriptions(Nidx) ->
550 574 {ok, Subscriptions} = mod_pubsub_db_backend:get_node_subscriptions(Nidx),
551 574 {result, Subscriptions}.
552
553 get_subscriptions(Nidx, #jid{} = Owner) ->
554 5 get_subscriptions(Nidx, jid:to_lower(Owner));
555 get_subscriptions(Nidx, LOwner) ->
556 23 {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LOwner),
557 23 {result, Subscriptions}.
558
559 set_subscriptions(Nidx, #jid{} = Owner, Subscription, SubId) ->
560 5 set_subscriptions(Nidx, jid:to_lower(Owner), Subscription, SubId);
561 set_subscriptions(Nidx, LOwner, Subscription, SubId) ->
562 13 {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LOwner),
563 13 case {SubId, Subscriptions} of
564 {_, []} ->
565 6 case Subscription of
566 none ->
567
:-(
{error,
568 ?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"not-subscribed">>)};
569 _ ->
570 6 NewSubId = make_subid(),
571 6 mod_pubsub_db_backend:add_subscription(Nidx, LOwner, Subscription, NewSubId, [])
572 end;
573 {<<>>, [{_, SID, _}]} ->
574 2 case Subscription of
575 2 none -> mod_pubsub_db_backend:delete_subscription(Nidx, LOwner, SID);
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 5 case Subscription of
583 2 none -> mod_pubsub_db_backend:delete_subscription(Nidx, LOwner, SubId);
584 3 _ -> mod_pubsub_db_backend:update_subscription(Nidx, LOwner, Subscription, SubId)
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 2 LOwner = jid:to_lower(Owner),
592 2 {ok, Nidxs} = mod_pubsub_db_backend:get_idxs_of_own_nodes_with_pending_subs(LOwner),
593 2 HT = mod_pubsub:host_to_host_type(Host),
594 2 NodeTree = mod_pubsub:tree(HT),
595 2 {result,
596 lists:foldl(fun(N, Acc) ->
597 2 case gen_pubsub_nodetree:get_node(NodeTree, N) of
598 2 #pubsub_node{nodeid = {_, Node}} -> [Node | Acc];
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 51 case mod_pubsub_db_backend:get_items(Nidx, Opts) of
614 {ok, Result} ->
615 51 {result, Result};
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 38 LJID = jid:to_lower(JID),
625 38 {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, LJID),
626 38 {ok, BareSubscriptions}
627 = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, jid:to_bare(LJID)),
628 38 {ok, FullSubscriptions}
629 = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LJID),
630 38 Whitelisted = can_fetch_item(Affiliation, BareSubscriptions) orelse
631 25 can_fetch_item(Affiliation, FullSubscriptions),
632 38 case authorize_get_item(Affiliation, AccessModel, PresenceSubscription,
633 RosterGroup, Whitelisted) of
634 ok ->
635 38 LowLevelOpts = #{rsm => RSM,
636 max_items => maps:get(max_items, Opts, undefined),
637 item_ids => maps:get(item_ids, Opts, undefined)},
638 38 get_items(Nidx, JID, LowLevelOpts);
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 127 case mod_pubsub_db_backend:get_item(Nidx, ItemId) of
646 {ok, Item} ->
647 8 {result, Item};
648 {error, Error} ->
649 119 {error, Error}
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 38 ok.
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 8 mod_pubsub_db_backend:del_item(Nidx, ItemId).
685
686 del_items(Nidx, ItemIds) ->
687 123 mod_pubsub_db_backend:del_items(Nidx, ItemIds).
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 135 [(Node)].
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 8 should_delete_when_owner_removed() -> false.
708
709 13 can_fetch_item(owner, _) -> true;
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 50 can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
715 %can_fetch_item(_Affiliation, _Subscription) -> false.
716
717 is_subscribed(Subscriptions) ->
718 50 lists:any(fun
719 4 ({subscribed, _SubId, _}) -> true;
720
:-(
(_) -> false
721 end,
722 Subscriptions).
723
724 make_subid() ->
725 94 mongoose_bin:gen_from_timestamp().
726
727 remove_user(LUser, LServer) ->
728 78 mod_pubsub_db_backend:dirty(fun() ->
729 78 LJID = {LUser, LServer, <<>>},
730 78 mod_pubsub_db_backend:delete_user_subscriptions(LJID),
731 78 NodesAndAffs = mod_pubsub_db_backend:find_nodes_by_affiliated_user(LJID),
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 78 lists:foreach(
737 fun(NodeAffPair) ->
738 14 remove_user_by_affiliation_and_node(NodeAffPair, LJID)
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 12 case mod_pubsub:plugin_call(mod_pubsub:plugin(Node#pubsub_node.type),
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 4 mod_pubsub_db_backend:delete_node(Node),
754 4 mod_pubsub_db_backend:del_node(Nidx);
755 _ ->
756 8 mod_pubsub_db_backend:set_affiliation(Nidx, LJID, none)
757 end;
758 remove_user_by_affiliation_and_node({#pubsub_node{ id = Nidx }, _}, LJID) ->
759 2 mod_pubsub_db_backend:set_affiliation(Nidx, LJID, none).
760
Line Hits Source