./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 44 ok.
93
94 terminate(_Host, _ServerHost) ->
95 44 ok.
96
97 options() ->
98 153 [{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 740 [<<"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 108 Allowed = case jid:to_lower(Owner) of
146 {<<"">>, Host, <<"">>} ->
147 2 true; % pubsub service always allowed
148 _ ->
149 106 {ok, HostType} = mongoose_domain_api:get_domain_host_type(ServerHost),
150 106 acl:match_rule(HostType, ServerHost, Access, Owner) =:= allow
151 end,
152 108 {result, Allowed}.
153
154 create_node(Nidx, Owner) ->
155 211 mod_pubsub_db_backend:create_node(Nidx, jid:to_lower(Owner)),
156 205 {result, {default, broadcast}}.
157
158 delete_node(Nodes) ->
159 149 Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) ->
160 235 lists:map(fun (S) -> {J, S} end, Ss)
161 end,
162 149 Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) ->
163 150 {ok, States} = mod_pubsub_db_backend:del_node(Nidx),
164 145 {PubsubNode, lists:flatmap(Tr, States)}
165 end, Nodes),
166 144 {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 89 SenderMatchesSubscriber = jid:are_bare_equal(Sender, Subscriber),
202 89 {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, Subscriber),
203 89 {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, Subscriber),
204 89 Whitelisted = lists:member(Affiliation, [member, publisher, owner]),
205 89 PendingSubscription = lists:any(fun
206
:-(
({pending, _, _}) -> true;
207
:-(
(_) -> false
208 end,
209 Subscriptions),
210 89 case authorize_subscription(SenderMatchesSubscriber, Affiliation, PendingSubscription,
211 AccessModel, PresenceSubscription, RosterGroup, Whitelisted) of
212 ok ->
213 87 {NewSub, SubId} = case Subscriptions of
214 [{subscribed, Id, _}|_] ->
215
:-(
{subscribed, Id};
216 [] ->
217 87 Id = make_subid(),
218 87 Sub = access_model_to_subscription(AccessModel),
219 87 mod_pubsub_db_backend:add_subscription(Nidx, Subscriber, Sub, Id, Options),
220 87 {Sub, Id}
221 end,
222 87 case {NewSub, SendLast} of
223 {subscribed, never} ->
224 71 {result, {default, subscribed, SubId}};
225 {subscribed, _} ->
226 7 {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 78 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 87 ok.
267
268 %% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p>
269 unsubscribe_node(Nidx, Sender, Subscriber, SubId) ->
270 6 SenderMatchesSubscriber = jid:are_bare_equal(Subscriber, Sender),
271 6 {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, Subscriber),
272 6 SubIdExists = case SubId of
273 5 <<>> -> false;
274
:-(
Binary when is_binary(Binary) -> true;
275 1 _ -> false
276 end,
277 6 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 1 mod_pubsub_db_backend:delete_all_subscriptions(Nidx, Subscriber),
290 1 {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 1 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 120 BarePublisher = jid:to_bare(Publisher),
348 120 SubKey = jid:to_lower(Publisher),
349 120 GenKey = jid:to_lower(BarePublisher),
350 120 {ok, GenState} = mod_pubsub_db_backend:get_state(Nidx, GenKey),
351 120 SubState = case Publisher#jid.lresource of
352 <<>> ->
353
:-(
GenState;
354 _ ->
355 120 {ok, SubState0} = mod_pubsub_db_backend:get_state(Nidx, SubKey),
356 120 SubState0
357 end,
358 120 {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, GenKey),
359 120 Subscribed = case PublishModel of
360
:-(
subscribers -> is_subscribed(GenState#pubsub_state.subscriptions) orelse
361
:-(
is_subscribed(SubState#pubsub_state.subscriptions);
362 120 _ -> 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 120 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 120 case Allowed of
373 false ->
374 6 {error, mongoose_xmpp_errors:forbidden()};
375 true ->
376 114 case MaxItems > 0 of
377 true ->
378 111 Now = os:system_time(microsecond),
379 111 Item = make_pubsub_item(Nidx, ItemId, Now, SubKey, GenKey,
380 Payload, Publisher, ItemPublisher),
381 111 Items = [ItemId | GenState#pubsub_state.items -- [ItemId]],
382 111 {result, {_NI, OI}} = remove_extra_items(Nidx, MaxItems, Items),
383 111 mod_pubsub_db_backend:add_item(Nidx, GenKey, Item),
384 111 mod_pubsub_db_backend:remove_items(Nidx, GenKey, OI),
385 111 {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 111 PubId = {Now, SubKey},
393 111 case get_item(Nidx, ItemId) of
394 {result, OldItem} ->
395 2 OldItem#pubsub_item{modification = PubId,
396 payload = Payload};
397 _ ->
398 109 Publisher0 = case ItemPublisher of
399 4 true -> Publisher;
400 105 false -> undefined
401 end,
402 109 #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 111 NewItems = lists:sublist(ItemIds, MaxItems),
423 111 OldItems = lists:nthtail(length(NewItems), ItemIds),
424 111 del_items(Nidx, OldItems),
425 111 {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 328 {ok, States} = mod_pubsub_db_backend:get_states_by_bare(LOwner),
500 328 NodeTree = mod_pubsub:tree(Host),
501 328 Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) ->
502 83 case gen_pubsub_nodetree:get_node(NodeTree, N) of
503 76 #pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc];
504 7 _ -> Acc
505 end
506 end,
507 [], States),
508 328 {result, Reply}.
509
510 get_node_affiliations(Nidx) ->
511 6 {ok, States} = mod_pubsub_db_backend:get_states(Nidx),
512 6 Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end,
513 6 {result, lists:map(Tr, States)}.
514
515 get_affiliation(Nidx, Owner) ->
516 186 {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, jid:to_lower(Owner)),
517 186 {result, Affiliation}.
518
519 set_affiliation(Nidx, JID, Affiliation) ->
520 44 mod_pubsub_db_backend:set_affiliation(Nidx, JID, Affiliation).
521
522 get_entity_subscriptions(Host, Owner) ->
523 42 LOwner = jid:to_lower(Owner),
524 42 States = case Owner#jid.lresource of
525 <<>> ->
526 16 {ok, States0} = mod_pubsub_db_backend:get_states_by_lus(LOwner),
527 16 States0;
528 _ ->
529 26 {ok, States0} = mod_pubsub_db_backend:get_states_by_bare_and_full(LOwner),
530 26 States0
531 end,
532 42 NodeTree = mod_pubsub:tree(Host),
533 42 Reply = lists:foldl(fun (PubSubState, Acc) ->
534 14 get_entity_subscriptions_loop(NodeTree, PubSubState, Acc)
535 end,
536 [], States),
537 42 {result, Reply}.
538
539 get_entity_subscriptions_loop(NodeTree, #pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) ->
540 14 case gen_pubsub_nodetree:get_node(NodeTree, N) of
541 #pubsub_node{} = Node ->
542 14 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 554 {ok, Subscriptions} = mod_pubsub_db_backend:get_node_subscriptions(Nidx),
549 546 {result, Subscriptions}.
550
551 get_subscriptions(Nidx, #jid{} = Owner) ->
552 5 get_subscriptions(Nidx, jid:to_lower(Owner));
553 get_subscriptions(Nidx, LOwner) ->
554 23 {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LOwner),
555 23 {result, Subscriptions}.
556
557 set_subscriptions(Nidx, #jid{} = Owner, Subscription, SubId) ->
558 5 set_subscriptions(Nidx, jid:to_lower(Owner), Subscription, SubId);
559 set_subscriptions(Nidx, LOwner, Subscription, SubId) ->
560 13 {ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LOwner),
561 13 case {SubId, Subscriptions} of
562 {_, []} ->
563 6 case Subscription of
564 none ->
565
:-(
{error,
566 ?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"not-subscribed">>)};
567 _ ->
568 6 NewSubId = make_subid(),
569 6 mod_pubsub_db_backend:add_subscription(Nidx, LOwner, Subscription, NewSubId, [])
570 end;
571 {<<>>, [{_, SID, _}]} ->
572 2 case Subscription of
573 2 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 5 case Subscription of
581 2 none -> mod_pubsub_db_backend:delete_subscription(Nidx, LOwner, SubId);
582 3 _ -> 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 2 LOwner = jid:to_lower(Owner),
590 2 {ok, Nidxs} = mod_pubsub_db_backend:get_idxs_of_own_nodes_with_pending_subs(LOwner),
591 2 NodeTree = mod_pubsub:tree(Host),
592 2 {result,
593 lists:foldl(fun(N, Acc) ->
594 4 case gen_pubsub_nodetree:get_node(NodeTree, N) of
595 4 #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 51 case mod_pubsub_db_backend:get_items(Nidx, Opts) of
611 {ok, Result} ->
612 51 {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 38 LJID = jid:to_lower(JID),
622 38 {ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, LJID),
623 38 {ok, BareSubscriptions}
624 = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, jid:to_bare(LJID)),
625 38 {ok, FullSubscriptions}
626 = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LJID),
627 38 Whitelisted = can_fetch_item(Affiliation, BareSubscriptions) orelse
628 25 can_fetch_item(Affiliation, FullSubscriptions),
629 38 case authorize_get_item(Affiliation, AccessModel, PresenceSubscription,
630 RosterGroup, Whitelisted) of
631 ok ->
632 38 LowLevelOpts = #{rsm => RSM,
633 max_items => maps:get(max_items, Opts, undefined),
634 item_ids => maps:get(item_ids, Opts, undefined)},
635 38 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 119 case mod_pubsub_db_backend:get_item(Nidx, ItemId) of
643 {ok, Item} ->
644 8 {result, Item};
645 {error, Error} ->
646 111 {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 38 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 8 mod_pubsub_db_backend:del_item(Nidx, ItemId).
682
683 del_items(Nidx, ItemIds) ->
684 115 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 120 [(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 8 should_delete_when_owner_removed() -> false.
705
706 13 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 50 can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions).
712 %can_fetch_item(_Affiliation, _Subscription) -> false.
713
714 is_subscribed(Subscriptions) ->
715 50 lists:any(fun
716 4 ({subscribed, _SubId, _}) -> true;
717
:-(
(_) -> false
718 end,
719 Subscriptions).
720
721 make_subid() ->
722 93 mongoose_bin:gen_from_timestamp().
723
724 remove_user(LUser, LServer) ->
725 134 mod_pubsub_db_backend:dirty(fun() ->
726 134 LJID = {LUser, LServer, <<>>},
727 134 mod_pubsub_db_backend:delete_user_subscriptions(LJID),
728 134 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 134 lists:foreach(
734 fun(NodeAffPair) ->
735 21 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 19 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 11 mod_pubsub_db_backend:delete_node(Node),
751 11 mod_pubsub_db_backend:del_node(Nidx);
752 _ ->
753 8 mod_pubsub_db_backend:set_affiliation(Nidx, LJID, none)
754 end;
755 remove_user_by_affiliation_and_node({#pubsub_node{ id = Nidx }, _}, LJID) ->
756 2 mod_pubsub_db_backend:set_affiliation(Nidx, LJID, none).
757
Line Hits Source