Prepare required data folder for integration tests
[prosody.git] / util / pubsub.lua
blobe5e0cb7c120ee0a4223ac5ab9b692cd6fc502a63
1 local events = require "util.events";
2 local cache = require "util.cache";
4 local service_mt = {};
6 local default_config = {
7 itemstore = function (config, _) return cache.new(config["max_items"]) end;
8 broadcaster = function () end;
9 itemcheck = function () return true; end;
10 get_affiliation = function () end;
11 normalize_jid = function (jid) return jid; end;
12 capabilities = {
13 outcast = {
14 create = false;
15 publish = false;
16 retract = false;
17 get_nodes = false;
19 subscribe = false;
20 unsubscribe = false;
21 get_subscription = true;
22 get_subscriptions = true;
23 get_items = false;
25 subscribe_other = false;
26 unsubscribe_other = false;
27 get_subscription_other = false;
28 get_subscriptions_other = false;
30 be_subscribed = false;
31 be_unsubscribed = true;
33 set_affiliation = false;
35 none = {
36 create = false;
37 publish = false;
38 retract = false;
39 get_nodes = true;
41 subscribe = true;
42 unsubscribe = true;
43 get_subscription = true;
44 get_subscriptions = true;
45 get_items = false;
47 subscribe_other = false;
48 unsubscribe_other = false;
49 get_subscription_other = false;
50 get_subscriptions_other = false;
52 be_subscribed = true;
53 be_unsubscribed = true;
55 set_affiliation = false;
57 member = {
58 create = false;
59 publish = false;
60 retract = false;
61 get_nodes = true;
63 subscribe = true;
64 unsubscribe = true;
65 get_subscription = true;
66 get_subscriptions = true;
67 get_items = true;
69 subscribe_other = false;
70 unsubscribe_other = false;
71 get_subscription_other = false;
72 get_subscriptions_other = false;
74 be_subscribed = true;
75 be_unsubscribed = true;
77 set_affiliation = false;
79 publisher = {
80 create = false;
81 publish = true;
82 retract = true;
83 get_nodes = true;
84 get_configuration = true;
86 subscribe = true;
87 unsubscribe = true;
88 get_subscription = true;
89 get_subscriptions = true;
90 get_items = true;
92 subscribe_other = false;
93 unsubscribe_other = false;
94 get_subscription_other = false;
95 get_subscriptions_other = false;
97 be_subscribed = true;
98 be_unsubscribed = true;
100 set_affiliation = false;
102 owner = {
103 create = true;
104 publish = true;
105 retract = true;
106 delete = true;
107 get_nodes = true;
108 configure = true;
109 get_configuration = true;
111 subscribe = true;
112 unsubscribe = true;
113 get_subscription = true;
114 get_subscriptions = true;
115 get_items = true;
118 subscribe_other = true;
119 unsubscribe_other = true;
120 get_subscription_other = true;
121 get_subscriptions_other = true;
123 be_subscribed = true;
124 be_unsubscribed = true;
126 set_affiliation = true;
130 local default_config_mt = { __index = default_config };
132 local default_node_config = {
133 ["persist_items"] = false;
134 ["max_items"] = 20;
135 ["access_model"] = "open";
136 ["publish_model"] = "publishers";
138 local default_node_config_mt = { __index = default_node_config };
140 -- Storage helper functions
142 local function load_node_from_store(service, node_name)
143 local node = service.config.nodestore:get(node_name);
144 node.config = setmetatable(node.config or {}, {__index=service.node_defaults});
145 return node;
148 local function save_node_to_store(service, node)
149 return service.config.nodestore:set(node.name, {
150 name = node.name;
151 config = node.config;
152 subscribers = node.subscribers;
153 affiliations = node.affiliations;
157 local function delete_node_in_store(service, node_name)
158 return service.config.nodestore:set(node_name, nil);
161 -- Create and return a new service object
162 local function new(config)
163 config = config or {};
165 local service = setmetatable({
166 config = setmetatable(config, default_config_mt);
167 node_defaults = setmetatable(config.node_defaults or {}, default_node_config_mt);
168 affiliations = {};
169 subscriptions = {};
170 nodes = {};
171 data = {};
172 events = events.new();
173 }, service_mt);
175 -- Load nodes from storage, if we have a store and it supports iterating over stored items
176 if config.nodestore and config.nodestore.users then
177 for node_name in config.nodestore:users() do
178 service.nodes[node_name] = load_node_from_store(service, node_name);
179 service.data[node_name] = config.itemstore(service.nodes[node_name].config, node_name);
181 for jid in pairs(service.nodes[node_name].subscribers) do
182 local normal_jid = service.config.normalize_jid(jid);
183 local subs = service.subscriptions[normal_jid];
184 if subs then
185 if not subs[jid] then
186 subs[jid] = { [node_name] = true };
187 else
188 subs[jid][node_name] = true;
190 else
191 service.subscriptions[normal_jid] = { [jid] = { [node_name] = true } };
197 return service;
200 --- Service methods
202 local service = {};
203 service_mt.__index = service;
205 function service:jids_equal(jid1, jid2) --> boolean
206 local normalize = self.config.normalize_jid;
207 return normalize(jid1) == normalize(jid2);
210 function service:may(node, actor, action) --> boolean
211 if actor == true then return true; end
213 local node_obj = self.nodes[node];
214 local node_aff = node_obj and (node_obj.affiliations[actor]
215 or node_obj.affiliations[self.config.normalize_jid(actor)]);
216 local service_aff = self.affiliations[actor]
217 or self.config.get_affiliation(actor, node, action);
218 local default_aff = self:get_default_affiliation(node, actor) or "none";
220 -- Check if node allows/forbids it
221 local node_capabilities = node_obj and node_obj.capabilities;
222 if node_capabilities then
223 local caps = node_capabilities[node_aff or service_aff or default_aff];
224 if caps then
225 local can = caps[action];
226 if can ~= nil then
227 return can;
232 -- Check service-wide capabilities instead
233 local service_capabilities = self.config.capabilities;
234 local caps = service_capabilities[node_aff or service_aff or default_aff];
235 if caps then
236 local can = caps[action];
237 if can ~= nil then
238 return can;
242 return false;
245 function service:get_default_affiliation(node, actor) --> affiliation
246 local node_obj = self.nodes[node];
247 local access_model = node_obj and node_obj.config.access_model
248 or self.node_defaults.access_model;
250 if access_model == "open" then
251 return "member";
252 elseif access_model == "whitelist" then
253 return "outcast";
256 if self.config.access_models then
257 local check = self.config.access_models[access_model];
258 if check then
259 local aff = check(actor);
260 if aff then
261 return aff;
267 function service:set_affiliation(node, actor, jid, affiliation) --> ok, err
268 -- Access checking
269 if not self:may(node, actor, "set_affiliation") then
270 return false, "forbidden";
273 local node_obj = self.nodes[node];
274 if not node_obj then
275 return false, "item-not-found";
277 jid = self.config.normalize_jid(jid);
278 local old_affiliation = node_obj.affiliations[jid];
279 node_obj.affiliations[jid] = affiliation;
281 if self.config.nodestore then
282 local ok, err = save_node_to_store(self, node_obj);
283 if not ok then
284 node_obj.affiliations[jid] = old_affiliation;
285 return ok, "internal-server-error";
289 local _, jid_sub = self:get_subscription(node, true, jid);
290 if not jid_sub and not self:may(node, jid, "be_unsubscribed") then
291 local ok, err = self:add_subscription(node, true, jid);
292 if not ok then
293 return ok, err;
295 elseif jid_sub and not self:may(node, jid, "be_subscribed") then
296 local ok, err = self:add_subscription(node, true, jid);
297 if not ok then
298 return ok, err;
301 return true;
304 function service:add_subscription(node, actor, jid, options) --> ok, err
305 -- Access checking
306 local cap;
307 if actor == true or jid == actor or self:jids_equal(actor, jid) then
308 cap = "subscribe";
309 else
310 cap = "subscribe_other";
312 if not self:may(node, actor, cap) then
313 return false, "forbidden";
315 if not self:may(node, jid, "be_subscribed") then
316 return false, "forbidden";
319 local node_obj = self.nodes[node];
320 if not node_obj then
321 if not self.config.autocreate_on_subscribe then
322 return false, "item-not-found";
323 else
324 local ok, err = self:create(node, true);
325 if not ok then
326 return ok, err;
328 node_obj = self.nodes[node];
331 local old_subscription = node_obj.subscribers[jid];
332 node_obj.subscribers[jid] = options or true;
333 local normal_jid = self.config.normalize_jid(jid);
334 local subs = self.subscriptions[normal_jid];
335 if subs then
336 if not subs[jid] then
337 subs[jid] = { [node] = true };
338 else
339 subs[jid][node] = true;
341 else
342 self.subscriptions[normal_jid] = { [jid] = { [node] = true } };
345 if self.config.nodestore then
346 local ok, err = save_node_to_store(self, node_obj);
347 if not ok then
348 node_obj.subscribers[jid] = old_subscription;
349 self.subscriptions[normal_jid][jid][node] = old_subscription and true or nil;
350 return ok, "internal-server-error";
354 self.events.fire_event("subscription-added", { service = self, node = node, jid = jid, normalized_jid = normal_jid, options = options });
355 return true;
358 function service:remove_subscription(node, actor, jid) --> ok, err
359 -- Access checking
360 local cap;
361 if actor == true or jid == actor or self:jids_equal(actor, jid) then
362 cap = "unsubscribe";
363 else
364 cap = "unsubscribe_other";
366 if not self:may(node, actor, cap) then
367 return false, "forbidden";
369 if not self:may(node, jid, "be_unsubscribed") then
370 return false, "forbidden";
373 local node_obj = self.nodes[node];
374 if not node_obj then
375 return false, "item-not-found";
377 if not node_obj.subscribers[jid] then
378 return false, "not-subscribed";
380 local old_subscription = node_obj.subscribers[jid];
381 node_obj.subscribers[jid] = nil;
382 local normal_jid = self.config.normalize_jid(jid);
383 local subs = self.subscriptions[normal_jid];
384 if subs then
385 local jid_subs = subs[jid];
386 if jid_subs then
387 jid_subs[node] = nil;
388 if next(jid_subs) == nil then
389 subs[jid] = nil;
392 if next(subs) == nil then
393 self.subscriptions[normal_jid] = nil;
397 if self.config.nodestore then
398 local ok, err = save_node_to_store(self, node_obj);
399 if not ok then
400 node_obj.subscribers[jid] = old_subscription;
401 self.subscriptions[normal_jid][jid][node] = old_subscription and true or nil;
402 return ok, "internal-server-error";
406 self.events.fire_event("subscription-removed", { service = self, node = node, jid = jid, normalized_jid = normal_jid });
407 return true;
410 function service:get_subscription(node, actor, jid) --> (true, subscription) or (false, err)
411 -- Access checking
412 local cap;
413 if actor == true or jid == actor or self:jids_equal(actor, jid) then
414 cap = "get_subscription";
415 else
416 cap = "get_subscription_other";
418 if not self:may(node, actor, cap) then
419 return false, "forbidden";
422 local node_obj = self.nodes[node];
423 if not node_obj then
424 return false, "item-not-found";
426 return true, node_obj.subscribers[jid];
429 function service:create(node, actor, options) --> ok, err
430 -- Access checking
431 if not self:may(node, actor, "create") then
432 return false, "forbidden";
435 if self.nodes[node] then
436 return false, "conflict";
439 local config = setmetatable(options or {}, {__index=self.node_defaults});
441 if self.config.check_node_config then
442 local ok = self.config.check_node_config(node, actor, config);
443 if not ok then
444 return false, "not-acceptable";
448 self.nodes[node] = {
449 name = node;
450 subscribers = {};
451 config = config;
452 affiliations = {};
455 if self.config.nodestore then
456 local ok, err = save_node_to_store(self, self.nodes[node]);
457 if not ok then
458 self.nodes[node] = nil;
459 return ok, "internal-server-error";
463 self.data[node] = self.config.itemstore(self.nodes[node].config, node);
464 self.events.fire_event("node-created", { service = self, node = node, actor = actor });
465 if actor ~= true then
466 local ok, err = self:set_affiliation(node, true, actor, "owner");
467 if not ok then
468 self.nodes[node] = nil;
469 self.data[node] = nil;
470 return ok, err;
474 return true;
477 function service:delete(node, actor) --> ok, err
478 -- Access checking
479 if not self:may(node, actor, "delete") then
480 return false, "forbidden";
483 local node_obj = self.nodes[node];
484 if not node_obj then
485 return false, "item-not-found";
487 self.nodes[node] = nil;
488 if self.data[node] and self.data[node].clear then
489 self.data[node]:clear();
491 self.data[node] = nil;
493 if self.config.nodestore then
494 local ok, err = delete_node_in_store(self, node);
495 if not ok then
496 self.nodes[node] = nil;
497 return ok, err;
501 self.events.fire_event("node-deleted", { service = self, node = node, actor = actor });
502 self.config.broadcaster("delete", node, node_obj.subscribers, nil, actor, node_obj, self);
503 return true;
506 -- Used to check that the config of a node is as expected (i.e. 'publish-options')
507 local function check_preconditions(node_config, required_config)
508 if not (node_config and required_config) then
509 return false;
511 for config_field, value in pairs(required_config) do
512 if node_config[config_field] ~= value then
513 return false;
516 return true;
519 function service:publish(node, actor, id, item, requested_config) --> ok, err
520 -- Access checking
521 local may_publish = false;
523 if self:may(node, actor, "publish") then
524 may_publish = true;
525 else
526 local node_obj = self.nodes[node];
527 local publish_model = node_obj and node_obj.config.publish_model;
528 if publish_model == "open"
529 or (publish_model == "subscribers" and node_obj.subscribers[actor]) then
530 may_publish = true;
533 if not may_publish then
534 return false, "forbidden";
537 local node_obj = self.nodes[node];
538 if not node_obj then
539 if not self.config.autocreate_on_publish then
540 return false, "item-not-found";
542 local ok, err = self:create(node, true, requested_config);
543 if not ok then
544 return ok, err;
546 node_obj = self.nodes[node];
547 elseif requested_config and not requested_config._defaults_only then
548 -- Check that node has the requested config before we publish
549 if not check_preconditions(node_obj.config, requested_config) then
550 return false, "precondition-not-met";
553 if not self.config.itemcheck(item) then
554 return nil, "invalid-item";
556 local node_data = self.data[node];
557 local ok = node_data:set(id, item);
558 if not ok then
559 return nil, "internal-server-error";
561 if type(ok) == "string" then id = ok; end
562 local event_data = { service = self, node = node, actor = actor, id = id, item = item };
563 self.events.fire_event("item-published/"..node, event_data);
564 self.events.fire_event("item-published", event_data);
565 self.config.broadcaster("items", node, node_obj.subscribers, item, actor, node_obj, self);
566 return true;
569 function service:retract(node, actor, id, retract) --> ok, err
570 -- Access checking
571 if not self:may(node, actor, "retract") then
572 return false, "forbidden";
575 local node_obj = self.nodes[node];
576 if (not node_obj) or (not self.data[node]:get(id)) then
577 return false, "item-not-found";
579 local ok = self.data[node]:set(id, nil);
580 if not ok then
581 return nil, "internal-server-error";
583 self.events.fire_event("item-retracted", { service = self, node = node, actor = actor, id = id });
584 if retract then
585 self.config.broadcaster("retract", node, node_obj.subscribers, retract, actor, node_obj, self);
587 return true
590 function service:purge(node, actor, notify) --> ok, err
591 -- Access checking
592 if not self:may(node, actor, "retract") then
593 return false, "forbidden";
596 local node_obj = self.nodes[node];
597 if not node_obj then
598 return false, "item-not-found";
600 if self.data[node] and self.data[node].clear then
601 self.data[node]:clear()
602 else
603 self.data[node] = self.config.itemstore(self.nodes[node].config, node);
605 self.events.fire_event("node-purged", { service = self, node = node, actor = actor });
606 if notify then
607 self.config.broadcaster("purge", node, node_obj.subscribers, nil, actor, node_obj, self);
609 return true
612 function service:get_items(node, actor, ids) --> (true, { id, [id] = node }) or (false, err)
613 -- Access checking
614 if not self:may(node, actor, "get_items") then
615 return false, "forbidden";
618 local node_obj = self.nodes[node];
619 if not node_obj then
620 return false, "item-not-found";
622 if type(ids) == "string" then -- COMPAT see #1305
623 ids = { ids };
625 local data = {};
626 if ids then
627 for _, key in ipairs(ids) do
628 local value = self.data[node]:get(key);
629 if value then
630 data[#data+1] = key;
631 data[key] = value;
634 else
635 for key, value in self.data[node]:items() do
636 data[#data+1] = key;
637 data[key] = value;
640 return true, data;
643 function service:get_last_item(node, actor) --> (true, id, node) or (false, err)
644 -- Access checking
645 if not self:may(node, actor, "get_items") then
646 return false, "forbidden";
650 -- Check node exists
651 if not self.nodes[node] then
652 return false, "item-not-found";
655 -- Returns success, id, item
656 return true, self.data[node]:head();
659 function service:get_nodes(actor) --> (true, map) or (false, err)
660 -- Access checking
661 if not self:may(nil, actor, "get_nodes") then
662 return false, "forbidden";
665 return true, self.nodes;
668 local function flatten_subscriptions(ret, serv, subs, node, node_obj)
669 for subscribed_jid, subscribed_nodes in pairs(subs) do
670 if node then -- Return only subscriptions to this node
671 if subscribed_nodes[node] then
672 ret[#ret+1] = {
673 node = node;
674 jid = subscribed_jid;
675 subscription = node_obj.subscribers[subscribed_jid];
678 else -- Return subscriptions to all nodes
679 local nodes = serv.nodes;
680 for subscribed_node in pairs(subscribed_nodes) do
681 ret[#ret+1] = {
682 node = subscribed_node;
683 jid = subscribed_jid;
684 subscription = nodes[subscribed_node].subscribers[subscribed_jid];
691 function service:get_subscriptions(node, actor, jid) --> (true, array) or (false, err)
692 -- Access checking
693 local cap;
694 if actor == true or jid == actor or self:jids_equal(actor, jid) then
695 cap = "get_subscriptions";
696 else
697 cap = "get_subscriptions_other";
699 if not self:may(node, actor, cap) then
700 return false, "forbidden";
703 local node_obj;
704 if node then
705 node_obj = self.nodes[node];
706 if not node_obj then
707 return false, "item-not-found";
710 local ret = {};
711 if jid == nil then
712 for _, subs in pairs(self.subscriptions) do
713 flatten_subscriptions(ret, self, subs, node, node_obj)
715 return true, ret;
717 local normal_jid = self.config.normalize_jid(jid);
718 local subs = self.subscriptions[normal_jid];
719 -- We return the subscription object from the node to save
720 -- a get_subscription() call for each node.
721 if subs then
722 flatten_subscriptions(ret, self, subs, node, node_obj)
724 return true, ret;
727 -- Access models only affect 'none' affiliation caps, service/default access level...
728 function service:set_node_capabilities(node, actor, capabilities) --> ok, err
729 -- Access checking
730 if not self:may(node, actor, "configure") then
731 return false, "forbidden";
734 local node_obj = self.nodes[node];
735 if not node_obj then
736 return false, "item-not-found";
738 node_obj.capabilities = capabilities;
739 return true;
742 function service:set_node_config(node, actor, new_config) --> ok, err
743 if not self:may(node, actor, "configure") then
744 return false, "forbidden";
747 local node_obj = self.nodes[node];
748 if not node_obj then
749 return false, "item-not-found";
752 setmetatable(new_config, {__index=self.node_defaults})
754 if self.config.check_node_config then
755 local ok = self.config.check_node_config(node, actor, new_config);
756 if not ok then
757 return false, "not-acceptable";
761 local old_config = node_obj.config;
762 node_obj.config = new_config;
764 if self.config.nodestore then
765 local ok, err = save_node_to_store(self, node_obj);
766 if not ok then
767 node_obj.config = old_config;
768 return ok, "internal-server-error";
772 if old_config["access_model"] ~= node_obj.config["access_model"] then
773 for subscriber in pairs(node_obj.subscribers) do
774 if not self:may(node, subscriber, "be_subscribed") then
775 local ok, err = self:remove_subscription(node, true, subscriber);
776 if not ok then
777 node_obj.config = old_config;
778 return ok, err;
784 if old_config["persist_items"] ~= node_obj.config["persist_items"] then
785 self.data[node] = self.config.itemstore(self.nodes[node].config, node);
786 elseif old_config["max_items"] ~= node_obj.config["max_items"] then
787 self.data[node]:resize(self.nodes[node].config["max_items"]);
790 return true;
793 function service:get_node_config(node, actor) --> (true, config) or (false, err)
794 if not self:may(node, actor, "get_configuration") then
795 return false, "forbidden";
798 local node_obj = self.nodes[node];
799 if not node_obj then
800 return false, "item-not-found";
803 local config_table = {};
804 for k, v in pairs(default_node_config) do
805 config_table[k] = v;
807 for k, v in pairs(self.node_defaults) do
808 config_table[k] = v;
810 for k, v in pairs(node_obj.config) do
811 config_table[k] = v;
814 return true, config_table;
817 return {
818 new = new;