1 local events
= require
"util.events";
2 local cache
= require
"util.cache";
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;
21 get_subscription
= true;
22 get_subscriptions
= true;
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;
43 get_subscription
= true;
44 get_subscriptions
= true;
47 subscribe_other
= false;
48 unsubscribe_other
= false;
49 get_subscription_other
= false;
50 get_subscriptions_other
= false;
53 be_unsubscribed
= true;
55 set_affiliation
= false;
65 get_subscription
= true;
66 get_subscriptions
= true;
69 subscribe_other
= false;
70 unsubscribe_other
= false;
71 get_subscription_other
= false;
72 get_subscriptions_other
= false;
75 be_unsubscribed
= true;
77 set_affiliation
= false;
84 get_configuration
= true;
88 get_subscription
= true;
89 get_subscriptions
= true;
92 subscribe_other
= false;
93 unsubscribe_other
= false;
94 get_subscription_other
= false;
95 get_subscriptions_other
= false;
98 be_unsubscribed
= true;
100 set_affiliation
= false;
109 get_configuration
= true;
113 get_subscription
= true;
114 get_subscriptions
= 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;
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
});
148 local function save_node_to_store(service
, node
)
149 return service
.config
.nodestore
:set(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
);
172 events
= events
.new();
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
];
185 if not subs
[jid
] then
186 subs
[jid
] = { [node_name
] = true };
188 subs
[jid
][node_name
] = true;
191 service
.subscriptions
[normal_jid
] = { [jid
] = { [node_name
] = true } };
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
];
225 local can
= caps
[action
];
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
];
236 local can
= caps
[action
];
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
252 elseif access_model
== "whitelist" then
256 if self
.config
.access_models
then
257 local check
= self
.config
.access_models
[access_model
];
259 local aff
= check(actor
);
267 function service
:set_affiliation(node
, actor
, jid
, affiliation
) --> ok, err
269 if not self
:may(node
, actor
, "set_affiliation") then
270 return false, "forbidden";
273 local node_obj
= self
.nodes
[node
];
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
);
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
);
295 elseif jid_sub
and not self
:may(node
, jid
, "be_subscribed") then
296 local ok
, err
= self
:add_subscription(node
, true, jid
);
304 function service
:add_subscription(node
, actor
, jid
, options
) --> ok, err
307 if actor
== true or jid
== actor
or self
:jids_equal(actor
, jid
) then
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
];
321 if not self
.config
.autocreate_on_subscribe
then
322 return false, "item-not-found";
324 local ok
, err
= self
:create(node
, true);
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
];
336 if not subs
[jid
] then
337 subs
[jid
] = { [node
] = true };
339 subs
[jid
][node
] = true;
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
);
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
});
358 function service
:remove_subscription(node
, actor
, jid
) --> ok, err
361 if actor
== true or jid
== actor
or self
:jids_equal(actor
, jid
) then
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
];
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
];
385 local jid_subs
= subs
[jid
];
387 jid_subs
[node
] = nil;
388 if next(jid_subs
) == nil then
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
);
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
});
410 function service
:get_subscription(node
, actor
, jid
) --> (true, subscription) or (false, err)
413 if actor
== true or jid
== actor
or self
:jids_equal(actor
, jid
) then
414 cap
= "get_subscription";
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
];
424 return false, "item-not-found";
426 return true, node_obj
.subscribers
[jid
];
429 function service
:create(node
, actor
, options
) --> ok, err
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
);
444 return false, "not-acceptable";
455 if self
.config
.nodestore
then
456 local ok
, err
= save_node_to_store(self
, self
.nodes
[node
]);
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");
468 self
.nodes
[node
] = nil;
469 self
.data
[node
] = nil;
477 function service
:delete(node
, actor
) --> ok, err
479 if not self
:may(node
, actor
, "delete") then
480 return false, "forbidden";
483 local node_obj
= self
.nodes
[node
];
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
);
496 self
.nodes
[node
] = nil;
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
);
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
511 for config_field
, value
in pairs(required_config
) do
512 if node_config
[config_field
] ~= value
then
519 function service
:publish(node
, actor
, id
, item
, requested_config
) --> ok, err
521 local may_publish
= false;
523 if self
:may(node
, actor
, "publish") then
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
533 if not may_publish
then
534 return false, "forbidden";
537 local node_obj
= self
.nodes
[node
];
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
);
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
);
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
);
569 function service
:retract(node
, actor
, id
, retract
) --> ok, err
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);
581 return nil, "internal-server-error";
583 self
.events
.fire_event("item-retracted", { service
= self
, node
= node
, actor
= actor
, id
= id
});
585 self
.config
.broadcaster("retract", node
, node_obj
.subscribers
, retract
, actor
, node_obj
, self
);
590 function service
:purge(node
, actor
, notify
) --> ok, err
592 if not self
:may(node
, actor
, "retract") then
593 return false, "forbidden";
596 local node_obj
= self
.nodes
[node
];
598 return false, "item-not-found";
600 if self
.data
[node
] and self
.data
[node
].clear
then
601 self
.data
[node
]:clear()
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
});
607 self
.config
.broadcaster("purge", node
, node_obj
.subscribers
, nil, actor
, node_obj
, self
);
612 function service
:get_items(node
, actor
, ids
) --> (true, { id, [id] = node }) or (false, err)
614 if not self
:may(node
, actor
, "get_items") then
615 return false, "forbidden";
618 local node_obj
= self
.nodes
[node
];
620 return false, "item-not-found";
622 if type(ids
) == "string" then -- COMPAT see #1305
627 for _
, key
in ipairs(ids
) do
628 local value
= self
.data
[node
]:get(key
);
635 for key
, value
in self
.data
[node
]:items() do
643 function service
:get_last_item(node
, actor
) --> (true, id, node) or (false, err)
645 if not self
:may(node
, actor
, "get_items") then
646 return false, "forbidden";
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)
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
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
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)
694 if actor
== true or jid
== actor
or self
:jids_equal(actor
, jid
) then
695 cap
= "get_subscriptions";
697 cap
= "get_subscriptions_other";
699 if not self
:may(node
, actor
, cap
) then
700 return false, "forbidden";
705 node_obj
= self
.nodes
[node
];
707 return false, "item-not-found";
712 for _
, subs
in pairs(self
.subscriptions
) do
713 flatten_subscriptions(ret
, self
, subs
, node
, node_obj
)
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.
722 flatten_subscriptions(ret
, self
, subs
, node
, node_obj
)
727 -- Access models only affect 'none' affiliation caps, service/default access level...
728 function service
:set_node_capabilities(node
, actor
, capabilities
) --> ok, err
730 if not self
:may(node
, actor
, "configure") then
731 return false, "forbidden";
734 local node_obj
= self
.nodes
[node
];
736 return false, "item-not-found";
738 node_obj
.capabilities
= capabilities
;
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
];
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
);
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
);
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
);
777 node_obj
.config
= old_config
;
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"]);
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
];
800 return false, "item-not-found";
803 local config_table
= {};
804 for k
, v
in pairs(default_node_config
) do
807 for k
, v
in pairs(self
.node_defaults
) do
810 for k
, v
in pairs(node_obj
.config
) do
814 return true, config_table
;