mirror of
https://github.com/bjc/prosody.git
synced 2025-04-01 20:27:39 +03:00
Allows granting read only access to other sets of users using a separate access control capability, which makes sense as some properties may be intended to be public but read-only.
896 lines
23 KiB
Lua
896 lines
23 KiB
Lua
local events = require "prosody.util.events";
|
|
local cache = require "prosody.util.cache";
|
|
|
|
local service_mt = {};
|
|
|
|
local default_config = {
|
|
max_items = 256;
|
|
itemstore = function (config, _) return cache.new(config["max_items"]) end;
|
|
broadcaster = function () end;
|
|
subscriber_filter = function (subs) return subs end;
|
|
itemcheck = function () return true; end;
|
|
get_affiliation = function () end;
|
|
normalize_jid = function (jid) return jid; end;
|
|
metadata_subset = {};
|
|
capabilities = {
|
|
outcast = {
|
|
create = false;
|
|
publish = false;
|
|
retract = false;
|
|
get_nodes = false;
|
|
|
|
subscribe = false;
|
|
unsubscribe = false;
|
|
get_subscription = true;
|
|
get_subscriptions = true;
|
|
get_items = false;
|
|
|
|
subscribe_other = false;
|
|
unsubscribe_other = false;
|
|
get_subscription_other = false;
|
|
get_subscriptions_other = false;
|
|
|
|
be_subscribed = false;
|
|
be_unsubscribed = true;
|
|
|
|
set_affiliation = false;
|
|
};
|
|
none = {
|
|
create = false;
|
|
publish = false;
|
|
retract = false;
|
|
get_nodes = true;
|
|
|
|
subscribe = true;
|
|
unsubscribe = true;
|
|
get_subscription = true;
|
|
get_subscriptions = true;
|
|
get_items = false;
|
|
get_metadata = true;
|
|
|
|
subscribe_other = false;
|
|
unsubscribe_other = false;
|
|
get_subscription_other = false;
|
|
get_subscriptions_other = false;
|
|
|
|
be_subscribed = true;
|
|
be_unsubscribed = true;
|
|
|
|
set_affiliation = false;
|
|
};
|
|
member = {
|
|
create = false;
|
|
publish = false;
|
|
retract = false;
|
|
get_nodes = true;
|
|
|
|
subscribe = true;
|
|
unsubscribe = true;
|
|
get_subscription = true;
|
|
get_subscriptions = true;
|
|
get_items = true;
|
|
get_metadata = true;
|
|
|
|
subscribe_other = false;
|
|
unsubscribe_other = false;
|
|
get_subscription_other = false;
|
|
get_subscriptions_other = false;
|
|
|
|
be_subscribed = true;
|
|
be_unsubscribed = true;
|
|
|
|
set_affiliation = false;
|
|
};
|
|
publisher = {
|
|
create = false;
|
|
publish = true;
|
|
retract = true;
|
|
get_nodes = true;
|
|
get_configuration = true;
|
|
|
|
subscribe = true;
|
|
unsubscribe = true;
|
|
get_subscription = true;
|
|
get_subscriptions = true;
|
|
get_items = true;
|
|
get_metadata = true;
|
|
|
|
subscribe_other = false;
|
|
unsubscribe_other = false;
|
|
get_subscription_other = false;
|
|
get_subscriptions_other = false;
|
|
|
|
be_subscribed = true;
|
|
be_unsubscribed = true;
|
|
|
|
set_affiliation = false;
|
|
};
|
|
owner = {
|
|
create = true;
|
|
publish = true;
|
|
retract = true;
|
|
delete = true;
|
|
get_nodes = true;
|
|
configure = true;
|
|
get_configuration = true;
|
|
|
|
subscribe = true;
|
|
unsubscribe = true;
|
|
get_subscription = true;
|
|
get_subscriptions = true;
|
|
get_items = true;
|
|
get_metadata = true;
|
|
|
|
|
|
subscribe_other = true;
|
|
unsubscribe_other = true;
|
|
get_subscription_other = true;
|
|
get_subscriptions_other = true;
|
|
|
|
be_subscribed = true;
|
|
be_unsubscribed = true;
|
|
|
|
set_affiliation = true;
|
|
};
|
|
};
|
|
};
|
|
local default_config_mt = { __index = default_config };
|
|
|
|
local default_node_config = {
|
|
["persist_items"] = true;
|
|
["max_items"] = 20;
|
|
["access_model"] = "open";
|
|
["publish_model"] = "publishers";
|
|
["send_last_published_item"] = "never";
|
|
};
|
|
local default_node_config_mt = { __index = default_node_config };
|
|
|
|
-- Storage helper functions
|
|
|
|
local function load_node_from_store(service, node_name)
|
|
local node = service.config.nodestore:get(node_name);
|
|
node.config = setmetatable(node.config or {}, {__index=service.node_defaults});
|
|
return node;
|
|
end
|
|
|
|
local function save_node_to_store(service, node)
|
|
return service.config.nodestore:set(node.name, {
|
|
name = node.name;
|
|
config = node.config;
|
|
subscribers = node.subscribers;
|
|
affiliations = node.affiliations;
|
|
});
|
|
end
|
|
|
|
local function delete_node_in_store(service, node_name)
|
|
return service.config.nodestore:set(node_name, nil);
|
|
end
|
|
|
|
-- Create and return a new service object
|
|
local function new(config)
|
|
config = config or {};
|
|
|
|
local service = setmetatable({
|
|
config = setmetatable(config, default_config_mt);
|
|
node_defaults = setmetatable(config.node_defaults or {}, default_node_config_mt);
|
|
affiliations = {};
|
|
subscriptions = {};
|
|
nodes = {};
|
|
data = {};
|
|
events = events.new();
|
|
}, service_mt);
|
|
|
|
-- Load nodes from storage, if we have a store and it supports iterating over stored items
|
|
if config.nodestore and config.nodestore.users then
|
|
for node_name in config.nodestore:users() do
|
|
local node = load_node_from_store(service, node_name);
|
|
service.nodes[node_name] = node;
|
|
if node.config.persist_items then
|
|
service.data[node_name] = config.itemstore(service.nodes[node_name].config, node_name);
|
|
end
|
|
|
|
for jid in pairs(service.nodes[node_name].subscribers) do
|
|
local normal_jid = service.config.normalize_jid(jid);
|
|
local subs = service.subscriptions[normal_jid];
|
|
if subs then
|
|
if not subs[jid] then
|
|
subs[jid] = { [node_name] = true };
|
|
else
|
|
subs[jid][node_name] = true;
|
|
end
|
|
else
|
|
service.subscriptions[normal_jid] = { [jid] = { [node_name] = true } };
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return service;
|
|
end
|
|
|
|
--- Service methods
|
|
|
|
local service = {};
|
|
service_mt.__index = service;
|
|
|
|
function service:jids_equal(jid1, jid2) --> boolean
|
|
local normalize = self.config.normalize_jid;
|
|
return normalize(jid1) == normalize(jid2);
|
|
end
|
|
|
|
function service:may(node, actor, action) --> boolean
|
|
if actor == true then return true; end
|
|
|
|
local node_obj = self.nodes[node];
|
|
local node_aff = node_obj and (node_obj.affiliations[actor]
|
|
or node_obj.affiliations[self.config.normalize_jid(actor)]);
|
|
local service_aff = self.affiliations[actor]
|
|
or self.config.get_affiliation(actor, node, action);
|
|
local default_aff = self:get_default_affiliation(node, actor) or "none";
|
|
|
|
-- Check if node allows/forbids it
|
|
local node_capabilities = node_obj and node_obj.capabilities;
|
|
if node_capabilities then
|
|
local caps = node_capabilities[node_aff or service_aff or default_aff];
|
|
if caps then
|
|
local can = caps[action];
|
|
if can ~= nil then
|
|
return can;
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Check service-wide capabilities instead
|
|
local service_capabilities = self.config.capabilities;
|
|
local caps = service_capabilities[node_aff or service_aff or default_aff];
|
|
if caps then
|
|
local can = caps[action];
|
|
if can ~= nil then
|
|
return can;
|
|
end
|
|
end
|
|
|
|
return false;
|
|
end
|
|
|
|
function service:get_default_affiliation(node, actor) --> affiliation
|
|
local node_obj = self.nodes[node];
|
|
local access_model = node_obj and node_obj.config.access_model
|
|
or self.node_defaults.access_model;
|
|
|
|
if access_model == "open" then
|
|
return "member";
|
|
elseif access_model == "whitelist" then
|
|
return "outcast";
|
|
end
|
|
|
|
if self.config.access_models then
|
|
local check = self.config.access_models[access_model];
|
|
if check then
|
|
local aff = check(actor, node_obj);
|
|
if aff then
|
|
return aff;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function service:set_affiliation(node, actor, jid, affiliation) --> ok, err
|
|
-- Access checking
|
|
if not self:may(node, actor, "set_affiliation") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
jid = self.config.normalize_jid(jid);
|
|
local old_affiliation = node_obj.affiliations[jid];
|
|
node_obj.affiliations[jid] = affiliation;
|
|
|
|
if self.config.nodestore then
|
|
-- TODO pass the error from storage to caller eg wrapped in an util.error
|
|
local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err
|
|
if not ok then
|
|
node_obj.affiliations[jid] = old_affiliation;
|
|
return ok, "internal-server-error";
|
|
end
|
|
end
|
|
|
|
local _, jid_sub = self:get_subscription(node, true, jid);
|
|
if not jid_sub and not self:may(node, jid, "be_unsubscribed") then
|
|
local ok, err = self:add_subscription(node, true, jid);
|
|
if not ok then
|
|
return ok, err;
|
|
end
|
|
elseif jid_sub and not self:may(node, jid, "be_subscribed") then
|
|
local ok, err = self:add_subscription(node, true, jid);
|
|
if not ok then
|
|
return ok, err;
|
|
end
|
|
end
|
|
return true;
|
|
end
|
|
|
|
function service:add_subscription(node, actor, jid, options) --> ok, err
|
|
-- Access checking
|
|
local cap;
|
|
if actor == true or jid == actor or self:jids_equal(actor, jid) then
|
|
cap = "subscribe";
|
|
else
|
|
cap = "subscribe_other";
|
|
end
|
|
if not self:may(node, actor, cap) then
|
|
return false, "forbidden";
|
|
end
|
|
if not self:may(node, jid, "be_subscribed") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
if not self.config.autocreate_on_subscribe then
|
|
return false, "item-not-found";
|
|
else
|
|
local ok, err = self:create(node, true);
|
|
if not ok then
|
|
return ok, err;
|
|
end
|
|
node_obj = self.nodes[node];
|
|
end
|
|
end
|
|
local old_subscription = node_obj.subscribers[jid];
|
|
node_obj.subscribers[jid] = options or true;
|
|
local normal_jid = self.config.normalize_jid(jid);
|
|
local subs = self.subscriptions[normal_jid];
|
|
if subs then
|
|
if not subs[jid] then
|
|
subs[jid] = { [node] = true };
|
|
else
|
|
subs[jid][node] = true;
|
|
end
|
|
else
|
|
self.subscriptions[normal_jid] = { [jid] = { [node] = true } };
|
|
end
|
|
|
|
if self.config.nodestore then
|
|
-- TODO pass the error from storage to caller eg wrapped in an util.error
|
|
local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err
|
|
if not ok then
|
|
node_obj.subscribers[jid] = old_subscription;
|
|
self.subscriptions[normal_jid][jid][node] = old_subscription and true or nil;
|
|
return ok, "internal-server-error";
|
|
end
|
|
end
|
|
|
|
self.events.fire_event("subscription-added", { service = self, node = node, jid = jid, normalized_jid = normal_jid, options = options });
|
|
return true;
|
|
end
|
|
|
|
function service:remove_subscription(node, actor, jid) --> ok, err
|
|
-- Access checking
|
|
local cap;
|
|
if actor == true or jid == actor or self:jids_equal(actor, jid) then
|
|
cap = "unsubscribe";
|
|
else
|
|
cap = "unsubscribe_other";
|
|
end
|
|
if not self:may(node, actor, cap) then
|
|
return false, "forbidden";
|
|
end
|
|
if not self:may(node, jid, "be_unsubscribed") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
if not node_obj.subscribers[jid] then
|
|
return false, "not-subscribed";
|
|
end
|
|
local old_subscription = node_obj.subscribers[jid];
|
|
node_obj.subscribers[jid] = nil;
|
|
local normal_jid = self.config.normalize_jid(jid);
|
|
local subs = self.subscriptions[normal_jid];
|
|
if subs then
|
|
local jid_subs = subs[jid];
|
|
if jid_subs then
|
|
jid_subs[node] = nil;
|
|
if next(jid_subs) == nil then
|
|
subs[jid] = nil;
|
|
end
|
|
end
|
|
if next(subs) == nil then
|
|
self.subscriptions[normal_jid] = nil;
|
|
end
|
|
end
|
|
|
|
if self.config.nodestore then
|
|
-- TODO pass the error from storage to caller eg wrapped in an util.error
|
|
local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err
|
|
if not ok then
|
|
node_obj.subscribers[jid] = old_subscription;
|
|
self.subscriptions[normal_jid][jid][node] = old_subscription and true or nil;
|
|
return ok, "internal-server-error";
|
|
end
|
|
end
|
|
|
|
self.events.fire_event("subscription-removed", { service = self, node = node, jid = jid, normalized_jid = normal_jid });
|
|
return true;
|
|
end
|
|
|
|
function service:get_subscription(node, actor, jid) --> (true, subscription) or (false, err)
|
|
-- Access checking
|
|
local cap;
|
|
if actor == true or jid == actor or self:jids_equal(actor, jid) then
|
|
cap = "get_subscription";
|
|
else
|
|
cap = "get_subscription_other";
|
|
end
|
|
if not self:may(node, actor, cap) then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
return true, node_obj.subscribers[jid];
|
|
end
|
|
|
|
function service:create(node, actor, options) --> ok, err
|
|
-- Access checking
|
|
if not self:may(node, actor, "create") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
if self.nodes[node] then
|
|
return false, "conflict";
|
|
end
|
|
|
|
local config = setmetatable(options or {}, {__index=self.node_defaults});
|
|
|
|
if self.config.check_node_config then
|
|
local ok = self.config.check_node_config(node, actor, config);
|
|
if not ok then
|
|
return false, "not-acceptable";
|
|
end
|
|
end
|
|
|
|
self.nodes[node] = {
|
|
name = node;
|
|
subscribers = {};
|
|
config = config;
|
|
affiliations = {};
|
|
};
|
|
|
|
if self.config.nodestore then
|
|
-- TODO pass the error from storage to caller eg wrapped in an util.error
|
|
local ok, err = save_node_to_store(self, self.nodes[node]); -- luacheck: ignore 211/err
|
|
if not ok then
|
|
self.nodes[node] = nil;
|
|
return ok, "internal-server-error";
|
|
end
|
|
end
|
|
|
|
if config.persist_items then
|
|
self.data[node] = self.config.itemstore(self.nodes[node].config, node);
|
|
end
|
|
|
|
self.events.fire_event("node-created", { service = self, node = node, actor = actor });
|
|
if actor ~= true then
|
|
local ok, err = self:set_affiliation(node, true, actor, "owner");
|
|
if not ok then
|
|
self.nodes[node] = nil;
|
|
self.data[node] = nil;
|
|
return ok, err;
|
|
end
|
|
end
|
|
|
|
return true;
|
|
end
|
|
|
|
function service:delete(node, actor) --> ok, err
|
|
-- Access checking
|
|
if not self:may(node, actor, "delete") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
self.nodes[node] = nil;
|
|
if self.data[node] and self.data[node].clear then
|
|
self.data[node]:clear();
|
|
end
|
|
self.data[node] = nil;
|
|
|
|
if self.config.nodestore then
|
|
local ok, err = delete_node_in_store(self, node);
|
|
if not ok then
|
|
self.nodes[node] = nil;
|
|
return ok, err;
|
|
end
|
|
end
|
|
|
|
self.events.fire_event("node-deleted", { service = self, node = node, actor = actor });
|
|
self:broadcast("delete", node, node_obj.subscribers, nil, actor, node_obj);
|
|
return true;
|
|
end
|
|
|
|
-- Used to check that the config of a node is as expected (i.e. 'publish-options')
|
|
local function check_preconditions(node_config, required_config)
|
|
if not (node_config and required_config) then
|
|
return false;
|
|
end
|
|
for config_field, value in pairs(required_config) do
|
|
if node_config[config_field] ~= value then
|
|
return false, config_field;
|
|
end
|
|
end
|
|
return true;
|
|
end
|
|
|
|
function service:publish(node, actor, id, item, requested_config) --> ok, err
|
|
-- Access checking
|
|
local may_publish = false;
|
|
|
|
if self:may(node, actor, "publish") then
|
|
may_publish = true;
|
|
else
|
|
local node_obj = self.nodes[node];
|
|
local publish_model = node_obj and node_obj.config.publish_model;
|
|
if publish_model == "open"
|
|
or (publish_model == "subscribers" and node_obj.subscribers[actor]) then
|
|
may_publish = true;
|
|
end
|
|
end
|
|
if not may_publish then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
if not self.config.autocreate_on_publish then
|
|
return false, "item-not-found";
|
|
end
|
|
local ok, err = self:create(node, true, requested_config);
|
|
if not ok then
|
|
return ok, err;
|
|
end
|
|
node_obj = self.nodes[node];
|
|
elseif requested_config and not requested_config._defaults_only then
|
|
-- Check that node has the requested config before we publish
|
|
local ok, field = check_preconditions(node_obj.config, requested_config);
|
|
if not ok then
|
|
return false, "precondition-not-met", { field = field };
|
|
end
|
|
end
|
|
if not self.config.itemcheck(item) then
|
|
return nil, "invalid-item";
|
|
end
|
|
if node_obj.config.persist_items then
|
|
if not self.data[node] then
|
|
self.data[node] = self.config.itemstore(self.nodes[node].config, node);
|
|
end
|
|
local ok = self.data[node]:set(id, item);
|
|
if not ok then
|
|
return nil, "internal-server-error";
|
|
end
|
|
if type(ok) == "string" then id = ok; end
|
|
end
|
|
local event_data = { service = self, node = node, actor = actor, id = id, item = item };
|
|
self.events.fire_event("item-published/"..node, event_data);
|
|
self.events.fire_event("item-published", event_data);
|
|
self:broadcast("items", node, node_obj.subscribers, item, actor, node_obj);
|
|
return true;
|
|
end
|
|
|
|
function service:broadcast(event, node, subscribers, item, actor, node_obj)
|
|
subscribers = self.config.subscriber_filter(subscribers, node, event);
|
|
return self.config.broadcaster(event, node, subscribers, item, actor, node_obj, self);
|
|
end
|
|
|
|
function service:retract(node, actor, id, retract) --> ok, err
|
|
-- Access checking
|
|
if not self:may(node, actor, "retract") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
if self.data[node] then
|
|
if not self.data[node]:get(id) then
|
|
return false, "item-not-found";
|
|
end
|
|
local ok = self.data[node]:set(id, nil);
|
|
if not ok then
|
|
return nil, "internal-server-error";
|
|
end
|
|
end
|
|
self.events.fire_event("item-retracted", { service = self, node = node, actor = actor, id = id });
|
|
if retract then
|
|
self:broadcast("retract", node, node_obj.subscribers, retract, actor, node_obj);
|
|
end
|
|
return true
|
|
end
|
|
|
|
function service:purge(node, actor, notify) --> ok, err
|
|
-- Access checking
|
|
if not self:may(node, actor, "retract") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
if self.data[node] then
|
|
if self.data[node].clear then
|
|
self.data[node]:clear()
|
|
else
|
|
self.data[node] = self.config.itemstore(self.nodes[node].config, node);
|
|
end
|
|
end
|
|
self.events.fire_event("node-purged", { service = self, node = node, actor = actor });
|
|
if notify then
|
|
self:broadcast("purge", node, node_obj.subscribers, nil, actor, node_obj);
|
|
end
|
|
return true
|
|
end
|
|
|
|
function service:get_items(node, actor, ids, resultspec) --> (true, { id, [id] = node }) or (false, err)
|
|
-- Access checking
|
|
if not self:may(node, actor, "get_items") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
if not self.data[node] then
|
|
-- Disabled rather than unsupported, but close enough.
|
|
return false, "persistent-items-unsupported";
|
|
end
|
|
if type(ids) == "string" then -- COMPAT see #1305
|
|
ids = { ids };
|
|
end
|
|
local data = {};
|
|
local limit = resultspec and resultspec.max;
|
|
if ids then
|
|
for _, key in ipairs(ids) do
|
|
local value = self.data[node]:get(key);
|
|
if value then
|
|
data[#data+1] = key;
|
|
data[key] = value;
|
|
-- Limits and ids seem like a problematic combination.
|
|
if limit and #data >= limit then break end
|
|
end
|
|
end
|
|
else
|
|
for key, value in self.data[node]:items() do
|
|
data[#data+1] = key;
|
|
data[key] = value;
|
|
if limit and #data >= limit then break
|
|
end
|
|
end
|
|
end
|
|
return true, data;
|
|
end
|
|
|
|
function service:get_last_item(node, actor) --> (true, id, node) or (false, err)
|
|
-- Access checking
|
|
if not self:may(node, actor, "get_items") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
|
|
-- Check node exists
|
|
if not self.nodes[node] then
|
|
return false, "item-not-found";
|
|
end
|
|
|
|
if not self.data[node] then
|
|
-- FIXME Should this be a success or failure?
|
|
return true, nil;
|
|
end
|
|
|
|
-- Returns success, id, item
|
|
return true, self.data[node]:head();
|
|
end
|
|
|
|
function service:get_nodes(actor) --> (true, map) or (false, err)
|
|
-- Access checking
|
|
if not self:may(nil, actor, "get_nodes") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
return true, self.nodes;
|
|
end
|
|
|
|
local function flatten_subscriptions(ret, serv, subs, node, node_obj)
|
|
for subscribed_jid, subscribed_nodes in pairs(subs) do
|
|
if node then -- Return only subscriptions to this node
|
|
if subscribed_nodes[node] then
|
|
ret[#ret+1] = {
|
|
node = node;
|
|
jid = subscribed_jid;
|
|
subscription = node_obj.subscribers[subscribed_jid];
|
|
};
|
|
end
|
|
else -- Return subscriptions to all nodes
|
|
local nodes = serv.nodes;
|
|
for subscribed_node in pairs(subscribed_nodes) do
|
|
ret[#ret+1] = {
|
|
node = subscribed_node;
|
|
jid = subscribed_jid;
|
|
subscription = nodes[subscribed_node].subscribers[subscribed_jid];
|
|
};
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function service:get_subscriptions(node, actor, jid) --> (true, array) or (false, err)
|
|
-- Access checking
|
|
local cap;
|
|
if actor == true or jid == actor or self:jids_equal(actor, jid) then
|
|
cap = "get_subscriptions";
|
|
else
|
|
cap = "get_subscriptions_other";
|
|
end
|
|
if not self:may(node, actor, cap) then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj;
|
|
if node then
|
|
node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
end
|
|
local ret = {};
|
|
if jid == nil then
|
|
for _, subs in pairs(self.subscriptions) do
|
|
flatten_subscriptions(ret, self, subs, node, node_obj)
|
|
end
|
|
return true, ret;
|
|
end
|
|
local normal_jid = self.config.normalize_jid(jid);
|
|
local subs = self.subscriptions[normal_jid];
|
|
-- We return the subscription object from the node to save
|
|
-- a get_subscription() call for each node.
|
|
if subs then
|
|
flatten_subscriptions(ret, self, subs, node, node_obj)
|
|
end
|
|
return true, ret;
|
|
end
|
|
|
|
-- Access models only affect 'none' affiliation caps, service/default access level...
|
|
function service:set_node_capabilities(node, actor, capabilities) --> ok, err
|
|
-- Access checking
|
|
if not self:may(node, actor, "configure") then
|
|
return false, "forbidden";
|
|
end
|
|
--
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
node_obj.capabilities = capabilities;
|
|
return true;
|
|
end
|
|
|
|
function service:set_node_config(node, actor, new_config) --> ok, err
|
|
if not self:may(node, actor, "configure") then
|
|
return false, "forbidden";
|
|
end
|
|
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
|
|
setmetatable(new_config, {__index=self.node_defaults})
|
|
|
|
if self.config.check_node_config then
|
|
local ok = self.config.check_node_config(node, actor, new_config);
|
|
if not ok then
|
|
return false, "not-acceptable";
|
|
end
|
|
end
|
|
|
|
local old_config = node_obj.config;
|
|
node_obj.config = new_config;
|
|
|
|
if self.config.nodestore then
|
|
-- TODO pass the error from storage to caller eg wrapped in an util.error
|
|
local ok, err = save_node_to_store(self, node_obj); -- luacheck: ignore 211/err
|
|
if not ok then
|
|
node_obj.config = old_config;
|
|
return ok, "internal-server-error";
|
|
end
|
|
end
|
|
|
|
if old_config["access_model"] ~= node_obj.config["access_model"] then
|
|
for subscriber in pairs(node_obj.subscribers) do
|
|
if not self:may(node, subscriber, "be_subscribed") then
|
|
local ok, err = self:remove_subscription(node, true, subscriber);
|
|
if not ok then
|
|
node_obj.config = old_config;
|
|
return ok, err;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if old_config["persist_items"] ~= node_obj.config["persist_items"] then
|
|
if node_obj.config["persist_items"] then
|
|
self.data[node] = self.config.itemstore(self.nodes[node].config, node);
|
|
elseif self.data[node] then
|
|
if self.data[node].clear then
|
|
self.data[node]:clear()
|
|
end
|
|
self.data[node] = nil;
|
|
end
|
|
elseif old_config["max_items"] ~= node_obj.config["max_items"] then
|
|
if self.data[node] then
|
|
local max_items = self.nodes[node].config["max_items"];
|
|
if max_items == "max" then
|
|
max_items = self.config.max_items;
|
|
end
|
|
self.data[node]:resize(max_items);
|
|
end
|
|
end
|
|
|
|
return true;
|
|
end
|
|
|
|
function service:get_node_config(node, actor) --> (true, config) or (false, err)
|
|
if not self:may(node, actor, "get_configuration") then
|
|
return false, "forbidden";
|
|
end
|
|
|
|
local node_obj = self.nodes[node];
|
|
if not node_obj then
|
|
return false, "item-not-found";
|
|
end
|
|
|
|
local config_table = {};
|
|
for k, v in pairs(default_node_config) do
|
|
config_table[k] = v;
|
|
end
|
|
for k, v in pairs(self.node_defaults) do
|
|
config_table[k] = v;
|
|
end
|
|
for k, v in pairs(node_obj.config) do
|
|
config_table[k] = v;
|
|
end
|
|
|
|
return true, config_table;
|
|
end
|
|
|
|
function service:get_node_metadata(node, actor)
|
|
if not self:may(node, actor, "get_metadata") then
|
|
return false, "forbidden";
|
|
end
|
|
|
|
local ok, config = self:get_node_config(node, true);
|
|
if not ok then return ok, config; end
|
|
local meta = {};
|
|
for _, k in ipairs(self.config.metadata_subset) do
|
|
meta[k] = config[k];
|
|
end
|
|
return true, meta;
|
|
end
|
|
|
|
return {
|
|
new = new;
|
|
};
|