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 core of the PubSub |
27 |
|
%%% extension. It relies on PubSub plugins for a large part of its functions. |
28 |
|
%%% |
29 |
|
%%% @headerfile "pubsub.hrl" |
30 |
|
%%% |
31 |
|
%%% @reference See <a href="http://www.xmpp.org/extensions/xep-0060.html">XEP-0060: Pubsub</a> for |
32 |
|
%%% the latest version of the PubSub specification. |
33 |
|
%%% This module uses version 1.12 of the specification as a base. |
34 |
|
%%% Most of the specification is implemented. |
35 |
|
%%% Functions concerning configuration should be rewritten. |
36 |
|
%%% |
37 |
|
%%% Support for subscription-options and multi-subscribe features was |
38 |
|
%%% added by Brian Cully (bjc AT kublai.com). For information on |
39 |
|
%%% subscription-options and multi-subscribe see XEP-0060 sections 6.1.6, |
40 |
|
%%% 6.2.3.1, 6.2.3.5, and 6.3. For information on subscription leases see |
41 |
|
%%% XEP-0060 section 12.18. |
42 |
|
|
43 |
|
-module(mod_pubsub). |
44 |
|
-behaviour(gen_mod). |
45 |
|
-behaviour(gen_server). |
46 |
|
-behaviour(mongoose_packet_handler). |
47 |
|
-behaviour(mongoose_module_metrics). |
48 |
|
-author('christophe.romain@process-one.net'). |
49 |
|
|
50 |
|
-xep([{xep, 60}, {version, "1.25.0"}]). |
51 |
|
-xep([{xep, 163}, {version, "1.2.2"}]). |
52 |
|
-xep([{xep, 248}, {version, "0.3.0"}]). |
53 |
|
-xep([{xep, 277}, {version, "0.6.5"}]). |
54 |
|
|
55 |
|
%% https://xmpp.org/extensions/xep-0384.html#server-side |
56 |
|
-xep([{xep, 384}, {version, "0.8.3"}]). |
57 |
|
|
58 |
|
-include("mongoose.hrl"). |
59 |
|
-include("adhoc.hrl"). |
60 |
|
-include("jlib.hrl"). |
61 |
|
-include("pubsub.hrl"). |
62 |
|
-include("mongoose_config_spec.hrl"). |
63 |
|
-include("session.hrl"). |
64 |
|
|
65 |
|
-define(STDTREE, nodetree_tree). |
66 |
|
-define(STDNODE, <<"flat">>). |
67 |
|
-define(STDNODE_MODULE, node_flat). |
68 |
|
-define(PEPNODE, <<"pep">>). |
69 |
|
-define(PUSHNODE, <<"push">>). |
70 |
|
|
71 |
|
%% exports for hooks |
72 |
|
-export([presence_probe/3, |
73 |
|
caps_recognised/3, |
74 |
|
in_subscription/3, |
75 |
|
out_subscription/3, |
76 |
|
on_user_offline/3, |
77 |
|
remove_user/3, |
78 |
|
disco_local_features/3, |
79 |
|
disco_sm_identity/3, |
80 |
|
disco_sm_features/3, |
81 |
|
disco_sm_items/3, |
82 |
|
handle_pep_authorization_response/3, |
83 |
|
foreign_event/3]). |
84 |
|
|
85 |
|
%% exported iq handlers |
86 |
|
-export([iq_sm/4]). |
87 |
|
|
88 |
|
%% exports for console debug manual use |
89 |
|
-export([create_node/5, create_node/7, delete_node/3, |
90 |
|
subscribe_node/5, unsubscribe_node/5, publish_item/6, |
91 |
|
delete_item/4, send_items/7, get_items/2, get_item/3, |
92 |
|
get_cached_item/2, tree_action/3, node_action/4]). |
93 |
|
|
94 |
|
%% general helpers for plugins |
95 |
|
-export([subscription_to_string/1, affiliation_to_string/1, |
96 |
|
string_to_subscription/1, string_to_affiliation/1, |
97 |
|
extended_error/2, extended_error/3, service_jid/1, |
98 |
|
tree/1, plugin/1, plugin_call/3, host/2, serverhost/1, |
99 |
|
host_to_host_type/1]). |
100 |
|
|
101 |
|
%% API and gen_server callbacks |
102 |
|
-export([start_link/2, start/2, stop/1, deps/2, init/1, |
103 |
|
handle_call/3, handle_cast/2, handle_info/2, |
104 |
|
terminate/2, code_change/3]). |
105 |
|
|
106 |
|
%% Config callbacks |
107 |
|
-export([config_spec/0, process_pep_mapping/1]). |
108 |
|
|
109 |
|
-export([default_host/0]). |
110 |
|
|
111 |
|
-export([get_personal_data/3]). |
112 |
|
|
113 |
|
%% packet handler export |
114 |
|
-export([process_packet/5]). |
115 |
|
|
116 |
|
-export([config_metrics/1]). |
117 |
|
|
118 |
|
%% Private export for wpool worker callbacks |
119 |
|
-export([handle_msg/1]). |
120 |
|
|
121 |
|
-define(MOD_PUBSUB_DB_BACKEND, mod_pubsub_db_backend). |
122 |
|
-ignore_xref([ |
123 |
|
{?MOD_PUBSUB_DB_BACKEND, transaction, 2}, |
124 |
|
{?MOD_PUBSUB_DB_BACKEND, get_user_nodes, 2}, |
125 |
|
{?MOD_PUBSUB_DB_BACKEND, get_user_payloads, 2}, |
126 |
|
{?MOD_PUBSUB_DB_BACKEND, get_user_subscriptions, 2}, |
127 |
|
{?MOD_PUBSUB_DB_BACKEND, start, 0}, |
128 |
|
{?MOD_PUBSUB_DB_BACKEND, set_subscription_opts, 4}, |
129 |
|
{?MOD_PUBSUB_DB_BACKEND, stop, 0}, |
130 |
|
affiliation_to_string/1, |
131 |
|
create_node/7, |
132 |
|
default_host/0, |
133 |
|
delete_item/4, |
134 |
|
delete_node/3, |
135 |
|
extended_error/3, |
136 |
|
get_cached_item/2, |
137 |
|
get_item/3, |
138 |
|
get_items/2, |
139 |
|
host/2, |
140 |
|
iq_sm/4, |
141 |
|
node_action/4, |
142 |
|
node_call/4, |
143 |
|
plugin/2, |
144 |
|
plugin/1, |
145 |
|
publish_item/6, |
146 |
|
send_items/7, |
147 |
|
serverhost/1, |
148 |
|
start_link/2, |
149 |
|
string_to_affiliation/1, |
150 |
|
string_to_subscription/1, |
151 |
|
subscribe_node/5, |
152 |
|
subscription_to_string/1, |
153 |
|
tree_action/3, |
154 |
|
unsubscribe_node/5, |
155 |
|
handle_msg/1 |
156 |
|
]). |
157 |
|
|
158 |
|
-type plugin_name() :: binary(). |
159 |
|
|
160 |
|
-export_type([ |
161 |
|
host/0, |
162 |
|
hostPubsub/0, |
163 |
|
hostPEP/0, |
164 |
|
%% |
165 |
|
nodeIdx/0, |
166 |
|
nodeId/0, |
167 |
|
itemId/0, |
168 |
|
subId/0, |
169 |
|
payload/0, |
170 |
|
%% |
171 |
|
nodeOption/0, |
172 |
|
nodeOptions/0, |
173 |
|
subOption/0, |
174 |
|
subOptions/0, |
175 |
|
%% |
176 |
|
affiliation/0, |
177 |
|
subscription/0, |
178 |
|
accessModel/0, |
179 |
|
publishModel/0 |
180 |
|
]). |
181 |
|
|
182 |
|
%% -type payload() defined here because the -type exml:element() is not accessible |
183 |
|
%% from pubsub.hrl |
184 |
|
-type(payload() :: [] | [exml:element(), ...]). |
185 |
|
-type(publishOptions() :: undefined | exml:element()). |
186 |
|
|
187 |
|
-export_type([ |
188 |
|
pubsubNode/0, |
189 |
|
pubsubState/0, |
190 |
|
pubsubItem/0, |
191 |
|
pubsubLastItem/0, |
192 |
|
publishOptions/0 |
193 |
|
]). |
194 |
|
|
195 |
|
-type(pubsubNode() :: |
196 |
|
#pubsub_node{ |
197 |
|
nodeid :: {Host::mod_pubsub:host(), Node::mod_pubsub:nodeId()}, |
198 |
|
id :: Nidx::mod_pubsub:nodeIdx(), |
199 |
|
parents :: [Node::mod_pubsub:nodeId()], |
200 |
|
type :: Type::binary(), |
201 |
|
owners :: [Owner::jid:ljid(), ...], |
202 |
|
options :: Opts::mod_pubsub:nodeOptions() |
203 |
|
} |
204 |
|
). |
205 |
|
|
206 |
|
-type(pubsubState() :: |
207 |
|
#pubsub_state{ |
208 |
|
stateid :: {Entity::jid:ljid(), Nidx::mod_pubsub:nodeIdx()}, |
209 |
|
items :: [ItemId::mod_pubsub:itemId()], |
210 |
|
affiliation :: Affs::mod_pubsub:affiliation(), |
211 |
|
subscriptions :: [{Sub::mod_pubsub:subscription(), SubId::mod_pubsub:subId()}] |
212 |
|
} |
213 |
|
). |
214 |
|
|
215 |
|
-type(pubsubItem() :: |
216 |
|
#pubsub_item{ |
217 |
|
itemid :: {ItemId::mod_pubsub:itemId(), Nidx::mod_pubsub:nodeIdx()}, |
218 |
|
creation :: {integer(), jid:ljid()}, |
219 |
|
modification :: {integer(), jid:ljid()}, |
220 |
|
payload :: mod_pubsub:payload() |
221 |
|
} |
222 |
|
). |
223 |
|
|
224 |
|
-type(pubsubLastItem() :: |
225 |
|
#pubsub_last_item{ |
226 |
|
nodeid :: mod_pubsub:nodeIdx(), |
227 |
|
itemid :: mod_pubsub:itemId(), |
228 |
|
creation :: {integer(), jid:ljid()}, |
229 |
|
payload :: mod_pubsub:payload() |
230 |
|
} |
231 |
|
). |
232 |
|
|
233 |
|
-record(state, |
234 |
|
{ |
235 |
|
server_host, |
236 |
|
host, |
237 |
|
access, |
238 |
|
pep_mapping = #{}, |
239 |
|
ignore_pep_from_offline = true, |
240 |
|
last_item_cache = false, |
241 |
|
max_items_node = ?MAXITEMS, |
242 |
|
max_subscriptions_node = undefined, |
243 |
|
default_node_config = [], |
244 |
|
nodetree = ?STDTREE, |
245 |
|
plugins = [?STDNODE] :: [plugin_name()] |
246 |
|
}). |
247 |
|
|
248 |
|
-type(state() :: |
249 |
|
#state{ |
250 |
|
server_host :: binary(), |
251 |
|
host :: mod_pubsub:hostPubsub(), |
252 |
|
access :: atom(), |
253 |
|
pep_mapping :: map(), |
254 |
|
ignore_pep_from_offline :: boolean(), |
255 |
|
last_item_cache :: mnesia | rdbms | false, |
256 |
|
max_items_node :: non_neg_integer(), |
257 |
|
max_subscriptions_node :: non_neg_integer()|undefined, |
258 |
|
default_node_config :: [{atom(), binary()|boolean()|integer()|atom()}], |
259 |
|
nodetree :: module(), |
260 |
|
plugins :: [binary(), ...] |
261 |
|
} |
262 |
|
|
263 |
|
). |
264 |
|
|
265 |
|
%%==================================================================== |
266 |
|
%% API |
267 |
|
%%==================================================================== |
268 |
|
|
269 |
|
%% @doc: Starts the server. |
270 |
|
-spec start_link(mongooseim:domain_name(), gen_mod:module_opts()) -> {ok, pid()} | ignore | {error, any()}. |
271 |
|
start_link(Host, Opts) -> |
272 |
39 |
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), |
273 |
39 |
gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []). |
274 |
|
|
275 |
|
deps(_Host, _Opts) -> |
276 |
239 |
[{mod_caps, #{cache_size => 1000, cache_life_time => timer:hours(24) div 1000}, optional}]. |
277 |
|
|
278 |
|
start(Host, Opts) -> |
279 |
39 |
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), |
280 |
39 |
ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, |
281 |
|
transient, 1000, worker, [?MODULE]}, |
282 |
39 |
ensure_metrics(Host), |
283 |
39 |
start_pool(Host, Opts), |
284 |
39 |
ejabberd_sup:start_child(ChildSpec). |
285 |
|
|
286 |
|
start_pool(HostType, #{wpool := WpoolOpts}) -> |
287 |
39 |
{ok, _} = mongoose_wpool:start(generic, HostType, pubsub_notify, maps:to_list(WpoolOpts)). |
288 |
|
|
289 |
|
stop(Host) -> |
290 |
39 |
Proc = gen_mod:get_module_proc(Host, ?PROCNAME), |
291 |
39 |
gen_server:call(Proc, stop), |
292 |
39 |
mongoose_wpool:stop(generic, Host, pubsub_notify), |
293 |
39 |
ejabberd_sup:stop_child(Proc). |
294 |
|
|
295 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
296 |
|
config_spec() -> |
297 |
208 |
#section{ |
298 |
|
items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(), |
299 |
|
<<"host">> => #option{type = string, |
300 |
|
validate = subdomain_template, |
301 |
|
process = fun mongoose_subdomain_utils:make_subdomain_pattern/1}, |
302 |
|
<<"backend">> => #option{type = atom, |
303 |
|
validate = {module, mod_pubsub_db}}, |
304 |
|
<<"access_createnode">> => #option{type = atom, |
305 |
|
validate = access_rule}, |
306 |
|
<<"max_items_node">> => #option{type = integer, |
307 |
|
validate = non_negative}, |
308 |
|
<<"max_subscriptions_node">> => #option{type = integer, |
309 |
|
validate = non_negative}, |
310 |
|
<<"nodetree">> => #option{type = binary, |
311 |
|
validate = {module, nodetree}, |
312 |
|
process = fun tree_mod/1}, |
313 |
|
<<"ignore_pep_from_offline">> => #option{type = boolean}, |
314 |
|
<<"last_item_cache">> => #option{type = atom, |
315 |
|
validate = {enum, [mnesia, rdbms, false]}}, |
316 |
|
<<"plugins">> => #list{items = #option{type = binary, |
317 |
|
validate = {module, node}}}, |
318 |
|
<<"pep_mapping">> => #list{items = pep_mapping_config_spec(), |
319 |
|
format_items = map}, |
320 |
|
<<"default_node_config">> => default_node_config_spec(), |
321 |
|
<<"item_publisher">> => #option{type = boolean}, |
322 |
|
<<"sync_broadcast">> => #option{type = boolean}, |
323 |
|
<<"wpool">> => wpool_spec() |
324 |
|
}, |
325 |
|
defaults = #{<<"iqdisc">> => one_queue, |
326 |
|
<<"host">> => default_host(), |
327 |
|
<<"backend">> => mnesia, |
328 |
|
<<"access_createnode">> => all, |
329 |
|
<<"max_items_node">> => ?MAXITEMS, |
330 |
|
<<"nodetree">> => ?STDTREE, |
331 |
|
<<"ignore_pep_from_offline">> => true, |
332 |
|
<<"last_item_cache">> => false, |
333 |
|
<<"plugins">> => [?STDNODE], |
334 |
|
<<"pep_mapping">> => #{}, |
335 |
|
<<"default_node_config">> => [], |
336 |
|
<<"item_publisher">> => false, |
337 |
|
<<"sync_broadcast">> => false} |
338 |
|
}. |
339 |
|
|
340 |
|
wpool_spec() -> |
341 |
208 |
Wpool = mongoose_config_spec:wpool(#{}), |
342 |
208 |
Wpool#section{include = always}. |
343 |
|
|
344 |
|
pep_mapping_config_spec() -> |
345 |
208 |
#section{ |
346 |
|
items = #{<<"namespace">> => #option{type = binary, |
347 |
|
validate = non_empty}, |
348 |
|
<<"node">> => #option{type = binary, |
349 |
|
validate = non_empty}}, |
350 |
|
required = all, |
351 |
|
process = fun ?MODULE:process_pep_mapping/1 |
352 |
|
}. |
353 |
|
|
354 |
|
default_node_config_spec() -> |
355 |
208 |
#section{ |
356 |
|
items = #{<<"access_model">> => #option{type = atom, |
357 |
|
validate = non_empty}, |
358 |
|
<<"deliver_notifications">> => #option{type = boolean}, |
359 |
|
<<"deliver_payloads">> => #option{type = boolean}, |
360 |
|
<<"max_items">> => #option{type = integer, |
361 |
|
validate = non_negative}, |
362 |
|
<<"max_payload_size">> => #option{type = integer, |
363 |
|
validate = non_negative}, |
364 |
|
<<"node_type">> => #option{type = atom, |
365 |
|
validate = non_empty}, |
366 |
|
<<"notification_type">> => #option{type = atom, |
367 |
|
validate = non_empty}, |
368 |
|
<<"notify_config">> => #option{type = boolean}, |
369 |
|
<<"notify_delete">> => #option{type = boolean}, |
370 |
|
<<"notify_retract">> => #option{type = boolean}, |
371 |
|
<<"persist_items">> => #option{type = boolean}, |
372 |
|
<<"presence_based_delivery">> => #option{type = boolean}, |
373 |
|
<<"publish_model">> => #option{type = atom, |
374 |
|
validate = non_empty}, |
375 |
|
<<"purge_offline">> => #option{type = boolean}, |
376 |
|
<<"roster_groups_allowed">> => #list{items = #option{type = binary, |
377 |
|
validate = non_empty}}, |
378 |
|
<<"send_last_published_item">> => #option{type = atom, |
379 |
|
validate = non_empty}, |
380 |
|
<<"subscribe">> => #option{type = boolean} |
381 |
|
}, |
382 |
|
format_items = list |
383 |
|
}. |
384 |
|
|
385 |
|
process_pep_mapping(#{namespace := NameSpace, node := Node}) -> |
386 |
:-( |
{NameSpace, Node}. |
387 |
|
|
388 |
|
-spec default_host() -> mongoose_subdomain_utils:subdomain_pattern(). |
389 |
|
default_host() -> |
390 |
208 |
mongoose_subdomain_utils:make_subdomain_pattern(<<"pubsub.@HOST@">>). |
391 |
|
|
392 |
|
%% State is an extra data, required for processing |
393 |
|
-spec process_packet(Acc :: mongoose_acc:t(), From ::jid:jid(), To ::jid:jid(), El :: exml:element(), |
394 |
|
#{state := #state{}}) -> mongoose_acc:t(). |
395 |
|
process_packet(Acc, From, To, El, #{state := State}) -> |
396 |
834 |
#state{server_host = ServerHost, access = Access, plugins = Plugins} = State, |
397 |
834 |
do_route(Acc, ServerHost, Access, Plugins, To#jid.lserver, From, To, El). |
398 |
|
|
399 |
|
%%==================================================================== |
400 |
|
%% GDPR callback |
401 |
|
%%==================================================================== |
402 |
|
|
403 |
|
-spec get_personal_data(Acc, Params, Extra) -> {ok, Acc} when |
404 |
|
Acc :: gdpr:personal_data(), |
405 |
|
Params :: #{jid := jid:jid()}, |
406 |
|
Extra :: gen_hook:extra(). |
407 |
|
get_personal_data(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, _) -> |
408 |
21 |
Payloads = mod_pubsub_db_backend:get_user_payloads(LUser, LServer), |
409 |
21 |
Nodes = mod_pubsub_db_backend:get_user_nodes(LUser, LServer), |
410 |
21 |
Subscriptions = mod_pubsub_db_backend:get_user_subscriptions(LUser, LServer), |
411 |
|
|
412 |
21 |
NewAcc = [{pubsub_payloads, ["node_name", "item_id", "payload"], Payloads}, |
413 |
|
{pubsub_nodes, ["node_name", "type"], Nodes}, |
414 |
|
{pubsub_subscriptions, ["node_name"], Subscriptions} | Acc], |
415 |
21 |
{ok, NewAcc}. |
416 |
|
|
417 |
|
%%==================================================================== |
418 |
|
%% gen_server callbacks |
419 |
|
%%==================================================================== |
420 |
|
|
421 |
|
-spec init([binary() | gen_mod:module_opts(), ...]) -> {'ok', state()}. |
422 |
|
init([ServerHost, Opts = #{host := SubdomainPattern}]) -> |
423 |
39 |
?LOG_DEBUG(#{what => pubsub_init, server => ServerHost, opts => Opts}), |
424 |
39 |
Host = mongoose_subdomain_utils:get_fqdn(SubdomainPattern, ServerHost), |
425 |
|
|
426 |
39 |
init_backend(ServerHost, Opts), |
427 |
39 |
Plugins = init_plugins(Host, ServerHost, Opts), |
428 |
|
|
429 |
39 |
gen_hook:add_handlers(hooks(ServerHost)), |
430 |
39 |
case lists:member(?PEPNODE, Plugins) of |
431 |
|
true -> |
432 |
4 |
gen_hook:add_handlers(pep_hooks(ServerHost)), |
433 |
4 |
add_pep_iq_handlers(ServerHost, Opts); |
434 |
|
false -> |
435 |
35 |
ok |
436 |
|
end, |
437 |
39 |
State = init_state(ServerHost, Opts, Plugins), |
438 |
|
|
439 |
|
%% Pass State as extra into ?MODULE:process_packet/5 function |
440 |
39 |
PacketHandler = mongoose_packet_handler:new(?MODULE, #{state => State}), |
441 |
|
%% TODO: Conversion of this module is not done, it doesn't support dynamic |
442 |
|
%% domains yet. Only subdomain registration is done properly. |
443 |
39 |
mongoose_domain_api:register_subdomain(ServerHost, SubdomainPattern, PacketHandler), |
444 |
39 |
{ok, State}. |
445 |
|
|
446 |
|
init_backend(ServerHost, Opts) -> |
447 |
39 |
mod_pubsub_db_backend:init(ServerHost, Opts), |
448 |
39 |
maybe_start_cache_module(ServerHost, Opts). |
449 |
|
|
450 |
|
hooks(ServerHost) -> |
451 |
78 |
[{disco_local_features, ServerHost, fun ?MODULE:disco_local_features/3, #{}, 75}, |
452 |
|
{sm_remove_connection_hook, ServerHost, fun ?MODULE:on_user_offline/3, #{}, 75}, |
453 |
|
{presence_probe_hook, ServerHost, fun ?MODULE:presence_probe/3, #{}, 80}, |
454 |
|
{roster_in_subscription, ServerHost, fun ?MODULE:in_subscription/3, #{}, 50}, |
455 |
|
{roster_out_subscription, ServerHost, fun ?MODULE:out_subscription/3, #{}, 50}, |
456 |
|
{remove_user, ServerHost, fun ?MODULE:remove_user/3, #{}, 50}, |
457 |
|
{anonymous_purge_hook, ServerHost, fun ?MODULE:remove_user/3, #{}, 50}, |
458 |
|
{get_personal_data, ServerHost, fun ?MODULE:get_personal_data/3, #{}, 50}]. |
459 |
|
|
460 |
|
pep_hooks(ServerHost) -> |
461 |
8 |
[ |
462 |
|
{caps_recognised, ServerHost, fun ?MODULE:caps_recognised/3, #{}, 80}, |
463 |
|
{disco_sm_identity, ServerHost, fun ?MODULE:disco_sm_identity/3, #{}, 75}, |
464 |
|
{disco_sm_features, ServerHost, fun ?MODULE:disco_sm_features/3, #{}, 75}, |
465 |
|
{disco_sm_items, ServerHost, fun ?MODULE:disco_sm_items/3, #{}, 75}, |
466 |
|
{filter_local_packet, ServerHost, fun ?MODULE:handle_pep_authorization_response/3, #{}, 1}, |
467 |
|
{foreign_event, ServerHost, fun ?MODULE:foreign_event/3, #{}, 100} |
468 |
|
]. |
469 |
|
|
470 |
|
add_pep_iq_handlers(ServerHost, #{iqdisc := IQDisc}) -> |
471 |
4 |
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB, ?MODULE, iq_sm, IQDisc), |
472 |
4 |
gen_iq_handler:add_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER, |
473 |
|
?MODULE, iq_sm, IQDisc). |
474 |
|
|
475 |
|
delete_pep_iq_handlers(ServerHost) -> |
476 |
4 |
gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB), |
477 |
4 |
gen_iq_handler:remove_iq_handler(ejabberd_sm, ServerHost, ?NS_PUBSUB_OWNER). |
478 |
|
|
479 |
|
%% Plugins is a subset of configured plugins which are able to start |
480 |
|
%% TODO Evaluate if we should just use plugins from the config instead |
481 |
|
init_state(ServerHost, #{last_item_cache := LastItemCache, max_items_node := MaxItemsNode, |
482 |
|
pep_mapping := PepMapping, ignore_pep_from_offline := PepOffline, |
483 |
|
access_createnode := Access}, Plugins) -> |
484 |
39 |
HostType = host_to_host_type(ServerHost), |
485 |
39 |
NodeTree = tree(HostType), |
486 |
39 |
Host = host(HostType, ServerHost), |
487 |
39 |
#state{host = Host, server_host = ServerHost, |
488 |
|
access = Access, pep_mapping = PepMapping, |
489 |
|
ignore_pep_from_offline = PepOffline, |
490 |
|
last_item_cache = LastItemCache, |
491 |
|
max_items_node = MaxItemsNode, nodetree = NodeTree, |
492 |
|
plugins = Plugins }. |
493 |
|
|
494 |
|
%% @doc Call the init/1 function for each plugin declared in the config file. |
495 |
|
%% The default plugin module is implicit. |
496 |
|
%% <p>The Erlang code for the plugin is located in a module called |
497 |
|
%% <em>node_plugin</em>. The 'node_' prefix is mandatory.</p> |
498 |
|
%% <p>The modules are initialized in alphabetical order and the list is checked |
499 |
|
%% and sorted to ensure that each module is initialized only once.</p> |
500 |
|
%% <p>See {@link node_hometree:init/1} for an example implementation.</p> |
501 |
|
init_plugins(Host, ServerHost, Opts = #{nodetree := TreePlugin, plugins := Plugins}) -> |
502 |
39 |
{ok, HostType} = mongoose_domain_api:get_host_type(ServerHost), |
503 |
39 |
?LOG_DEBUG(#{what => pubsub_tree_plugin, tree_plugin => TreePlugin}), |
504 |
39 |
gen_pubsub_nodetree:init(TreePlugin, HostType, Opts), |
505 |
39 |
PluginsOK = lists:foldl(pa:bind(fun init_plugin/5, Host, ServerHost, Opts), [], Plugins), |
506 |
39 |
lists:reverse(PluginsOK). |
507 |
|
|
508 |
|
init_plugin(Host, ServerHost, Opts, Name, Acc) -> |
509 |
58 |
Plugin = plugin(Name), |
510 |
58 |
case catch apply(Plugin, init, [Host, ServerHost, Opts]) of |
511 |
|
{'EXIT', Error} -> |
512 |
:-( |
?LOG_ERROR(#{what => pubsub_plugin_init_failed, plugin => Plugin, |
513 |
:-( |
server => ServerHost, sub_host => Host, opts => Opts, error => Error}), |
514 |
:-( |
Acc; |
515 |
|
_ -> |
516 |
58 |
?LOG_DEBUG(#{what => pubsub_init_plugin, plugin_name => Name}), |
517 |
58 |
[Name | Acc] |
518 |
|
end. |
519 |
|
|
520 |
|
terminate_plugins(Host, ServerHost, Plugins, TreePlugin) -> |
521 |
39 |
lists:foreach( |
522 |
|
fun (Name) -> |
523 |
58 |
?LOG_DEBUG(#{what => pubsub_terminate_plugin, plugin_name => Name}), |
524 |
58 |
Plugin = plugin(Name), |
525 |
58 |
gen_pubsub_node:terminate(Plugin, Host, ServerHost) |
526 |
|
end, |
527 |
|
Plugins), |
528 |
39 |
gen_pubsub_nodetree:terminate(TreePlugin, Host, ServerHost), |
529 |
39 |
ok. |
530 |
|
|
531 |
|
notify_worker(HostType, HashKey, Request) -> |
532 |
404 |
mongoose_wpool:cast(generic, HostType, pubsub_notify, HashKey, |
533 |
|
{?MODULE, handle_msg, [Request]}). |
534 |
|
|
535 |
|
handle_msg({send_last_pubsub_items, Host, Recipient, Plugins}) -> |
536 |
377 |
send_last_pubsub_items(Host, Recipient, Plugins); |
537 |
|
handle_msg({send_last_pep_items, Host, IgnorePepFromOffline, RecipientJID, RecipientPid, Features}) -> |
538 |
9 |
send_last_pep_items(Host, IgnorePepFromOffline, RecipientJID, RecipientPid, Features); |
539 |
|
handle_msg({send_last_items_from_owner, Host, NodeOwner, RecipientInfo}) -> |
540 |
18 |
send_last_items_from_owner(Host, NodeOwner, RecipientInfo). |
541 |
|
|
542 |
|
send_last_pubsub_items(Host, Recipient, Plugins) -> |
543 |
377 |
F = fun(PluginType) -> send_last_pubsub_items_for_plugin(Host, PluginType, Recipient) end, |
544 |
377 |
lists:foreach(F, Plugins). |
545 |
|
|
546 |
|
send_last_pubsub_items_for_plugin(Host, PluginType, Recipient) -> |
547 |
552 |
JIDs = [Recipient, jid:to_lower(Recipient), jid:to_bare(Recipient)], |
548 |
552 |
Subs = get_subscriptions_for_send_last(Host, PluginType, JIDs), |
549 |
552 |
lists:foreach( |
550 |
|
fun({#pubsub_node{nodeid={_, Node}, id=Nidx, options=Options}, _, _, SubJID}) -> |
551 |
:-( |
send_items(Host, Node, Nidx, PluginType, Options, SubJID, last) |
552 |
|
end, |
553 |
|
lists:usort(Subs)). |
554 |
|
|
555 |
|
send_last_pep_items(Host, IgnorePepFromOffline, RecipientJID, RecipientPid, Features) -> |
556 |
9 |
RecipientLJID = jid:to_lower(RecipientJID), |
557 |
9 |
[send_last_item_to_jid(NodeOwnerJID, NodeRec, RecipientLJID) || |
558 |
9 |
NodeOwnerJID <- get_contacts_for_sending_last_item(RecipientPid, IgnorePepFromOffline), |
559 |
10 |
NodeRec = #pubsub_node{nodeid = {_, Node}} <- get_nodes_for_sending_last_item(Host, NodeOwnerJID), |
560 |
2 |
lists:member(<<Node/binary, "+notify">>, Features)], |
561 |
9 |
ok. |
562 |
|
|
563 |
|
get_contacts_for_sending_last_item(RecipientPid, IgnorePepFromOffline) -> |
564 |
9 |
case catch mod_presence:get_subscribed(RecipientPid) of |
565 |
|
Contacts when is_list(Contacts) -> |
566 |
8 |
[Contact || Contact = #jid{luser = U, lserver = S} <- Contacts, |
567 |
10 |
user_resources(U, S) /= [] orelse not IgnorePepFromOffline]; |
568 |
|
_ -> |
569 |
1 |
[] |
570 |
|
end. |
571 |
|
|
572 |
|
send_last_items_from_owner(Host, NodeOwner, _Recipient = {U, S, Resources}) -> |
573 |
18 |
[send_last_item_to_jid(NodeOwner, NodeRec, {U, S, R}) || |
574 |
18 |
NodeRec = #pubsub_node{nodeid = {_, Node}} <- get_nodes_for_sending_last_item(Host, NodeOwner), |
575 |
7 |
R <- Resources, |
576 |
7 |
not should_drop_pep_message(Node, jid:make(U, S, R)) |
577 |
|
], |
578 |
18 |
ok. |
579 |
|
|
580 |
|
should_drop_pep_message(Node, RecipientJid) -> |
581 |
7 |
case ejabberd_sm:get_session_pid(RecipientJid) of |
582 |
:-( |
none -> true; |
583 |
|
Pid -> |
584 |
7 |
Event = {filter_pep_recipient, RecipientJid, <<Node/binary, "+notify">>}, |
585 |
7 |
try mongoose_c2s:call(Pid, ?MODULE, Event) |
586 |
:-( |
catch exit:timeout -> true |
587 |
|
end |
588 |
|
end. |
589 |
|
|
590 |
|
get_nodes_for_sending_last_item(Host, NodeOwnerJID) -> |
591 |
28 |
lists:filter(fun(#pubsub_node{options = Options}) -> |
592 |
9 |
match_option(Options, send_last_published_item, on_sub_and_presence) |
593 |
|
end, |
594 |
|
get_nodes_owned_by(Host, NodeOwnerJID)). |
595 |
|
|
596 |
|
get_nodes_owned_by(Host, OwnerJID) -> |
597 |
28 |
OwnerBLJID = jid:to_bare(jid:to_lower(OwnerJID)), |
598 |
28 |
tree_action(Host, get_nodes, [OwnerBLJID, OwnerJID]). |
599 |
|
|
600 |
|
send_last_item_to_jid(NodeOwner, #pubsub_node{nodeid = {_, Node}, type = NodeType, |
601 |
|
id = Nidx, options = NodeOptions}, RecipientJID) -> |
602 |
6 |
NodeOwnerBLJID = jid:to_bare(jid:to_lower(NodeOwner)), |
603 |
6 |
case is_subscribed(RecipientJID, NodeOwnerBLJID, NodeOptions) of |
604 |
6 |
true -> send_items(NodeOwnerBLJID, Node, Nidx, NodeType, NodeOptions, RecipientJID, last); |
605 |
:-( |
false -> ok |
606 |
|
end. |
607 |
|
|
608 |
|
is_subscribed(Recipient, NodeOwner, NodeOptions) -> |
609 |
6 |
case get_option(NodeOptions, access_model) of |
610 |
:-( |
open -> true; |
611 |
6 |
presence -> true; |
612 |
:-( |
whitelist -> false; % subscribers are added manually |
613 |
:-( |
authorize -> false; % likewise |
614 |
|
roster -> |
615 |
:-( |
Grps = get_option(NodeOptions, roster_groups_allowed, []), |
616 |
:-( |
{OU, OS, _} = NodeOwner, |
617 |
:-( |
element(2, get_roster_info(OU, OS, Recipient, Grps)) |
618 |
|
end. |
619 |
|
|
620 |
|
%% ------- |
621 |
|
%% disco hooks handling functions |
622 |
|
%% |
623 |
|
|
624 |
|
-spec identities(jid:lserver(), ejabberd:lang()) -> [mongoose_disco:identity()]. |
625 |
|
identities(Host, Lang) -> |
626 |
4 |
pubsub_identity(Lang) ++ node_identity(Host, ?PEPNODE) ++ node_identity(Host, ?PUSHNODE). |
627 |
|
|
628 |
|
-spec pubsub_identity(ejabberd:lang()) -> [mongoose_disco:identity()]. |
629 |
|
pubsub_identity(Lang) -> |
630 |
4 |
[#{category => <<"pubsub">>, |
631 |
|
type => <<"service">>, |
632 |
|
name => translate:translate(Lang, <<"Publish-Subscribe">>)}]. |
633 |
|
|
634 |
|
-spec node_identity(jid:lserver(), binary()) -> [mongoose_disco:identity()]. |
635 |
|
node_identity(Host, Type) -> |
636 |
8 |
case lists:member(Type, plugins(Host)) of |
637 |
2 |
true -> [#{category => <<"pubsub">>, type => Type}]; |
638 |
6 |
false -> [] |
639 |
|
end. |
640 |
|
|
641 |
|
|
642 |
|
-spec disco_local_features(mongoose_disco:feature_acc(), |
643 |
|
map(), |
644 |
|
map()) -> {ok, mongoose_disco:feature_acc()}. |
645 |
|
disco_local_features(Acc = #{to_jid := #jid{lserver = LServer}, node := <<>>}, _, _) -> |
646 |
113 |
Features = [?NS_PUBSUB | [feature(F) || F <- features(LServer, <<>>)]], |
647 |
113 |
{ok, mongoose_disco:add_features(Features, Acc)}; |
648 |
|
disco_local_features(Acc, _, _) -> |
649 |
:-( |
{ok, Acc}. |
650 |
|
|
651 |
|
-spec disco_sm_identity(Acc, Params, Extra) -> {ok, Acc} when |
652 |
|
Acc :: mongoose_disco:identity_acc(), |
653 |
|
Params :: map(), |
654 |
|
Extra :: gen_hook:extra(). |
655 |
|
disco_sm_identity(Acc = #{from_jid := From, to_jid := To, node := Node}, _, _) -> |
656 |
1 |
Identities = disco_identity(jid:to_lower(jid:to_bare(To)), Node, From), |
657 |
1 |
{ok, mongoose_disco:add_identities(Identities, Acc)}. |
658 |
|
|
659 |
|
disco_identity(error, _Node, _From) -> |
660 |
:-( |
[]; |
661 |
|
disco_identity(_Host, <<>>, _From) -> |
662 |
1 |
[pep_identity()]; |
663 |
|
disco_identity(Host, Node, From) -> |
664 |
:-( |
Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = Owners}) -> |
665 |
:-( |
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of |
666 |
|
{result, _} -> |
667 |
:-( |
{result, [pep_identity(), pep_identity(Options)]}; |
668 |
|
_ -> |
669 |
:-( |
{result, []} |
670 |
|
end |
671 |
|
end, |
672 |
:-( |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
673 |
:-( |
{result, {_, Result}} -> Result; |
674 |
:-( |
_ -> [] |
675 |
|
end. |
676 |
|
|
677 |
|
pep_identity(Options) -> |
678 |
:-( |
Identity = pep_identity(), |
679 |
:-( |
case get_option(Options, title) of |
680 |
:-( |
false -> Identity; |
681 |
:-( |
[Title] -> Identity#{name => Title} |
682 |
|
end. |
683 |
|
|
684 |
|
pep_identity() -> |
685 |
1 |
#{category => <<"pubsub">>, type => <<"pep">>}. |
686 |
|
|
687 |
|
-spec disco_sm_features(Acc, Params, Extra) -> {ok, Acc} when |
688 |
|
Acc :: mongoose_disco:feature_acc(), |
689 |
|
Params :: map(), |
690 |
|
Extra :: gen_hook:extra(). |
691 |
|
disco_sm_features(Acc = #{from_jid := From, to_jid := To, node := Node}, _, _) -> |
692 |
1 |
Features = disco_features(jid:to_lower(jid:to_bare(To)), Node, From), |
693 |
1 |
{ok, mongoose_disco:add_features(Features, Acc)}. |
694 |
|
|
695 |
|
-spec disco_features(error | jid:simple_jid(), binary(), jid:jid()) -> [mongoose_disco:feature()]. |
696 |
|
disco_features(error, _Node, _From) -> |
697 |
:-( |
[]; |
698 |
|
disco_features(_Host, <<>>, _From) -> |
699 |
1 |
[?NS_PUBSUB | [feature(F) || F <- plugin_features(<<"pep">>)]]; |
700 |
|
disco_features(Host, Node, From) -> |
701 |
:-( |
Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = Owners}) -> |
702 |
:-( |
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of |
703 |
|
{result, _} -> |
704 |
:-( |
{result, [?NS_PUBSUB | [feature(F) || F <- plugin_features(<<"pep">>)]]}; |
705 |
|
_ -> |
706 |
:-( |
{result, []} |
707 |
|
end |
708 |
|
end, |
709 |
:-( |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
710 |
:-( |
{result, {_, Result}} -> Result; |
711 |
:-( |
_ -> [] |
712 |
|
end. |
713 |
|
|
714 |
|
-spec disco_sm_items(Acc, Params, Extra) -> {ok, Acc} when |
715 |
|
Acc :: mongoose_disco:item_acc(), |
716 |
|
Params :: map(), |
717 |
|
Extra :: gen_hook:extra(). |
718 |
|
disco_sm_items(Acc = #{from_jid := From, to_jid := To, node := Node}, _, _) -> |
719 |
2 |
Items = disco_items(jid:to_lower(jid:to_bare(To)), Node, From), |
720 |
2 |
{ok, mongoose_disco:add_items(Items, Acc)}. |
721 |
|
|
722 |
|
-spec disco_items(mod_pubsub:host(), mod_pubsub:nodeId(), jid:jid()) -> [mongoose_disco:item()]. |
723 |
|
disco_items(Host, <<>>, From) -> |
724 |
2 |
Action = fun (#pubsub_node{nodeid = {_, Node}, |
725 |
|
options = Options, type = Type, id = Nidx, owners = Owners}, |
726 |
|
Acc) -> |
727 |
1 |
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of |
728 |
|
{result, _} -> |
729 |
1 |
[disco_item(Node, Host, Options) | Acc]; |
730 |
|
_ -> |
731 |
:-( |
Acc |
732 |
|
end |
733 |
|
end, |
734 |
2 |
NodeBloc = fun() -> |
735 |
2 |
{result, |
736 |
|
lists:foldl(Action, [], tree_call(Host, get_nodes, [Host, From]))} |
737 |
|
end, |
738 |
2 |
ErrorDebug = #{ |
739 |
|
action => disco_items, |
740 |
|
pubsub_host => Host, |
741 |
|
from => From |
742 |
|
}, |
743 |
2 |
case mod_pubsub_db_backend:dirty(NodeBloc, ErrorDebug) of |
744 |
2 |
{result, Items} -> Items; |
745 |
:-( |
_ -> [] |
746 |
|
end; |
747 |
|
disco_items(Host, Node, From) -> |
748 |
:-( |
Action = fun (#pubsub_node{id = Nidx, type = Type, options = Options, owners = Owners}) -> |
749 |
:-( |
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) of |
750 |
|
{result, Items} -> |
751 |
:-( |
{result, [disco_item(Host, ItemId) || |
752 |
:-( |
#pubsub_item{itemid = {ItemId, _}} <- Items]}; |
753 |
|
_ -> |
754 |
:-( |
{result, []} |
755 |
|
end |
756 |
|
end, |
757 |
:-( |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
758 |
:-( |
{result, {_, Result}} -> Result; |
759 |
:-( |
_ -> [] |
760 |
|
end. |
761 |
|
|
762 |
|
disco_item(Node, Host, Options) -> |
763 |
1 |
Item = #{node => Node, |
764 |
|
jid => jid:to_binary(Host)}, |
765 |
1 |
case get_option(Options, title) of |
766 |
1 |
false -> Item; |
767 |
:-( |
[Title] -> Item#{name => Title} |
768 |
|
end. |
769 |
|
|
770 |
|
disco_item(Host, ItemId) -> |
771 |
:-( |
#{jid => jid:to_binary(Host), |
772 |
|
name => ItemId}. |
773 |
|
|
774 |
|
%% ------- |
775 |
|
%% callback that prevents routing subscribe authorizations back to the sender |
776 |
|
%% |
777 |
|
|
778 |
|
-spec handle_pep_authorization_response(Acc, Params, Extra) -> {ok, Acc} when |
779 |
|
Acc :: mongoose_hooks:filter_packet_acc(), |
780 |
|
Params :: map(), |
781 |
|
Extra :: gen_hook:extra(). |
782 |
|
handle_pep_authorization_response({From, To, Acc, #xmlel{ name = Name } = Packet}, _, _) -> |
783 |
625 |
Type = mongoose_acc:stanza_type(Acc), |
784 |
625 |
{ok, handle_pep_authorization_response(Name, Type, From, To, Acc, Packet)}. |
785 |
|
|
786 |
|
handle_pep_authorization_response(_, <<"error">>, From, To, Acc, Packet) -> |
787 |
13 |
{From, To, Acc, Packet}; |
788 |
|
handle_pep_authorization_response(<<"message">>, _, From, To, Acc, Packet) |
789 |
|
when From#jid.luser == To#jid.luser, From#jid.lserver == To#jid.lserver -> |
790 |
2 |
case find_authorization_response(Packet) of |
791 |
|
none -> |
792 |
:-( |
{From, To, Acc, Packet}; |
793 |
|
invalid -> |
794 |
1 |
{From, To, Acc, Packet}; |
795 |
|
KVs -> |
796 |
1 |
handle_authorization_response(Acc, jid:to_lower(To), From, To, Packet, KVs), |
797 |
1 |
drop |
798 |
|
end; |
799 |
|
handle_pep_authorization_response(_, _, From, To, Acc, Packet) -> |
800 |
610 |
{From, To, Acc, Packet}. |
801 |
|
|
802 |
|
%% ------- |
803 |
|
%% callback for foreign_event calls, to distribute pep messages from the node owner c2s process |
804 |
|
%% |
805 |
|
-spec foreign_event(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) -> |
806 |
|
mongoose_c2s_hooks:result(). |
807 |
|
foreign_event(Acc, #{c2s_data := StateData, |
808 |
|
event_type := {call, From}, |
809 |
|
event_tag := ?MODULE, |
810 |
|
event_content := {get_pep_recipients, Feature}}, _Extra) -> |
811 |
18 |
Reply = mongoose_hooks:get_pep_recipients(StateData, Feature), |
812 |
18 |
Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Reply}]), |
813 |
18 |
{stop, Acc1}; |
814 |
|
foreign_event(Acc, #{c2s_data := StateData, |
815 |
|
event_type := {call, From}, |
816 |
|
event_tag := ?MODULE, |
817 |
|
event_content := {filter_pep_recipient, To, Feature}}, _Extra) -> |
818 |
7 |
Reply = mongoose_hooks:filter_pep_recipient(StateData, Feature, To), |
819 |
7 |
Acc1 = mongoose_c2s_acc:to_acc(Acc, actions, [{reply, From, Reply}]), |
820 |
7 |
{stop, Acc1}; |
821 |
|
foreign_event(Acc, _Params, _Extra) -> |
822 |
:-( |
{ok, Acc}. |
823 |
|
|
824 |
|
%% ------- |
825 |
|
%% presence hooks handling functions |
826 |
|
%% |
827 |
|
|
828 |
|
-spec caps_recognised(Acc, Params, Extra) -> {ok, Acc} when |
829 |
|
Acc :: mongoose_acc:t(), |
830 |
|
Params :: #{from := jid:jid(), pid := pid(), features := [binary()]}, |
831 |
|
Extra :: gen_hook:extra(). |
832 |
|
caps_recognised(Acc, #{from := #jid{ luser = U, lserver = S } = JID, pid := Pid, features := Features}, _) -> |
833 |
9 |
Host = host(S, S), |
834 |
9 |
IgnorePepFromOffline = gen_mod:get_module_opt(S, ?MODULE, ignore_pep_from_offline), |
835 |
9 |
notify_worker(S, U, {send_last_pep_items, Host, IgnorePepFromOffline, JID, Pid, Features}), |
836 |
9 |
{ok, Acc}. |
837 |
|
|
838 |
|
-spec presence_probe(Acc, Params, Extra) -> {ok, Acc} when |
839 |
|
Acc :: mongoose_acc:t(), |
840 |
|
Params :: #{from := jid:jid(), to := jid:jid()}, |
841 |
|
Extra :: gen_hook:extra(). |
842 |
|
presence_probe(Acc, #{from := #jid{luser = U, lserver = S, lresource = _R} = JID, to := JID}, _) -> |
843 |
|
%% Get subdomain |
844 |
377 |
Host = host(S, S), |
845 |
377 |
Plugins = plugins(S), |
846 |
377 |
notify_worker(S, U, {send_last_pubsub_items, Host, _Recipient = JID, Plugins}), |
847 |
377 |
{ok, Acc}; |
848 |
|
presence_probe(Acc, _, _) -> |
849 |
7 |
{ok, Acc}. |
850 |
|
|
851 |
|
%% ------- |
852 |
|
%% subscription hooks handling functions |
853 |
|
%% |
854 |
|
|
855 |
|
-spec out_subscription(Acc, Params, Extra) -> {ok, Acc} when |
856 |
|
Acc :: mongoose_acc:t(), |
857 |
|
Params :: #{to := jid:jid(), |
858 |
|
from := jid:jid(), |
859 |
|
type := mod_roster:sub_presence()}, |
860 |
|
Extra :: gen_hook:extra(). |
861 |
|
out_subscription(Acc, |
862 |
|
#{to := #jid{luser = PUser, lserver = PServer, lresource = PResource}, |
863 |
|
from := #jid{luser = LUser, lserver = LServer} = FromJID, |
864 |
|
type := subscribed}, |
865 |
|
_) -> |
866 |
18 |
PResources = case PResource of |
867 |
18 |
<<>> -> user_resources(PUser, PServer); |
868 |
:-( |
_ -> [PResource] |
869 |
|
end, |
870 |
18 |
Host = host(LServer, LServer), |
871 |
18 |
notify_worker(LServer, LUser, {send_last_items_from_owner, Host, FromJID, |
872 |
|
{PUser, PServer, PResources}}), |
873 |
18 |
{ok, Acc}; |
874 |
|
out_subscription(Acc, _, _) -> |
875 |
20 |
{ok, Acc}. |
876 |
|
|
877 |
|
-spec in_subscription(Acc, Params, Extra) -> {ok, Acc} when |
878 |
|
Acc :: mongoose_acc:t(), |
879 |
|
Params :: #{to := jid:jid(), |
880 |
|
from := jid:jid(), |
881 |
|
type := mod_roster:sub_presence()}, |
882 |
|
Extra :: gen_hook:extra(). |
883 |
|
in_subscription(Acc, #{to := ToJID, from := OwnerJID, type := unsubscribed}, _) -> |
884 |
2 |
unsubscribe_user(ToJID, OwnerJID), |
885 |
2 |
{ok, Acc}; |
886 |
|
in_subscription(Acc, _, _) -> |
887 |
36 |
{ok, Acc}. |
888 |
|
|
889 |
|
unsubscribe_user(Entity, Owner) -> |
890 |
2 |
ServerHosts = lists:usort(lists:foldl( |
891 |
|
fun(UserHost, Acc) -> |
892 |
4 |
case gen_mod:is_loaded(UserHost, mod_pubsub) of |
893 |
4 |
true -> [UserHost|Acc]; |
894 |
:-( |
false -> Acc |
895 |
|
end |
896 |
|
end, [], [Entity#jid.lserver, Owner#jid.lserver])), |
897 |
2 |
[unsubscribe_user(ServerHost, Entity, Owner) || ServerHost <- ServerHosts], |
898 |
2 |
ok. |
899 |
|
|
900 |
|
unsubscribe_user(Host, Entity, Owner) -> |
901 |
2 |
BJID = jid:to_lower(jid:to_bare(Owner)), |
902 |
2 |
lists:foreach(fun (PType) -> |
903 |
4 |
unsubscribe_user_per_plugin(Host, Entity, BJID, PType) |
904 |
|
end, plugins(Host)). |
905 |
|
|
906 |
|
unsubscribe_user_per_plugin(Host, Entity, BJID, PType) -> |
907 |
4 |
{result, Subs} = node_action(Host, PType, get_entity_subscriptions, [Host, Entity]), |
908 |
4 |
lists:foreach(fun({#pubsub_node{options = Options, owners = Owners, id = Nidx}, |
909 |
|
subscribed, _, JID}) -> |
910 |
2 |
Unsubscribe = match_option(Options, access_model, presence) |
911 |
2 |
andalso lists:member(BJID, Owners), |
912 |
2 |
case Unsubscribe of |
913 |
|
true -> |
914 |
2 |
node_action(Host, PType, |
915 |
|
unsubscribe_node, [Nidx, Entity, JID, all]); |
916 |
|
false -> |
917 |
:-( |
ok |
918 |
|
end; |
919 |
|
(_) -> |
920 |
:-( |
ok |
921 |
|
end, Subs). |
922 |
|
|
923 |
|
%% ------- |
924 |
|
%% user remove hook handling function |
925 |
|
%% |
926 |
|
-spec remove_user(Acc, Params, Extra) -> {ok, Acc} when |
927 |
|
Acc :: mongoose_acc:t(), |
928 |
|
Params :: #{jid := jid:jid()}, |
929 |
|
Extra :: #{host_type := mongooseim:host_type()}. |
930 |
|
remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, _) -> |
931 |
26 |
lists:foreach(fun(PType) -> |
932 |
78 |
remove_user_per_plugin_safe(LUser, LServer, plugin(PType)) |
933 |
|
end, plugins(LServer)), |
934 |
26 |
{ok, Acc}. |
935 |
|
|
936 |
|
remove_user_per_plugin_safe(LUser, LServer, Plugin) -> |
937 |
78 |
try |
938 |
78 |
plugin_call(Plugin, remove_user, [LUser, LServer]) |
939 |
|
catch |
940 |
|
Class:Reason:StackTrace -> |
941 |
:-( |
?LOG_WARNING(#{what => pubsub_delete_user_failed, user => LUser, |
942 |
|
server => LServer, class => Class, reason => Reason, |
943 |
:-( |
stacktrace => StackTrace}) |
944 |
|
end. |
945 |
|
|
946 |
|
handle_call(server_host, _From, State) -> |
947 |
:-( |
{reply, State#state.server_host, State}; |
948 |
|
handle_call(plugins, _From, State) -> |
949 |
1203 |
{reply, State#state.plugins, State}; |
950 |
|
handle_call(pep_mapping, _From, State) -> |
951 |
:-( |
{reply, State#state.pep_mapping, State}; |
952 |
|
handle_call(nodetree, _From, State) -> |
953 |
:-( |
{reply, State#state.nodetree, State}; |
954 |
|
handle_call(stop, _From, State) -> |
955 |
39 |
{stop, normal, ok, State}. |
956 |
|
|
957 |
|
%%-------------------------------------------------------------------- |
958 |
|
%% Function: handle_cast(Msg, State) -> {noreply, State} | |
959 |
|
%% {noreply, State, Timeout} | |
960 |
|
%% {stop, Reason, State} |
961 |
|
%% Description: Handling cast messages |
962 |
|
%%-------------------------------------------------------------------- |
963 |
|
%% @private |
964 |
:-( |
handle_cast(_Msg, State) -> {noreply, State}. |
965 |
|
|
966 |
|
%%-------------------------------------------------------------------- |
967 |
|
%% Function: handle_info(Info, State) -> {noreply, State} | |
968 |
|
%% {noreply, State, Timeout} | |
969 |
|
%% {stop, Reason, State} |
970 |
|
%% Description: Handling all non call/cast messages |
971 |
|
%%-------------------------------------------------------------------- |
972 |
|
%% @private |
973 |
|
handle_info(_Info, State) -> |
974 |
:-( |
{noreply, State}. |
975 |
|
|
976 |
|
%%-------------------------------------------------------------------- |
977 |
|
%% Function: terminate(Reason, State) -> void() |
978 |
|
%% Description: This function is called by a gen_server when it is about to |
979 |
|
%% terminate. It should be the opposite of Module:init/1 and do any necessary |
980 |
|
%% cleaning up. When it returns, the gen_server terminates with Reason. |
981 |
|
%% The return value is ignored. |
982 |
|
%%-------------------------------------------------------------------- |
983 |
|
%% @private |
984 |
|
terminate(_Reason, #state{host = Host, server_host = ServerHost, |
985 |
|
nodetree = TreePlugin, plugins = Plugins}) -> |
986 |
39 |
SubdomainPattern = gen_mod:get_module_opt(ServerHost, ?MODULE, host), |
987 |
39 |
mongoose_domain_api:unregister_subdomain(ServerHost, SubdomainPattern), |
988 |
39 |
case lists:member(?PEPNODE, Plugins) of |
989 |
|
true -> |
990 |
4 |
gen_hook:delete_handlers(pep_hooks(ServerHost)), |
991 |
4 |
delete_pep_iq_handlers(ServerHost); |
992 |
35 |
false -> ok |
993 |
|
end, |
994 |
39 |
gen_hook:delete_handlers(hooks(ServerHost)), |
995 |
39 |
case whereis(gen_mod:get_module_proc(ServerHost, ?LOOPNAME)) of |
996 |
|
undefined -> |
997 |
39 |
?LOG_ERROR(#{what => pubsub_process_is_dead, |
998 |
|
text => <<"process is dead, pubsub was broken">>, |
999 |
:-( |
process => ?LOOPNAME}); |
1000 |
|
Pid -> |
1001 |
:-( |
Pid ! stop |
1002 |
|
end, |
1003 |
39 |
terminate_plugins(Host, ServerHost, Plugins, TreePlugin), |
1004 |
39 |
mod_pubsub_db_backend:stop(). |
1005 |
|
|
1006 |
|
%%-------------------------------------------------------------------- |
1007 |
|
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState} |
1008 |
|
%% Description: Convert process state when code is changed |
1009 |
|
%%-------------------------------------------------------------------- |
1010 |
|
%% @private |
1011 |
:-( |
code_change(_OldVsn, State, _Extra) -> {ok, State}. |
1012 |
|
|
1013 |
|
-spec do_route( |
1014 |
|
Acc :: mongoose_acc:t(), |
1015 |
|
ServerHost :: binary(), |
1016 |
|
Access :: atom(), |
1017 |
|
Plugins :: [binary(), ...], |
1018 |
|
Host :: mod_pubsub:hostPubsub(), |
1019 |
|
From ::jid:jid(), |
1020 |
|
To ::jid:jid(), |
1021 |
|
Packet :: exml:element()) |
1022 |
|
-> mongoose_acc:t(). |
1023 |
|
|
1024 |
|
%%-------------------------------------------------------------------- |
1025 |
|
%%% Internal functions |
1026 |
|
%%-------------------------------------------------------------------- |
1027 |
|
do_route(Acc, ServerHost, Access, Plugins, Host, From, |
1028 |
|
#jid{luser = <<>>, lresource = <<>>} = To, |
1029 |
|
#xmlel{ name = <<"iq">> } = Packet) -> |
1030 |
829 |
case jlib:iq_query_info(Packet) of |
1031 |
|
#iq{type = get, xmlns = ?NS_DISCO_INFO, sub_el = SubEl, lang = Lang} = IQ -> |
1032 |
4 |
#xmlel{attrs = QAttrs} = SubEl, |
1033 |
4 |
Node = xml:get_attr_s(<<"node">>, QAttrs), |
1034 |
4 |
InfoXML = mongoose_disco:get_info(ServerHost, ?MODULE, <<>>, <<>>), |
1035 |
4 |
Res = case iq_disco_info(ServerHost, Host, Node, From, Lang) of |
1036 |
|
{result, IQRes} -> |
1037 |
4 |
jlib:iq_to_xml(IQ#iq{type = result, |
1038 |
|
sub_el = |
1039 |
|
[#xmlel{name = <<"query">>, |
1040 |
|
attrs = QAttrs, |
1041 |
|
children = IQRes ++ InfoXML}]}); |
1042 |
|
{error, Error} -> |
1043 |
:-( |
make_error_reply(Packet, Error) |
1044 |
|
end, |
1045 |
4 |
ejabberd_router:route(To, From, Acc, Res); |
1046 |
|
#iq{type = get, xmlns = ?NS_DISCO_ITEMS, sub_el = SubEl} = IQ -> |
1047 |
15 |
#xmlel{attrs = QAttrs} = SubEl, |
1048 |
15 |
Node = xml:get_attr_s(<<"node">>, QAttrs), |
1049 |
15 |
Res = case iq_disco_items(Host, Node, From, jlib:rsm_decode(IQ)) of |
1050 |
|
{result, IQRes} -> |
1051 |
12 |
jlib:iq_to_xml(IQ#iq{type = result, |
1052 |
|
sub_el = |
1053 |
|
[#xmlel{name = <<"query">>, |
1054 |
|
attrs = QAttrs, |
1055 |
|
children = IQRes}]}); |
1056 |
|
{error, Error} -> |
1057 |
3 |
make_error_reply(Packet, Error) |
1058 |
|
end, |
1059 |
15 |
ejabberd_router:route(To, From, Acc, Res); |
1060 |
|
#iq{type = IQType, xmlns = ?NS_PUBSUB, lang = Lang, sub_el = SubEl} = IQ -> |
1061 |
549 |
Res = case iq_pubsub(Host, ServerHost, From, IQType, |
1062 |
|
SubEl, Lang, Access, Plugins) |
1063 |
|
of |
1064 |
|
{result, IQRes} -> |
1065 |
517 |
jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes}); |
1066 |
|
{error, Error} -> |
1067 |
32 |
make_error_reply(Packet, Error) |
1068 |
|
end, |
1069 |
549 |
ejabberd_router:route(To, From, Acc, Res); |
1070 |
|
#iq{type = IQType, xmlns = ?NS_PUBSUB_OWNER, lang = Lang, sub_el = SubEl} = IQ -> |
1071 |
255 |
Res = case iq_pubsub_owner(Host, ServerHost, From, |
1072 |
|
IQType, SubEl, Lang) |
1073 |
|
of |
1074 |
|
{result, IQRes} -> |
1075 |
233 |
jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes}); |
1076 |
|
{error, {Error, NewPayload}} -> |
1077 |
2 |
make_error_reply(Packet#xmlel{ children = NewPayload }, Error); |
1078 |
|
{error, Error} -> |
1079 |
20 |
make_error_reply(Packet, Error) |
1080 |
|
end, |
1081 |
255 |
ejabberd_router:route(To, From, Acc, Res); |
1082 |
|
#iq{type = get, xmlns = (?NS_VCARD) = XMLNS, lang = Lang, sub_el = _SubEl} = IQ -> |
1083 |
:-( |
Res = IQ#iq{type = result, |
1084 |
|
sub_el = |
1085 |
|
[#xmlel{name = <<"vCard">>, |
1086 |
|
attrs = [{<<"xmlns">>, XMLNS}], |
1087 |
|
children = iq_get_vcard(Lang)}]}, |
1088 |
:-( |
ejabberd_router:route(To, From, Acc, jlib:iq_to_xml(Res)); |
1089 |
|
#iq{type = set, xmlns = ?NS_COMMANDS} = IQ -> |
1090 |
6 |
Res = case iq_command(Host, ServerHost, From, IQ, Access, Plugins) of |
1091 |
|
{error, Error} -> |
1092 |
2 |
make_error_reply(Packet, Error); |
1093 |
|
{result, IQRes} -> |
1094 |
4 |
jlib:iq_to_xml(IQ#iq{type = result, sub_el = IQRes}) |
1095 |
|
end, |
1096 |
6 |
ejabberd_router:route(To, From, Acc, Res); |
1097 |
|
#iq{} -> |
1098 |
:-( |
Err = make_error_reply(Packet, mongoose_xmpp_errors:feature_not_implemented()), |
1099 |
:-( |
ejabberd_router:route(To, From, Acc, Err); |
1100 |
|
_ -> |
1101 |
:-( |
Acc |
1102 |
|
end; |
1103 |
|
do_route(Acc, _ServerHost, _Access, _Plugins, Host, From, |
1104 |
|
#jid{luser = <<>>, lresource = <<>>} = To, |
1105 |
|
#xmlel{ name = <<"message">> } = Packet) -> |
1106 |
5 |
case exml_query:attr(Packet, <<"type">>) of |
1107 |
|
<<"error">> -> |
1108 |
1 |
ok; |
1109 |
|
_ -> |
1110 |
4 |
case find_authorization_response(Packet) of |
1111 |
|
none -> |
1112 |
:-( |
Acc; |
1113 |
|
invalid -> |
1114 |
:-( |
Err = make_error_reply(Packet, mongoose_xmpp_errors:bad_request()), |
1115 |
:-( |
ejabberd_router:route(To, From, Acc, Err); |
1116 |
|
XFields -> |
1117 |
4 |
handle_authorization_response(Acc, Host, From, To, Packet, XFields) |
1118 |
|
end |
1119 |
|
end; |
1120 |
|
do_route(Acc, _ServerHost, _Access, _Plugins, _Host, _From, |
1121 |
|
#jid{luser = <<>>, lresource = <<>>} = _To, _Packet) -> |
1122 |
:-( |
Acc; |
1123 |
|
do_route(Acc, _ServerHost, _Access, _Plugins, _Host, From, To, Packet) -> |
1124 |
:-( |
case exml_query:attr(Packet, <<"type">>) of |
1125 |
|
<<"error">> -> |
1126 |
:-( |
Acc; |
1127 |
|
<<"result">> -> |
1128 |
:-( |
Acc; |
1129 |
|
_ -> |
1130 |
:-( |
Err = make_error_reply(Packet, mongoose_xmpp_errors:item_not_found()), |
1131 |
:-( |
ejabberd_router:route(To, From, Acc, Err) |
1132 |
|
end. |
1133 |
|
|
1134 |
|
command_disco_info(_Host, ?NS_COMMANDS, _From) -> |
1135 |
:-( |
IdentityEl = #xmlel{name = <<"identity">>, |
1136 |
|
attrs = [{<<"category">>, <<"automation">>}, |
1137 |
|
{<<"type">>, <<"command-list">>}]}, |
1138 |
:-( |
{result, [IdentityEl]}; |
1139 |
|
command_disco_info(_Host, ?NS_PUBSUB_GET_PENDING, _From) -> |
1140 |
:-( |
IdentityEl = #xmlel{name = <<"identity">>, |
1141 |
|
attrs = [{<<"category">>, <<"automation">>}, |
1142 |
|
{<<"type">>, <<"command-node">>}]}, |
1143 |
:-( |
FeaturesEl = #xmlel{name = <<"feature">>, |
1144 |
|
attrs = [{<<"var">>, ?NS_COMMANDS}]}, |
1145 |
:-( |
{result, [IdentityEl, FeaturesEl]}. |
1146 |
|
|
1147 |
|
node_disco_info(Host, Node, From) -> |
1148 |
:-( |
node_disco_info(Host, Node, From, true, true). |
1149 |
|
|
1150 |
|
node_disco_info(Host, Node, _From, _Identity, _Features) -> |
1151 |
:-( |
Action = fun (#pubsub_node{type = Type, options = Options}) -> |
1152 |
:-( |
NodeType = case get_option(Options, node_type) of |
1153 |
:-( |
collection -> <<"collection">>; |
1154 |
:-( |
_ -> <<"leaf">> |
1155 |
|
end, |
1156 |
:-( |
I = #xmlel{name = <<"identity">>, |
1157 |
|
attrs = [{<<"category">>, <<"pubsub">>}, |
1158 |
|
{<<"type">>, NodeType}]}, |
1159 |
:-( |
F = [#xmlel{name = <<"feature">>, |
1160 |
|
attrs = [{<<"var">>, ?NS_PUBSUB}]} |
1161 |
:-( |
| [#xmlel{name = <<"feature">>, |
1162 |
|
attrs = [{<<"var">>, feature(F)}]} |
1163 |
:-( |
|| F <- plugin_features(Type)]], |
1164 |
:-( |
{result, [I | F]} |
1165 |
|
end, |
1166 |
:-( |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
1167 |
:-( |
{result, {_, Result}} -> {result, Result}; |
1168 |
:-( |
Other -> Other |
1169 |
|
end. |
1170 |
|
|
1171 |
|
iq_disco_info(ServerHost, Host, SNode, From, Lang) -> |
1172 |
4 |
[Node | _] = case SNode of |
1173 |
4 |
<<>> -> [<<>>]; |
1174 |
:-( |
_ -> mongoose_bin:tokens(SNode, <<"!">>) |
1175 |
|
end, |
1176 |
4 |
case Node of |
1177 |
|
<<>> -> |
1178 |
4 |
Identities = identities(ServerHost, Lang), |
1179 |
4 |
Features = [?NS_DISCO_INFO, |
1180 |
|
?NS_DISCO_ITEMS, |
1181 |
|
?NS_PUBSUB, |
1182 |
|
?NS_COMMANDS, |
1183 |
151 |
?NS_VCARD] ++ [feature(F) || F <- features(ServerHost, Node)], |
1184 |
4 |
{result, mongoose_disco:identities_to_xml(Identities) ++ |
1185 |
|
mongoose_disco:features_to_xml(Features)}; |
1186 |
|
?NS_COMMANDS -> |
1187 |
:-( |
command_disco_info(Host, Node, From); |
1188 |
|
?NS_PUBSUB_GET_PENDING -> |
1189 |
:-( |
command_disco_info(Host, Node, From); |
1190 |
|
_ -> |
1191 |
:-( |
node_disco_info(Host, Node, From) |
1192 |
|
end. |
1193 |
|
|
1194 |
|
-spec iq_disco_items( |
1195 |
|
Host :: mod_pubsub:host(), |
1196 |
|
Node :: <<>> | mod_pubsub:nodeId(), |
1197 |
|
From ::jid:jid(), |
1198 |
|
Rsm :: none | jlib:rsm_in()) |
1199 |
|
-> {result, [exml:element()]} | {error, term()}. |
1200 |
|
iq_disco_items(Host, <<>>, From, _RSM) -> |
1201 |
8 |
{result, |
1202 |
|
lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, options = Options}) -> |
1203 |
117 |
Attrs = case get_option(Options, title) of |
1204 |
|
false -> |
1205 |
117 |
[{<<"jid">>, Host} |
1206 |
|
| node_attr(SubNode)]; |
1207 |
|
[Title] -> |
1208 |
:-( |
[{<<"jid">>, Host}, |
1209 |
|
{<<"name">>, Title} |
1210 |
|
| node_attr(SubNode)] |
1211 |
|
end, |
1212 |
117 |
#xmlel{name = <<"item">>, attrs = Attrs} |
1213 |
|
end, |
1214 |
|
tree_action(Host, get_subnodes, [Host, <<>>, From]))}; |
1215 |
|
iq_disco_items(Host, ?NS_COMMANDS, _From, _RSM) -> |
1216 |
:-( |
{result, [#xmlel{name = <<"item">>, |
1217 |
|
attrs = [{<<"jid">>, Host}, |
1218 |
|
{<<"node">>, ?NS_PUBSUB_GET_PENDING}, |
1219 |
|
{<<"name">>, <<"Get Pending">>}]}]}; |
1220 |
|
iq_disco_items(_Host, ?NS_PUBSUB_GET_PENDING, _From, _RSM) -> |
1221 |
:-( |
{result, []}; |
1222 |
|
iq_disco_items(Host, Item, From, RSM) -> |
1223 |
7 |
case mongoose_bin:tokens(Item, <<"!">>) of |
1224 |
|
[_Node, _ItemId] -> |
1225 |
:-( |
{result, []}; |
1226 |
|
[Node] -> |
1227 |
7 |
Action = fun (PubSubNode) -> |
1228 |
4 |
iq_disco_items_transaction(Host, From, Node, RSM, PubSubNode) |
1229 |
|
end, |
1230 |
7 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
1231 |
4 |
{result, {_, Result}} -> {result, Result}; |
1232 |
3 |
Other -> Other |
1233 |
|
end |
1234 |
|
end. |
1235 |
|
|
1236 |
|
iq_disco_items_transaction(Host, From, Node, RSM, |
1237 |
|
#pubsub_node{id = Nidx, type = Type, options = Options, owners = Owners}) -> |
1238 |
4 |
{NodeItems, RsmOut} = case get_allowed_items_call(Host, Nidx, |
1239 |
|
From, Type, Options, Owners, RSM) |
1240 |
|
of |
1241 |
4 |
{result, R} -> R; |
1242 |
:-( |
_ -> {[], none} |
1243 |
|
end, |
1244 |
4 |
Nodes = lists:map(fun (#pubsub_node{nodeid = {_, SubNode}, options = SubOptions}) -> |
1245 |
3 |
Attrs = case get_option(SubOptions, title) of |
1246 |
|
false -> |
1247 |
3 |
[{<<"jid">>, Host} |
1248 |
|
| node_attr(SubNode)]; |
1249 |
|
[Title] -> |
1250 |
:-( |
[{<<"jid">>, Host}, |
1251 |
|
{<<"name">>, Title} |
1252 |
|
| node_attr(SubNode)] |
1253 |
|
end, |
1254 |
3 |
#xmlel{name = <<"item">>, attrs = Attrs} |
1255 |
|
end, |
1256 |
|
tree_call(Host, get_subnodes, [Host, Node, From])), |
1257 |
4 |
Items = lists:map(fun (#pubsub_item{itemid = {RN, _}}) -> |
1258 |
:-( |
{result, Name} = node_call(Type, get_item_name, |
1259 |
|
[Host, Node, RN]), |
1260 |
:-( |
#xmlel{name = <<"item">>, |
1261 |
|
attrs = [{<<"jid">>, Host}, {<<"name">>, Name}]} |
1262 |
|
end, |
1263 |
|
NodeItems), |
1264 |
4 |
{result, Nodes ++ Items ++ jlib:rsm_encode(RsmOut)}. |
1265 |
|
|
1266 |
|
-spec iq_sm(From ::jid:jid(), |
1267 |
|
To ::jid:jid(), |
1268 |
|
Acc :: mongoose_acc:t(), |
1269 |
|
IQ :: jlib:iq()) |
1270 |
|
-> {mongoose_acc:t(), jlib:iq()}. |
1271 |
|
iq_sm(From, To, Acc, #iq{type = Type, sub_el = SubEl, xmlns = XMLNS, lang = Lang} = IQ) -> |
1272 |
42 |
ServerHost = To#jid.lserver, |
1273 |
42 |
LOwner = jid:to_lower(jid:to_bare(To)), |
1274 |
42 |
Res = case XMLNS of |
1275 |
|
?NS_PUBSUB -> |
1276 |
37 |
iq_pubsub(LOwner, ServerHost, From, Type, SubEl, Lang); |
1277 |
|
?NS_PUBSUB_OWNER -> |
1278 |
5 |
iq_pubsub_owner(LOwner, ServerHost, From, Type, SubEl, Lang) |
1279 |
|
end, |
1280 |
42 |
case Res of |
1281 |
37 |
{result, IQRes} -> {Acc, IQ#iq{type = result, sub_el = IQRes}}; |
1282 |
5 |
{error, Error} -> {Acc, make_error_reply(IQ, Error)} |
1283 |
|
end. |
1284 |
|
|
1285 |
|
iq_get_vcard(Lang) -> |
1286 |
:-( |
Desc = <<(translate:translate(Lang, <<"ejabberd Publish-Subscribe module">>))/binary, |
1287 |
|
"\nCopyright (c) 2004-2015 ProcessOne">>, |
1288 |
:-( |
[#xmlel{name = <<"FN">>, attrs = [], |
1289 |
|
children = [#xmlcdata{content = <<"ejabberd/mod_pubsub">>}]}, |
1290 |
|
#xmlel{name = <<"URL">>, attrs = [], |
1291 |
|
children = [#xmlcdata{content = ?MONGOOSE_URI}]}, |
1292 |
|
#xmlel{name = <<"DESC">>, attrs = [], |
1293 |
|
children = [#xmlcdata{content = Desc}]}]. |
1294 |
|
|
1295 |
|
-spec iq_pubsub(Host :: mod_pubsub:host(), |
1296 |
|
ServerHost :: binary(), |
1297 |
|
From ::jid:jid(), |
1298 |
|
IQType :: get | set, |
1299 |
|
QueryEl :: exml:element(), |
1300 |
|
Lang :: binary()) -> {result, [exml:element()]} | {error, exml:element()}. |
1301 |
|
iq_pubsub(Host, ServerHost, From, IQType, QueryEl, Lang) -> |
1302 |
37 |
iq_pubsub(Host, ServerHost, From, IQType, QueryEl, Lang, all, plugins(ServerHost)). |
1303 |
|
|
1304 |
|
-spec iq_pubsub(Host :: mod_pubsub:host(), |
1305 |
|
ServerHost :: binary(), |
1306 |
|
From ::jid:jid(), |
1307 |
|
IQType :: 'get' | 'set', |
1308 |
|
QueryEl :: exml:element(), |
1309 |
|
Lang :: binary(), |
1310 |
|
Access :: atom(), |
1311 |
|
Plugins :: [binary(), ...]) -> {result, [exml:element()]} | {error, exml:element()}. |
1312 |
|
iq_pubsub(Host, ServerHost, From, IQType, #xmlel{children = SubEls} = QueryEl, |
1313 |
|
Lang, Access, Plugins) -> |
1314 |
586 |
case xml:remove_cdata(SubEls) of |
1315 |
|
[#xmlel{name = Name} = ActionEl | _] -> |
1316 |
586 |
report_iq_action_metrics_before_result(ServerHost, IQType, Name), |
1317 |
586 |
Node = exml_query:attr(ActionEl, <<"node">>, <<>>), |
1318 |
586 |
{Time, Result} = timer:tc(fun iq_pubsub_action/6, |
1319 |
|
[IQType, Name, Host, Node, From, |
1320 |
|
#{server_host => ServerHost, |
1321 |
|
plugins => Plugins, |
1322 |
|
access => Access, |
1323 |
|
action_el => ActionEl, |
1324 |
|
query_el => QueryEl, |
1325 |
|
lang => Lang}]), |
1326 |
586 |
report_iq_action_metrics_after_return(ServerHost, Result, Time, IQType, Name), |
1327 |
586 |
Result; |
1328 |
|
Other -> |
1329 |
:-( |
?LOG_INFO(#{what => pubsub_bad_request, exml_packet => Other}), |
1330 |
:-( |
{error, mongoose_xmpp_errors:bad_request()} |
1331 |
|
end. |
1332 |
|
|
1333 |
|
iq_pubsub_action(IQType, Name, Host, Node, From, ExtraArgs) -> |
1334 |
586 |
case {IQType, Name} of |
1335 |
|
{set, <<"create">>} -> |
1336 |
216 |
iq_pubsub_set_create(Host, Node, From, ExtraArgs); |
1337 |
|
{set, <<"publish">>} -> |
1338 |
204 |
iq_pubsub_set_publish(Host, Node, From, ExtraArgs); |
1339 |
|
{set, <<"retract">>} -> |
1340 |
8 |
iq_pubsub_set_retract(Host, Node, From, ExtraArgs); |
1341 |
|
{set, <<"subscribe">>} -> |
1342 |
92 |
iq_pubsub_set_subscribe(Host, Node, From, ExtraArgs); |
1343 |
|
{set, <<"unsubscribe">>} -> |
1344 |
5 |
iq_pubsub_set_unsubscribe(Host, Node, From, ExtraArgs); |
1345 |
|
{get, <<"items">>} -> |
1346 |
33 |
iq_pubsub_get_items(Host, Node, From, ExtraArgs); |
1347 |
|
{get, <<"subscriptions">>} -> |
1348 |
10 |
get_subscriptions(Host, Node, From, ExtraArgs); |
1349 |
|
{get, <<"affiliations">>} -> |
1350 |
:-( |
get_affiliations(Host, Node, From, ExtraArgs); |
1351 |
|
{get, <<"options">>} -> |
1352 |
14 |
iq_pubsub_get_options(Host, Node, From, ExtraArgs); |
1353 |
|
{set, <<"options">>} -> |
1354 |
4 |
iq_pubsub_set_options(Host, Node, ExtraArgs); |
1355 |
|
_ -> |
1356 |
:-( |
{error, mongoose_xmpp_errors:feature_not_implemented()} |
1357 |
|
end. |
1358 |
|
|
1359 |
|
ensure_metrics(Host) -> |
1360 |
39 |
[mongoose_metrics:ensure_metric(Host, metric_name(IQType, Name, MetricSuffix), Type) || |
1361 |
39 |
{IQType, Name} <- all_metrics(), |
1362 |
663 |
{MetricSuffix, Type} <- [{count, spiral}, |
1363 |
|
{errors, spiral}, |
1364 |
|
{time, histogram}]]. |
1365 |
|
|
1366 |
|
all_metrics() -> |
1367 |
39 |
[{set, create}, |
1368 |
|
{set, publish}, |
1369 |
|
{set, retract}, |
1370 |
|
{set, subscribe}, |
1371 |
|
{set, unsubscribe}, |
1372 |
|
{get, items}, |
1373 |
|
{get, options}, |
1374 |
|
{set, options}, |
1375 |
|
{get, configure}, |
1376 |
|
{set, configure}, |
1377 |
|
{get, default}, |
1378 |
|
{set, delete}, |
1379 |
|
{set, purge}, |
1380 |
|
{get, subscriptions}, |
1381 |
|
{set, subscriptions}, |
1382 |
|
{get, affiliations}, |
1383 |
|
{set, affiliations}]. |
1384 |
|
|
1385 |
432 |
iq_action_to_metric_name(<<"create">>) -> create; |
1386 |
408 |
iq_action_to_metric_name(<<"publish">>) -> publish; |
1387 |
16 |
iq_action_to_metric_name(<<"retract">>) -> retract; |
1388 |
184 |
iq_action_to_metric_name(<<"subscribe">>) -> subscribe; |
1389 |
10 |
iq_action_to_metric_name(<<"unsubscribe">>) -> unsubscribe; |
1390 |
66 |
iq_action_to_metric_name(<<"items">>) -> items; |
1391 |
36 |
iq_action_to_metric_name(<<"options">>) -> options; |
1392 |
42 |
iq_action_to_metric_name(<<"configure">>) -> configure; |
1393 |
4 |
iq_action_to_metric_name(<<"default">>) -> default; |
1394 |
302 |
iq_action_to_metric_name(<<"delete">>) -> delete; |
1395 |
16 |
iq_action_to_metric_name(<<"purge">>) -> purge; |
1396 |
52 |
iq_action_to_metric_name(<<"subscriptions">>) -> subscriptions; |
1397 |
120 |
iq_action_to_metric_name(<<"affiliations">>) -> affiliations. |
1398 |
|
|
1399 |
|
|
1400 |
|
metric_name(IQType, Name, MetricSuffix) when is_binary(Name) -> |
1401 |
1688 |
NameAtom = iq_action_to_metric_name(Name), |
1402 |
1688 |
metric_name(IQType, NameAtom, MetricSuffix); |
1403 |
|
metric_name(IQType, Name, MetricSuffix) when is_atom(Name) -> |
1404 |
3677 |
[pubsub, IQType, Name, MetricSuffix]. |
1405 |
|
|
1406 |
|
report_iq_action_metrics_before_result(Host, IQType, Name) -> |
1407 |
844 |
mongoose_metrics:update(Host, metric_name(IQType, Name, count), 1). |
1408 |
|
|
1409 |
|
report_iq_action_metrics_after_return(Host, Result, Time, IQType, Name) -> |
1410 |
844 |
case Result of |
1411 |
|
{error, _} -> |
1412 |
57 |
mongoose_metrics:update(Host, metric_name(IQType, Name, erros), 1); |
1413 |
|
_ -> |
1414 |
787 |
mongoose_metrics:update(Host, metric_name(IQType, Name, time), Time) |
1415 |
|
end. |
1416 |
|
|
1417 |
|
iq_pubsub_set_create(Host, Node, From, |
1418 |
|
#{server_host := ServerHost, access := Access, plugins := Plugins, |
1419 |
|
action_el := CreateEl, query_el := QueryEl}) -> |
1420 |
216 |
Config = exml_query:subelement(QueryEl, <<"configure">>), |
1421 |
216 |
Type = exml_query:attr(CreateEl, <<"type">>, hd(Plugins)), |
1422 |
216 |
case lists:member(Type, Plugins) of |
1423 |
|
false -> |
1424 |
:-( |
{error, |
1425 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"create-nodes">>)}; |
1426 |
|
true -> |
1427 |
216 |
create_node(Host, ServerHost, Node, From, Type, Access, Config) |
1428 |
|
end. |
1429 |
|
|
1430 |
|
iq_pubsub_set_publish(_Host, <<>>, _From, _ExtraArgs) -> |
1431 |
2 |
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"nodeid-required">>)}; |
1432 |
|
iq_pubsub_set_publish(Host, Node, From, #{server_host := ServerHost, access := Access, |
1433 |
|
action_el := ActionEl, query_el := QueryEl}) -> |
1434 |
202 |
case xml:remove_cdata(ActionEl#xmlel.children) of |
1435 |
|
[#xmlel{name = <<"item">>, attrs = ItemAttrs, children = Payload}] -> |
1436 |
202 |
ItemId = xml:get_attr_s(<<"id">>, ItemAttrs), |
1437 |
202 |
PublishOptions = exml_query:subelement(QueryEl, <<"publish-options">>), |
1438 |
202 |
publish_item(Host, ServerHost, Node, From, ItemId, |
1439 |
|
Payload, Access, PublishOptions); |
1440 |
|
[] -> |
1441 |
:-( |
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"item-required">>)}; |
1442 |
|
_ -> |
1443 |
:-( |
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"invalid-payload">>)} |
1444 |
|
end. |
1445 |
|
|
1446 |
|
iq_pubsub_set_retract(Host, Node, From, |
1447 |
|
#{action_el := #xmlel{attrs = RetractAttrs, children = RetractSubEls}}) -> |
1448 |
8 |
ForceNotify = case xml:get_attr_s(<<"notify">>, RetractAttrs) of |
1449 |
:-( |
<<"1">> -> true; |
1450 |
:-( |
<<"true">> -> true; |
1451 |
8 |
_ -> false |
1452 |
|
end, |
1453 |
8 |
case xml:remove_cdata(RetractSubEls) of |
1454 |
|
[#xmlel{name = <<"item">>, attrs = ItemAttrs}] -> |
1455 |
8 |
ItemId = xml:get_attr_s(<<"id">>, ItemAttrs), |
1456 |
8 |
delete_item(Host, Node, From, ItemId, ForceNotify); |
1457 |
|
_ -> |
1458 |
:-( |
{error, |
1459 |
|
extended_error(mongoose_xmpp_errors:bad_request(), <<"item-required">>)} |
1460 |
|
end. |
1461 |
|
|
1462 |
|
iq_pubsub_set_subscribe(Host, Node, From, #{query_el := QueryEl, |
1463 |
|
action_el := #xmlel{attrs = SubscribeAttrs}}) -> |
1464 |
92 |
ConfigXForm = case exml_query:subelement(QueryEl, <<"options">>) of |
1465 |
82 |
undefined -> undefined; |
1466 |
10 |
Options -> mongoose_data_forms:find_form(Options) |
1467 |
|
end, |
1468 |
92 |
JID = xml:get_attr_s(<<"jid">>, SubscribeAttrs), |
1469 |
92 |
subscribe_node(Host, Node, From, JID, ConfigXForm). |
1470 |
|
|
1471 |
|
iq_pubsub_set_unsubscribe(Host, Node, From, #{action_el := #xmlel{attrs = UnsubscribeAttrs}}) -> |
1472 |
5 |
JID = xml:get_attr_s(<<"jid">>, UnsubscribeAttrs), |
1473 |
5 |
SubId = xml:get_attr_s(<<"subid">>, UnsubscribeAttrs), |
1474 |
5 |
unsubscribe_node(Host, Node, From, JID, SubId). |
1475 |
|
|
1476 |
|
iq_pubsub_get_items(Host, Node, From, |
1477 |
|
#{query_el := QueryEl, |
1478 |
|
action_el := #xmlel{attrs = GetItemsAttrs, children = GetItemsSubEls}}) -> |
1479 |
33 |
MaxItems = xml:get_attr_s(<<"max_items">>, GetItemsAttrs), |
1480 |
33 |
SubId = xml:get_attr_s(<<"subid">>, GetItemsAttrs), |
1481 |
33 |
ItemIds = extract_item_ids(GetItemsSubEls), |
1482 |
33 |
get_items(Host, Node, From, SubId, MaxItems, ItemIds, jlib:rsm_decode(QueryEl)). |
1483 |
|
|
1484 |
|
extract_item_ids(GetItemsSubEls) -> |
1485 |
33 |
case lists:foldl(fun extract_item_id/2, [], GetItemsSubEls) of |
1486 |
|
[] -> |
1487 |
25 |
undefined; |
1488 |
|
List -> |
1489 |
8 |
List |
1490 |
|
end. |
1491 |
|
|
1492 |
|
extract_item_id(#xmlel{name = <<"item">>} = Item, Acc) -> |
1493 |
8 |
case exml_query:attr(Item, <<"id">>) of |
1494 |
:-( |
undefined -> Acc; |
1495 |
8 |
ItemId -> [ItemId | Acc] |
1496 |
|
end; |
1497 |
:-( |
extract_item_id(_, Acc) -> Acc. |
1498 |
|
|
1499 |
|
|
1500 |
|
iq_pubsub_get_options(Host, Node, Lang, #{action_el := #xmlel{attrs = GetOptionsAttrs}}) -> |
1501 |
14 |
SubId = xml:get_attr_s(<<"subid">>, GetOptionsAttrs), |
1502 |
14 |
JID = xml:get_attr_s(<<"jid">>, GetOptionsAttrs), |
1503 |
14 |
get_options(Host, Node, JID, SubId, Lang). |
1504 |
|
|
1505 |
|
iq_pubsub_set_options(Host, Node, #{action_el := #xmlel{attrs = SetOptionsAttrs} = ActionEl}) -> |
1506 |
4 |
XForm = mongoose_data_forms:find_form(ActionEl), |
1507 |
4 |
SubId = xml:get_attr_s(<<"subid">>, SetOptionsAttrs), |
1508 |
4 |
JID = xml:get_attr_s(<<"jid">>, SetOptionsAttrs), |
1509 |
4 |
set_options(Host, Node, JID, SubId, XForm). |
1510 |
|
|
1511 |
|
-spec iq_pubsub_owner( |
1512 |
|
Host :: mod_pubsub:host(), |
1513 |
|
ServerHost :: binary(), |
1514 |
|
From ::jid:jid(), |
1515 |
|
IQType :: 'get' | 'set', |
1516 |
|
SubEl :: exml:element(), |
1517 |
|
Lang :: binary()) |
1518 |
|
-> {result, [exml:element()]} |
1519 |
|
| {error, exml:element() | [exml:element()] | {exml:element(), [exml:element()]}}. |
1520 |
|
iq_pubsub_owner(Host, ServerHost, From, IQType, SubEl, Lang) -> |
1521 |
260 |
#xmlel{children = SubEls} = SubEl, |
1522 |
260 |
Action = xml:remove_cdata(SubEls), |
1523 |
260 |
case Action of |
1524 |
|
[#xmlel{name = Name} = ActionEl] -> |
1525 |
258 |
report_iq_action_metrics_before_result(ServerHost, IQType, Name), |
1526 |
258 |
Node = exml_query:attr(ActionEl, <<"node">>, <<>>), |
1527 |
258 |
{Time, Result} = timer:tc(fun iq_pubsub_owner_action/6, |
1528 |
|
[IQType, Name, Host, From, Node, |
1529 |
|
#{server_host => ServerHost, |
1530 |
|
action_el => ActionEl, |
1531 |
|
lang => Lang}]), |
1532 |
258 |
report_iq_action_metrics_after_return(ServerHost, Result, Time, IQType, Name), |
1533 |
258 |
Result; |
1534 |
|
_ -> |
1535 |
2 |
?LOG_INFO(#{what => pubsub_too_many_actions, exml_packet => Action}), |
1536 |
2 |
{error, mongoose_xmpp_errors:bad_request()} |
1537 |
|
end. |
1538 |
|
|
1539 |
|
iq_pubsub_owner_action(IQType, Name, Host, From, Node, ExtraParams) -> |
1540 |
258 |
case {IQType, Name} of |
1541 |
|
{get, <<"configure">>} -> |
1542 |
7 |
get_configure(Host, Node, From, ExtraParams); |
1543 |
|
{set, <<"configure">>} -> |
1544 |
14 |
set_configure(Host, Node, From, ExtraParams); |
1545 |
|
{get, <<"default">>} -> |
1546 |
2 |
get_default(Host, Node, From, ExtraParams); |
1547 |
|
{set, <<"delete">>} -> |
1548 |
151 |
delete_node(Host, Node, From); |
1549 |
|
{set, <<"purge">>} -> |
1550 |
8 |
purge_node(Host, Node, From); |
1551 |
|
{get, <<"subscriptions">>} -> |
1552 |
10 |
get_subscriptions(Host, Node, From); |
1553 |
|
{set, <<"subscriptions">>} -> |
1554 |
6 |
set_subscriptions(Host, Node, From, ExtraParams); |
1555 |
|
{get, <<"affiliations">>} -> |
1556 |
8 |
get_affiliations(Host, Node, From); |
1557 |
|
{set, <<"affiliations">>} -> |
1558 |
52 |
set_affiliations(Host, Node, From, ExtraParams); |
1559 |
|
_ -> |
1560 |
:-( |
{error, mongoose_xmpp_errors:feature_not_implemented()} |
1561 |
|
end. |
1562 |
|
|
1563 |
|
iq_command(Host, ServerHost, From, IQ, Access, Plugins) -> |
1564 |
6 |
case adhoc:parse_request(IQ) of |
1565 |
|
Req when is_record(Req, adhoc_request) -> |
1566 |
6 |
case adhoc_request(Host, ServerHost, From, Req, Access, Plugins) of |
1567 |
|
Resp when is_record(Resp, xmlel) -> |
1568 |
4 |
{result, [Resp]}; |
1569 |
|
Error -> |
1570 |
2 |
Error |
1571 |
|
end; |
1572 |
:-( |
Err -> Err |
1573 |
|
end. |
1574 |
|
|
1575 |
|
%% @doc <p>Processes an Ad Hoc Command.</p> |
1576 |
|
adhoc_request(Host, _ServerHost, Owner, |
1577 |
|
Request = #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, |
1578 |
|
action = <<"execute">>, |
1579 |
|
xdata = false}, |
1580 |
|
_Access, Plugins) -> |
1581 |
2 |
send_pending_node_form(Request, Host, Owner, Plugins); |
1582 |
|
adhoc_request(Host, _ServerHost, Owner, |
1583 |
|
Request = #adhoc_request{node = ?NS_PUBSUB_GET_PENDING, |
1584 |
|
action = <<"execute">>, xdata = XData}, |
1585 |
|
_Access, _Plugins) -> |
1586 |
4 |
ParseOptions = adhoc_get_pending_parse_options(Host, XData), |
1587 |
4 |
case ParseOptions of |
1588 |
|
{result, XForm} -> |
1589 |
2 |
case lists:keysearch(node, 1, XForm) of |
1590 |
2 |
{value, {_, Node}} -> send_pending_auth_events(Request, Host, Node, Owner); |
1591 |
:-( |
false -> {error, extended_error(mongoose_xmpp_errors:bad_request(), <<"bad-payload">>)} |
1592 |
|
end; |
1593 |
2 |
Error -> Error |
1594 |
|
end; |
1595 |
|
adhoc_request(_Host, _ServerHost, _Owner, |
1596 |
|
#adhoc_request{action = <<"cancel">>} = Request, _Access, |
1597 |
|
_Plugins) -> |
1598 |
:-( |
adhoc:produce_response(Request, canceled); |
1599 |
|
adhoc_request(Host, ServerHost, Owner, |
1600 |
|
#adhoc_request{action = <<>>} = R, Access, Plugins) -> |
1601 |
:-( |
adhoc_request(Host, ServerHost, Owner, |
1602 |
|
R#adhoc_request{action = <<"execute">>}, Access, |
1603 |
|
Plugins); |
1604 |
|
adhoc_request(_Host, _ServerHost, _Owner, Other, _Access, _Plugins) -> |
1605 |
:-( |
?LOG_DEBUG(#{what => pubsub_adhoc_request_error, |
1606 |
:-( |
text => <<"Couldn't process ad hoc command">>, command => Other}), |
1607 |
:-( |
{error, mongoose_xmpp_errors:item_not_found()}. |
1608 |
|
|
1609 |
|
%% @doc <p>Sends the process pending subscriptions XForm for Host to Owner.</p> |
1610 |
|
send_pending_node_form(Request, Host, Owner, Plugins) -> |
1611 |
2 |
Filter = fun (Type) -> |
1612 |
2 |
lists:member(<<"get-pending">>, plugin_features(Type)) |
1613 |
|
end, |
1614 |
2 |
case lists:filter(Filter, Plugins) of |
1615 |
|
[] -> |
1616 |
:-( |
{error, mongoose_xmpp_errors:feature_not_implemented()}; |
1617 |
|
Ps -> |
1618 |
2 |
Options = get_pending_nodes(Host, Owner, Ps), |
1619 |
2 |
Field = #{type => <<"list-single">>, var => <<"pubsub#node">>, |
1620 |
|
options => lists:usort(Options)}, |
1621 |
2 |
Form = mongoose_data_forms:form(#{fields => [Field]}), |
1622 |
2 |
adhoc:produce_response(Request, executing, <<"execute">>, [Form]) |
1623 |
|
end. |
1624 |
|
|
1625 |
|
get_pending_nodes(Host, Owner, Plugins) -> |
1626 |
2 |
Tr = fun (Type) -> |
1627 |
2 |
case node_call(Type, get_pending_nodes, [Host, Owner]) of |
1628 |
2 |
{result, Nodes} -> Nodes; |
1629 |
:-( |
_ -> [] |
1630 |
|
end |
1631 |
|
end, |
1632 |
2 |
Action = fun() -> {result, lists:flatmap(Tr, Plugins)} end, |
1633 |
2 |
ErrorDebug = #{ |
1634 |
|
action => get_pending_nodes, |
1635 |
|
pubsub_host => Host, |
1636 |
|
owner => Owner, |
1637 |
|
plugins => Plugins |
1638 |
|
}, |
1639 |
2 |
case mod_pubsub_db_backend:dirty(Action, ErrorDebug) of |
1640 |
2 |
{result, Res} -> Res; |
1641 |
:-( |
Err -> Err |
1642 |
|
end. |
1643 |
|
|
1644 |
|
adhoc_get_pending_parse_options(Host, XEl) -> |
1645 |
4 |
case mongoose_data_forms:parse_form_fields(XEl) of |
1646 |
|
#{type := <<"submit">>, kvs := KVs} -> |
1647 |
2 |
case set_xoption(Host, maps:to_list(KVs), []) of |
1648 |
2 |
NewOpts when is_list(NewOpts) -> {result, NewOpts}; |
1649 |
:-( |
Err -> Err |
1650 |
|
end; |
1651 |
|
#{} -> |
1652 |
2 |
{error, mongoose_xmpp_errors:bad_request(<<"en">>, <<"Invalid form type">>)} |
1653 |
|
end. |
1654 |
|
|
1655 |
|
%% @doc <p>Send a subscription approval form to Owner for all pending |
1656 |
|
%% subscriptions on Host and Node.</p> |
1657 |
|
send_pending_auth_events(Request, Host, Node, Owner) -> |
1658 |
2 |
?LOG_DEBUG(#{what => pubsub_sending_pending_auth_events, |
1659 |
2 |
owner => jid:to_binary(Owner), sub_host => Host, pubsub_node => Node}), |
1660 |
2 |
Action = fun(PubSubNode) -> |
1661 |
2 |
get_node_subscriptions_transaction(Owner, PubSubNode) |
1662 |
|
end, |
1663 |
2 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
1664 |
|
{result, {N, Subs}} -> |
1665 |
2 |
lists:foreach(fun |
1666 |
4 |
({J, pending, _SubId, _}) -> send_authorization_request(N, jid:make(J)); |
1667 |
:-( |
(_) -> ok |
1668 |
|
end, |
1669 |
|
Subs), |
1670 |
2 |
adhoc:produce_response(Request, undefined); |
1671 |
|
Err -> |
1672 |
:-( |
Err |
1673 |
|
end. |
1674 |
|
|
1675 |
|
get_node_subscriptions_transaction(Owner, #pubsub_node{id = Nidx, type = Type}) -> |
1676 |
2 |
case lists:member(<<"get-pending">>, plugin_features(Type)) of |
1677 |
|
true -> |
1678 |
2 |
case node_call(Type, get_affiliation, [Nidx, Owner]) of |
1679 |
2 |
{result, owner} -> node_call(Type, get_node_subscriptions, [Nidx]); |
1680 |
:-( |
_ -> {error, mongoose_xmpp_errors:forbidden()} |
1681 |
|
end; |
1682 |
|
false -> |
1683 |
:-( |
{error, mongoose_xmpp_errors:feature_not_implemented()} |
1684 |
|
end. |
1685 |
|
|
1686 |
|
%%% authorization handling |
1687 |
|
|
1688 |
|
send_authorization_request(#pubsub_node{nodeid = {Host, Node}, owners = Owners}, |
1689 |
|
Subscriber) -> |
1690 |
13 |
Lang = <<"en">>, |
1691 |
13 |
Title = translate:translate(Lang, <<"PubSub subscriber request">>), |
1692 |
13 |
Instructions = translate:translate(Lang, <<"Choose whether to approve this entity's " |
1693 |
|
"subscription.">>), |
1694 |
13 |
Fields = [#{var => <<"pubsub#node">>, |
1695 |
|
type => <<"text-single">>, |
1696 |
|
label => translate:translate(Lang, <<"Node ID">>), |
1697 |
|
values => [Node]}, |
1698 |
|
#{var => <<"pubsub#subscriber_jid">>, |
1699 |
|
type => <<"jid-single">>, |
1700 |
|
label => translate:translate(Lang, <<"Subscriber Address">>), |
1701 |
|
values => [jid:to_binary(Subscriber)]}, |
1702 |
|
#{var => <<"pubsub#allow">>, |
1703 |
|
type => <<"boolean">>, |
1704 |
|
label => translate:translate(Lang, <<"Allow this Jabber ID to subscribe to " |
1705 |
|
"this pubsub node?">>), |
1706 |
|
values => [<<"false">>]}], |
1707 |
13 |
Form = mongoose_data_forms:form(#{title => Title, instructions => Instructions, |
1708 |
|
ns => ?NS_PUBSUB_SUB_AUTH, fields => Fields}), |
1709 |
13 |
Stanza = #xmlel{name = <<"message">>, |
1710 |
|
attrs = [{<<"id">>, mongoose_bin:gen_from_crypto()}], |
1711 |
|
children = [Form]}, |
1712 |
13 |
lists:foreach(fun(Owner) -> |
1713 |
13 |
ejabberd_router:route(service_jid(Host), jid:make(Owner), Stanza) |
1714 |
|
end, Owners). |
1715 |
|
|
1716 |
|
find_authorization_response(El) -> |
1717 |
6 |
case mongoose_data_forms:find_form(El) of |
1718 |
|
undefined -> |
1719 |
:-( |
none; |
1720 |
|
Form -> |
1721 |
6 |
case mongoose_data_forms:parse_form_fields(Form) of |
1722 |
|
#{type := <<"submit">>, ns := ?NS_PUBSUB_SUB_AUTH, kvs := KVs} -> |
1723 |
5 |
KVs; |
1724 |
|
_ -> |
1725 |
1 |
invalid |
1726 |
|
end |
1727 |
|
end. |
1728 |
|
|
1729 |
|
%% @doc Send a message to JID with the supplied Subscription |
1730 |
|
send_authorization_approval(Host, JID, SNode, Subscription) -> |
1731 |
5 |
SubAttrs = case Subscription of |
1732 |
|
%{S, SID} -> |
1733 |
|
% [{<<"subscription">>, subscription_to_string(S)}, |
1734 |
|
% {<<"subid">>, SID}]; |
1735 |
|
S -> |
1736 |
5 |
[{<<"subscription">>, subscription_to_string(S)}] |
1737 |
|
end, |
1738 |
5 |
Stanza = event_stanza(<<"subscription">>, |
1739 |
|
[{<<"jid">>, jid:to_binary(JID)} |
1740 |
|
| node_attr(SNode)] |
1741 |
|
++ SubAttrs), |
1742 |
5 |
ejabberd_router:route(service_jid(Host), JID, Stanza). |
1743 |
|
|
1744 |
|
handle_authorization_response(Acc, Host, From, To, Packet, XFields) -> |
1745 |
5 |
case XFields of |
1746 |
|
#{<<"pubsub#node">> := [Node], |
1747 |
|
<<"pubsub#subscriber_jid">> := [SSubscriber], |
1748 |
|
<<"pubsub#allow">> := [SAllow]} -> |
1749 |
5 |
FromLJID = jid:to_lower(jid:to_bare(From)), |
1750 |
5 |
Subscriber = jid:from_binary(SSubscriber), |
1751 |
5 |
Allow = string_allow_to_boolean(SAllow), |
1752 |
5 |
Action = fun (PubSubNode) -> |
1753 |
5 |
handle_authorization_response_transaction(Host, FromLJID, Subscriber, |
1754 |
|
Allow, Node, PubSubNode) |
1755 |
|
end, |
1756 |
5 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
1757 |
|
{error, Error} -> |
1758 |
:-( |
Err = make_error_reply(Packet, Error), |
1759 |
:-( |
ejabberd_router:route(To, From, Acc, Err); |
1760 |
|
{result, {_, _NewSubscription}} -> |
1761 |
|
%% XXX: notify about subscription state change, section 12.11 |
1762 |
5 |
Acc; |
1763 |
|
_ -> |
1764 |
:-( |
Err = make_error_reply(Packet, mongoose_xmpp_errors:internal_server_error()), |
1765 |
:-( |
ejabberd_router:route(To, From, Acc, Err) |
1766 |
|
end; |
1767 |
|
_ -> |
1768 |
:-( |
Err = make_error_reply(Packet, mongoose_xmpp_errors:not_acceptable()), |
1769 |
:-( |
ejabberd_router:route(To, From, Acc, Err) |
1770 |
|
end. |
1771 |
|
|
1772 |
:-( |
string_allow_to_boolean(<<"1">>) -> true; |
1773 |
3 |
string_allow_to_boolean(<<"true">>) -> true; |
1774 |
2 |
string_allow_to_boolean(_) -> false. |
1775 |
|
|
1776 |
|
handle_authorization_response_transaction(Host, FromLJID, Subscriber, Allow, Node, |
1777 |
|
#pubsub_node{type = Type, id = Nidx, owners = Owners}) -> |
1778 |
5 |
case lists:member(FromLJID, Owners) of |
1779 |
|
true -> |
1780 |
5 |
{result, Subs} = node_call(Type, get_subscriptions, [Nidx, Subscriber]), |
1781 |
5 |
update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs); |
1782 |
|
false -> |
1783 |
:-( |
{error, mongoose_xmpp_errors:forbidden()} |
1784 |
|
end. |
1785 |
|
|
1786 |
|
update_auth(Host, Node, Type, Nidx, Subscriber, Allow, Subs) -> |
1787 |
5 |
Sub = lists:filter(fun |
1788 |
5 |
({pending, _, _}) -> true; |
1789 |
:-( |
(_) -> false |
1790 |
|
end, |
1791 |
|
Subs), |
1792 |
5 |
case Sub of |
1793 |
|
[{pending, SubId, _}] -> |
1794 |
5 |
NewSub = case Allow of |
1795 |
3 |
true -> subscribed; |
1796 |
2 |
false -> none |
1797 |
|
end, |
1798 |
5 |
node_call(Type, set_subscriptions, [Nidx, Subscriber, NewSub, SubId]), |
1799 |
5 |
send_authorization_approval(Host, Subscriber, Node, NewSub), |
1800 |
5 |
{result, ok}; |
1801 |
|
_ -> |
1802 |
:-( |
{error, mongoose_xmpp_errors:unexpected_request()} |
1803 |
|
end. |
1804 |
|
|
1805 |
|
-define(XFIELD(Type, Label, Var, Val), |
1806 |
|
#{type => Type, |
1807 |
|
label => translate:translate(Lang, Label), |
1808 |
|
var => Var, |
1809 |
|
values => [Val]}). |
1810 |
|
|
1811 |
|
-define(BOOLXFIELD(Label, Var, Val), |
1812 |
|
?XFIELD(<<"boolean">>, Label, Var, |
1813 |
|
case Val of |
1814 |
|
true -> <<"1">>; |
1815 |
|
_ -> <<"0">> |
1816 |
|
end)). |
1817 |
|
|
1818 |
|
-define(STRINGXFIELD(Label, Var, Val), |
1819 |
|
?XFIELD(<<"text-single">>, Label, Var, Val)). |
1820 |
|
|
1821 |
|
-define(STRINGMXFIELD(Label, Var, Vals), |
1822 |
|
#{type => <<"text-multi">>, |
1823 |
|
label => translate:translate(Lang, Label), |
1824 |
|
var => Var, |
1825 |
|
values => Vals}). |
1826 |
|
|
1827 |
|
-define(XFIELDOPT(Type, Label, Var, Val, Opts), |
1828 |
|
#{type => Type, |
1829 |
|
label => translate:translate(Lang, Label), |
1830 |
|
var => Var, |
1831 |
|
options => Opts, |
1832 |
|
values => [Val]}). |
1833 |
|
|
1834 |
|
-define(LISTXFIELD(Label, Var, Val, Opts), |
1835 |
|
?XFIELDOPT(<<"list-single">>, Label, Var, Val, Opts)). |
1836 |
|
|
1837 |
|
-define(LISTMXFIELD(Label, Var, Vals, Opts), |
1838 |
|
#{type => <<"list-multi">>, |
1839 |
|
label => translate:translate(Lang, Label), |
1840 |
|
var => Var, |
1841 |
|
options => Opts, |
1842 |
|
values => Vals}). |
1843 |
|
|
1844 |
|
%% @doc <p>Create new pubsub nodes</p> |
1845 |
|
%%<p>In addition to method-specific error conditions, there are several general reasons |
1846 |
|
%% why the node creation request might fail:</p> |
1847 |
|
%%<ul> |
1848 |
|
%%<li>The service does not support node creation.</li> |
1849 |
|
%%<li>Only entities that are registered with the service are allowed to create nodes |
1850 |
|
%% but the requesting entity is not registered.</li> |
1851 |
|
%%<li>The requesting entity does not have sufficient privileges to create nodes.</li> |
1852 |
|
%%<li>The requested Node already exists.</li> |
1853 |
|
%%<li>The request did not include a Node and "instant nodes" are not supported.</li> |
1854 |
|
%%</ul> |
1855 |
|
%%<p>Note: node creation is a particular case, error return code is evaluated at many places:</p> |
1856 |
|
%%<ul> |
1857 |
|
%%<li>iq_pubsub checks if service supports node creation (type exists)</li> |
1858 |
|
%%<li>create_node checks if instant nodes are supported</li> |
1859 |
|
%%<li>create_node asks node plugin if entity have sufficient privilege</li> |
1860 |
|
%%<li>nodetree create_node checks if nodeid already exists</li> |
1861 |
|
%%<li>node plugin create_node just sets default affiliation/subscription</li> |
1862 |
|
%%</ul> |
1863 |
|
%% @end |
1864 |
|
|
1865 |
|
create_node(Host, ServerHost, Node, Owner, Type) -> |
1866 |
2 |
create_node(Host, ServerHost, Node, Owner, Type, all, undefined). |
1867 |
|
|
1868 |
|
-spec create_node(Host, ServerHost, Node, Owner, Type, Access, Configuration) -> R when |
1869 |
|
Host :: mod_pubsub:host(), |
1870 |
|
ServerHost :: binary(), |
1871 |
|
Node :: <<>> | mod_pubsub:nodeId(), |
1872 |
|
Owner :: jid:jid(), |
1873 |
|
Type :: binary(), |
1874 |
|
Access :: atom(), |
1875 |
|
Configuration :: exml:element() | undefined, |
1876 |
|
R :: {result, [exml:element(), ...]} |
1877 |
|
| {error, exml:element()}. |
1878 |
|
create_node(Host, ServerHost, <<>>, Owner, Type, Access, Configuration) -> |
1879 |
:-( |
case lists:member(<<"instant-nodes">>, plugin_features(Type)) of |
1880 |
|
true -> |
1881 |
:-( |
Node = mongoose_bin:gen_from_crypto(), |
1882 |
:-( |
case create_node(Host, ServerHost, Node, Owner, Type, Access, Configuration) of |
1883 |
|
{result, _} -> |
1884 |
:-( |
{result, [#xmlel{name = <<"pubsub">>, |
1885 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB}], |
1886 |
|
children = [#xmlel{name = <<"create">>, |
1887 |
|
attrs = node_attr(Node)}]}]}; |
1888 |
|
Error -> |
1889 |
:-( |
Error |
1890 |
|
end; |
1891 |
|
false -> |
1892 |
:-( |
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), <<"nodeid-required">>)} |
1893 |
|
end; |
1894 |
|
create_node(Host, ServerHost, Node, Owner, GivenType, Access, Configuration) -> |
1895 |
234 |
Type = select_type(ServerHost, Host, Node, GivenType), |
1896 |
234 |
case parse_create_node_options(Host, Type, Configuration) of |
1897 |
|
{result, NodeOptions} -> |
1898 |
230 |
CreateNode = fun () -> |
1899 |
230 |
create_node_transaction(Host, ServerHost, Node, Owner, |
1900 |
|
Type, Access, NodeOptions) |
1901 |
|
end, |
1902 |
230 |
ErrorDebug = #{ |
1903 |
|
action => create_node, |
1904 |
|
pubsub_host => Host, |
1905 |
|
owner => Owner, |
1906 |
|
node_name => Node }, |
1907 |
230 |
case mod_pubsub_db_backend:transaction(CreateNode, ErrorDebug) of |
1908 |
|
{result, {Nidx, SubsByDepth, {Result, broadcast}}} -> |
1909 |
229 |
broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth), |
1910 |
229 |
create_node_reply(Node, Result); |
1911 |
|
{result, {_Nidx, _SubsByDepth, Result}} -> |
1912 |
:-( |
create_node_reply(Node, Result); |
1913 |
|
Error -> |
1914 |
|
%% in case we change transaction to sync_dirty... |
1915 |
|
%% node_call(Type, delete_node, [Host, Node]), |
1916 |
|
%% tree_call(Host, delete_node, [Host, Node]), |
1917 |
1 |
Error |
1918 |
|
end; |
1919 |
|
Error -> |
1920 |
4 |
?LOG_INFO(#{what => pubsub_bad_node_configuration, |
1921 |
4 |
pubsub_node => Node, configuration => Configuration}), |
1922 |
4 |
Error |
1923 |
|
end. |
1924 |
|
|
1925 |
|
parse_create_node_options(Host, Type, undefined) -> |
1926 |
114 |
{result, node_options(Host, Type)}; |
1927 |
|
parse_create_node_options(Host, Type, Configuration) -> |
1928 |
120 |
case mongoose_data_forms:find_and_parse_form(Configuration) of |
1929 |
|
#{type := <<"submit">>, kvs := KVs} -> |
1930 |
116 |
case set_xoption(Host, maps:to_list(KVs), node_options(Host, Type)) of |
1931 |
114 |
NewOpts when is_list(NewOpts) -> {result, NewOpts}; |
1932 |
2 |
Err -> Err |
1933 |
|
end; |
1934 |
|
#{} -> |
1935 |
2 |
{error, mongoose_xmpp_errors:bad_request(<<"en">>, <<"Invalid form type">>)}; |
1936 |
|
{error, _} -> |
1937 |
2 |
{result, node_options(Host, Type)} |
1938 |
|
end. |
1939 |
|
|
1940 |
|
create_node_transaction(Host, ServerHost, Node, Owner, Type, Access, NodeOptions) -> |
1941 |
230 |
Parent = get_parent(Type, Node), |
1942 |
230 |
case node_call(Type, create_node_permission, |
1943 |
|
[Host, ServerHost, Node, Parent, Owner, Access]) of |
1944 |
|
{result, true} -> |
1945 |
230 |
create_node_authorized_transaction(Host, Node, Parent, Owner, Type, NodeOptions); |
1946 |
|
_ -> |
1947 |
:-( |
{error, mongoose_xmpp_errors:forbidden()} |
1948 |
|
end. |
1949 |
|
|
1950 |
|
get_parent(Type, Node) -> |
1951 |
230 |
case node_call(Type, node_to_path, [Node]) of |
1952 |
|
{result, [Node]} -> |
1953 |
220 |
<<>>; |
1954 |
|
{result, Path} -> |
1955 |
10 |
element(2, node_call(Type, path_to_node, [lists:sublist(Path, length(Path)-1)])) |
1956 |
|
end. |
1957 |
|
|
1958 |
|
create_node_authorized_transaction(Host, Node, Parent, Owner, Type, NodeOptions) -> |
1959 |
230 |
Parents = case Parent of |
1960 |
221 |
<<>> -> []; |
1961 |
9 |
_ -> [Parent] |
1962 |
|
end, |
1963 |
230 |
case tree_call(Host, create_node, [Host, Node, Type, Owner, NodeOptions, Parents]) of |
1964 |
|
{ok, Nidx} -> |
1965 |
229 |
SubsByDepth = get_node_subs_by_depth(Host, Node, Owner), |
1966 |
229 |
case node_call(Type, create_node, [Nidx, Owner]) of |
1967 |
229 |
{result, Result} -> {result, {Nidx, SubsByDepth, Result}}; |
1968 |
:-( |
Error -> Error |
1969 |
|
end; |
1970 |
|
{error, {virtual, Nidx}} -> |
1971 |
:-( |
case node_call(Type, create_node, [Nidx, Owner]) of |
1972 |
:-( |
{result, Result} -> {result, {Nidx, [], Result}}; |
1973 |
:-( |
Error -> Error |
1974 |
|
end; |
1975 |
|
Error -> |
1976 |
1 |
Error |
1977 |
|
end. |
1978 |
|
|
1979 |
|
create_node_reply(Node, default) -> |
1980 |
229 |
{result, create_node_make_reply(Node)}; |
1981 |
|
create_node_reply(_Node, Result) -> |
1982 |
:-( |
{result, Result}. |
1983 |
|
|
1984 |
|
create_node_make_reply(Node) -> |
1985 |
229 |
[#xmlel{name = <<"pubsub">>, |
1986 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB}], |
1987 |
|
children = [#xmlel{name = <<"create">>, |
1988 |
|
attrs = node_attr(Node)}]}]. |
1989 |
|
|
1990 |
|
%% @doc <p>Delete specified node and all childs.</p> |
1991 |
|
%%<p>There are several reasons why the node deletion request might fail:</p> |
1992 |
|
%%<ul> |
1993 |
|
%%<li>The requesting entity does not have sufficient privileges to delete the node.</li> |
1994 |
|
%%<li>The node is the root collection node, which cannot be deleted.</li> |
1995 |
|
%%<li>The specified node does not exist.</li> |
1996 |
|
%%</ul> |
1997 |
|
-spec delete_node( |
1998 |
|
Host :: mod_pubsub:host(), |
1999 |
|
Node :: mod_pubsub:nodeId(), |
2000 |
|
Owner :: jid:jid()) |
2001 |
|
-> {result, [exml:element(), ...]} | {error, exml:element()}. |
2002 |
|
delete_node(_Host, <<>>, _Owner) -> |
2003 |
:-( |
{error, mongoose_xmpp_errors:not_allowed()}; |
2004 |
|
delete_node(Host, Node, Owner) -> |
2005 |
151 |
Action = fun (PubSubNode) -> delete_node_transaction(Host, Owner, Node, PubSubNode) end, |
2006 |
151 |
case transaction(Host, Node, Action, ?FUNCTION_NAME) of |
2007 |
|
{result, {_, {SubsByDepth, {Result, broadcast, Removed}}}} -> |
2008 |
147 |
lists:foreach(fun ({RNode, _RSubs}) -> |
2009 |
148 |
{RH, RN} = RNode#pubsub_node.nodeid, |
2010 |
148 |
RNidx = RNode#pubsub_node.id, |
2011 |
148 |
RType = RNode#pubsub_node.type, |
2012 |
148 |
ROptions = RNode#pubsub_node.options, |
2013 |
148 |
broadcast_removed_node(RH, RN, RNidx, |
2014 |
|
RType, ROptions, SubsByDepth) |
2015 |
|
end, |
2016 |
|
Removed), |
2017 |
147 |
case Result of |
2018 |
147 |
default -> {result, []}; |
2019 |
:-( |
_ -> {result, Result} |
2020 |
|
end; |
2021 |
|
{result, {_, {_, {Result, _}}}} -> |
2022 |
4 |
case Result of |
2023 |
:-( |
default -> {result, []}; |
2024 |
4 |
_ -> {result, Result} |
2025 |
|
end; |
2026 |
|
Error -> |
2027 |
:-( |
Error |
2028 |
|
end. |
2029 |
|
|
2030 |
|
delete_node_transaction(Host, Owner, Node, #pubsub_node{type = Type, id = Nidx}) -> |
2031 |
151 |
case node_call(Type, get_affiliation, [Nidx, Owner]) of |
2032 |
|
{result, owner} -> |
2033 |
151 |
SubsByDepth = get_node_subs_by_depth(Host, Node, service_jid(Host)), |
2034 |
151 |
Removed = tree_call(Host, delete_node, [Host, Node]), |
2035 |
151 |
case node_call(Type, delete_node, [Removed]) of |
2036 |
151 |
{result, Res} -> {result, {SubsByDepth, Res}}; |
2037 |
:-( |
Error -> Error |
2038 |
|
end; |
2039 |
|
_ -> |
2040 |
:-( |
{error, mongoose_xmpp_errors:forbidden()} |
2041 |
|
end. |
2042 |
|
|
2043 |
|
%% @see node_hometree:subscribe_node/5 |
2044 |
|
%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p> |
2045 |
|
%%<p>There are several reasons why the subscription request might fail:</p> |
2046 |
|
%%<ul> |
2047 |
|
%%<li>The bare JID portions of the JIDs do not match.</li> |
2048 |
|
%%<li>The node has an access model of "presence" and the requesting entity |
2049 |
|
%% is not subscribed to the owner's presence.</li> |
2050 |
|
%%<li>The node has an access model of "roster" and the requesting entity |
2051 |
|
%% is not in one of the authorized roster groups.</li> |
2052 |
|
%%<li>The node has an access model of "whitelist" |
2053 |
|
%% and the requesting entity is not on the whitelist.</li> |
2054 |
|
%%<li>The service requires payment for subscriptions to the node.</li> |
2055 |
|
%%<li>The requesting entity is anonymous and the service |
2056 |
|
%% does not allow anonymous entities to subscribe.</li> |
2057 |
|
%%<li>The requesting entity has a pending subscription.</li> |
2058 |
|
%%<li>The requesting entity is blocked from subscribing |
2059 |
|
%% (e.g., because having an affiliation of outcast).</li> |
2060 |
|
%%<li>The node does not support subscriptions.</li> |
2061 |
|
%%<li>The node does not exist.</li> |
2062 |
|
%%</ul> |
2063 |
|
-spec subscribe_node( |
2064 |
|
Host :: mod_pubsub:host(), |
2065 |
|
Node :: mod_pubsub:nodeId(), |
2066 |
|
From ::jid:jid(), |
2067 |
|
JID :: binary(), |
2068 |
|
ConfigurationXForm :: exml:element() | undefined) |
2069 |
|
-> {result, [exml:element(), ...]} | {error, exml:element()}. |
2070 |
|
subscribe_node(Host, Node, From, JID, ConfigurationXForm) -> |
2071 |
92 |
SubOpts = case pubsub_form_utils:parse_sub_xform(ConfigurationXForm) of |
2072 |
92 |
{ok, GoodSubOpts} -> GoodSubOpts; |
2073 |
:-( |
_ -> invalid |
2074 |
|
end, |
2075 |
92 |
Subscriber = string_to_ljid(JID), |
2076 |
92 |
Action = fun (PubSubNode) -> |
2077 |
92 |
subscribe_node_transaction(Host, SubOpts, From, Subscriber, PubSubNode) |
2078 |
|
end, |
2079 |
92 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2080 |
|
{result, {TNode, {Result, subscribed, SubId, send_last}}} -> |
2081 |
8 |
Nidx = TNode#pubsub_node.id, |
2082 |
8 |
Type = TNode#pubsub_node.type, |
2083 |
8 |
Options = TNode#pubsub_node.options, |
2084 |
8 |
send_items(Host, Node, Nidx, Type, Options, Subscriber, last), |
2085 |
8 |
case Result of |
2086 |
8 |
default -> {result, subscribe_node_reply(Subscriber, Node, {subscribed, SubId})}; |
2087 |
:-( |
_ -> {result, Result} |
2088 |
|
end; |
2089 |
|
{result, {_TNode, {default, subscribed, SubId}}} -> |
2090 |
71 |
{result, subscribe_node_reply(Subscriber, Node, {subscribed, SubId})}; |
2091 |
|
{result, {_TNode, {Result, subscribed, _SubId}}} -> |
2092 |
:-( |
{result, Result}; |
2093 |
|
{result, {TNode, {default, pending, _SubId}}} -> |
2094 |
9 |
send_authorization_request(TNode, Subscriber), |
2095 |
9 |
{result, subscribe_node_reply(Subscriber, Node, pending)}; |
2096 |
|
{result, {TNode, {Result, pending}}} -> |
2097 |
:-( |
send_authorization_request(TNode, Subscriber), |
2098 |
:-( |
{result, Result}; |
2099 |
|
{result, {_, Result}} -> |
2100 |
:-( |
{result, Result}; |
2101 |
4 |
Error -> Error |
2102 |
|
end. |
2103 |
|
|
2104 |
|
subscribe_node_transaction(Host, SubOpts, From, Subscriber, PubSubNode) -> |
2105 |
92 |
Features = plugin_features(PubSubNode#pubsub_node.type), |
2106 |
92 |
subscribe_node_transaction_step1(Host, SubOpts, From, Subscriber, PubSubNode, Features). |
2107 |
|
|
2108 |
|
subscribe_node_transaction_step1(Host, SubOpts, From, Subscriber, PubSubNode, Features) -> |
2109 |
92 |
case lists:member(<<"subscribe">>, Features) of |
2110 |
|
false -> |
2111 |
:-( |
{error, extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"subscribe">>)}; |
2112 |
|
true -> |
2113 |
92 |
subscribe_node_transaction_step2(Host, SubOpts, From, Subscriber, PubSubNode, Features) |
2114 |
|
end. |
2115 |
|
|
2116 |
|
subscribe_node_transaction_step2(Host, SubOpts, From, Subscriber, PubSubNode, Features) -> |
2117 |
92 |
case get_option(PubSubNode#pubsub_node.options, subscribe) of |
2118 |
|
false -> |
2119 |
:-( |
{error, extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"subscribe">>)}; |
2120 |
|
true -> |
2121 |
92 |
subscribe_node_transaction_step3(Host, SubOpts, From, Subscriber, PubSubNode, Features) |
2122 |
|
end. |
2123 |
|
|
2124 |
|
subscribe_node_transaction_step3(Host, SubOpts, From, Subscriber, PubSubNode, Features) -> |
2125 |
92 |
case {SubOpts /= [], lists:member(<<"subscription-options">>, Features)} of |
2126 |
|
{true, false} -> |
2127 |
:-( |
{error, |
2128 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"subscription-options">>)}; |
2129 |
|
_ -> |
2130 |
92 |
subscribe_node_transaction_step4(Host, SubOpts, From, Subscriber, PubSubNode) |
2131 |
|
end. |
2132 |
|
|
2133 |
|
subscribe_node_transaction_step4(_Host, invalid, _From, _Subscriber, _PubSubNode) -> |
2134 |
:-( |
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"invalid-options">>)}; |
2135 |
|
subscribe_node_transaction_step4(Host, SubOpts, From, Subscriber, |
2136 |
|
#pubsub_node{options = Options, type = Type, |
2137 |
|
id = Nidx, owners = Owners}) -> |
2138 |
92 |
case check_subs_limit(Host, Type, Nidx) of |
2139 |
|
true -> |
2140 |
2 |
{error, extended_error(mongoose_xmpp_errors:not_allowed(), <<"closed-node">>)}; |
2141 |
|
false -> |
2142 |
90 |
AccessModel = get_option(Options, access_model), |
2143 |
90 |
SendLast = get_option(Options, send_last_published_item), |
2144 |
90 |
AllowedGroups = get_option(Options, roster_groups_allowed, []), |
2145 |
|
|
2146 |
90 |
{PS, RG} = get_presence_and_roster_permissions(Host, Subscriber, |
2147 |
|
Owners, AccessModel, AllowedGroups), |
2148 |
90 |
node_call(Type, subscribe_node, |
2149 |
|
[Nidx, From, Subscriber, AccessModel, |
2150 |
|
SendLast, PS, RG, SubOpts]) |
2151 |
|
end. |
2152 |
|
|
2153 |
|
check_subs_limit(Host, Type, Nidx) -> |
2154 |
92 |
case get_max_subscriptions_node(Host) of |
2155 |
|
Max when is_integer(Max) -> |
2156 |
4 |
case node_call(Type, get_node_subscriptions, [Nidx]) of |
2157 |
4 |
{result, NodeSubs} -> count_subscribed(NodeSubs) >= Max; |
2158 |
:-( |
_ -> false |
2159 |
|
end; |
2160 |
|
_ -> |
2161 |
88 |
false |
2162 |
|
end. |
2163 |
|
|
2164 |
|
count_subscribed(NodeSubs) -> |
2165 |
4 |
lists:foldl( |
2166 |
2 |
fun ({_JID, subscribed, _SubId, _Opts}, Acc) -> Acc+1; |
2167 |
:-( |
(_, Acc) -> Acc |
2168 |
|
end, 0, NodeSubs). |
2169 |
|
|
2170 |
|
subscribe_node_reply(Subscriber, Node, {subscribed, SubId}) -> |
2171 |
79 |
SubAttrs = [{<<"subscription">>, subscription_to_string(subscribed)}, |
2172 |
|
{<<"subid">>, SubId}, {<<"node">>, Node}], |
2173 |
79 |
subscribe_node_reply(Subscriber, SubAttrs); |
2174 |
|
subscribe_node_reply(Subscriber, Node, Subscription) -> |
2175 |
9 |
SubAttrs = [{<<"subscription">>, subscription_to_string(Subscription)}, |
2176 |
|
{<<"node">>, Node}], |
2177 |
9 |
subscribe_node_reply(Subscriber, SubAttrs). |
2178 |
|
|
2179 |
|
subscribe_node_reply(Subscriber, SubAttrs) -> |
2180 |
88 |
[#xmlel{name = <<"pubsub">>, |
2181 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB}], |
2182 |
|
children = [#xmlel{name = <<"subscription">>, |
2183 |
|
attrs = [{<<"jid">>, jid:to_binary(Subscriber)} |
2184 |
|
| SubAttrs]}]}]. |
2185 |
|
|
2186 |
|
%% @doc <p>Unsubscribe <tt>JID</tt> from the <tt>Node</tt>.</p> |
2187 |
|
%%<p>There are several reasons why the unsubscribe request might fail:</p> |
2188 |
|
%%<ul> |
2189 |
|
%%<li>The requesting entity has multiple subscriptions to the node |
2190 |
|
%% but does not specify a subscription ID.</li> |
2191 |
|
%%<li>The request does not specify an existing subscriber.</li> |
2192 |
|
%%<li>The requesting entity does not have sufficient privileges |
2193 |
|
%% to unsubscribe the specified JID.</li> |
2194 |
|
%%<li>The node does not exist.</li> |
2195 |
|
%%<li>The request specifies a subscription ID that is not valid or current.</li> |
2196 |
|
%%</ul> |
2197 |
|
-spec unsubscribe_node( |
2198 |
|
Host :: mod_pubsub:host(), |
2199 |
|
Node :: mod_pubsub:nodeId(), |
2200 |
|
From ::jid:jid(), |
2201 |
|
JID :: binary() | jid:ljid(), |
2202 |
|
SubId :: mod_pubsub:subId()) |
2203 |
|
-> {result, []} | {error, exml:element()}. |
2204 |
|
unsubscribe_node(Host, Node, From, JID, SubId) when is_binary(JID) -> |
2205 |
5 |
unsubscribe_node(Host, Node, From, string_to_ljid(JID), SubId); |
2206 |
|
unsubscribe_node(Host, Node, From, Subscriber, SubId) -> |
2207 |
5 |
Action = fun (#pubsub_node{type = Type, id = Nidx}) -> |
2208 |
5 |
node_call(Type, unsubscribe_node, [Nidx, From, Subscriber, SubId]) |
2209 |
|
end, |
2210 |
5 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2211 |
5 |
{result, {_, default}} -> {result, []}; |
2212 |
|
% {result, {_, Result}} -> {result, Result}; |
2213 |
:-( |
Error -> Error |
2214 |
|
end. |
2215 |
|
|
2216 |
|
%% @doc <p>Publish item to a PubSub node.</p> |
2217 |
|
%% <p>The permission to publish an item must be verified by the plugin implementation.</p> |
2218 |
|
%%<p>There are several reasons why the publish request might fail:</p> |
2219 |
|
%%<ul> |
2220 |
|
%%<li>The requesting entity does not have sufficient privileges to publish.</li> |
2221 |
|
%%<li>The node does not support item publication.</li> |
2222 |
|
%%<li>The node does not exist.</li> |
2223 |
|
%%<li>The payload size exceeds a service-defined limit.</li> |
2224 |
|
%%<li>The item contains more than one payload element or the namespace of the root payload element |
2225 |
|
%% does not match the configured namespace for the node.</li> |
2226 |
|
%%<li>The request does not match the node configuration.</li> |
2227 |
|
%%</ul> |
2228 |
|
-spec publish_item( |
2229 |
|
Host :: mod_pubsub:host(), |
2230 |
|
ServerHost :: binary(), |
2231 |
|
Node :: mod_pubsub:nodeId(), |
2232 |
|
Publisher ::jid:jid(), |
2233 |
|
ItemId :: <<>> | mod_pubsub:itemId(), |
2234 |
|
Payload :: mod_pubsub:payload()) |
2235 |
|
-> {result, [exml:element(), ...]} | {error, exml:element()}. |
2236 |
|
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload) -> |
2237 |
16 |
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, all). |
2238 |
|
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access) -> |
2239 |
16 |
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access, undefined). |
2240 |
|
publish_item(Host, ServerHost, Node, Publisher, <<>>, Payload, Access, PublishOptions) -> |
2241 |
61 |
publish_item(Host, ServerHost, Node, Publisher, uniqid(), Payload, Access, PublishOptions); |
2242 |
|
publish_item(Host, ServerHost, Node, Publisher, ItemId, Payload, Access, PublishOptions) -> |
2243 |
218 |
ItemPublisher = config(serverhost(Host), item_publisher), |
2244 |
218 |
Action = |
2245 |
|
fun (#pubsub_node{options = Options, type = Type, id = Nidx}) -> |
2246 |
202 |
Features = plugin_features(Type), |
2247 |
202 |
PublishFeature = lists:member(<<"publish">>, Features), |
2248 |
202 |
PubOptsFeature = lists:member(<<"publish-options">>, Features), |
2249 |
202 |
PublishModel = get_option(Options, publish_model), |
2250 |
202 |
DeliverPayloads = get_option(Options, deliver_payloads), |
2251 |
202 |
PersistItems = get_option(Options, persist_items), |
2252 |
202 |
MaxItems = max_items(Host, Options), |
2253 |
202 |
PayloadCount = payload_xmlelements(Payload), |
2254 |
202 |
PayloadSize = byte_size(term_to_binary(Payload)) - 2, |
2255 |
202 |
PayloadMaxSize = get_option(Options, max_payload_size), |
2256 |
|
|
2257 |
202 |
Errors = [ %% [{Condition :: boolean(), Reason :: term()}] |
2258 |
|
{not PublishFeature, |
2259 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"publish">>)}, |
2260 |
202 |
{not PubOptsFeature andalso PublishOptions /= undefined, |
2261 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, |
2262 |
|
<<"publish-options">>)}, |
2263 |
|
{PayloadSize > PayloadMaxSize, |
2264 |
|
extended_error(mongoose_xmpp_errors:not_acceptable(), <<"payload-too-big">>)}, |
2265 |
|
{(PayloadCount == 0) and (Payload == []), |
2266 |
|
extended_error(mongoose_xmpp_errors:bad_request(), <<"payload-required">>)}, |
2267 |
|
{(PayloadCount > 1) or (PayloadCount == 0), |
2268 |
|
extended_error(mongoose_xmpp_errors:bad_request(), <<"invalid-payload">>)}, |
2269 |
|
{(DeliverPayloads == false) and (PersistItems == false) and (PayloadSize > 0), |
2270 |
|
extended_error(mongoose_xmpp_errors:bad_request(), <<"item-forbidden">>)}, |
2271 |
|
{((DeliverPayloads == true) or (PersistItems == true)) and (PayloadSize == 0), |
2272 |
|
extended_error(mongoose_xmpp_errors:bad_request(), <<"item-required">>)}, |
2273 |
202 |
{PubOptsFeature andalso check_publish_options(Type, PublishOptions, Options), |
2274 |
|
extended_error(mongoose_xmpp_errors:conflict(), <<"precondition-not-met">>)} |
2275 |
|
], |
2276 |
|
|
2277 |
202 |
case lists:keyfind(true, 1, Errors) of |
2278 |
|
{true, Reason} -> |
2279 |
6 |
{error, Reason}; |
2280 |
|
false -> |
2281 |
196 |
node_call(Type, publish_item, |
2282 |
|
[ServerHost, Nidx, Publisher, PublishModel, MaxItems, ItemId, |
2283 |
|
ItemPublisher, Payload, PublishOptions]) |
2284 |
|
end |
2285 |
|
end, |
2286 |
218 |
Reply = [#xmlel{name = <<"pubsub">>, |
2287 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB}], |
2288 |
|
children = [#xmlel{name = <<"publish">>, attrs = node_attr(Node), |
2289 |
|
children = [#xmlel{name = <<"item">>, |
2290 |
|
attrs = item_attr(ItemId)}]}]}], |
2291 |
218 |
ErrorItemNotFound = mongoose_xmpp_errors:item_not_found(), |
2292 |
218 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2293 |
|
{result, {TNode, {Result, Broadcast, Removed}}} -> |
2294 |
122 |
Nidx = TNode#pubsub_node.id, |
2295 |
122 |
Type = TNode#pubsub_node.type, |
2296 |
122 |
Options = TNode#pubsub_node.options, |
2297 |
122 |
BrPayload = case Broadcast of |
2298 |
122 |
broadcast -> Payload; |
2299 |
:-( |
PluginPayload -> PluginPayload |
2300 |
|
end, |
2301 |
122 |
set_cached_item(Host, Nidx, ItemId, Publisher, BrPayload), |
2302 |
122 |
case get_option(Options, deliver_notifications) of |
2303 |
|
true -> |
2304 |
119 |
broadcast_publish_item(Host, Node, Nidx, Type, Options, ItemId, |
2305 |
|
Publisher, BrPayload, Removed, ItemPublisher); |
2306 |
|
false -> |
2307 |
3 |
ok |
2308 |
|
end, |
2309 |
122 |
case Result of |
2310 |
122 |
default -> {result, Reply}; |
2311 |
:-( |
_ -> {result, Result} |
2312 |
|
end; |
2313 |
|
{result, {TNode, {default, Removed}}} -> |
2314 |
:-( |
Nidx = TNode#pubsub_node.id, |
2315 |
:-( |
Type = TNode#pubsub_node.type, |
2316 |
:-( |
Options = TNode#pubsub_node.options, |
2317 |
:-( |
broadcast_retract_items(Host, Node, Nidx, Type, Options, Removed), |
2318 |
:-( |
set_cached_item(Host, Nidx, ItemId, Publisher, Payload), |
2319 |
:-( |
{result, Reply}; |
2320 |
|
{result, {TNode, {Result, Removed}}} -> |
2321 |
:-( |
Nidx = TNode#pubsub_node.id, |
2322 |
:-( |
Type = TNode#pubsub_node.type, |
2323 |
:-( |
Options = TNode#pubsub_node.options, |
2324 |
:-( |
broadcast_retract_items(Host, Node, Nidx, Type, Options, Removed), |
2325 |
:-( |
set_cached_item(Host, Nidx, ItemId, Publisher, Payload), |
2326 |
:-( |
{result, Result}; |
2327 |
|
{result, {_, default}} -> |
2328 |
62 |
{result, Reply}; |
2329 |
|
{result, {_, Result}} -> |
2330 |
:-( |
{result, Result}; |
2331 |
|
{error, ErrorItemNotFound} -> |
2332 |
16 |
Type = select_type(ServerHost, Host, Node), |
2333 |
16 |
autocreate_if_supported_and_publish(Host, ServerHost, Node, Publisher, |
2334 |
|
Type, Access, ItemId, Payload, PublishOptions); |
2335 |
|
Error -> |
2336 |
18 |
Error |
2337 |
|
end. |
2338 |
|
|
2339 |
|
autocreate_if_supported_and_publish(Host, ServerHost, Node, Publisher, |
2340 |
|
Type, Access, ItemId, Payload, PublishOptions) -> |
2341 |
16 |
ErrorItemNotFound = mongoose_xmpp_errors:item_not_found(), |
2342 |
16 |
case lists:member(<<"auto-create">>, plugin_features(Type)) of |
2343 |
|
true -> |
2344 |
16 |
case create_node(Host, ServerHost, Node, Publisher, Type, Access, PublishOptions) of |
2345 |
|
{result, |
2346 |
|
[#xmlel{name = <<"pubsub">>, |
2347 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB}], |
2348 |
|
children = [#xmlel{name = <<"create">>, |
2349 |
|
attrs = [{<<"node">>, NewNode}]}]}]} -> |
2350 |
16 |
publish_item(Host, ServerHost, NewNode, Publisher, ItemId, Payload); |
2351 |
|
_ -> |
2352 |
:-( |
{error, ErrorItemNotFound} |
2353 |
|
end; |
2354 |
|
false -> |
2355 |
:-( |
{error, ErrorItemNotFound} |
2356 |
|
end. |
2357 |
|
|
2358 |
|
%% @doc <p>Delete item from a PubSub node.</p> |
2359 |
|
%% <p>The permission to delete an item must be verified by the plugin implementation.</p> |
2360 |
|
%%<p>There are several reasons why the item retraction request might fail:</p> |
2361 |
|
%%<ul> |
2362 |
|
%%<li>The publisher does not have sufficient privileges to delete the requested item.</li> |
2363 |
|
%%<li>The node or item does not exist.</li> |
2364 |
|
%%<li>The request does not specify a node.</li> |
2365 |
|
%%<li>The request does not include an <item/> element |
2366 |
|
%% or the <item/> element does not specify an ItemId.</li> |
2367 |
|
%%<li>The node does not support persistent items.</li> |
2368 |
|
%%<li>The service does not support the deletion of items.</li> |
2369 |
|
%%</ul> |
2370 |
|
-spec delete_item( |
2371 |
|
Host :: mod_pubsub:host(), |
2372 |
|
Node :: mod_pubsub:nodeId(), |
2373 |
|
Publisher ::jid:jid(), |
2374 |
|
ItemId :: mod_pubsub:itemId()) |
2375 |
|
-> {result, []} | {error, exml:element()}. |
2376 |
|
delete_item(Host, Node, Publisher, ItemId) -> |
2377 |
:-( |
delete_item(Host, Node, Publisher, ItemId, false). |
2378 |
|
delete_item(_, <<>>, _, _, _) -> |
2379 |
:-( |
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"node-required">>)}; |
2380 |
|
delete_item(Host, Node, Publisher, ItemId, ForceNotify) -> |
2381 |
8 |
Action = fun(PubSubNode) -> delete_item_transaction(Publisher, ItemId, PubSubNode) end, |
2382 |
8 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2383 |
|
{result, {TNode, {Result, broadcast}}} -> |
2384 |
6 |
Nidx = TNode#pubsub_node.id, |
2385 |
6 |
Type = TNode#pubsub_node.type, |
2386 |
6 |
Options = TNode#pubsub_node.options, |
2387 |
6 |
broadcast_retract_items(Host, Node, Nidx, Type, Options, [ItemId], ForceNotify), |
2388 |
6 |
case get_cached_item(Host, Nidx) of |
2389 |
:-( |
#pubsub_item{itemid = {ItemId, Nidx}} -> unset_cached_item(Host, Nidx); |
2390 |
6 |
_ -> ok |
2391 |
|
end, |
2392 |
6 |
case Result of |
2393 |
6 |
default -> {result, []}; |
2394 |
:-( |
_ -> {result, Result} |
2395 |
|
end; |
2396 |
|
{result, {_, default}} -> |
2397 |
:-( |
{result, []}; |
2398 |
|
{result, {_, Result}} -> |
2399 |
:-( |
{result, Result}; |
2400 |
|
Error -> |
2401 |
2 |
Error |
2402 |
|
end. |
2403 |
|
|
2404 |
|
delete_item_transaction(Publisher, ItemId, |
2405 |
|
#pubsub_node{options = Options, type = Type, id = Nidx}) -> |
2406 |
8 |
Features = plugin_features(Type), |
2407 |
8 |
case lists:member(<<"persistent-items">>, Features) of |
2408 |
|
true -> |
2409 |
8 |
case lists:member(<<"delete-items">>, Features) of |
2410 |
|
true -> |
2411 |
8 |
PublishModel = get_option(Options, publish_model), |
2412 |
8 |
node_call(Type, delete_item, [Nidx, Publisher, PublishModel, ItemId]); |
2413 |
|
false -> |
2414 |
:-( |
{error, |
2415 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"delete-items">>)} |
2416 |
|
end; |
2417 |
|
false -> |
2418 |
:-( |
{error, |
2419 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"persistent-items">>)} |
2420 |
|
end. |
2421 |
|
|
2422 |
|
%% @doc <p>Delete all items of specified node owned by JID.</p> |
2423 |
|
%%<p>There are several reasons why the node purge request might fail:</p> |
2424 |
|
%%<ul> |
2425 |
|
%%<li>The node or service does not support node purging.</li> |
2426 |
|
%%<li>The requesting entity does not have sufficient privileges to purge the node.</li> |
2427 |
|
%%<li>The node is not configured to persist items.</li> |
2428 |
|
%%<li>The specified node does not exist.</li> |
2429 |
|
%%</ul> |
2430 |
|
-spec purge_node( |
2431 |
|
Host :: mod_pubsub:host(), |
2432 |
|
Node :: mod_pubsub:nodeId(), |
2433 |
|
Owner :: jid:jid()) |
2434 |
|
-> {result, []} | {error, exml:element()}. |
2435 |
|
purge_node(Host, Node, Owner) -> |
2436 |
8 |
Action = fun (PubSubNode) -> purge_node_transaction(Owner, PubSubNode) end, |
2437 |
8 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2438 |
|
{result, {TNode, {Result, broadcast}}} -> |
2439 |
4 |
Nidx = TNode#pubsub_node.id, |
2440 |
4 |
Type = TNode#pubsub_node.type, |
2441 |
4 |
Options = TNode#pubsub_node.options, |
2442 |
4 |
broadcast_purge_node(Host, Node, Nidx, Type, Options), |
2443 |
4 |
unset_cached_item(Host, Nidx), |
2444 |
4 |
case Result of |
2445 |
4 |
default -> {result, []}; |
2446 |
:-( |
_ -> {result, Result} |
2447 |
|
end; |
2448 |
|
{result, {_, default}} -> |
2449 |
:-( |
{result, []}; |
2450 |
|
{result, {_, Result}} -> |
2451 |
:-( |
{result, Result}; |
2452 |
|
Error -> |
2453 |
4 |
Error |
2454 |
|
end. |
2455 |
|
|
2456 |
|
purge_node_transaction(Owner, #pubsub_node{options = Options, type = Type, id = Nidx}) -> |
2457 |
8 |
Features = plugin_features(Type), |
2458 |
8 |
case {lists:member(<<"purge-nodes">>, Features), |
2459 |
|
lists:member(<<"persistent-items">>, Features), |
2460 |
|
get_option(Options, persist_items)} of |
2461 |
|
{false, _, _} -> |
2462 |
:-( |
{error, |
2463 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"purge-nodes">>)}; |
2464 |
|
{_, false, _} -> |
2465 |
:-( |
{error, |
2466 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"persistent-items">>)}; |
2467 |
|
{_, _, false} -> |
2468 |
:-( |
{error, |
2469 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"persistent-items">>)}; |
2470 |
8 |
_ -> node_call(Type, purge_node, [Nidx, Owner]) |
2471 |
|
end. |
2472 |
|
|
2473 |
|
%% @doc <p>Return the items of a given node.</p> |
2474 |
|
%% <p>The number of items to return is limited by MaxItems.</p> |
2475 |
|
%% <p>The permission are not checked in this function.</p> |
2476 |
|
%% @todo We probably need to check that the user doing the query has the right |
2477 |
|
%% to read the items. |
2478 |
|
-spec get_items(Host :: mod_pubsub:host(), |
2479 |
|
Node :: mod_pubsub:nodeId(), |
2480 |
|
From ::jid:jid(), |
2481 |
|
SubId :: mod_pubsub:subId(), |
2482 |
|
SMaxItems :: binary(), |
2483 |
|
ItemIds :: [mod_pubsub:itemId()], |
2484 |
|
Rsm :: none | jlib:rsm_in()) -> {result, [exml:element(), ...]} | {error, exml:element()}. |
2485 |
|
get_items(Host, Node, From, SubId, <<>>, ItemIds, RSM) -> |
2486 |
33 |
MaxItems = case get_max_items_node(Host) of |
2487 |
:-( |
undefined -> ?MAXITEMS; |
2488 |
33 |
Max -> Max |
2489 |
|
end, |
2490 |
33 |
get_items_with_limit(Host, Node, From, SubId, ItemIds, RSM, MaxItems); |
2491 |
|
get_items(Host, Node, From, SubId, SMaxItems, ItemIds, RSM) -> |
2492 |
:-( |
MaxItems = case catch binary_to_integer(SMaxItems) of |
2493 |
:-( |
{'EXIT', _} -> {error, mongoose_xmpp_errors:bad_request()}; |
2494 |
:-( |
Val -> Val |
2495 |
|
end, |
2496 |
:-( |
get_items_with_limit(Host, Node, From, SubId, ItemIds, RSM, MaxItems). |
2497 |
|
|
2498 |
|
get_items_with_limit(_Host, _Node, _From, _SubId, _ItemIds, _RSM, {error, _} = Err) -> |
2499 |
:-( |
Err; |
2500 |
|
get_items_with_limit(Host, Node, From, SubId, ItemIds, RSM, MaxItems) -> |
2501 |
33 |
Action = fun (PubSubNode) -> |
2502 |
33 |
get_items_transaction(Host, From, RSM, SubId, PubSubNode, MaxItems, ItemIds) |
2503 |
|
end, |
2504 |
33 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2505 |
|
{result, {_, {Items, RsmOut}}} -> |
2506 |
33 |
{result, |
2507 |
|
[#xmlel{name = <<"pubsub">>, |
2508 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB}], |
2509 |
|
children = |
2510 |
|
[#xmlel{name = <<"items">>, attrs = node_attr(Node), |
2511 |
|
children = items_els(Items)} |
2512 |
|
| jlib:rsm_encode(RsmOut)]}]}; |
2513 |
|
Error -> |
2514 |
:-( |
Error |
2515 |
|
end. |
2516 |
|
|
2517 |
|
get_items_transaction(Host, From, RSM, SubId, |
2518 |
|
#pubsub_node{options = Options, type = Type, id = Nidx, owners = Owners}, |
2519 |
|
MaxItems, ItemIds) -> |
2520 |
33 |
Features = plugin_features(Type), |
2521 |
33 |
case {lists:member(<<"retrieve-items">>, Features), |
2522 |
|
lists:member(<<"persistent-items">>, Features)} of |
2523 |
|
{false, _} -> |
2524 |
:-( |
{error, |
2525 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"retrieve-items">>)}; |
2526 |
|
{_, false} -> |
2527 |
:-( |
{error, |
2528 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"persistent-items">>)}; |
2529 |
|
_ -> |
2530 |
33 |
AccessModel = get_option(Options, access_model), |
2531 |
33 |
AllowedGroups = get_option(Options, roster_groups_allowed, []), |
2532 |
33 |
{PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, |
2533 |
|
AccessModel, AllowedGroups), |
2534 |
33 |
Opts = #{access_model => AccessModel, |
2535 |
|
presence_permission => PS, |
2536 |
|
roster_permission => RG, |
2537 |
|
rsm => RSM, |
2538 |
|
max_items => MaxItems, |
2539 |
|
item_ids => ItemIds, |
2540 |
|
subscription_id => SubId}, |
2541 |
|
|
2542 |
33 |
node_call(Type, get_items_if_authorised, [Nidx, From, Opts]) |
2543 |
|
end. |
2544 |
|
|
2545 |
|
get_items(Host, Node) -> |
2546 |
4 |
Action = fun (#pubsub_node{type = Type, id = Nidx}) -> |
2547 |
2 |
node_call(Type, get_items, [Nidx, service_jid(Host), #{}]) |
2548 |
|
end, |
2549 |
4 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2550 |
2 |
{result, {_, {Items, _}}} -> Items; |
2551 |
2 |
Error -> Error |
2552 |
|
end. |
2553 |
|
|
2554 |
|
get_item(Host, Node, ItemId) -> |
2555 |
4 |
Action = fun (#pubsub_node{type = Type, id = Nidx}) -> |
2556 |
4 |
node_call(Type, get_item, [Nidx, ItemId]) |
2557 |
|
end, |
2558 |
4 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2559 |
2 |
{result, {_, Items}} -> Items; |
2560 |
2 |
Error -> Error |
2561 |
|
end. |
2562 |
|
|
2563 |
|
get_allowed_items_call(Host, Nidx, From, Type, Options, Owners) -> |
2564 |
1 |
case get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, none) of |
2565 |
1 |
{result, {Items, _RSM}} -> {result, Items}; |
2566 |
:-( |
Error -> Error |
2567 |
|
end. |
2568 |
|
get_allowed_items_call(Host, Nidx, From, Type, Options, Owners, RSM) -> |
2569 |
5 |
AccessModel = get_option(Options, access_model), |
2570 |
5 |
AllowedGroups = get_option(Options, roster_groups_allowed, []), |
2571 |
5 |
{PS, RG} = get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups), |
2572 |
5 |
Opts = #{access_model => AccessModel, |
2573 |
|
presence_permission => PS, |
2574 |
|
roster_permission => RG, |
2575 |
|
rsm => RSM}, |
2576 |
5 |
node_call(Type, get_items_if_authorised, [Nidx, From, Opts]). |
2577 |
|
|
2578 |
|
get_last_item(Host, Type, Nidx, LJID) -> |
2579 |
14 |
case get_cached_item(Host, Nidx) of |
2580 |
|
false -> |
2581 |
9 |
case node_action(Host, Type, get_items, [Nidx, LJID, #{}]) of |
2582 |
5 |
{result, {[LastItem|_], _}} -> LastItem; |
2583 |
4 |
_ -> undefined |
2584 |
|
end; |
2585 |
|
LastItem -> |
2586 |
5 |
LastItem |
2587 |
|
end. |
2588 |
|
|
2589 |
|
get_last_items(Host, Type, Nidx, LJID, Number) -> |
2590 |
:-( |
case node_action(Host, Type, get_items, [Nidx, LJID, #{}]) of |
2591 |
:-( |
{result, {Items, _}} -> lists:sublist(Items, Number); |
2592 |
:-( |
_ -> [] |
2593 |
|
end. |
2594 |
|
|
2595 |
|
%% @doc <p>Resend the items of a node to the user.</p> |
2596 |
|
%% @todo use cache-last-item feature |
2597 |
|
send_items(Host, Node, Nidx, Type, Options, LJID, last) -> |
2598 |
14 |
case get_last_item(Host, Type, Nidx, LJID) of |
2599 |
|
undefined -> |
2600 |
4 |
ok; |
2601 |
|
LastItem -> |
2602 |
10 |
Stanza = items_event_stanza(Node, [LastItem]), |
2603 |
10 |
dispatch_items(Host, LJID, Options, Stanza) |
2604 |
|
end; |
2605 |
|
send_items(Host, Node, Nidx, Type, Options, LJID, Number) when Number > 0 -> |
2606 |
:-( |
Stanza = items_event_stanza(Node, get_last_items(Host, Type, Nidx, Number, LJID)), |
2607 |
:-( |
dispatch_items(Host, LJID, Options, Stanza); |
2608 |
|
send_items(Host, Node, _Nidx, _Type, Options, LJID, _) -> |
2609 |
:-( |
Stanza = items_event_stanza(Node, []), |
2610 |
:-( |
dispatch_items(Host, LJID, Options, Stanza). |
2611 |
|
|
2612 |
|
dispatch_items({_, FromS, _} = From, To, Options, Stanza) -> |
2613 |
6 |
NotificationType = get_option(Options, notification_type, headline), |
2614 |
6 |
Message = add_message_type(Stanza, NotificationType), |
2615 |
6 |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(FromS), |
2616 |
6 |
FromJid = jid:make(From), |
2617 |
6 |
ToJid = jid:make(To), |
2618 |
6 |
AccParams = #{host_type => HostType, lserver => FromS, location => ?LOCATION, |
2619 |
|
element => Message, from_jid => FromJid, to_jid => ToJid}, |
2620 |
6 |
Acc = mongoose_acc:new(AccParams), |
2621 |
6 |
ejabberd_router:route(FromJid, ToJid, Acc); |
2622 |
|
dispatch_items(From, To, Options, Stanza) -> |
2623 |
4 |
NotificationType = get_option(Options, notification_type, headline), |
2624 |
4 |
Message = add_message_type(Stanza, NotificationType), |
2625 |
4 |
ejabberd_router:route(service_jid(From), jid:make(To), Message). |
2626 |
|
|
2627 |
|
%% @doc <p>Return the list of affiliations as an XMPP response.</p> |
2628 |
|
-spec get_affiliations( |
2629 |
|
Host :: mod_pubsub:host(), |
2630 |
|
Node :: mod_pubsub:nodeId(), |
2631 |
|
JID :: jid:jid(), |
2632 |
|
Plugins :: #{plugins := [binary()]}) |
2633 |
|
-> {result, [exml:element()]} | {error, exml:element()}. |
2634 |
|
get_affiliations(Host, Node, JID, #{plugins := Plugins}) when is_list(Plugins) -> |
2635 |
:-( |
Result = lists:foldl( |
2636 |
|
fun(Type, {Status, Acc}) -> |
2637 |
:-( |
Features = plugin_features(Type), |
2638 |
:-( |
case lists:member(<<"retrieve-affiliations">>, Features) of |
2639 |
|
true -> |
2640 |
:-( |
{result, Affs} = node_action(Host, Type, get_entity_affiliations, |
2641 |
|
[Host, JID]), |
2642 |
:-( |
{Status, [Affs | Acc]}; |
2643 |
|
false -> |
2644 |
:-( |
{{error, |
2645 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), |
2646 |
|
unsupported, <<"retrieve-affiliations">>)}, |
2647 |
|
Acc} |
2648 |
|
end |
2649 |
|
end, {ok, []}, Plugins), |
2650 |
:-( |
case Result of |
2651 |
|
{ok, Affs} -> |
2652 |
:-( |
Entities = lists:flatmap( |
2653 |
|
fun ({_, none}) -> |
2654 |
:-( |
[]; |
2655 |
|
({#pubsub_node{nodeid = {_, NodeId}}, Aff}) when |
2656 |
|
Node == <<>> orelse Node == NodeId -> |
2657 |
:-( |
[#xmlel{name = <<"affiliation">>, |
2658 |
|
attrs = [{<<"affiliation">>, affiliation_to_string(Aff)} |
2659 |
|
| node_attr(NodeId)]}]; |
2660 |
|
(_) -> |
2661 |
:-( |
[] |
2662 |
|
end, |
2663 |
|
lists:usort(lists:flatten(Affs))), |
2664 |
:-( |
{result, |
2665 |
|
[#xmlel{name = <<"pubsub">>, |
2666 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB}], |
2667 |
|
children = [#xmlel{name = <<"affiliations">>, attrs = [], |
2668 |
|
children = Entities}]}]}; |
2669 |
|
{Error, _} -> |
2670 |
:-( |
Error |
2671 |
|
end. |
2672 |
|
|
2673 |
|
-spec get_affiliations(Host :: mod_pubsub:host(), Node :: mod_pubsub:nodeId(), JID :: jid:jid()) -> |
2674 |
|
{result, [exml:element(), ...]} | {error, exml:element()}. |
2675 |
|
get_affiliations(Host, Node, JID) -> |
2676 |
8 |
Action = fun (PubSubNode) -> get_affiliations_transaction(JID, PubSubNode) end, |
2677 |
8 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2678 |
|
{result, {_, []}} -> |
2679 |
:-( |
{error, mongoose_xmpp_errors:item_not_found()}; |
2680 |
|
{result, {_, Affs}} -> |
2681 |
6 |
Entities = |
2682 |
|
lists:flatmap(fun({_, none}) -> |
2683 |
:-( |
[]; |
2684 |
|
({AJID, Aff}) -> |
2685 |
6 |
[#xmlel{ |
2686 |
|
name = <<"affiliation">>, |
2687 |
|
attrs = [{<<"jid">>, jid:to_binary(AJID)}, |
2688 |
|
{<<"affiliation">>, affiliation_to_string(Aff)}]}] |
2689 |
|
end, Affs), |
2690 |
6 |
{result, |
2691 |
|
[#xmlel{name = <<"pubsub">>, |
2692 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], |
2693 |
|
children = [#xmlel{name = <<"affiliations">>, |
2694 |
|
attrs = node_attr(Node), children = Entities}]}]}; |
2695 |
|
Error -> |
2696 |
2 |
Error |
2697 |
|
end. |
2698 |
|
|
2699 |
|
get_affiliations_transaction(JID, #pubsub_node{type = Type, id = Nidx}) -> |
2700 |
8 |
Features = plugin_features(Type), |
2701 |
8 |
case lists:member(<<"modify-affiliations">>, Features) of |
2702 |
|
true -> |
2703 |
8 |
case node_call(Type, get_affiliation, [Nidx, JID]) of |
2704 |
|
{result, owner} -> |
2705 |
6 |
node_call(Type, get_node_affiliations, [Nidx]); |
2706 |
|
_ -> |
2707 |
2 |
{error, mongoose_xmpp_errors:forbidden()} |
2708 |
|
end; |
2709 |
|
false -> |
2710 |
:-( |
{error, |
2711 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"modify-affiliations">>)} |
2712 |
|
end. |
2713 |
|
|
2714 |
|
-spec set_affiliations( |
2715 |
|
Host :: mod_pubsub:host(), |
2716 |
|
Node :: mod_pubsub:nodeId(), |
2717 |
|
From ::jid:jid(), |
2718 |
|
EntitiesEls :: #{action_el := exml:element()}) |
2719 |
|
-> {result, []} | {error, exml:element() | {exml:element(), [exml:element()]}} | {error, exml:element()}. |
2720 |
|
set_affiliations(Host, Node, From, #{action_el := ActionEl} ) -> |
2721 |
52 |
EntitiesEls = xml:remove_cdata(ActionEl#xmlel.children), |
2722 |
52 |
Owner = jid:to_lower(jid:to_bare(From)), |
2723 |
52 |
Entities = lists:foldl(fun |
2724 |
|
(_, error) -> |
2725 |
:-( |
error; |
2726 |
|
(#xmlel{name = <<"affiliation">>, attrs = Attrs}, Acc) -> |
2727 |
56 |
JID = jid:from_binary(xml:get_attr_s(<<"jid">>, Attrs)), |
2728 |
56 |
Affiliation = string_to_affiliation( |
2729 |
|
xml:get_attr_s(<<"affiliation">>, Attrs)), |
2730 |
56 |
case (JID == error) or (Affiliation == false) of |
2731 |
:-( |
true -> error; |
2732 |
56 |
false -> [{jid:to_lower(JID), Affiliation} | Acc] |
2733 |
|
end; |
2734 |
|
(_, _) -> |
2735 |
:-( |
error |
2736 |
|
end, |
2737 |
|
[], EntitiesEls), |
2738 |
52 |
case Entities of |
2739 |
|
error -> |
2740 |
:-( |
{error, mongoose_xmpp_errors:bad_request()}; |
2741 |
|
_ -> |
2742 |
52 |
Action = fun (PubSubNode) -> |
2743 |
52 |
set_affiliations_transaction(Host, Owner, PubSubNode, Entities) |
2744 |
|
end, |
2745 |
52 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2746 |
50 |
{result, {_, Result}} -> {result, Result}; |
2747 |
2 |
Other -> Other |
2748 |
|
end |
2749 |
|
end. |
2750 |
|
|
2751 |
|
set_affiliations_transaction(Host, Owner, |
2752 |
|
#pubsub_node{owners = Owners, nodeid = {_, NodeId}} = N, Entities) -> |
2753 |
52 |
case lists:member(Owner, Owners) of |
2754 |
|
true -> |
2755 |
|
% It is a very simple check, as XEP doesn't state any |
2756 |
|
% other invalid affiliation transitions |
2757 |
52 |
OwnersDryRun = lists:foldl( |
2758 |
|
fun({JID, owner}, Acc) -> |
2759 |
2 |
sets:add_element(jid:to_bare(JID), Acc); |
2760 |
|
({JID, _}, Acc) -> |
2761 |
54 |
sets:del_element(jid:to_bare(JID), Acc) |
2762 |
|
end, sets:from_list(Owners), Entities), |
2763 |
52 |
case sets:size(OwnersDryRun) of |
2764 |
|
0 -> |
2765 |
2 |
OwnersPayload = [ #xmlel{ name = <<"affiliation">>, |
2766 |
|
attrs = [{<<"jid">>, jid:to_binary(Unchanged)}, |
2767 |
|
{<<"affiliation">>, <<"owner">>}] } |
2768 |
2 |
|| Unchanged <- Owners ], |
2769 |
2 |
AffiliationsPayload = #xmlel{ name = <<"affiliations">>, |
2770 |
|
attrs = [{<<"node">>, NodeId}], |
2771 |
|
children = OwnersPayload }, |
2772 |
2 |
NewPubSubPayload = #xmlel{ name = <<"pubsub">>, |
2773 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], |
2774 |
|
children = [AffiliationsPayload] }, |
2775 |
2 |
{error, {mongoose_xmpp_errors:not_acceptable(), [NewPubSubPayload]}}; |
2776 |
|
_ -> |
2777 |
50 |
set_validated_affiliations_transaction(Host, N, Owners, Entities), |
2778 |
50 |
{result, []} |
2779 |
|
end; |
2780 |
|
_ -> |
2781 |
:-( |
{error, mongoose_xmpp_errors:forbidden()} |
2782 |
|
end. |
2783 |
|
|
2784 |
|
set_validated_affiliations_transaction(Host, #pubsub_node{ type = Type, id = Nidx } = N, |
2785 |
|
Owners, Entities) -> |
2786 |
50 |
lists:foreach(fun ({JID, owner}) -> |
2787 |
2 |
node_call(Type, set_affiliation, [Nidx, JID, owner]), |
2788 |
2 |
NewOwner = jid:to_bare(JID), |
2789 |
2 |
NewOwners = [NewOwner | Owners], |
2790 |
2 |
tree_call(Host, |
2791 |
|
set_node, |
2792 |
|
[N#pubsub_node{owners = NewOwners}]); |
2793 |
|
({JID, none}) -> |
2794 |
2 |
node_call(Type, set_affiliation, [Nidx, JID, none]), |
2795 |
2 |
OldOwner = jid:to_bare(JID), |
2796 |
2 |
case lists:member(OldOwner, Owners) of |
2797 |
|
true -> |
2798 |
2 |
NewOwners = Owners -- [OldOwner], |
2799 |
2 |
tree_call(Host, |
2800 |
|
set_node, |
2801 |
|
[N#pubsub_node{owners = NewOwners}]); |
2802 |
|
_ -> |
2803 |
:-( |
ok |
2804 |
|
end; |
2805 |
|
({JID, Affiliation}) -> |
2806 |
50 |
node_call(Type, set_affiliation, [Nidx, JID, Affiliation]) |
2807 |
|
end, |
2808 |
|
Entities). |
2809 |
|
|
2810 |
|
get_options(Host, Node, JID, SubId, Lang) -> |
2811 |
14 |
Action = fun(PubSubNode) -> |
2812 |
14 |
get_options_transaction(Node, JID, SubId, Lang, PubSubNode) |
2813 |
|
end, |
2814 |
14 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2815 |
8 |
{result, {_Node, XForm}} -> {result, [XForm]}; |
2816 |
6 |
Error -> Error |
2817 |
|
end. |
2818 |
|
|
2819 |
|
get_options_transaction(Node, JID, SubId, Lang, #pubsub_node{type = Type, id = Nidx}) -> |
2820 |
14 |
case lists:member(<<"subscription-options">>, plugin_features(Type)) of |
2821 |
|
true -> |
2822 |
14 |
get_sub_options_xml(JID, Lang, Node, Nidx, SubId, Type); |
2823 |
|
false -> |
2824 |
:-( |
{error, |
2825 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), |
2826 |
|
unsupported, <<"subscription-options">>)} |
2827 |
|
end. |
2828 |
|
|
2829 |
|
% TODO: Support Lang at some point again |
2830 |
|
get_sub_options_xml(JID, _Lang, Node, Nidx, RequestedSubId, Type) -> |
2831 |
14 |
Subscriber = string_to_ljid(JID), |
2832 |
14 |
{result, Subs} = node_call(Type, get_subscriptions, [Nidx, Subscriber]), |
2833 |
14 |
SubscribedSubs = [{Id, Opts} || {Sub, Id, Opts} <- Subs, Sub == subscribed], |
2834 |
|
|
2835 |
14 |
case {RequestedSubId, SubscribedSubs} of |
2836 |
|
{_, []} -> |
2837 |
6 |
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), <<"not-subscribed">>)}; |
2838 |
|
{<<>>, [{TheOnlySID, Opts}]} -> |
2839 |
8 |
make_and_wrap_sub_xform(Opts, Node, Subscriber, TheOnlySID); |
2840 |
|
{<<>>, _} -> |
2841 |
:-( |
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), <<"subid-required">>)}; |
2842 |
|
{_, _} -> |
2843 |
:-( |
case lists:keyfind(RequestedSubId, 1, SubscribedSubs) of |
2844 |
|
{_, Opts} -> |
2845 |
:-( |
make_and_wrap_sub_xform(Opts, Node, Subscriber, RequestedSubId); |
2846 |
|
_ -> |
2847 |
:-( |
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), |
2848 |
|
<<"invalid-subid">>)} |
2849 |
|
end |
2850 |
|
end. |
2851 |
|
|
2852 |
|
make_and_wrap_sub_xform(Options, Node, Subscriber, SubId) -> |
2853 |
8 |
{ok, XForm} = pubsub_form_utils:make_sub_xform(Options), |
2854 |
8 |
OptionsEl = #xmlel{name = <<"options">>, |
2855 |
|
attrs = [{<<"jid">>, jid:to_binary(Subscriber)}, |
2856 |
|
{<<"subid">>, SubId} |
2857 |
|
| node_attr(Node)], |
2858 |
|
children = [XForm]}, |
2859 |
8 |
PubSubEl = #xmlel{name = <<"pubsub">>, |
2860 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB}], |
2861 |
|
children = [OptionsEl]}, |
2862 |
8 |
{result, PubSubEl}. |
2863 |
|
|
2864 |
|
set_options(Host, Node, JID, SubId, ConfigXForm) -> |
2865 |
4 |
Action = fun(PubSubNode) -> |
2866 |
4 |
ok = set_options_transaction(JID, SubId, ConfigXForm, PubSubNode), |
2867 |
4 |
{result, []} |
2868 |
|
end, |
2869 |
4 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
2870 |
4 |
{result, {_Node, Result}} -> {result, Result}; |
2871 |
:-( |
Error -> Error |
2872 |
|
end. |
2873 |
|
|
2874 |
|
set_options_transaction(JID, SubId, ConfigXForm, #pubsub_node{type = Type, id = Nidx}) -> |
2875 |
4 |
case lists:member(<<"subscription-options">>, plugin_features(Type)) of |
2876 |
|
true -> |
2877 |
4 |
validate_and_set_options_helper(ConfigXForm, JID, Nidx, SubId, Type); |
2878 |
|
false -> |
2879 |
:-( |
{error, |
2880 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), |
2881 |
|
unsupported, <<"subscription-options">>)} |
2882 |
|
end. |
2883 |
|
|
2884 |
|
validate_and_set_options_helper(ConfigXForm, JID, Nidx, SubId, Type) -> |
2885 |
4 |
SubOpts = pubsub_form_utils:parse_sub_xform(ConfigXForm), |
2886 |
4 |
set_options_helper(SubOpts, JID, Nidx, SubId, Type). |
2887 |
|
|
2888 |
|
set_options_helper({error, Reason}, JID, Nidx, RequestedSubId, _Type) -> |
2889 |
|
% TODO: Make smarter logging (better details formatting) |
2890 |
:-( |
?LOG_DEBUG(#{what => pubsub_invalid_subscription_options, jid => JID, |
2891 |
:-( |
nidx => Nidx, sub_id => RequestedSubId, reason => Reason}), |
2892 |
:-( |
{error, extended_error(mongoose_xmpp_errors:bad_request(), <<"invalid-options">>)}; |
2893 |
|
set_options_helper({ok, []}, _JID, _Nidx, _RequestedSubId, _Type) -> |
2894 |
:-( |
{result, []}; |
2895 |
|
set_options_helper({ok, SubOpts}, JID, Nidx, RequestedSubId, Type) -> |
2896 |
4 |
Subscriber = string_to_ljid(JID), |
2897 |
4 |
{result, Subs} = node_call(Type, get_subscriptions, [Nidx, Subscriber]), |
2898 |
4 |
SubIds = [Id || {Sub, Id, _Opts} <- Subs, Sub == subscribed], |
2899 |
4 |
case {RequestedSubId, SubIds} of |
2900 |
|
{_, []} -> |
2901 |
:-( |
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), <<"not-subscribed">>)}; |
2902 |
|
{<<>>, [TheOnlySID]} -> |
2903 |
4 |
mod_pubsub_db_backend:set_subscription_opts(Nidx, Subscriber, TheOnlySID, SubOpts); |
2904 |
|
{<<>>, _} -> |
2905 |
:-( |
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), <<"subid-required">>)}; |
2906 |
|
{_, _} -> |
2907 |
:-( |
case lists:member(RequestedSubId, SubIds) of |
2908 |
|
true -> |
2909 |
:-( |
mod_pubsub_db_backend:set_subscription_opts(Nidx, Subscriber, RequestedSubId, |
2910 |
|
SubOpts); |
2911 |
|
false -> |
2912 |
:-( |
{error, extended_error(mongoose_xmpp_errors:not_acceptable(), |
2913 |
|
<<"invalid-subid">>)} |
2914 |
|
end |
2915 |
|
end. |
2916 |
|
|
2917 |
|
%% @doc <p>Return the list of subscriptions as an XMPP response.</p> |
2918 |
|
-spec get_subscriptions(Host, Node, JID, Plugins) -> {error, Reason} | {result, Response} when |
2919 |
|
Host :: host(), |
2920 |
|
Node :: pubsubNode(), |
2921 |
|
JID :: jid:jid(), |
2922 |
|
Plugins :: map(), |
2923 |
|
Reason :: any(), |
2924 |
|
Response :: [exml:element()]. |
2925 |
|
get_subscriptions(Host, Node, JID, #{plugins := Plugins}) when is_list(Plugins) -> |
2926 |
10 |
Result = lists:foldl(fun (Type, {Status, Acc}) -> |
2927 |
11 |
Features = plugin_features(Type), |
2928 |
11 |
case lists:member(<<"retrieve-subscriptions">>, Features) of |
2929 |
|
true -> |
2930 |
11 |
Subscriber = jid:to_bare(JID), |
2931 |
11 |
{result, Subs} = node_action(Host, Type, |
2932 |
|
get_entity_subscriptions, |
2933 |
|
[Host, Subscriber]), |
2934 |
11 |
{Status, [Subs | Acc]}; |
2935 |
|
false -> |
2936 |
:-( |
{{error, |
2937 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), |
2938 |
|
unsupported, |
2939 |
|
<<"retrieve-subscriptions">>)}, |
2940 |
|
Acc} |
2941 |
|
end |
2942 |
|
end, |
2943 |
|
{ok, []}, Plugins), |
2944 |
10 |
case Result of |
2945 |
|
{ok, Subs} -> |
2946 |
10 |
Entities = lists:flatmap(fun(Sub) -> subscription_to_xmlel(Sub, Node) end, |
2947 |
|
lists:usort(lists:flatten(Subs))), |
2948 |
10 |
{result, |
2949 |
|
[#xmlel{name = <<"pubsub">>, |
2950 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB}], |
2951 |
|
children = [#xmlel{name = <<"subscriptions">>, attrs = [], |
2952 |
|
children = Entities}]}]}; |
2953 |
|
{Error, _} -> |
2954 |
:-( |
Error |
2955 |
|
end. |
2956 |
|
|
2957 |
|
%% 2-element tuples are not used by any node type probably |
2958 |
|
subscription_to_xmlel({_, none}, _Node) -> |
2959 |
:-( |
[]; |
2960 |
|
subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub}, <<>>) -> |
2961 |
:-( |
[#xmlel{name = <<"subscription">>, |
2962 |
|
attrs = |
2963 |
|
[{<<"subscription">>, subscription_to_string(Sub)} |
2964 |
|
| node_attr(SubsNode)]}]; |
2965 |
|
subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub}, SubsNode) -> |
2966 |
:-( |
[#xmlel{name = <<"subscription">>, |
2967 |
|
attrs = |
2968 |
|
[{<<"subscription">>, subscription_to_string(Sub)}]}]; |
2969 |
|
subscription_to_xmlel({#pubsub_node{nodeid = {_, _}}, _}, _) -> |
2970 |
:-( |
[]; |
2971 |
|
%% no idea how to trigger this one |
2972 |
|
subscription_to_xmlel({_, none, _}, _Node) -> |
2973 |
:-( |
[]; |
2974 |
|
%% sometimes used by node_pep |
2975 |
|
subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubId, SubJID}, <<>>) -> |
2976 |
9 |
[#xmlel{name = <<"subscription">>, |
2977 |
|
attrs = |
2978 |
|
[{<<"jid">>, jid:to_binary(SubJID)}, |
2979 |
|
{<<"subid">>, SubId}, |
2980 |
|
{<<"subscription">>, subscription_to_string(Sub)} |
2981 |
|
| node_attr(SubsNode)]}]; |
2982 |
|
subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubId, SubJID}, SubsNode) -> |
2983 |
:-( |
[#xmlel{name = <<"subscription">>, |
2984 |
|
attrs = |
2985 |
|
[{<<"jid">>, jid:to_binary(SubJID)}, |
2986 |
|
{<<"subid">>, SubId}, |
2987 |
|
{<<"subscription">>, subscription_to_string(Sub)}]}]; |
2988 |
|
subscription_to_xmlel({#pubsub_node{nodeid = {_, _}}, _, _, _}, _Node) -> |
2989 |
:-( |
[]; |
2990 |
|
%% used by node_flat (therefore by dag, hometree and push as well) |
2991 |
|
subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubJID}, <<>>) -> |
2992 |
1 |
[#xmlel{name = <<"subscription">>, |
2993 |
|
attrs = |
2994 |
|
[{<<"jid">>, jid:to_binary(SubJID)}, |
2995 |
|
{<<"subscription">>, subscription_to_string(Sub)} |
2996 |
|
| node_attr(SubsNode)]}]; |
2997 |
|
subscription_to_xmlel({#pubsub_node{nodeid = {_, SubsNode}}, Sub, SubJID}, SubsNode) -> |
2998 |
:-( |
[#xmlel{name = <<"subscription">>, |
2999 |
|
attrs = |
3000 |
|
[{<<"jid">>, jid:to_binary(SubJID)}, |
3001 |
|
{<<"subscription">>, subscription_to_string(Sub)}]}]; |
3002 |
|
subscription_to_xmlel({#pubsub_node{nodeid = {_, _}}, _, _}, _Node) -> |
3003 |
:-( |
[]. |
3004 |
|
|
3005 |
|
get_subscriptions(Host, Node, JID) -> |
3006 |
10 |
Action = fun (PubSubNode) -> get_subscriptions_transaction(JID, PubSubNode) end, |
3007 |
10 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
3008 |
|
{result, {_, Subs}} -> |
3009 |
8 |
Entities = |
3010 |
|
lists:flatmap(fun({_, pending, _, _}) -> |
3011 |
:-( |
[]; |
3012 |
|
({AJID, Sub, SubId, _}) -> |
3013 |
12 |
[#xmlel{name = <<"subscription">>, |
3014 |
|
attrs = |
3015 |
|
[{<<"jid">>, jid:to_binary(AJID)}, |
3016 |
|
{<<"subscription">>, subscription_to_string(Sub)}, |
3017 |
|
{<<"subid">>, SubId}]}] |
3018 |
|
end, Subs), |
3019 |
8 |
{result, |
3020 |
|
[#xmlel{name = <<"pubsub">>, |
3021 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], |
3022 |
|
children = [#xmlel{name = <<"subscriptions">>, |
3023 |
|
attrs = node_attr(Node), |
3024 |
|
children = Entities}]}]}; |
3025 |
|
Error -> |
3026 |
2 |
Error |
3027 |
|
end. |
3028 |
|
|
3029 |
|
get_subscriptions_transaction(JID, #pubsub_node{type = Type, id = Nidx}) -> |
3030 |
10 |
Features = plugin_features(Type), |
3031 |
10 |
case lists:member(<<"manage-subscriptions">>, Features) of |
3032 |
|
true -> |
3033 |
10 |
case node_call(Type, get_affiliation, [Nidx, JID]) of |
3034 |
|
{result, owner} -> |
3035 |
8 |
node_call(Type, get_node_subscriptions, [Nidx]); |
3036 |
|
_ -> |
3037 |
2 |
{error, mongoose_xmpp_errors:forbidden()} |
3038 |
|
end; |
3039 |
|
false -> |
3040 |
:-( |
{error, |
3041 |
|
extended_error(mongoose_xmpp_errors:feature_not_implemented(), unsupported, <<"manage-subscriptions">>)} |
3042 |
|
end. |
3043 |
|
|
3044 |
|
get_subscriptions_for_send_last(Host, PType, [JID, LJID, BJID]) -> |
3045 |
552 |
{result, Subs} = node_action(Host, PType, |
3046 |
|
get_entity_subscriptions, |
3047 |
|
[Host, JID]), |
3048 |
552 |
[{Node, Sub, SubId, SubJID} |
3049 |
552 |
|| {Node, Sub, SubId, SubJID} <- Subs, |
3050 |
2 |
Sub =:= subscribed, (SubJID == LJID) or (SubJID == BJID), |
3051 |
2 |
match_option(Node, send_last_published_item, on_sub_and_presence)]; |
3052 |
|
get_subscriptions_for_send_last(_Host, _PType, _JIDs) -> |
3053 |
:-( |
[]. |
3054 |
|
|
3055 |
|
set_subscriptions(Host, Node, From, #{action_el := ActionEl} ) -> |
3056 |
6 |
EntitiesEls = xml:remove_cdata(ActionEl#xmlel.children), |
3057 |
6 |
Owner = jid:to_lower(jid:to_bare(From)), |
3058 |
6 |
Entities = lists:foldl(fun(_, error) -> |
3059 |
:-( |
error; |
3060 |
|
(#xmlel{name = <<"subscription">>, attrs = Attrs}, Acc) -> |
3061 |
10 |
JID = jid:from_binary(xml:get_attr_s(<<"jid">>, Attrs)), |
3062 |
10 |
Sub = string_to_subscription(xml:get_attr_s(<<"subscription">>, |
3063 |
|
Attrs)), |
3064 |
10 |
SubId = xml:get_attr_s(<<"subid">>, Attrs), |
3065 |
10 |
case (JID == error) or (Sub == false) of |
3066 |
:-( |
true -> error; |
3067 |
10 |
false -> [{jid:to_lower(JID), Sub, SubId} | Acc] |
3068 |
|
end; |
3069 |
|
(_, _) -> |
3070 |
:-( |
error |
3071 |
|
end, [], EntitiesEls), |
3072 |
6 |
case Entities of |
3073 |
|
error -> |
3074 |
:-( |
{error, mongoose_xmpp_errors:bad_request()}; |
3075 |
|
_ -> |
3076 |
6 |
Action = fun (PubSubNode) -> |
3077 |
6 |
set_subscriptions_transaction(Host, Owner, Node, PubSubNode, Entities) |
3078 |
|
end, |
3079 |
6 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
3080 |
4 |
{result, {_, Result}} -> {result, Result}; |
3081 |
2 |
Other -> Other |
3082 |
|
end |
3083 |
|
end. |
3084 |
|
|
3085 |
|
set_subscriptions_transaction(Host, Owner, Node, |
3086 |
|
#pubsub_node{type = Type, id = Nidx, owners = Owners}, Entities) -> |
3087 |
6 |
case lists:member(Owner, Owners) of |
3088 |
|
true -> |
3089 |
4 |
Result = |
3090 |
|
lists:foldl(fun(Entity, Acc) -> |
3091 |
8 |
set_subscription_transaction(Host, Node, Nidx, Type, Entity, Acc) |
3092 |
|
end, |
3093 |
|
[], Entities), |
3094 |
4 |
case Result of |
3095 |
4 |
[] -> {result, []}; |
3096 |
:-( |
_ -> {error, mongoose_xmpp_errors:not_acceptable()} |
3097 |
|
end; |
3098 |
|
_ -> |
3099 |
2 |
{error, mongoose_xmpp_errors:forbidden()} |
3100 |
|
end. |
3101 |
|
|
3102 |
|
set_subscription_transaction(Host, Node, Nidx, Type, {JID, Sub, SubId}, Acc) -> |
3103 |
8 |
case node_call(Type, set_subscriptions, [Nidx, JID, Sub, SubId]) of |
3104 |
:-( |
{error, Err} -> [{error, Err} | Acc]; |
3105 |
8 |
_ -> notify_subscription_change(Host, Node, JID, Sub), Acc |
3106 |
|
end. |
3107 |
|
|
3108 |
|
notify_subscription_change(Host, Node, JID, Sub) -> |
3109 |
8 |
SubscriptionEl = #xmlel{name = <<"subscription">>, |
3110 |
|
attrs = [{<<"jid">>, jid:to_binary(JID)}, |
3111 |
|
{<<"subscription">>, subscription_to_string(Sub)} |
3112 |
|
| node_attr(Node)]}, |
3113 |
8 |
PubSubEl = #xmlel{name = <<"pubsub">>, |
3114 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB}], |
3115 |
|
children = [SubscriptionEl]}, |
3116 |
8 |
Stanza = #xmlel{name = <<"message">>, attrs = [], children = [PubSubEl]}, |
3117 |
8 |
ejabberd_router:route(service_jid(Host), jid:make(JID), Stanza). |
3118 |
|
|
3119 |
|
-spec get_presence_and_roster_permissions(Host :: mod_pubsub:host(), |
3120 |
|
From :: jid:jid() | jid:ljid(), |
3121 |
|
Owners :: [jid:ljid(), ...], |
3122 |
|
AccessModel :: mod_pubsub:accessModel(), |
3123 |
|
AllowedGroups :: [binary()]) -> |
3124 |
|
{PresenceSubscription :: boolean(), RosterGroup :: boolean()}. |
3125 |
|
get_presence_and_roster_permissions(Host, From, Owners, AccessModel, AllowedGroups) |
3126 |
|
when (AccessModel == presence) orelse (AccessModel == roster) -> |
3127 |
3 |
case Host of |
3128 |
|
{User, Server, _} -> |
3129 |
3 |
get_roster_info(User, Server, From, AllowedGroups); |
3130 |
|
_ -> |
3131 |
:-( |
[{OUser, OServer, _} | _] = Owners, |
3132 |
:-( |
get_roster_info(OUser, OServer, From, AllowedGroups) |
3133 |
|
end; |
3134 |
|
get_presence_and_roster_permissions(_Host, _From, _Owners, _AccessModel, _AllowedGroups) -> |
3135 |
125 |
{true, true}. |
3136 |
|
|
3137 |
|
get_roster_info(_, _, {<<>>, <<>>, _}, _) -> |
3138 |
:-( |
{false, false}; |
3139 |
|
get_roster_info(OwnerUser, OwnerServer, {SubscriberUser, SubscriberServer, _}, AllowedGroups) -> |
3140 |
3 |
LJID = {SubscriberUser, SubscriberServer, <<>>}, |
3141 |
3 |
{Subscription, Groups} = mongoose_hooks:roster_get_jid_info( |
3142 |
|
OwnerServer, |
3143 |
|
jid:make_bare(OwnerUser, OwnerServer), LJID), |
3144 |
3 |
PresenceSubscription = Subscription == both orelse |
3145 |
1 |
Subscription == from orelse |
3146 |
1 |
{OwnerUser, OwnerServer} == {SubscriberUser, SubscriberServer}, |
3147 |
3 |
RosterGroup = lists:any(fun (Group) -> |
3148 |
:-( |
lists:member(Group, AllowedGroups) |
3149 |
|
end, |
3150 |
|
Groups), |
3151 |
3 |
{PresenceSubscription, RosterGroup}; |
3152 |
|
get_roster_info(OwnerUser, OwnerServer, JID, AllowedGroups) -> |
3153 |
1 |
get_roster_info(OwnerUser, OwnerServer, jid:to_lower(JID), AllowedGroups). |
3154 |
|
|
3155 |
2 |
string_to_affiliation(<<"owner">>) -> owner; |
3156 |
2 |
string_to_affiliation(<<"publisher">>) -> publisher; |
3157 |
46 |
string_to_affiliation(<<"publish-only">>) -> publish_only; |
3158 |
4 |
string_to_affiliation(<<"member">>) -> member; |
3159 |
:-( |
string_to_affiliation(<<"outcast">>) -> outcast; |
3160 |
2 |
string_to_affiliation(<<"none">>) -> none; |
3161 |
:-( |
string_to_affiliation(_) -> false. |
3162 |
|
|
3163 |
8 |
string_to_subscription(<<"subscribed">>) -> subscribed; |
3164 |
:-( |
string_to_subscription(<<"pending">>) -> pending; |
3165 |
2 |
string_to_subscription(<<"none">>) -> none; |
3166 |
:-( |
string_to_subscription(_) -> false. |
3167 |
|
|
3168 |
6 |
affiliation_to_string(owner) -> <<"owner">>; |
3169 |
:-( |
affiliation_to_string(publisher) -> <<"publisher">>; |
3170 |
:-( |
affiliation_to_string(publish_only) -> <<"publish-only">>; |
3171 |
:-( |
affiliation_to_string(member) -> <<"member">>; |
3172 |
:-( |
affiliation_to_string(outcast) -> <<"outcast">>; |
3173 |
:-( |
affiliation_to_string(_) -> <<"none">>. |
3174 |
|
|
3175 |
108 |
subscription_to_string(subscribed) -> <<"subscribed">>; |
3176 |
11 |
subscription_to_string(pending) -> <<"pending">>; |
3177 |
4 |
subscription_to_string(_) -> <<"none">>. |
3178 |
|
|
3179 |
|
-spec service_jid(mod_pubsub:host()) -> jid:jid(). |
3180 |
|
service_jid({U, S, _}) -> |
3181 |
62 |
jid:make_bare(U, S); |
3182 |
|
service_jid(Host) -> |
3183 |
602 |
jid:make_bare(<<>>, Host). |
3184 |
|
|
3185 |
|
%% @doc <p>Check if a notification must be delivered or not based on |
3186 |
|
%% node and subscription options.</p> |
3187 |
|
-spec is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) -> boolean() when |
3188 |
|
LJID :: jid:ljid(), |
3189 |
|
NotifyType :: items | nodes, |
3190 |
|
Depth :: integer(), |
3191 |
|
NodeOptions :: [{atom(), term()}], |
3192 |
|
SubOptions :: [{atom(), term()}]. |
3193 |
|
is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) -> |
3194 |
50 |
sub_to_deliver(LJID, NotifyType, Depth, SubOptions) |
3195 |
48 |
andalso node_to_deliver(LJID, NodeOptions). |
3196 |
|
|
3197 |
|
sub_to_deliver(_LJID, NotifyType, Depth, SubOptions) -> |
3198 |
50 |
lists:all(fun (Option) -> |
3199 |
4 |
sub_option_can_deliver(NotifyType, Depth, Option) |
3200 |
|
end, |
3201 |
|
SubOptions). |
3202 |
|
|
3203 |
|
node_to_deliver(LJID, NodeOptions) -> |
3204 |
48 |
presence_can_deliver(LJID, get_option(NodeOptions, presence_based_delivery)). |
3205 |
|
|
3206 |
:-( |
sub_option_can_deliver(items, _, {subscription_type, nodes}) -> false; |
3207 |
:-( |
sub_option_can_deliver(nodes, _, {subscription_type, items}) -> false; |
3208 |
:-( |
sub_option_can_deliver(_, _, {subscription_depth, all}) -> true; |
3209 |
:-( |
sub_option_can_deliver(_, Depth, {subscription_depth, D}) -> Depth =< D; |
3210 |
2 |
sub_option_can_deliver(_, _, {deliver, false}) -> false; |
3211 |
:-( |
sub_option_can_deliver(_, _, {expire, When}) -> timestamp() < When; |
3212 |
2 |
sub_option_can_deliver(_, _, _) -> true. |
3213 |
|
|
3214 |
|
-spec presence_can_deliver(Entity :: jid:ljid(), PresenceBasedDelivery :: boolean()) -> boolean(). |
3215 |
|
presence_can_deliver(_, false) -> |
3216 |
45 |
true; |
3217 |
|
presence_can_deliver({User, Server, <<>>}, true) -> |
3218 |
2 |
ejabberd_sm:get_user_present_resources(jid:make_noprep(User, Server, <<>>)) =/= []; |
3219 |
|
presence_can_deliver({User, Server, Resource}, true) -> |
3220 |
1 |
JID = jid:make_noprep(User, Server, Resource), |
3221 |
1 |
case ejabberd_sm:get_session(JID) of |
3222 |
1 |
#session{priority = SPriority} when SPriority /= undefined -> true; |
3223 |
:-( |
_ -> false |
3224 |
|
end. |
3225 |
|
|
3226 |
|
-spec state_can_deliver( |
3227 |
|
Entity::jid:ljid(), |
3228 |
|
SubOptions :: mod_pubsub:subOptions() | []) |
3229 |
|
-> [jid:ljid()]. |
3230 |
45 |
state_can_deliver({U, S, R}, []) -> [{U, S, R}]; |
3231 |
|
state_can_deliver({U, S, R}, SubOptions) -> |
3232 |
2 |
case lists:keysearch(show_values, 1, SubOptions) of |
3233 |
|
%% If not in suboptions, item can be delivered, case doesn't apply |
3234 |
2 |
false -> [{U, S, R}]; |
3235 |
|
%% If in a suboptions ... |
3236 |
|
{_, {_, ShowValues}} -> |
3237 |
:-( |
Resources = case R of |
3238 |
|
%% If the subscriber JID is a bare one, get all its resources |
3239 |
:-( |
<<>> -> user_resources(U, S); |
3240 |
|
%% If the subscriber JID is a full one, use its resource |
3241 |
:-( |
R -> [R] |
3242 |
|
end, |
3243 |
:-( |
lists:foldl(fun (Resource, Acc) -> |
3244 |
:-( |
get_resource_state({U, S, Resource}, ShowValues, Acc) |
3245 |
|
end, |
3246 |
|
[], Resources) |
3247 |
|
end. |
3248 |
|
|
3249 |
|
-spec get_resource_state( |
3250 |
|
Entity :: jid:ljid(), |
3251 |
|
ShowValues :: [binary()], |
3252 |
|
JIDs :: [jid:ljid()]) |
3253 |
|
-> [jid:ljid()]. |
3254 |
|
get_resource_state({U, S, R}, ShowValues, JIDs) -> |
3255 |
:-( |
case ejabberd_sm:get_session_pid(jid:make_noprep(U, S, R)) of |
3256 |
|
none -> |
3257 |
|
%% If no PID, item can be delivered |
3258 |
:-( |
lists:append([{U, S, R}], JIDs); |
3259 |
|
Pid -> |
3260 |
:-( |
Show = case mod_presence:get_presence(Pid) of |
3261 |
:-( |
{_, _, <<"available">>, _} -> <<"online">>; |
3262 |
:-( |
{_, _, State, _} -> State |
3263 |
|
end, |
3264 |
:-( |
case lists:member(Show, ShowValues) of |
3265 |
|
%% If yes, item can be delivered |
3266 |
:-( |
true -> lists:append([{U, S, R}], JIDs); |
3267 |
|
%% If no, item can't be delivered |
3268 |
:-( |
false -> JIDs |
3269 |
|
end |
3270 |
|
end. |
3271 |
|
|
3272 |
|
-spec payload_xmlelements( |
3273 |
|
Payload :: mod_pubsub:payload()) |
3274 |
|
-> Count :: non_neg_integer(). |
3275 |
|
payload_xmlelements(Payload) -> |
3276 |
202 |
payload_xmlelements(Payload, 0). |
3277 |
|
|
3278 |
202 |
payload_xmlelements([], Count) -> Count; |
3279 |
|
payload_xmlelements([#xmlel{} | Tail], Count) -> |
3280 |
202 |
payload_xmlelements(Tail, Count + 1); |
3281 |
|
payload_xmlelements([_ | Tail], Count) -> |
3282 |
:-( |
payload_xmlelements(Tail, Count). |
3283 |
|
|
3284 |
|
items_event_stanza(Node, Items) -> |
3285 |
10 |
MoreEls = |
3286 |
|
case Items of |
3287 |
|
[LastItem] -> |
3288 |
10 |
{ModifNow, ModifUSR} = LastItem#pubsub_item.modification, |
3289 |
10 |
Sec = erlang:convert_time_unit(ModifNow, microsecond, second), |
3290 |
10 |
TString = calendar:system_time_to_rfc3339(Sec, [{offset, "Z"}]), |
3291 |
10 |
[#xmlel{name = <<"delay">>, |
3292 |
|
attrs = [{<<"xmlns">>, ?NS_DELAY}, |
3293 |
|
{<<"from">>, jid:to_binary(ModifUSR)}, |
3294 |
|
{<<"stamp">>, list_to_binary(TString)}], |
3295 |
|
children = [{xmlcdata, <<>>}]}]; |
3296 |
|
_ -> |
3297 |
:-( |
[] |
3298 |
|
end, |
3299 |
10 |
event_stanza_with_els([#xmlel{name = <<"items">>, |
3300 |
|
attrs = [{<<"type">>, <<"headline">>} | node_attr(Node)], |
3301 |
|
children = items_els(Items)}], |
3302 |
|
MoreEls). |
3303 |
|
|
3304 |
|
event_stanza(Els) -> |
3305 |
355 |
event_stanza_with_els(Els, []). |
3306 |
|
event_stanza_with_els(Els, MoreEls) -> |
3307 |
370 |
#xmlel{name = <<"message">>, attrs = [], |
3308 |
|
children = [#xmlel{name = <<"event">>, |
3309 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB_EVENT}], |
3310 |
|
children = Els} |
3311 |
|
| MoreEls]}. |
3312 |
|
|
3313 |
|
event_stanza(Event, EvAttr) -> |
3314 |
5 |
event_stanza_with_els([#xmlel{name = Event, attrs = EvAttr}], []). |
3315 |
|
|
3316 |
|
%%%%%% broadcast functions |
3317 |
|
|
3318 |
|
broadcast_publish_item(Host, Node, Nidx, Type, NodeOptions, |
3319 |
|
ItemId, From, Payload, Removed, ItemPublisher) -> |
3320 |
119 |
case get_collection_subscriptions(Host, Node) of |
3321 |
|
SubsByDepth when is_list(SubsByDepth) -> |
3322 |
119 |
Content = case get_option(NodeOptions, deliver_payloads) of |
3323 |
116 |
true -> Payload; |
3324 |
3 |
false -> [] |
3325 |
|
end, |
3326 |
119 |
ItemAttr = case ItemPublisher of |
3327 |
4 |
true -> item_attr(ItemId, From); |
3328 |
115 |
false -> item_attr(ItemId) |
3329 |
|
end, |
3330 |
119 |
Stanza = event_stanza( |
3331 |
|
[#xmlel{name = <<"items">>, attrs = node_attr(Node), |
3332 |
|
children = [#xmlel{name = <<"item">>, attrs = ItemAttr, |
3333 |
|
children = Content}]}]), |
3334 |
119 |
broadcast_step(Host, fun() -> |
3335 |
119 |
broadcast_stanza(Host, From, Node, Nidx, Type, |
3336 |
|
NodeOptions, SubsByDepth, items, Stanza, true), |
3337 |
119 |
broadcast_auto_retract_notification(Host, Node, Nidx, Type, |
3338 |
|
NodeOptions, SubsByDepth, Removed) |
3339 |
|
end), |
3340 |
119 |
{result, true}; |
3341 |
|
_ -> |
3342 |
:-( |
{result, false} |
3343 |
|
end. |
3344 |
|
|
3345 |
|
broadcast_auto_retract_notification(_Host, _Node, _Nidx, _Type, _NodeOptions, _SubsByDepth, []) -> |
3346 |
113 |
ok; |
3347 |
|
broadcast_auto_retract_notification(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, Removed) -> |
3348 |
6 |
case get_option(NodeOptions, notify_retract) of |
3349 |
|
true -> |
3350 |
2 |
RetractEls = [#xmlel{name = <<"retract">>, attrs = item_attr(RId)} || RId <- Removed], |
3351 |
2 |
RetractStanza = event_stanza([#xmlel{name = <<"items">>, attrs = node_attr(Node), |
3352 |
|
children = RetractEls}]), |
3353 |
2 |
broadcast_stanza(Host, Node, Nidx, Type, |
3354 |
|
NodeOptions, SubsByDepth, |
3355 |
|
items, RetractStanza, true); |
3356 |
|
_ -> |
3357 |
4 |
ok |
3358 |
|
end. |
3359 |
|
|
3360 |
|
broadcast_retract_items(Host, Node, Nidx, Type, NodeOptions, ItemIds) -> |
3361 |
:-( |
broadcast_retract_items(Host, Node, Nidx, Type, NodeOptions, ItemIds, false). |
3362 |
|
broadcast_retract_items(_Host, _Node, _Nidx, _Type, _NodeOptions, [], _ForceNotify) -> |
3363 |
:-( |
{result, false}; |
3364 |
|
broadcast_retract_items(Host, Node, Nidx, Type, NodeOptions, ItemIds, ForceNotify) -> |
3365 |
8 |
case (get_option(NodeOptions, notify_retract) or ForceNotify) of |
3366 |
|
true -> |
3367 |
2 |
case get_collection_subscriptions(Host, Node) of |
3368 |
|
SubsByDepth when is_list(SubsByDepth) -> |
3369 |
2 |
Stanza = event_stanza( |
3370 |
|
[#xmlel{name = <<"items">>, attrs = node_attr(Node), |
3371 |
2 |
children = [#xmlel{name = <<"retract">>, |
3372 |
|
attrs = item_attr(ItemId)} |
3373 |
2 |
|| ItemId <- ItemIds]}]), |
3374 |
2 |
broadcast_step(Host, fun() -> |
3375 |
2 |
broadcast_stanza(Host, Node, Nidx, Type, |
3376 |
|
NodeOptions, SubsByDepth, items, Stanza, true) |
3377 |
|
end), |
3378 |
2 |
{result, true}; |
3379 |
|
_ -> |
3380 |
:-( |
{result, false} |
3381 |
|
end; |
3382 |
|
_ -> |
3383 |
6 |
{result, false} |
3384 |
|
end. |
3385 |
|
|
3386 |
|
broadcast_purge_node(Host, Node, Nidx, Type, NodeOptions) -> |
3387 |
4 |
case get_option(NodeOptions, notify_retract) of |
3388 |
|
true -> |
3389 |
:-( |
case get_collection_subscriptions(Host, Node) of |
3390 |
|
SubsByDepth when is_list(SubsByDepth) -> |
3391 |
:-( |
Stanza = event_stanza( |
3392 |
|
[#xmlel{name = <<"purge">>, attrs = node_attr(Node)}]), |
3393 |
:-( |
broadcast_step(Host, fun() -> |
3394 |
:-( |
broadcast_stanza(Host, Node, Nidx, Type, |
3395 |
|
NodeOptions, SubsByDepth, nodes, Stanza, false) |
3396 |
|
end), |
3397 |
:-( |
{result, true}; |
3398 |
|
_ -> |
3399 |
:-( |
{result, false} |
3400 |
|
end; |
3401 |
|
_ -> |
3402 |
4 |
{result, false} |
3403 |
|
end. |
3404 |
|
|
3405 |
|
broadcast_removed_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) -> |
3406 |
148 |
case get_option(NodeOptions, notify_delete) of |
3407 |
|
true -> |
3408 |
1 |
case SubsByDepth of |
3409 |
|
[] -> |
3410 |
:-( |
{result, false}; |
3411 |
|
_ -> |
3412 |
1 |
Stanza = event_stanza( |
3413 |
|
[#xmlel{name = <<"delete">>, attrs = node_attr(Node)}]), |
3414 |
1 |
broadcast_step(Host, fun() -> |
3415 |
1 |
broadcast_stanza(Host, Node, Nidx, Type, |
3416 |
|
NodeOptions, SubsByDepth, nodes, Stanza, false) |
3417 |
|
end), |
3418 |
1 |
{result, true} |
3419 |
|
end; |
3420 |
|
_ -> |
3421 |
147 |
{result, false} |
3422 |
|
end. |
3423 |
|
|
3424 |
|
broadcast_created_node(_, _, _, _, _, []) -> |
3425 |
:-( |
{result, false}; |
3426 |
|
broadcast_created_node(Host, Node, Nidx, Type, NodeOptions, SubsByDepth) -> |
3427 |
229 |
Stanza = event_stanza([#xmlel{name = <<"create">>, attrs = node_attr(Node)}]), |
3428 |
229 |
broadcast_step(Host, fun() -> |
3429 |
229 |
broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, nodes, Stanza, true) |
3430 |
|
end), |
3431 |
229 |
{result, true}. |
3432 |
|
|
3433 |
|
broadcast_config_notification(Host, Node, Nidx, Type, NodeOptions, Lang) -> |
3434 |
6 |
case get_option(NodeOptions, notify_config) of |
3435 |
|
true -> |
3436 |
2 |
case get_collection_subscriptions(Host, Node) of |
3437 |
|
SubsByDepth when is_list(SubsByDepth) -> |
3438 |
2 |
Content = payload_by_option(Type, NodeOptions, Lang), |
3439 |
2 |
Stanza = event_stanza([#xmlel{name = <<"configuration">>, |
3440 |
|
attrs = node_attr(Node), children = Content}]), |
3441 |
2 |
broadcast_step(Host, fun() -> |
3442 |
2 |
broadcast_stanza(Host, Node, Nidx, Type, |
3443 |
|
NodeOptions, SubsByDepth, nodes, Stanza, false) |
3444 |
|
end), |
3445 |
2 |
{result, true}; |
3446 |
|
_ -> |
3447 |
:-( |
{result, false} |
3448 |
|
end; |
3449 |
|
_ -> |
3450 |
4 |
{result, false} |
3451 |
|
end. |
3452 |
|
|
3453 |
|
payload_by_option(_Type, NodeOptions, Lang) -> |
3454 |
2 |
case get_option(NodeOptions, deliver_payloads) of |
3455 |
|
true -> |
3456 |
2 |
[configure_form(<<"result">>, NodeOptions, Lang, [])]; |
3457 |
|
false -> |
3458 |
:-( |
[] |
3459 |
|
end. |
3460 |
|
|
3461 |
|
get_collection_subscriptions(Host, Node) -> |
3462 |
123 |
Action = fun() -> |
3463 |
123 |
{result, get_node_subs_by_depth(Host, Node, service_jid(Host))} |
3464 |
|
end, |
3465 |
123 |
ErrorDebug = #{ |
3466 |
|
pubsub_host => Host, |
3467 |
|
action => get_collection_subscriptions, |
3468 |
|
node_name => Node |
3469 |
|
}, |
3470 |
123 |
case mod_pubsub_db_backend:dirty(Action, ErrorDebug) of |
3471 |
123 |
{result, CollSubs} -> CollSubs; |
3472 |
:-( |
_ -> [] |
3473 |
|
end. |
3474 |
|
|
3475 |
|
get_node_subs_by_depth(Host, Node, From) -> |
3476 |
503 |
ParentTree = tree_call(Host, get_parentnodes_tree, [Host, Node, From]), |
3477 |
503 |
[{Depth, [{N, get_node_subs(N)} || N <- Nodes]} || {Depth, Nodes} <- ParentTree]. |
3478 |
|
|
3479 |
|
get_node_subs(#pubsub_node{type = Type, id = Nidx}) -> |
3480 |
560 |
case node_call(Type, get_node_subscriptions, [Nidx]) of |
3481 |
|
{result, Subs} -> |
3482 |
|
% TODO: Replace with proper DB/plugin call with sub type filter |
3483 |
560 |
[{JID, SubID, Opts} || {JID, SubType, SubID, Opts} <- Subs, SubType == subscribed]; |
3484 |
|
Other -> |
3485 |
:-( |
Other |
3486 |
|
end. |
3487 |
|
|
3488 |
|
%% Execute broadcasting step in a new process |
3489 |
|
%% F contains one or more broadcast_stanza calls, executed sequentially |
3490 |
|
broadcast_step(Host, F) -> |
3491 |
353 |
case gen_mod:get_module_opt(serverhost(Host), ?MODULE, sync_broadcast) of |
3492 |
|
true -> |
3493 |
:-( |
F(); |
3494 |
|
false -> |
3495 |
353 |
proc_lib:spawn(F) |
3496 |
|
end. |
3497 |
|
|
3498 |
|
broadcast_stanza(Host, Node, _Nidx, _Type, NodeOptions, |
3499 |
|
SubsByDepth, NotifyType, BaseStanza, SHIM) -> |
3500 |
355 |
NotificationType = get_option(NodeOptions, notification_type, headline), |
3501 |
|
%% Option below is not standard, but useful |
3502 |
355 |
BroadcastAll = get_option(NodeOptions, broadcast_all_resources), |
3503 |
355 |
From = service_jid(Host), |
3504 |
355 |
Stanza = add_message_type(BaseStanza, NotificationType), |
3505 |
|
%% Handles explicit subscriptions |
3506 |
355 |
SubIDsByJID = subscribed_nodes_by_jid(NotifyType, SubsByDepth), |
3507 |
355 |
lists:foreach(fun ({LJID, SubNodeName, SubIDs}) -> |
3508 |
46 |
LJIDs = case BroadcastAll of |
3509 |
|
true -> |
3510 |
:-( |
{U, S, _} = LJID, |
3511 |
:-( |
[{U, S, R} || R <- user_resources(U, S)]; |
3512 |
|
false -> |
3513 |
46 |
[LJID] |
3514 |
|
end, |
3515 |
46 |
StanzaToSend = maybe_add_shim_headers(Stanza, SHIM, SubIDs, |
3516 |
|
Node, SubNodeName), |
3517 |
|
|
3518 |
46 |
lists:foreach(fun(To) -> |
3519 |
46 |
ejabberd_router:route(From, jid:make(To), |
3520 |
|
StanzaToSend) |
3521 |
|
end, LJIDs) |
3522 |
|
end, SubIDsByJID). |
3523 |
|
|
3524 |
|
broadcast_stanza({LUser, LServer, LResource}, Publisher, Node, Nidx, Type, NodeOptions, |
3525 |
|
SubsByDepth, NotifyType, BaseStanza, SHIM) -> |
3526 |
18 |
broadcast_stanza({LUser, LServer, LResource}, Node, Nidx, Type, NodeOptions, |
3527 |
|
SubsByDepth, NotifyType, BaseStanza, SHIM), |
3528 |
|
%% Handles implicit presence subscriptions |
3529 |
18 |
SenderResource = user_resource(LUser, LServer, LResource), |
3530 |
18 |
case ejabberd_sm:get_session_pid(jid:make(LUser, LServer, SenderResource)) of |
3531 |
|
C2SPid when is_pid(C2SPid) -> |
3532 |
18 |
NotificationType = get_option(NodeOptions, notification_type, headline), |
3533 |
18 |
Stanza = add_message_type(BaseStanza, NotificationType), |
3534 |
|
%% set the from address on the notification to the bare JID of the account owner |
3535 |
|
%% Also, add "replyto" if entity has presence subscription to the account owner |
3536 |
|
%% See XEP-0163 1.1 section 4.3.1 |
3537 |
18 |
ReplyTo = extended_headers([jid:to_binary(Publisher)]), |
3538 |
18 |
Feature = <<((Node))/binary, "+notify">>, |
3539 |
18 |
Recipients = mongoose_c2s:call(C2SPid, ?MODULE, {get_pep_recipients, Feature}), |
3540 |
18 |
Packet = add_extended_headers(Stanza, ReplyTo), |
3541 |
18 |
From = jid:make_bare(LUser, LServer), |
3542 |
18 |
lists:foreach(fun(USR) -> ejabberd_router:route(From, jid:make(USR), Packet) end, |
3543 |
|
lists:usort(Recipients)); |
3544 |
|
_ -> |
3545 |
:-( |
?LOG_DEBUG(#{what => pubsub_no_session, |
3546 |
|
text => <<"User has no session; cannot deliver stanza to contacts">>, |
3547 |
:-( |
user => LUser, server => LServer, exml_packet => BaseStanza}) |
3548 |
|
end; |
3549 |
|
broadcast_stanza(Host, _Publisher, Node, Nidx, Type, NodeOptions, |
3550 |
|
SubsByDepth, NotifyType, BaseStanza, SHIM) -> |
3551 |
101 |
broadcast_stanza(Host, Node, Nidx, Type, NodeOptions, SubsByDepth, |
3552 |
|
NotifyType, BaseStanza, SHIM). |
3553 |
|
|
3554 |
|
subscribed_nodes_by_jid(NotifyType, SubsByDepth) -> |
3555 |
355 |
DepthsToDeliver = |
3556 |
|
fun({Depth, SubsByNode}, Acc1) -> |
3557 |
389 |
lists:foldl(fun({Node, Subs}, Acc2) -> |
3558 |
391 |
nodes_to_deliver(NotifyType, Depth, Node, Subs, Acc2) |
3559 |
|
end, Acc1, SubsByNode) |
3560 |
|
end, |
3561 |
355 |
{_, JIDSubs} = lists:foldl(DepthsToDeliver, {[], []}, SubsByDepth), |
3562 |
355 |
JIDSubs. |
3563 |
|
|
3564 |
|
nodes_to_deliver(NotifyType, Depth, Node, Subs, Acc) -> |
3565 |
391 |
NodeName = case Node#pubsub_node.nodeid of |
3566 |
391 |
{_, N} -> N; |
3567 |
:-( |
Other -> Other |
3568 |
|
end, |
3569 |
391 |
NodeOptions = Node#pubsub_node.options, |
3570 |
391 |
lists:foldl(fun({LJID, SubID, SubOptions}, InnerAcc) -> |
3571 |
50 |
case is_to_deliver(LJID, NotifyType, Depth, NodeOptions, SubOptions) of |
3572 |
|
true -> |
3573 |
47 |
JIDsToDeliver = state_can_deliver(LJID, SubOptions), |
3574 |
47 |
process_jids_to_deliver(NodeName, SubID, JIDsToDeliver, InnerAcc); |
3575 |
|
false -> |
3576 |
3 |
InnerAcc |
3577 |
|
end |
3578 |
|
end, Acc, Subs). |
3579 |
|
|
3580 |
|
process_jids_to_deliver(NodeName, SubID, JIDsToDeliver, {JIDs, Recipients}) -> |
3581 |
47 |
lists:foldl( |
3582 |
|
fun(JIDToDeliver, {JIDsAcc, RecipientsAcc}) -> |
3583 |
47 |
process_jid_to_deliver(JIDs, SubID, NodeName, |
3584 |
|
JIDToDeliver, {JIDsAcc, RecipientsAcc}) |
3585 |
|
end, {JIDs, Recipients}, JIDsToDeliver). |
3586 |
|
|
3587 |
|
process_jid_to_deliver(JIDs, SubID, NodeName, JIDToDeliver, {JIDsAcc, RecipientsAcc}) -> |
3588 |
47 |
case lists:member(JIDToDeliver, JIDs) of |
3589 |
|
%% check if the JIDs co-accumulator contains the Subscription Jid, |
3590 |
|
false -> |
3591 |
|
%% - if not, |
3592 |
|
%% - add the Jid to JIDs list co-accumulator ; |
3593 |
|
%% - create a tuple of the Jid, Nidx, and SubID (as list), |
3594 |
|
%% and add the tuple to the Recipients list co-accumulator |
3595 |
46 |
{[JIDToDeliver | JIDsAcc], |
3596 |
|
[{JIDToDeliver, NodeName, [SubID]} |
3597 |
|
| RecipientsAcc]}; |
3598 |
|
true -> |
3599 |
|
%% - if the JIDs co-accumulator contains the Jid |
3600 |
|
%% get the tuple containing the Jid from the Recipient list co-accumulator |
3601 |
1 |
{_, {JIDToDeliver, NodeName1, SubIDs}} = |
3602 |
|
lists:keysearch(JIDToDeliver, 1, RecipientsAcc), |
3603 |
|
%% delete the tuple from the Recipients list |
3604 |
|
% v1 : Recipients1 = lists:keydelete(LJID, 1, Recipients), |
3605 |
|
% v2 : Recipients1 = lists:keyreplace(LJID, 1, Recipients, |
3606 |
|
% {LJID, Nidx1, [SubID | SubIDs]}), |
3607 |
|
%% add the SubID to the SubIDs list in the tuple, |
3608 |
|
%% and add the tuple back to the Recipients list co-accumulator |
3609 |
|
% v1.1 : {JIDs, lists:append(Recipients1, |
3610 |
|
% [{LJID, Nidx1, lists:append(SubIDs, [SubID])}])} |
3611 |
|
% v1.2 : {JIDs, [{LJID, Nidx1, [SubID | SubIDs]} | Recipients1]} |
3612 |
|
% v2: {JIDs, Recipients1} |
3613 |
1 |
{JIDsAcc, |
3614 |
|
lists:keyreplace(JIDToDeliver, 1, |
3615 |
|
RecipientsAcc, |
3616 |
|
{JIDToDeliver, NodeName1, |
3617 |
|
[SubID | SubIDs]})} |
3618 |
|
end. |
3619 |
|
|
3620 |
|
user_resources(User, Server) -> |
3621 |
419 |
JID = jid:make_bare(User, Server), |
3622 |
419 |
ejabberd_sm:get_user_resources(JID). |
3623 |
|
|
3624 |
|
user_resource(User, Server, <<>>) -> |
3625 |
18 |
case user_resources(User, Server) of |
3626 |
18 |
[R | _] -> R; |
3627 |
:-( |
_ -> <<>> |
3628 |
|
end; |
3629 |
|
user_resource(_, _, Resource) -> |
3630 |
:-( |
Resource. |
3631 |
|
|
3632 |
|
%%%%%%% Configuration handling |
3633 |
|
|
3634 |
|
get_configure(Host, Node, From, #{server_host := ServerHost, lang := Lang}) -> |
3635 |
7 |
Action = fun(PubSubNode) -> |
3636 |
7 |
get_configure_transaction(ServerHost, Node, From, Lang, PubSubNode) |
3637 |
|
end, |
3638 |
7 |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
3639 |
7 |
{result, {_, Result}} -> {result, Result}; |
3640 |
:-( |
Other -> Other |
3641 |
|
end. |
3642 |
|
|
3643 |
|
get_configure_transaction(ServerHost, Node, From, Lang, |
3644 |
|
#pubsub_node{options = Options, type = Type, id = Nidx}) -> |
3645 |
7 |
case node_call(Type, get_affiliation, [Nidx, From]) of |
3646 |
|
{result, owner} -> |
3647 |
7 |
Groups = mongoose_hooks:roster_groups(ServerHost), |
3648 |
7 |
XEl = configure_form(<<"form">>, Options, Lang, Groups), |
3649 |
7 |
ConfigureEl = #xmlel{name = <<"configure">>, |
3650 |
|
attrs = node_attr(Node), |
3651 |
|
children = [XEl]}, |
3652 |
7 |
{result, |
3653 |
|
[#xmlel{name = <<"pubsub">>, |
3654 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], |
3655 |
|
children = [ConfigureEl]}]}; |
3656 |
|
_ -> |
3657 |
:-( |
{error, mongoose_xmpp_errors:forbidden()} |
3658 |
|
end. |
3659 |
|
|
3660 |
|
get_default(Host, Node, _From, #{lang := Lang}) -> |
3661 |
2 |
Type = select_type(Host, Node), |
3662 |
2 |
Options = node_options(Host, Type), |
3663 |
2 |
XEl = configure_form(<<"form">>, Options, Lang, []), |
3664 |
2 |
DefaultEl = #xmlel{name = <<"default">>, attrs = [], children = [XEl]}, |
3665 |
2 |
{result, |
3666 |
|
[#xmlel{name = <<"pubsub">>, |
3667 |
|
attrs = [{<<"xmlns">>, ?NS_PUBSUB_OWNER}], |
3668 |
|
children = [DefaultEl]}]}. |
3669 |
|
|
3670 |
|
match_option(Node, Var, Val) when is_record(Node, pubsub_node) -> |
3671 |
2 |
match_option(Node#pubsub_node.options, Var, Val); |
3672 |
|
match_option(Options, Var, Val) when is_list(Options) -> |
3673 |
13 |
get_option(Options, Var) == Val; |
3674 |
|
match_option(_, _, _) -> |
3675 |
:-( |
false. |
3676 |
|
|
3677 |
95 |
get_option([], _) -> false; |
3678 |
2789 |
get_option(Options, Var) -> get_option(Options, Var, false). |
3679 |
|
|
3680 |
|
get_option(Options, Var, Def) -> |
3681 |
3333 |
case lists:keysearch(Var, 1, Options) of |
3682 |
2831 |
{value, {_Val, Ret}} -> Ret; |
3683 |
502 |
_ -> Def |
3684 |
|
end. |
3685 |
|
|
3686 |
|
-spec check_publish_options(binary(), undefined | exml:element(), mod_pubsub:nodeOptions()) -> |
3687 |
|
boolean(). |
3688 |
|
check_publish_options(Type, PublishOptions, Options) -> |
3689 |
92 |
ParsedPublishOptions = parse_publish_options(PublishOptions), |
3690 |
92 |
ConvertedOptions = convert_options(Options), |
3691 |
92 |
case node_call(Type, check_publish_options, [ParsedPublishOptions, ConvertedOptions]) of |
3692 |
|
{error, _} -> |
3693 |
:-( |
true; |
3694 |
|
{result, Result} -> |
3695 |
92 |
Result |
3696 |
|
end. |
3697 |
|
|
3698 |
|
-spec parse_publish_options(undefined | exml:element()) -> invalid_form | #{binary() => [binary()]}. |
3699 |
|
parse_publish_options(undefined) -> |
3700 |
18 |
#{}; |
3701 |
|
parse_publish_options(PublishOptions) -> |
3702 |
74 |
case mongoose_data_forms:find_and_parse_form(PublishOptions) of |
3703 |
|
#{type := <<"submit">>, kvs := KVs, ns := ?NS_PUBSUB_PUB_OPTIONS} -> |
3704 |
73 |
KVs; |
3705 |
|
_ -> |
3706 |
1 |
invalid_form |
3707 |
|
end. |
3708 |
|
|
3709 |
|
-spec convert_options(mod_pubsub:nodeOptions()) -> #{binary() => [binary()]}. |
3710 |
|
convert_options(Options) -> |
3711 |
92 |
ConvertedOptions = lists:map(fun({Key, Value}) -> |
3712 |
1472 |
{atom_to_binary(Key), convert_option_value(Value)} |
3713 |
|
end, Options), |
3714 |
92 |
maps:from_list(ConvertedOptions). |
3715 |
|
|
3716 |
|
-spec convert_option_value(binary() | [binary()] | atom() | non_neg_integer()) -> [binary()]. |
3717 |
|
convert_option_value(true) -> |
3718 |
391 |
[<<"1">>]; |
3719 |
|
convert_option_value(false) -> |
3720 |
437 |
[<<"0">>]; |
3721 |
|
convert_option_value(Element) when is_atom(Element) -> |
3722 |
368 |
[atom_to_binary(Element)]; |
3723 |
|
convert_option_value(Element) when is_integer(Element) -> |
3724 |
184 |
[integer_to_binary(Element)]; |
3725 |
|
convert_option_value(List) when is_list(List) -> |
3726 |
92 |
List. |
3727 |
|
|
3728 |
|
node_options(Host, Type) -> |
3729 |
234 |
ConfiguredOpts = lists:keysort(1, config(serverhost(Host), default_node_config)), |
3730 |
234 |
DefaultOpts = lists:keysort(1, node_plugin_options(Type)), |
3731 |
234 |
lists:keymerge(1, ConfiguredOpts, DefaultOpts). |
3732 |
|
|
3733 |
|
node_plugin_options(Type) -> |
3734 |
234 |
Module = plugin(Type), |
3735 |
234 |
case catch gen_pubsub_node:options(Module) of |
3736 |
|
{'EXIT', {undef, _}} -> |
3737 |
:-( |
DefaultModule = plugin(?STDNODE), |
3738 |
:-( |
gen_pubsub_node:options(DefaultModule); |
3739 |
|
Result -> |
3740 |
234 |
Result |
3741 |
|
end. |
3742 |
|
|
3743 |
|
%% @doc <p>Return the maximum number of items for a given node.</p> |
3744 |
|
%% <p>Unlimited means that there is no limit in the number of items that can |
3745 |
|
%% be stored.</p> |
3746 |
|
%% @todo In practice, the current data structure means that we cannot manage |
3747 |
|
%% millions of items on a given node. This should be addressed in a new |
3748 |
|
%% version. |
3749 |
|
-spec max_items(Host, Options) -> MaxItems when |
3750 |
|
Host :: host(), |
3751 |
|
Options :: [Option], |
3752 |
|
Option :: {Key :: atom(), Value :: term()}, |
3753 |
|
MaxItems :: integer() | unlimited. |
3754 |
|
max_items(Host, Options) -> |
3755 |
202 |
case get_option(Options, persist_items) of |
3756 |
|
true -> |
3757 |
130 |
case get_option(Options, max_items) of |
3758 |
:-( |
I when is_integer(I), I < 0 -> 0; |
3759 |
130 |
I when is_integer(I) -> I; |
3760 |
:-( |
_ -> ?MAXITEMS |
3761 |
|
end; |
3762 |
|
false -> |
3763 |
|
%% Don't publish if it means sending the item without a way to retrieve it |
3764 |
72 |
case get_option(Options, send_last_published_item) == never |
3765 |
69 |
orelse is_last_item_cache_enabled(Host) of |
3766 |
3 |
true -> 0; |
3767 |
69 |
false -> 1 |
3768 |
|
end |
3769 |
|
end. |
3770 |
|
|
3771 |
|
-define(BOOL_CONFIG_FIELD(Label, Var), |
3772 |
|
?BOOLXFIELD(Label, |
3773 |
|
<<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, |
3774 |
|
(get_option(Options, Var)))). |
3775 |
|
|
3776 |
|
-define(STRING_CONFIG_FIELD(Label, Var), |
3777 |
|
?STRINGXFIELD(Label, |
3778 |
|
<<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, |
3779 |
|
(get_option(Options, Var, <<>>)))). |
3780 |
|
|
3781 |
|
-define(INTEGER_CONFIG_FIELD(Label, Var), |
3782 |
|
?STRINGXFIELD(Label, |
3783 |
|
<<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, |
3784 |
|
(integer_to_binary(get_option(Options, Var))))). |
3785 |
|
|
3786 |
|
-define(JLIST_CONFIG_FIELD(Label, Var, Opts), |
3787 |
|
?LISTXFIELD(Label, |
3788 |
|
<<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, |
3789 |
|
(jid:to_binary(get_option(Options, Var))), |
3790 |
|
[jid:to_binary(O) || O <- Opts])). |
3791 |
|
|
3792 |
|
-define(ALIST_CONFIG_FIELD(Label, Var, Opts), |
3793 |
|
?LISTXFIELD(Label, |
3794 |
|
<<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, |
3795 |
|
(atom_to_binary(get_option(Options, Var), latin1)), |
3796 |
|
[atom_to_binary(O, latin1) || O <- Opts])). |
3797 |
|
|
3798 |
|
-define(LISTM_CONFIG_FIELD(Label, Var, Opts), |
3799 |
|
?LISTMXFIELD(Label, |
3800 |
|
<<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, |
3801 |
|
(get_option(Options, Var)), Opts)). |
3802 |
|
|
3803 |
|
-define(NLIST_CONFIG_FIELD(Label, Var), |
3804 |
|
?STRINGMXFIELD(Label, |
3805 |
|
<<"pubsub#", (atom_to_binary(Var, latin1))/binary>>, |
3806 |
|
get_option(Options, Var, []))). |
3807 |
|
|
3808 |
|
configure_form(Type, Options, Lang, Groups) -> |
3809 |
11 |
Fields = get_configure_xfields(Options, Lang, Groups), |
3810 |
11 |
mongoose_data_forms:form(#{type => Type, ns => ?NS_PUBSUB_NODE_CONFIG, fields => Fields}). |
3811 |
|
|
3812 |
|
get_configure_xfields(Options, Lang, Groups) -> |
3813 |
11 |
[?BOOL_CONFIG_FIELD(<<"Deliver payloads with event notifications">>, |
3814 |
11 |
deliver_payloads), |
3815 |
|
?BOOL_CONFIG_FIELD(<<"Deliver event notifications">>, |
3816 |
11 |
deliver_notifications), |
3817 |
|
?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node configuration changes">>, |
3818 |
11 |
notify_config), |
3819 |
|
?BOOL_CONFIG_FIELD(<<"Notify subscribers when the node is deleted">>, |
3820 |
11 |
notify_delete), |
3821 |
|
?BOOL_CONFIG_FIELD(<<"Notify subscribers when items are removed from the node">>, |
3822 |
11 |
notify_retract), |
3823 |
|
?BOOL_CONFIG_FIELD(<<"Persist items to storage">>, |
3824 |
11 |
persist_items), |
3825 |
|
?STRING_CONFIG_FIELD(<<"A friendly name for the node">>, |
3826 |
|
title), |
3827 |
|
?INTEGER_CONFIG_FIELD(<<"Max # of items to persist">>, |
3828 |
|
max_items), |
3829 |
|
?BOOL_CONFIG_FIELD(<<"Whether to allow subscriptions">>, |
3830 |
11 |
subscribe), |
3831 |
|
?ALIST_CONFIG_FIELD(<<"Specify the access model">>, |
3832 |
55 |
access_model, [open, authorize, presence, roster, whitelist]), |
3833 |
|
?LISTM_CONFIG_FIELD(<<"Roster groups allowed to subscribe">>, |
3834 |
|
roster_groups_allowed, Groups), |
3835 |
|
?ALIST_CONFIG_FIELD(<<"Specify the publisher model">>, |
3836 |
33 |
publish_model, [publishers, subscribers, open]), |
3837 |
|
?BOOL_CONFIG_FIELD(<<"Purge all items when the relevant publisher goes offline">>, |
3838 |
11 |
purge_offline), |
3839 |
|
?ALIST_CONFIG_FIELD(<<"Specify the event message type">>, |
3840 |
22 |
notification_type, [headline, normal]), |
3841 |
|
?INTEGER_CONFIG_FIELD(<<"Max payload size in bytes">>, |
3842 |
|
max_payload_size), |
3843 |
|
?ALIST_CONFIG_FIELD(<<"When to send the last published item">>, |
3844 |
33 |
send_last_published_item, [never, on_sub, on_sub_and_presence]), |
3845 |
|
?BOOL_CONFIG_FIELD(<<"Only deliver notifications to available users">>, |
3846 |
11 |
presence_based_delivery), |
3847 |
|
?STRING_CONFIG_FIELD(<<"Specify the type of payload data to be provided at this node">>, |
3848 |
|
type), |
3849 |
|
?NLIST_CONFIG_FIELD(<<"The collections with which a node is affiliated">>, |
3850 |
|
collection)]. |
3851 |
|
|
3852 |
|
%%<p>There are several reasons why the node configuration request might fail:</p> |
3853 |
|
%%<ul> |
3854 |
|
%%<li>The service does not support node configuration.</li> |
3855 |
|
%%<li>The requesting entity does not have sufficient privileges to configure the node.</li> |
3856 |
|
%%<li>The request did not specify a node.</li> |
3857 |
|
%%<li>The node has no configuration options.</li> |
3858 |
|
%%<li>The specified node does not exist.</li> |
3859 |
|
%%</ul> |
3860 |
|
set_configure(Host, Node, From, #{action_el := ActionEl, lang := Lang}) -> |
3861 |
14 |
case mongoose_data_forms:find_and_parse_form(ActionEl) of |
3862 |
|
#{type := <<"cancel">>} -> |
3863 |
:-( |
{result, []}; |
3864 |
|
#{type := <<"submit">>, kvs := KVs} -> |
3865 |
8 |
set_configure_submit(Host, Node, From, KVs, Lang); |
3866 |
|
{error, Msg} -> |
3867 |
4 |
{error, mongoose_xmpp_errors:bad_request(Lang, Msg)}; |
3868 |
|
_ -> |
3869 |
2 |
{error, mongoose_xmpp_errors:bad_request(Lang, <<"Invalid form type">>)} |
3870 |
|
end. |
3871 |
|
|
3872 |
|
set_configure_submit(Host, Node, User, KVs, Lang) -> |
3873 |
8 |
Action = fun(NodeRec) -> |
3874 |
8 |
set_configure_transaction(Host, User, KVs, NodeRec) |
3875 |
|
end, |
3876 |
8 |
case transaction(Host, Node, Action, ?FUNCTION_NAME) of |
3877 |
|
{result, {_OldNode, TNode}} -> |
3878 |
6 |
Nidx = TNode#pubsub_node.id, |
3879 |
6 |
Type = TNode#pubsub_node.type, |
3880 |
6 |
Options = TNode#pubsub_node.options, |
3881 |
6 |
broadcast_config_notification(Host, Node, Nidx, Type, Options, Lang), |
3882 |
6 |
{result, []}; |
3883 |
|
Other -> |
3884 |
2 |
Other |
3885 |
|
end. |
3886 |
|
|
3887 |
|
set_configure_transaction(Host, User, KVs, #pubsub_node{ type = Type, id = Nidx } = NodeRec) -> |
3888 |
8 |
case node_call(Type, get_affiliation, [Nidx, User]) of |
3889 |
|
{result, owner} -> |
3890 |
8 |
set_configure_valid_transaction(Host, NodeRec, KVs); |
3891 |
|
_ -> |
3892 |
:-( |
{error, mongoose_xmpp_errors:forbidden()} |
3893 |
|
end. |
3894 |
|
|
3895 |
|
set_configure_valid_transaction(Host, #pubsub_node{ type = Type, options = Options } = NodeRec, |
3896 |
|
KVs) -> |
3897 |
8 |
OldOpts = case Options of |
3898 |
:-( |
[] -> node_options(Host, Type); |
3899 |
8 |
_ -> Options |
3900 |
|
end, |
3901 |
8 |
case set_xoption(Host, maps:to_list(KVs), OldOpts) of |
3902 |
|
NewOpts when is_list(NewOpts) -> |
3903 |
6 |
NewNode = NodeRec#pubsub_node{options = NewOpts}, |
3904 |
6 |
case tree_call(Host, set_node, [NewNode]) of |
3905 |
6 |
{ok, _} -> {result, NewNode}; |
3906 |
:-( |
Err -> Err |
3907 |
|
end; |
3908 |
|
Error -> |
3909 |
2 |
Error |
3910 |
|
end. |
3911 |
|
|
3912 |
|
add_opt(Key, Value, Opts) -> |
3913 |
205 |
[{Key, Value} | lists:keydelete(Key, 1, Opts)]. |
3914 |
|
|
3915 |
|
-define(SET_BOOL_XOPT(Opt, Val), |
3916 |
|
BoolVal = case Val of |
3917 |
|
<<"0">> -> false; |
3918 |
|
<<"1">> -> true; |
3919 |
|
<<"false">> -> false; |
3920 |
|
<<"true">> -> true; |
3921 |
|
_ -> error |
3922 |
|
end, |
3923 |
|
case BoolVal of |
3924 |
|
error -> {error, mongoose_xmpp_errors:not_acceptable()}; |
3925 |
|
_ -> set_xoption(Host, Opts, add_opt(Opt, BoolVal, NewOpts)) |
3926 |
|
end). |
3927 |
|
|
3928 |
|
-define(SET_STRING_XOPT(Opt, Val), |
3929 |
|
set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))). |
3930 |
|
|
3931 |
|
-define(SET_INTEGER_XOPT(Opt, Val, Min, Max), |
3932 |
|
case catch binary_to_integer(Val) of |
3933 |
|
IVal when is_integer(IVal), IVal >= Min -> |
3934 |
|
if (Max =:= undefined) orelse (IVal =< Max) -> |
3935 |
|
set_xoption(Host, Opts, add_opt(Opt, IVal, NewOpts)); |
3936 |
|
true -> |
3937 |
|
{error, mongoose_xmpp_errors:not_acceptable()} |
3938 |
|
end; |
3939 |
|
_ -> |
3940 |
|
{error, mongoose_xmpp_errors:not_acceptable()} |
3941 |
|
end). |
3942 |
|
|
3943 |
|
-define(SET_ALIST_XOPT(Opt, Val, Vals), |
3944 |
|
case lists:member(Val, [atom_to_binary(V, latin1) || V <- Vals]) of |
3945 |
|
true -> |
3946 |
|
set_xoption(Host, Opts, add_opt(Opt, binary_to_atom(Val, utf8), NewOpts)); |
3947 |
|
false -> |
3948 |
|
{error, mongoose_xmpp_errors:not_acceptable()} |
3949 |
|
end). |
3950 |
|
|
3951 |
|
-define(SET_LIST_XOPT(Opt, Val), |
3952 |
|
set_xoption(Host, Opts, add_opt(Opt, Val, NewOpts))). |
3953 |
|
|
3954 |
122 |
set_xoption(_Host, [], NewOpts) -> NewOpts; |
3955 |
|
set_xoption(Host, [{<<"pubsub#roster_groups_allowed">>, Value} | Opts], NewOpts) -> |
3956 |
2 |
?SET_LIST_XOPT(roster_groups_allowed, Value); |
3957 |
|
set_xoption(Host, [{<<"pubsub#deliver_payloads">>, [Val]} | Opts], NewOpts) -> |
3958 |
5 |
?SET_BOOL_XOPT(deliver_payloads, Val); |
3959 |
|
set_xoption(Host, [{<<"pubsub#deliver_notifications">>, [Val]} | Opts], NewOpts) -> |
3960 |
5 |
?SET_BOOL_XOPT(deliver_notifications, Val); |
3961 |
|
set_xoption(Host, [{<<"pubsub#notify_config">>, [Val]} | Opts], NewOpts) -> |
3962 |
8 |
?SET_BOOL_XOPT(notify_config, Val); |
3963 |
|
set_xoption(Host, [{<<"pubsub#notify_delete">>, [Val]} | Opts], NewOpts) -> |
3964 |
2 |
?SET_BOOL_XOPT(notify_delete, Val); |
3965 |
|
set_xoption(Host, [{<<"pubsub#notify_retract">>, [Val]} | Opts], NewOpts) -> |
3966 |
6 |
?SET_BOOL_XOPT(notify_retract, Val); |
3967 |
|
set_xoption(Host, [{<<"pubsub#persist_items">>, [Val]} | Opts], NewOpts) -> |
3968 |
5 |
?SET_BOOL_XOPT(persist_items, Val); |
3969 |
|
set_xoption(Host, [{<<"pubsub#max_items">>, [Val]} | Opts], NewOpts) -> |
3970 |
4 |
MaxItems = get_max_items_node(Host), |
3971 |
4 |
?SET_INTEGER_XOPT(max_items, Val, 0, MaxItems); |
3972 |
|
set_xoption(Host, [{<<"pubsub#subscribe">>, [Val]} | Opts], NewOpts) -> |
3973 |
2 |
?SET_BOOL_XOPT(subscribe, Val); |
3974 |
|
set_xoption(Host, [{<<"pubsub#access_model">>, [Val]} | Opts], NewOpts) -> |
3975 |
54 |
?SET_ALIST_XOPT(access_model, Val, [open, authorize, presence, roster, whitelist]); |
3976 |
|
set_xoption(Host, [{<<"pubsub#publish_model">>, [Val]} | Opts], NewOpts) -> |
3977 |
47 |
?SET_ALIST_XOPT(publish_model, Val, [publishers, subscribers, open]); |
3978 |
|
set_xoption(Host, [{<<"pubsub#notification_type">>, [Val]} | Opts], NewOpts) -> |
3979 |
2 |
?SET_ALIST_XOPT(notification_type, Val, [headline, normal]); |
3980 |
|
set_xoption(Host, [{<<"pubsub#node_type">>, [Val]} | Opts], NewOpts) -> |
3981 |
19 |
?SET_ALIST_XOPT(node_type, Val, [leaf, collection]); |
3982 |
|
set_xoption(Host, [{<<"pubsub#max_payload_size">>, [Val]} | Opts], NewOpts) -> |
3983 |
2 |
?SET_INTEGER_XOPT(max_payload_size, Val, 0, (?MAX_PAYLOAD_SIZE)); |
3984 |
|
set_xoption(Host, [{<<"pubsub#send_last_published_item">>, [Val]} | Opts], NewOpts) -> |
3985 |
8 |
?SET_ALIST_XOPT(send_last_published_item, Val, [never, on_sub, on_sub_and_presence]); |
3986 |
|
set_xoption(Host, [{<<"pubsub#presence_based_delivery">>, [Val]} | Opts], NewOpts) -> |
3987 |
6 |
?SET_BOOL_XOPT(presence_based_delivery, Val); |
3988 |
|
set_xoption(Host, [{<<"pubsub#purge_offline">>, [Val]} | Opts], NewOpts) -> |
3989 |
4 |
?SET_BOOL_XOPT(purge_offline, Val); |
3990 |
|
set_xoption(Host, [{<<"pubsub#title">>, Value} | Opts], NewOpts) -> |
3991 |
4 |
?SET_STRING_XOPT(title, Value); |
3992 |
|
set_xoption(Host, [{<<"pubsub#type">>, Value} | Opts], NewOpts) -> |
3993 |
2 |
?SET_STRING_XOPT(type, Value); |
3994 |
|
set_xoption(Host, [{<<"pubsub#body_xslt">>, Value} | Opts], NewOpts) -> |
3995 |
:-( |
?SET_STRING_XOPT(body_xslt, Value); |
3996 |
|
set_xoption(Host, [{<<"pubsub#collection">>, Value} | Opts], NewOpts) -> |
3997 |
20 |
?SET_LIST_XOPT(collection, Value); |
3998 |
|
set_xoption(Host, [{<<"pubsub#node">>, [Value]} | Opts], NewOpts) -> |
3999 |
2 |
?SET_LIST_XOPT(node, Value); |
4000 |
|
set_xoption(Host, [_ | Opts], NewOpts) -> |
4001 |
:-( |
set_xoption(Host, Opts, NewOpts). |
4002 |
|
|
4003 |
|
get_max_items_node({_, ServerHost, _}) -> |
4004 |
:-( |
get_max_items_node(ServerHost); |
4005 |
|
get_max_items_node(Host) -> |
4006 |
37 |
config(serverhost(Host), max_items_node). |
4007 |
|
|
4008 |
|
get_max_subscriptions_node({_, ServerHost, _}) -> |
4009 |
3 |
get_max_subscriptions_node(ServerHost); |
4010 |
|
get_max_subscriptions_node(Host) -> |
4011 |
92 |
config(serverhost(Host), max_subscriptions_node). |
4012 |
|
|
4013 |
|
%%%% last item cache handling |
4014 |
|
maybe_start_cache_module(ServerHost, #{last_item_cache := Cache} = Opts) -> |
4015 |
39 |
case Cache of |
4016 |
36 |
false -> ok; |
4017 |
3 |
_Backend -> mod_pubsub_cache_backend:start(ServerHost, Opts) |
4018 |
|
end. |
4019 |
|
|
4020 |
|
is_last_item_cache_enabled(Host) -> |
4021 |
217 |
cache_backend(Host) =/= false. |
4022 |
|
|
4023 |
|
cache_backend(Host) -> |
4024 |
217 |
gen_mod:get_module_opt(serverhost(Host), mod_pubsub, last_item_cache). |
4025 |
|
|
4026 |
|
set_cached_item({_, ServerHost, _}, Nidx, ItemId, Publisher, Payload) -> |
4027 |
18 |
set_cached_item(ServerHost, Nidx, ItemId, Publisher, Payload); |
4028 |
|
set_cached_item(Host, Nidx, ItemId, Publisher, Payload) -> |
4029 |
122 |
is_last_item_cache_enabled(Host) andalso |
4030 |
14 |
mod_pubsub_cache_backend:upsert_last_item(serverhost(Host), Nidx, ItemId, Publisher, Payload). |
4031 |
|
|
4032 |
|
unset_cached_item({_, ServerHost, _}, Nidx) -> |
4033 |
:-( |
unset_cached_item(ServerHost, Nidx); |
4034 |
|
unset_cached_item(Host, Nidx) -> |
4035 |
4 |
is_last_item_cache_enabled(Host) andalso |
4036 |
2 |
mod_pubsub_cache_backend:delete_last_item(serverhost(Host), Nidx). |
4037 |
|
|
4038 |
|
-spec get_cached_item(ServerHost :: mod_pubsub:host(), |
4039 |
|
Nidx :: mod_pubsub:nodeIdx()) -> false | mod_pubsub:pubsubItem(). |
4040 |
|
get_cached_item({_, ServerHost, _}, Nidx) -> |
4041 |
8 |
get_cached_item(ServerHost, Nidx); |
4042 |
|
get_cached_item(Host, Nidx) -> |
4043 |
22 |
is_last_item_cache_enabled(Host) andalso |
4044 |
8 |
case mod_pubsub_cache_backend:get_last_item(serverhost(Host), Nidx) of |
4045 |
|
{ok, #pubsub_last_item{itemid = ItemId, creation = Creation, payload = Payload}} -> |
4046 |
5 |
#pubsub_item{itemid = {ItemId, Nidx}, |
4047 |
|
payload = Payload, creation = Creation, |
4048 |
|
modification = Creation}; |
4049 |
|
_ -> |
4050 |
3 |
false |
4051 |
|
end. |
4052 |
|
|
4053 |
|
%%%% plugin handling |
4054 |
|
|
4055 |
|
-spec host(HostType :: mongooseim:host_type(), ServerHost :: mongooseim:domain_name()) -> host(). |
4056 |
|
host(HostType, ServerHost) -> |
4057 |
809 |
SubdomainPattern = config(HostType, host), |
4058 |
809 |
mongoose_subdomain_utils:get_fqdn(SubdomainPattern, ServerHost). |
4059 |
|
|
4060 |
|
-spec serverhost(host()) -> host(). |
4061 |
|
serverhost({_U, Server, _R})-> |
4062 |
203 |
Server; |
4063 |
|
serverhost(Host) -> |
4064 |
3585 |
case config(Host, host) of |
4065 |
|
undefined -> |
4066 |
3503 |
[_, ServerHost] = binary:split(Host, <<".">>), |
4067 |
3503 |
ServerHost; |
4068 |
|
_ -> |
4069 |
82 |
Host |
4070 |
|
end. |
4071 |
|
|
4072 |
|
-spec host_to_host_type(mod_pubsub:host()) -> mongooseim:host_type(). |
4073 |
|
host_to_host_type(Host) -> |
4074 |
2611 |
SH = serverhost(Host), |
4075 |
2611 |
{ok, HT} = mongoose_domain_api:get_host_type(SH), |
4076 |
2611 |
HT. |
4077 |
|
|
4078 |
|
-spec tree(HostType :: mongooseim:host_type() | host()) -> module() | nodetree_virtual. |
4079 |
|
tree(HostType) -> |
4080 |
2611 |
try gen_mod:get_module_opt(HostType, ?MODULE, nodetree) |
4081 |
|
catch error:{badkey, _} -> |
4082 |
|
%todo remove when pubsub supports dynamic domains |
4083 |
1582 |
HT = host_to_host_type(HostType), |
4084 |
1582 |
gen_mod:get_module_opt(HT, ?MODULE, nodetree) |
4085 |
|
end. |
4086 |
|
|
4087 |
|
tree_mod(<<"virtual">>) -> |
4088 |
:-( |
nodetree_virtual; % special case, virtual does not use any backend |
4089 |
|
tree_mod(Name) -> |
4090 |
:-( |
binary_to_atom(<<"nodetree_", Name/binary>>, utf8). |
4091 |
|
|
4092 |
|
-spec plugin(Name :: plugin_name()) -> module(). |
4093 |
|
plugin(Name) -> |
4094 |
4817 |
binary_to_atom(<<"node_", Name/binary>>, utf8). |
4095 |
|
|
4096 |
|
-spec plugins(ServerHost :: mongooseim:domain_name()) -> [plugin_name()]. |
4097 |
|
plugins(ServerHost) -> |
4098 |
1203 |
Proc = gen_mod:get_module_proc(ServerHost, ?PROCNAME), |
4099 |
|
%% TODO This call could be replaced with persistent terms |
4100 |
1203 |
gen_server:call(Proc, plugins). |
4101 |
|
|
4102 |
|
config(ServerHost, Key) -> |
4103 |
5005 |
config(ServerHost, Key, undefined). |
4104 |
|
config(ServerHost, Key, Default) -> |
4105 |
5005 |
gen_mod:get_module_opt(ServerHost, ?MODULE, Key, Default). |
4106 |
|
|
4107 |
|
select_type(Host, Node) -> |
4108 |
2 |
select_type(serverhost(Host), Host, Node). |
4109 |
|
|
4110 |
|
select_type(ServerHost, Host, Node) -> |
4111 |
18 |
select_type(ServerHost, Host, Node, hd(plugins(ServerHost))). |
4112 |
|
|
4113 |
|
select_type(ServerHost, Host, Node, Type) -> |
4114 |
252 |
SelectedType = case Host of |
4115 |
|
{_User, _Server, _Resource} -> |
4116 |
30 |
case config(ServerHost, pep_mapping) of |
4117 |
:-( |
undefined -> ?PEPNODE; |
4118 |
30 |
Mapping -> maps:get(Node, Mapping, ?PEPNODE) |
4119 |
|
end; |
4120 |
|
_ -> |
4121 |
222 |
Type |
4122 |
|
end, |
4123 |
252 |
ConfiguredTypes = plugins(ServerHost), |
4124 |
252 |
case lists:member(SelectedType, ConfiguredTypes) of |
4125 |
252 |
true -> SelectedType; |
4126 |
:-( |
false -> hd(ConfiguredTypes) |
4127 |
|
end. |
4128 |
|
|
4129 |
4633 |
feature(Feature) -> <<(?NS_PUBSUB)/binary, "#", Feature/binary>>. |
4130 |
|
|
4131 |
|
features() -> |
4132 |
117 |
[% see plugin "access-authorize", % OPTIONAL |
4133 |
|
<<"access-open">>, % OPTIONAL this relates to access_model option in node_hometree |
4134 |
|
<<"access-presence">>, % OPTIONAL this relates to access_model option in node_pep |
4135 |
|
<<"access-whitelist">>, % OPTIONAL |
4136 |
|
<<"collections">>, % RECOMMENDED |
4137 |
|
<<"config-node">>, % RECOMMENDED |
4138 |
|
<<"create-and-configure">>, % RECOMMENDED |
4139 |
|
<<"item-ids">>, % RECOMMENDED |
4140 |
|
<<"last-published">>, % RECOMMENDED |
4141 |
|
<<"member-affiliation">>, % RECOMMENDED |
4142 |
|
<<"presence-notifications">>, % OPTIONAL |
4143 |
|
<<"presence-subscribe">>, % RECOMMENDED |
4144 |
|
<<"publisher-affiliation">>, % RECOMMENDED |
4145 |
|
<<"publish-only-affiliation">>, % OPTIONAL |
4146 |
|
<<"retrieve-default">>, |
4147 |
|
<<"rsm">>, % RECOMMENDED |
4148 |
|
<<"shim">>]. % RECOMMENDED |
4149 |
|
% see plugin "retrieve-items", % RECOMMENDED |
4150 |
|
% see plugin "retrieve-subscriptions", % RECOMMENDED |
4151 |
|
% see plugin "subscribe", % REQUIRED |
4152 |
|
% see plugin "subscription-options", % OPTIONAL |
4153 |
|
% see plugin "subscription-notifications" % OPTIONAL |
4154 |
|
|
4155 |
|
plugin_features(Type) -> |
4156 |
1223 |
Module = plugin(Type), |
4157 |
1223 |
case catch gen_pubsub_node:features(Module) of |
4158 |
:-( |
{'EXIT', {undef, _}} -> []; |
4159 |
1223 |
Result -> Result |
4160 |
|
end. |
4161 |
|
|
4162 |
|
features(Host, <<>>) -> |
4163 |
117 |
lists:usort(lists:foldl(fun (Plugin, Acc) -> |
4164 |
276 |
Acc ++ plugin_features(Plugin) |
4165 |
|
end, |
4166 |
|
features(), plugins(Host))); |
4167 |
|
features(Host, Node) when is_binary(Node) -> |
4168 |
:-( |
Action = fun (#pubsub_node{type = Type}) -> |
4169 |
:-( |
{result, plugin_features(Type)} |
4170 |
|
end, |
4171 |
:-( |
case dirty(Host, Node, Action, ?FUNCTION_NAME) of |
4172 |
:-( |
{result, Features} -> lists:usort(features() ++ Features); |
4173 |
:-( |
_ -> features() |
4174 |
|
end. |
4175 |
|
|
4176 |
|
%% @doc <p>node tree plugin call.</p> |
4177 |
|
tree_call(HostType, Function, Args) -> |
4178 |
1582 |
?LOG_DEBUG(#{what => pubsub_tree_call, action_function => Function, |
4179 |
1582 |
args => Args, host_type => HostType}), |
4180 |
1582 |
apply(tree(HostType), Function, Args). |
4181 |
|
|
4182 |
|
tree_action(HostType, Function, Args) -> |
4183 |
36 |
?LOG_DEBUG(#{what => pubsub_tree_action, |
4184 |
36 |
action_function => Function, args => Args}), |
4185 |
36 |
Fun = fun () -> tree_call(HostType, Function, Args) end, |
4186 |
36 |
ErrorDebug = #{ |
4187 |
|
action => tree_action, |
4188 |
|
host_type => HostType, |
4189 |
|
function => Function, |
4190 |
|
args => Args |
4191 |
|
}, |
4192 |
36 |
catch mod_pubsub_db_backend:dirty(Fun, ErrorDebug). |
4193 |
|
|
4194 |
|
%% @doc <p>node plugin call.</p> |
4195 |
|
node_call(Type, Function, Args) -> |
4196 |
3154 |
?LOG_DEBUG(#{what => pubsub_node_call, node_type => Type, |
4197 |
3154 |
action_function => Function, args => Args}), |
4198 |
3154 |
PluginModule = plugin(Type), |
4199 |
3154 |
plugin_call(PluginModule, Function, Args). |
4200 |
|
|
4201 |
|
-spec plugin_call(module(), atom(), [term()]) -> {result, any()} | {error, any()}. |
4202 |
|
plugin_call(PluginModule, Function, Args) -> |
4203 |
3244 |
CallModule = maybe_default_node(PluginModule, Function, Args), |
4204 |
3244 |
case apply(CallModule, Function, Args) of |
4205 |
|
{result, Result} -> |
4206 |
2733 |
{result, Result}; |
4207 |
|
{error, Error} -> |
4208 |
22 |
{error, Error}; |
4209 |
|
{'EXIT', Reason} -> |
4210 |
:-( |
{error, Reason}; |
4211 |
|
Result -> |
4212 |
489 |
{result, Result} %% any other return value is forced as result |
4213 |
|
end. |
4214 |
|
|
4215 |
|
maybe_default_node(PluginModule, Function, Args) -> |
4216 |
5474 |
case erlang:function_exported(PluginModule, Function, length(Args)) of |
4217 |
|
true -> |
4218 |
3244 |
PluginModule; |
4219 |
|
_ -> |
4220 |
2230 |
case gen_pubsub_node:based_on(PluginModule) of |
4221 |
|
none -> |
4222 |
:-( |
?LOG_ERROR(#{what => pubsub_undefined_function, |
4223 |
:-( |
node_plugin => PluginModule, action_function => Function}), |
4224 |
:-( |
exit(udefined_node_plugin_function); |
4225 |
|
BaseModule -> |
4226 |
2230 |
maybe_default_node(BaseModule, Function, Args) |
4227 |
|
end |
4228 |
|
end. |
4229 |
|
|
4230 |
|
node_action(Host, Type, Function, Args) -> |
4231 |
1003 |
?LOG_DEBUG(#{what => pubsub_node_action, sub_host => Host, |
4232 |
1003 |
node_type => Type, action_function => Function, args => Args}), |
4233 |
1003 |
ErrorDebug = #{ |
4234 |
|
action => {node_action, Function}, |
4235 |
|
pubsub_host => Host, |
4236 |
|
node_type => Type, |
4237 |
|
args => Args |
4238 |
|
}, |
4239 |
1003 |
mod_pubsub_db_backend:dirty(fun() -> |
4240 |
1003 |
node_call(Type, Function, Args) |
4241 |
|
end, ErrorDebug). |
4242 |
|
|
4243 |
|
dirty(Host, Node, Action, ActionName) -> |
4244 |
487 |
ErrorDebug = #{ |
4245 |
|
pubsub_host => Host, |
4246 |
|
node_name => Node, |
4247 |
|
action => ActionName }, |
4248 |
487 |
mod_pubsub_db_backend:dirty(db_call_fun(Host, Node, Action), ErrorDebug). |
4249 |
|
|
4250 |
|
transaction(Host, Node, Action, ActionName) -> |
4251 |
159 |
ErrorDebug = #{ |
4252 |
|
pubsub_host => Host, |
4253 |
|
node_name => Node, |
4254 |
|
action => ActionName |
4255 |
|
}, |
4256 |
159 |
mod_pubsub_db_backend:transaction(db_call_fun(Host, Node, Action), ErrorDebug). |
4257 |
|
|
4258 |
|
db_call_fun(Host, Node, Action) -> |
4259 |
646 |
fun () -> |
4260 |
646 |
case tree_call(Host, get_node, [Host, Node]) of |
4261 |
|
#pubsub_node{} = N -> |
4262 |
625 |
case Action(N) of |
4263 |
579 |
{result, Result} -> {result, {N, Result}}; |
4264 |
:-( |
{atomic, {result, Result}} -> {result, {N, Result}}; |
4265 |
46 |
Other -> Other |
4266 |
|
end; |
4267 |
21 |
Error -> Error |
4268 |
|
end |
4269 |
|
end. |
4270 |
|
|
4271 |
|
%%%% helpers |
4272 |
|
|
4273 |
|
%% Add pubsub-specific error element |
4274 |
|
extended_error(Error, Ext) -> |
4275 |
1228 |
extended_error(Error, Ext, [{<<"xmlns">>, ?NS_PUBSUB_ERRORS}]). |
4276 |
|
|
4277 |
|
extended_error(Error, unsupported, Feature) -> |
4278 |
|
%% Give a uniq identifier |
4279 |
404 |
extended_error(Error, <<"unsupported">>, |
4280 |
|
[{<<"xmlns">>, ?NS_PUBSUB_ERRORS}, |
4281 |
|
{<<"feature">>, Feature}]); |
4282 |
|
extended_error(#xmlel{name = Error, attrs = Attrs, children = SubEls}, Ext, ExtAttrs) -> |
4283 |
1632 |
#xmlel{name = Error, attrs = Attrs, |
4284 |
|
children = lists:reverse([#xmlel{name = Ext, attrs = ExtAttrs} | SubEls])}. |
4285 |
|
|
4286 |
|
string_to_ljid(JID) -> |
4287 |
115 |
case jid:from_binary(JID) of |
4288 |
|
error -> |
4289 |
:-( |
{<<>>, <<>>, <<>>}; |
4290 |
|
J -> |
4291 |
115 |
case jid:to_lower(J) of |
4292 |
:-( |
error -> {<<>>, <<>>, <<>>}; |
4293 |
115 |
J1 -> J1 |
4294 |
|
end |
4295 |
|
end. |
4296 |
|
|
4297 |
|
-spec uniqid() -> mod_pubsub:itemId(). |
4298 |
|
uniqid() -> |
4299 |
61 |
uuid:uuid_to_string(uuid:get_v4(), binary_standard). |
4300 |
|
|
4301 |
1017 |
node_attr(Node) -> [{<<"node">>, Node}]. |
4302 |
|
|
4303 |
:-( |
item_attr([]) -> []; |
4304 |
377 |
item_attr(ItemId) -> [{<<"id">>, ItemId}]. |
4305 |
|
|
4306 |
40 |
item_attr(ItemId, undefined) -> item_attr(ItemId); |
4307 |
:-( |
item_attr([], Publisher) -> [{<<"publisher">>, |
4308 |
|
jid:to_binary(jid:to_lower(Publisher))}]; |
4309 |
6 |
item_attr(ItemId, Publisher) -> [{<<"id">>, ItemId}, |
4310 |
|
{<<"publisher">>, |
4311 |
|
jid:to_binary(jid:to_lower(Publisher))}]. |
4312 |
|
|
4313 |
|
items_els(Items) -> |
4314 |
43 |
[#xmlel{name = <<"item">>, attrs = item_attr(ItemId, Publisher), children = Payload} |
4315 |
43 |
|| #pubsub_item{itemid = {ItemId, _}, publisher = Publisher, payload = Payload } <- Items]. |
4316 |
|
|
4317 |
|
-spec add_message_type(Message :: exml:element(), Type :: atom()) -> exml:element(). |
4318 |
1 |
add_message_type(Message, normal) -> Message; |
4319 |
|
add_message_type(#xmlel{name = <<"message">>, attrs = Attrs, children = Els}, Type) -> |
4320 |
382 |
#xmlel{name = <<"message">>, |
4321 |
|
attrs = [{<<"type">>, atom_to_binary(Type, utf8)} | Attrs], |
4322 |
|
children = Els}; |
4323 |
|
add_message_type(XmlEl, _Type) -> |
4324 |
:-( |
XmlEl. |
4325 |
|
|
4326 |
|
maybe_add_shim_headers(Stanza, false, _SubIDs, _OriginNode, _SubNode) -> |
4327 |
2 |
Stanza; |
4328 |
|
maybe_add_shim_headers(Stanza, true, SubIDs, OriginNode, SubNode) -> |
4329 |
44 |
Headers1 = case SubIDs of |
4330 |
|
[_OnlyOneSubID] -> |
4331 |
43 |
[]; |
4332 |
|
_ -> |
4333 |
1 |
subid_shim(SubIDs) |
4334 |
|
end, |
4335 |
44 |
Headers2 = case SubNode of |
4336 |
|
OriginNode -> |
4337 |
30 |
Headers1; |
4338 |
|
_ -> |
4339 |
14 |
[collection_shim(SubNode) | Headers1] |
4340 |
|
end, |
4341 |
44 |
add_headers(Stanza, <<"headers">>, ?NS_SHIM, Headers2). |
4342 |
|
|
4343 |
|
add_extended_headers(Stanza, HeaderEls) -> |
4344 |
18 |
add_headers(Stanza, <<"addresses">>, ?NS_ADDRESS, HeaderEls). |
4345 |
|
|
4346 |
|
add_headers(#xmlel{name = Name, attrs = Attrs, children = Els}, HeaderName, HeaderNS, HeaderEls) -> |
4347 |
62 |
HeaderEl = #xmlel{name = HeaderName, |
4348 |
|
attrs = [{<<"xmlns">>, HeaderNS}], |
4349 |
|
children = HeaderEls}, |
4350 |
62 |
#xmlel{name = Name, attrs = Attrs, |
4351 |
|
children = lists:append(Els, [HeaderEl])}. |
4352 |
|
|
4353 |
|
subid_shim(SubIds) -> |
4354 |
1 |
[#xmlel{ name = <<"header">>, |
4355 |
|
attrs = [{<<"name">>, <<"SubId">>}], |
4356 |
|
children = [#xmlcdata{ content = SubId }]} |
4357 |
1 |
|| SubId <- SubIds]. |
4358 |
|
|
4359 |
|
collection_shim(CollectionNode) -> |
4360 |
14 |
#xmlel{ name = <<"header">>, |
4361 |
|
attrs = [{<<"name">>, <<"Collection">>}], |
4362 |
|
children = [#xmlcdata{ content = CollectionNode }] }. |
4363 |
|
|
4364 |
|
%% The argument is a list of Jids because this function could be used |
4365 |
|
%% with the 'pubsub#replyto' (type=jid-multi) node configuration. |
4366 |
|
extended_headers(Jids) -> |
4367 |
18 |
[#xmlel{name = <<"address">>, |
4368 |
|
attrs = [{<<"type">>, <<"replyto">>}, {<<"jid">>, Jid}]} |
4369 |
18 |
|| Jid <- Jids]. |
4370 |
|
|
4371 |
|
-spec on_user_offline(Acc, Params, Extra) -> {ok, Acc} when |
4372 |
|
Acc :: mongoose_acc:t(), |
4373 |
|
Params :: #{jid := jid:jid()}, |
4374 |
|
Extra :: gen_hook:extra(). |
4375 |
|
on_user_offline(Acc, |
4376 |
|
#{jid := #jid{luser = User, lserver = Server, lresource = Resource}}, |
4377 |
|
#{host_type := HostType}) -> |
4378 |
373 |
case user_resources(User, Server) of |
4379 |
366 |
[] -> purge_offline(HostType, {User, Server, Resource}); |
4380 |
7 |
_ -> true |
4381 |
|
end, |
4382 |
373 |
{ok, Acc}. |
4383 |
|
|
4384 |
|
purge_offline(HT, {_, LServer, _} = LJID) -> |
4385 |
366 |
Host = host(HT, LServer), |
4386 |
366 |
Plugins = plugins(LServer), |
4387 |
366 |
Affs = lists:foldl( |
4388 |
|
fun (PluginType, Acc) -> |
4389 |
536 |
check_plugin_features_and_acc_affs(Host, PluginType, LJID, Acc) |
4390 |
|
end, [], Plugins), |
4391 |
366 |
lists:foreach( |
4392 |
|
fun ({Node, Affiliation}) -> |
4393 |
103 |
Options = Node#pubsub_node.options, |
4394 |
103 |
IsPublisherOrOwner = lists:member(Affiliation, [owner, publisher, publish_only]), |
4395 |
103 |
OpenNode = get_option(Options, publish_model) == open, |
4396 |
103 |
ShouldPurge = get_option(Options, purge_offline) |
4397 |
2 |
andalso get_option(Options, persist_items), |
4398 |
103 |
case (IsPublisherOrOwner or OpenNode) and ShouldPurge of |
4399 |
2 |
true -> purge_offline(Host, LJID, Node); |
4400 |
101 |
false -> ok |
4401 |
|
end |
4402 |
|
end, lists:usort(lists:flatten(Affs))). |
4403 |
|
|
4404 |
|
check_plugin_features_and_acc_affs(Host, PluginType, LJID, AffsAcc) -> |
4405 |
536 |
Features = plugin_features(PluginType), |
4406 |
536 |
case lists:member(<<"retract-items">>, Features) |
4407 |
421 |
andalso lists:member(<<"persistent-items">>, Features) |
4408 |
421 |
andalso lists:member(<<"retrieve-affiliations">>, Features) of |
4409 |
|
true -> |
4410 |
421 |
{result, Affs} = node_action(Host, PluginType, get_entity_affiliations, [Host, LJID]), |
4411 |
421 |
[Affs | AffsAcc]; |
4412 |
|
false -> |
4413 |
115 |
?LOG_DEBUG(#{what => pubsub_plugin_features_check_error, |
4414 |
|
text => <<"Cannot purge items on offline">>, |
4415 |
115 |
plugin => PluginType, user => jid:to_binary(LJID)}), |
4416 |
115 |
AffsAcc |
4417 |
|
end. |
4418 |
|
|
4419 |
|
purge_offline(Host, {User, Server, _} = _LJID, #pubsub_node{ id = Nidx, type = Type } = Node) -> |
4420 |
2 |
case node_action(Host, Type, get_items, [Nidx, service_jid(Host), #{}]) of |
4421 |
|
{result, {[], _}} -> |
4422 |
:-( |
ok; |
4423 |
|
{result, {Items, _}} -> |
4424 |
2 |
lists:foreach(fun(#pubsub_item{itemid = {ItemId, _}, modification = {_, {U, S, _}}}) |
4425 |
|
when (U == User) and (S == Server) -> |
4426 |
2 |
purge_item_of_offline_user(Host, Node, ItemId, U, S); |
4427 |
|
(_) -> |
4428 |
2 |
true |
4429 |
|
end, Items); |
4430 |
|
Error -> |
4431 |
:-( |
Error |
4432 |
|
end. |
4433 |
|
|
4434 |
|
purge_item_of_offline_user(Host, #pubsub_node{ id = Nidx, nodeid = {_, NodeId}, |
4435 |
|
options = Options, type = Type }, ItemId, U, S) -> |
4436 |
2 |
PublishModel = get_option(Options, publish_model), |
4437 |
2 |
ForceNotify = get_option(Options, notify_retract), |
4438 |
2 |
case node_action(Host, Type, delete_item, [Nidx, {U, S, <<>>}, PublishModel, ItemId]) of |
4439 |
|
{result, {_, broadcast}} -> |
4440 |
2 |
broadcast_retract_items(Host, NodeId, Nidx, Type, Options, [ItemId], ForceNotify), |
4441 |
2 |
case get_cached_item(Host, Nidx) of |
4442 |
:-( |
#pubsub_item{itemid = {ItemId, Nidx}} -> unset_cached_item(Host, Nidx); |
4443 |
2 |
_ -> ok |
4444 |
|
end; |
4445 |
|
{result, _} -> |
4446 |
:-( |
ok; |
4447 |
|
Error -> |
4448 |
:-( |
Error |
4449 |
|
end. |
4450 |
|
|
4451 |
|
timestamp() -> |
4452 |
:-( |
os:system_time(microsecond). |
4453 |
|
|
4454 |
|
make_error_reply(#iq{ sub_el = SubEl } = IQ, #xmlel{} = ErrorEl) -> |
4455 |
5 |
IQ#iq{type = error, sub_el = [ErrorEl, SubEl]}; |
4456 |
|
make_error_reply(#iq{ sub_el = SubEl } = IQ, Error) -> |
4457 |
:-( |
?LOG_ERROR(#{what => pubsub_crash, reason => Error}), |
4458 |
:-( |
IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:internal_server_error(), SubEl]}; |
4459 |
|
make_error_reply(Packet, #xmlel{} = ErrorEl) -> |
4460 |
59 |
jlib:make_error_reply(Packet, ErrorEl); |
4461 |
|
make_error_reply(Packet, Error) -> |
4462 |
:-( |
?LOG_ERROR(#{what => pubsub_crash, reason => Error}), |
4463 |
:-( |
jlib:make_error_reply(Packet, mongoose_xmpp_errors:internal_server_error()). |
4464 |
|
|
4465 |
|
config_metrics(Host) -> |
4466 |
24 |
mongoose_module_metrics:opts_for_module(Host, ?MODULE, [backend]). |