mod_mix: Major refactor
- Remove all non-0369 code as it was trash anyway - Migrate from using mod_pep to creating our own PubSub service - Remove indices from functions like find_channel
This commit is contained in:
parent
3d66be6a9f
commit
94e9bb1baa
18
mod_mix/forms.lib.lua
Normal file
18
mod_mix/forms.lib.lua
Normal file
@ -0,0 +1,18 @@
|
||||
local dataforms = require("util.dataforms");
|
||||
|
||||
local namespaces = module:require("mix/namespaces");
|
||||
|
||||
return {
|
||||
-- MIX
|
||||
mix_info = dataforms.new({
|
||||
{ name = "FORM_TYPE", type = "hidden", value = namespaces.mix_core },
|
||||
{ name = "Name", type = "text-single" },
|
||||
{ name = "Description", type = "text-single" },
|
||||
{ name = "Contact", type = "jid-multi" }});
|
||||
-- MAM
|
||||
mam_query = dataforms.new({
|
||||
{ name = "FORM_TYPE", type = "hidden", value = namespaces.mam },
|
||||
{ name = "with", type = "jid-single" },
|
||||
{ name = "start", type = "text-single" },
|
||||
{ name = "end", type = "text-single" }});
|
||||
};
|
@ -2,11 +2,16 @@ local st = require("util.stanza");
|
||||
local array = require("util.array");
|
||||
local jid_lib = require("util.jid");
|
||||
local uuid = require("util.uuid");
|
||||
local new_id = require("util.id").medium;
|
||||
local time = require("util.time");
|
||||
local datetime = require("util.datetime");
|
||||
local pubsub = require("util.pubsub");
|
||||
local lib_pubsub = module:require("pubsub");
|
||||
local storagemanager = require("core.storagemanager");
|
||||
|
||||
local helpers = module:require("mix/helpers");
|
||||
local namespaces = module:require("mix/namespaces");
|
||||
local pep = module:depends("pep");
|
||||
|
||||
local lib_forms = module:require("mix/forms");
|
||||
|
||||
local Participant = {};
|
||||
Participant.__index = Participant;
|
||||
@ -51,6 +56,106 @@ function Channel:from(config)
|
||||
return o;
|
||||
end
|
||||
|
||||
function Channel:get_broadcaster()
|
||||
local function broadcast(kind, node, jids, item, _, node_obj)
|
||||
if node == namespaces.presence then
|
||||
-- NOTE: This assumes that we already added all necessary MIX data
|
||||
-- before publishing this item
|
||||
local presence = {};
|
||||
|
||||
if kind == "retract" then
|
||||
presence = st.presence({
|
||||
type = "unavailable",
|
||||
from = self:get_encoded_participant_jid(item.attr.from),
|
||||
});
|
||||
presence:add_child(item:get_tag("mix", namespaces.mix_presence));
|
||||
else
|
||||
presence = stanza.clone(item);
|
||||
end
|
||||
|
||||
for jid in pairs(jids) do
|
||||
module:log("debug", "Sending presence notification to %s from %s", jid, item.attr.from);
|
||||
message.attr.to = jid;
|
||||
module:send(presence);
|
||||
end
|
||||
else
|
||||
if node_obj then
|
||||
if node_obj.config["notify_"..kind] == false then
|
||||
return;
|
||||
end
|
||||
end
|
||||
|
||||
if kind == "retract" then
|
||||
kind = "items"; -- XEP-0060 signals retraction in an <items> container
|
||||
end
|
||||
|
||||
if item then
|
||||
item = st.clone(item);
|
||||
item.attr.xmlns = nil; -- Clear the pubsub namespace
|
||||
|
||||
if kind == "items" then
|
||||
if node_obj and node_obj.config.include_payload == false then
|
||||
item:maptags(function () return nil; end);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local id = new_id();
|
||||
local message = st.message({ from = self.jid, type = "headline", id = id })
|
||||
:tag("event", { xmlns = namespaces.pubsub_event })
|
||||
:tag(kind, { node = node });
|
||||
|
||||
if item then
|
||||
message:add_child(item);
|
||||
end
|
||||
|
||||
for jid in pairs(jids) do
|
||||
module:log("debug", "Sending notification to %s from %s for node %s", jid, user_bare, node);
|
||||
message.attr.to = jid;
|
||||
module:send(message);
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return broadcast;
|
||||
end
|
||||
|
||||
-- PubSub stuff
|
||||
local services = {}; -- room@server -> PubSub services
|
||||
|
||||
local known_nodes = module:open_store("mix_pubsub");
|
||||
local node_config = module:open_store("mix_pubsub", "map");
|
||||
|
||||
local function itemstore(username)
|
||||
local driver = storagemanager.get_driver(module.host, "mix_data");
|
||||
return function (config, node)
|
||||
module:log("debug", "Creating new persistent item store for user %s, node %q", username, node);
|
||||
local archive = driver:open("mix_pubsub_"..node, "archive");
|
||||
return lib_pubsub.archive_itemstore(archive, config, username, node, false);
|
||||
end
|
||||
end
|
||||
|
||||
function Channel:get_pubsub_service()
|
||||
local service = services[self.jid];
|
||||
if service then
|
||||
return service;
|
||||
end
|
||||
|
||||
service = pubsub.new({
|
||||
node_defaults = {
|
||||
["persist_items"] = true;
|
||||
["access_model"] = "open"; -- TODO
|
||||
["max_items"] = 256; -- TODO: Once "max" is supported
|
||||
};
|
||||
|
||||
broadcaster = self:get_broadcaster();
|
||||
nodestore = known_nodes;
|
||||
itemstore = itemstore(self.jid);
|
||||
});
|
||||
services[self.jid] = service;
|
||||
return service;
|
||||
end
|
||||
|
||||
function Channel:get_spid(jid)
|
||||
-- Returns the Stable Participant ID for the *BARE* jid
|
||||
return self.spid[jid];
|
||||
@ -64,13 +169,22 @@ end
|
||||
function Channel:find_participant(jid)
|
||||
-- Returns the index of a participant in a channel. Returns -1
|
||||
-- if the participant is not found
|
||||
return helpers.find(self.participants, function(p) return p.jid == jid end);
|
||||
local function is_participant(p)
|
||||
return p.jid == jid;
|
||||
end
|
||||
local _, participant = helpers.find(self.participants, is_participant);
|
||||
return participant;
|
||||
end
|
||||
|
||||
function Channel:is_participant(jid)
|
||||
-- Returns true if jid is a participant of the channel. False otherwise.
|
||||
local i, _ = self:find_participant(jid);
|
||||
return i ~= -1;
|
||||
return self:find_participant(jid) ~= nil;
|
||||
end
|
||||
|
||||
function Channel:get_encoded_participant_jid(jid)
|
||||
-- TODO: This assumes that jid is a participant
|
||||
local spid = self:get_spid(jid_lib.bare(jid));
|
||||
return spid.."#"..self.jid;
|
||||
end
|
||||
|
||||
function Channel:is_subscribed(jid, node)
|
||||
@ -102,239 +216,6 @@ function Channel:debug_print()
|
||||
end
|
||||
end
|
||||
|
||||
function Channel:allow_jid(jid)
|
||||
local srv = pep.get_pep_service(jid_lib.node(self.jid));
|
||||
array.push(self.allowed, jid);
|
||||
srv:set_node_config(namespaces.allowed,
|
||||
true,
|
||||
{ ["max_items"] = #self.allowed + 1 });
|
||||
srv:publish(namespaces.allowed, true, jid, nil);
|
||||
end
|
||||
|
||||
function Channel:ban_jid(jid)
|
||||
local srv = pep.get_pep_service(jid_lib.node(self.jid));
|
||||
self.banned:push(jid);
|
||||
srv:set_node_config(namespaces.banned,
|
||||
true,
|
||||
{ ["max_items"] = #self.banned + 1 });
|
||||
srv:publish(namespaces.banned, true, jid, nil);
|
||||
|
||||
local i = helpers.find_str(jid, "@");
|
||||
if i ~= nil then
|
||||
-- "@" in JID => We're banning a real JID
|
||||
if self:is_participant(jid) then
|
||||
self:remove_participant(jid);
|
||||
end
|
||||
else
|
||||
-- No "@" in JID => We're banning a host
|
||||
for _, participant in self.participants do
|
||||
if jid_lib.host(participant.jid) == jid then
|
||||
self:remove_participant(participant.jid);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Channel:remove_participant(jid)
|
||||
-- Removes a user form the channel. May be a kick, may be a leave.
|
||||
local srv = pep.get_pep_service(jid_lib.node(self.jid));
|
||||
|
||||
-- Step 1: Unsubscribe from all subscribed nodes
|
||||
for _, node in ipairs(self.subscriptions[jid]) do
|
||||
srv:remove_subscription(node, true, jid);
|
||||
end
|
||||
self.subscriptions[jid] = nil;
|
||||
|
||||
-- Step 2: Remove affiliations to all nodes
|
||||
for _, node in ipairs(self.nodes) do
|
||||
srv:set_affiliation(node, true, jid, "outcast");
|
||||
end
|
||||
|
||||
-- Step 3: Remove jid as participant
|
||||
local i, _ = self:find_participant(jid);
|
||||
table.remove(self.participants, i);
|
||||
|
||||
-- Step 4: Retract jid from participants node
|
||||
local spid = self:get_spid(jid);
|
||||
local notifier = st.stanza("retract", { id = spid });
|
||||
srv:retract(namespaces.participants, true, jid, notifier);
|
||||
self:save_state();
|
||||
end
|
||||
|
||||
function Channel:is_allowed(jid)
|
||||
local i, _ = helpers.find(self.allowed, jid);
|
||||
local j, _ = helpers.find(self.allowed, jid_lib.host(jid));
|
||||
|
||||
return i ~= -1 and j ~= -1;
|
||||
end
|
||||
|
||||
function Channel:is_admin(jid)
|
||||
return helpers.in_array(self.administrators, jid);
|
||||
end
|
||||
|
||||
function Channel:is_owner(jid)
|
||||
return helpers.in_array(self.owners, jid);
|
||||
end
|
||||
|
||||
function Channel:may_subscribe(jid, node, joining)
|
||||
-- Returns true when a JID is allowed to subscribe to a node.
|
||||
-- If joining is true and the node is set to "participants", then
|
||||
-- true will be returned as the user will be a participant.
|
||||
-- NOTE: When using this function directly, be careful to
|
||||
-- check first whether the JID is allowed to join.
|
||||
-- TODO: Presence
|
||||
local group = "";
|
||||
if node == namespaces.info then
|
||||
group = self.config["Information Node Subscription"];
|
||||
elseif node == namespaces.participants then
|
||||
group = self.config["Participants Node Subscription"];
|
||||
elseif node == namespaces.messages then
|
||||
group = self.config["Messages Node Subscription"];
|
||||
elseif node == namespaces.allowed then
|
||||
group = self.config["Allowed Node Subscription"];
|
||||
elseif node == namespaces.banned then
|
||||
group = self.config["Banned Node Subscription"];
|
||||
elseif node == namespaces.config then
|
||||
group = "admins";
|
||||
elseif node == namespaces.avatar or node == namespaces.avatar_metadata then
|
||||
group = "participants";
|
||||
end
|
||||
|
||||
module:log("debug", "may_subscribe: Group: %s", group);
|
||||
module:log("debug", "Is Owner: %s", self:is_owner(jid));
|
||||
module:log("debug", "Is Admin: %s", self:is_admin(jid));
|
||||
module:log("debug", "Is Participant: %s", self:is_participant(jid));
|
||||
module:log("debug", "Is allowed: %s", self:is_allowed(jid));
|
||||
|
||||
if group == "anyone" then
|
||||
return true;
|
||||
elseif group == "allowed" then
|
||||
return self:is_allowed(jid);
|
||||
elseif group == "nobody" then
|
||||
return false;
|
||||
elseif group == "admins" then
|
||||
return self:is_admin(jid) or self:is_owner(jid);
|
||||
elseif group == "owners" then
|
||||
return self:is_owner(jid);
|
||||
elseif group == "participants" then
|
||||
return self:is_participant(jid) or self:is_admin(jid) or self:is_owner(jid) or joining;
|
||||
end
|
||||
|
||||
return false;
|
||||
end
|
||||
|
||||
function Channel:may_access(jid, node)
|
||||
-- Access means read and subscribe
|
||||
local group = "";
|
||||
if node == namespaces.config then
|
||||
-- TODO: For some reason, this is failing
|
||||
--group = self.config["Configuration Node Access"];
|
||||
group = "owners";
|
||||
end
|
||||
|
||||
if group == "nobody" then
|
||||
return false;
|
||||
elseif group == "allowed" then
|
||||
return self:is_allowed(jid);
|
||||
elseif group == "admins" then
|
||||
return self:is_admin(jid) or self:is_owner(jid);
|
||||
elseif group == "owners" then
|
||||
return self:is_owner(jid);
|
||||
elseif group == "participants" then
|
||||
return self:is_participant(jid) or self:is_admin(jid) or self:is_owner(jid);
|
||||
end
|
||||
|
||||
return false;
|
||||
end
|
||||
|
||||
function Channel:may_update(jid, node)
|
||||
module:log("debug", "Update node %s", node);
|
||||
local group = "";
|
||||
-- TODO: Deal with avatars
|
||||
if node == namespaces.info then
|
||||
group = self.config["Information Node Update Rights"];
|
||||
elseif node == namespaces.config then
|
||||
-- TODO: Make this configurable
|
||||
group = "admins";
|
||||
elseif node == namespaces.avatar or node == namespaces.avatar_metadata then
|
||||
group = self.config["Avatar Nodes Update Rights"];
|
||||
end
|
||||
|
||||
if group == "admins" then
|
||||
return self:is_admin(jid) or self:is_owner(jid);
|
||||
elseif group == "owners" then
|
||||
return self:is_owner(jid);
|
||||
elseif group == "participants" then
|
||||
return self:is_participant(jid) or self:is_admin(jid) or self:is_owner(jid);
|
||||
elseif group == "allowed" then
|
||||
return self:is_allowed(jid);
|
||||
end
|
||||
|
||||
return false;
|
||||
end
|
||||
|
||||
function Channel:may_join(jid)
|
||||
-- Does the banned node exist?
|
||||
if helpers.in_array(self.nodes, namespaces.banned) then
|
||||
local host = jid_lib.host(jid);
|
||||
if helpers.in_array(self.banned, host) or helpers.in_array(self.banned, jid) then
|
||||
-- User or host is banned
|
||||
return false;
|
||||
end
|
||||
end
|
||||
|
||||
-- Does the allowed node exist?
|
||||
if helpers.in_array(self.nodes, namespaces.allowed) then
|
||||
local host = jid_lib.host(jid);
|
||||
if helpers.in_array(self.allowed, host) or helpers.in_array(self.allowed, jid) then
|
||||
-- User or host is allowed
|
||||
return true;
|
||||
else
|
||||
-- "Whitelist" is on but user or host is not on it.
|
||||
return false;
|
||||
end
|
||||
end
|
||||
|
||||
return true;
|
||||
end
|
||||
|
||||
function Channel:may(jid, action, data)
|
||||
if action == "subscribe" then
|
||||
return self:may_subscribe(jid, data, false);
|
||||
elseif action == "access" then
|
||||
return self:may_access(jid, data);
|
||||
elseif action == "update" then
|
||||
return self:may_update(jid, data);
|
||||
elseif action == "pm" then
|
||||
return self.config["Private Messages"];
|
||||
elseif action == "join" then
|
||||
return self:may_join(jid);
|
||||
elseif action == "invite" then
|
||||
if not (self:is_admin(jid) or self:is_owner(jid)) then
|
||||
return self.config["Participation Addition by Invitation from Participant"];
|
||||
else
|
||||
return true;
|
||||
end
|
||||
elseif action == "retract" then
|
||||
-- Is this the same user
|
||||
-- TODO: jid == data may not work when using SPIDs
|
||||
if jid == data then
|
||||
return self.config["User Message Retraction"];
|
||||
else
|
||||
local group = self.config["Administrator Message Retraction Rights"];
|
||||
if group == "nobody" then
|
||||
return false;
|
||||
elseif group == "admins" then
|
||||
return self:is_admin(jid) or self:is_owner(jid);
|
||||
elseif group == "owners" then
|
||||
return self:is_owner(jid);
|
||||
end
|
||||
|
||||
return false;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function Channel:broadcast_message(message, participant, archive)
|
||||
-- Broadcast a message stanza according to rules layed out by
|
||||
-- XEP-0369
|
||||
@ -368,10 +249,10 @@ end
|
||||
|
||||
function Channel:publish_participant(spid, participant)
|
||||
-- Publish a new participant on the service
|
||||
local srv = pep.get_pep_service(jid_lib.node(self.jid));
|
||||
|
||||
-- NOTE: This function has be to called *after* the new participant
|
||||
-- has been added to the channel.participants array
|
||||
local srv = self:get_pubsub_service();
|
||||
|
||||
srv:set_node_config(namespaces.participants,
|
||||
true,
|
||||
{ ["max_items"] = #self.participants });
|
||||
@ -384,40 +265,135 @@ function Channel:publish_participant(spid, participant)
|
||||
:tag("jid"):text(participant["jid"]));
|
||||
end
|
||||
|
||||
local default_channel_configuration = {
|
||||
["Last Change Made By"] = "";
|
||||
["Owner"] = {}; -- Filled in during creation
|
||||
["Administrator"] = {};
|
||||
["End of Life"] = "";
|
||||
["Nodes Present"] = {}; -- Filled in during creation
|
||||
["Messages Node Subscription"] = "participants";
|
||||
["Presence Node Subscription"] = "participants";
|
||||
["Participants Node Subscription"] = "participants";
|
||||
["Information Node Subscription"] = "participants";
|
||||
["Allowed Node Subscription"] = "admins";
|
||||
["Banned Node Subscription"] = "admins";
|
||||
["Configuration Node Access"] = "owners";
|
||||
["Information Node Update Rights"] = "admins";
|
||||
["Avatar Node Update Rights"] = "admins";
|
||||
["Open Presence"] = false;
|
||||
["Participants Must Provide Presence"] = false;
|
||||
["User Message Retraction"] = false;
|
||||
["Administrator Message Retraction Rights"] = "owners";
|
||||
["Participation Addition by Invitation from Participant"] = false;
|
||||
["Private Messages"] = true;
|
||||
["Mandatory Nicks"] = true;
|
||||
};
|
||||
function Channel:remove_participant(jid)
|
||||
-- Removes a user form the channel. May be a kick, may be a leave.
|
||||
local srv = self:get_pubsub_service();
|
||||
|
||||
local default_participant_configuration = {
|
||||
["JID Visibility"] = "never";
|
||||
["Private Messages"] = "allow";
|
||||
["Presence"] = "share";
|
||||
["vCard"] = "block";
|
||||
};
|
||||
-- Step 1: Unsubscribe from all subscribed nodes
|
||||
for _, node in ipairs(self.subscriptions[jid]) do
|
||||
srv:remove_subscription(node, true, jid);
|
||||
end
|
||||
self.subscriptions[jid] = nil;
|
||||
|
||||
-- Step 2: Remove affiliations to all nodes
|
||||
for _, node in ipairs(self.nodes) do
|
||||
srv:set_affiliation(node, true, jid, "outcast");
|
||||
end
|
||||
|
||||
-- Step 3: Remove jid as participant
|
||||
local participant = self:find_participant(jid);
|
||||
self.participants = array.filter(self.participants, function (p) return p.jid ~= jid end);
|
||||
|
||||
-- Step 4: Retract jid from participants node
|
||||
local spid = self:get_spid(jid);
|
||||
local notifier = st.stanza("retract", { id = spid });
|
||||
srv:retract(namespaces.participants, true, jid, notifier);
|
||||
self:save_state();
|
||||
end
|
||||
|
||||
function Channel:publish_info(srv)
|
||||
local timestamp = datetime.datetime(time.now());
|
||||
local info = st.stanza("item", { id = timestamp, xmlns = namespaces.pubsub })
|
||||
:add_child(lib_forms.mix_info:form({
|
||||
["FORM_TYPE"] = "hidden",
|
||||
["Name"] = self.name,
|
||||
["Description"] = self.description,
|
||||
["Contact"] = self.contacts
|
||||
}, "result"));
|
||||
srv:publish(namespaces.info, true, timestamp, info);
|
||||
end
|
||||
|
||||
|
||||
local function get_node_access_model(node, adhoc)
|
||||
-- TODO
|
||||
if adhoc then
|
||||
return "whitelist";
|
||||
else
|
||||
return "open";
|
||||
end
|
||||
end
|
||||
|
||||
local function get_node_max_items(node)
|
||||
-- TODO: Would be nice if we could just return "max"
|
||||
-- TODO: Handle all nodes
|
||||
if node == namespaces.messages then
|
||||
return 0;
|
||||
elseif node == namespaces.info or
|
||||
node == namespaces.config then
|
||||
return 1;
|
||||
end
|
||||
|
||||
return 256;
|
||||
end
|
||||
|
||||
local function channel_not_found(stanza)
|
||||
-- Wrapper for returning a "Channel-not-found" error stanza
|
||||
return st.error_reply(stanza,
|
||||
"cancel",
|
||||
"item-not-found",
|
||||
"The MIX channel was not found");
|
||||
end
|
||||
|
||||
function Channel:set_affiliation(node, target, role)
|
||||
-- Set the affiliation of target to node depending on what
|
||||
-- node it is and whether target is the creator or not
|
||||
local srv = self:get_pubsub_service();
|
||||
local affiliation = "member";
|
||||
if node == namespaces.presence then
|
||||
affiliation = "none";
|
||||
end
|
||||
|
||||
-- TODO(MIX-ADMIN): Also handle OWNER, ADMIN
|
||||
if role == "creator" then
|
||||
if node == namespaces.info or
|
||||
node == namespaces.allowed or
|
||||
node == namespaces.banned or
|
||||
node == namespaces.config or
|
||||
node == namespaces.avatar or
|
||||
node == namespaces.avatar_metadata then
|
||||
affiliation = "publisher";
|
||||
end
|
||||
end
|
||||
|
||||
srv:set_affiliation(node, true, target, affiliation);
|
||||
end
|
||||
|
||||
function Channel:may_subscribe(actor, node, joining)
|
||||
if joining then
|
||||
-- TODO: Is this true?
|
||||
return true;
|
||||
end
|
||||
|
||||
if self:is_participant(actor) then
|
||||
-- TODO(MIX-ADMIN): This is possible if the actor is an owner or admin
|
||||
return node ~= namespaces.config;
|
||||
end
|
||||
|
||||
return false;
|
||||
end
|
||||
|
||||
function Channel:may_publish(actor, node)
|
||||
return node ~= namespaces.presence and node ~= namespaces.messages;
|
||||
end
|
||||
|
||||
function Channel:may_retract(actor, node)
|
||||
-- TODO: Maybe put may_{publish, retract} together
|
||||
return node ~= namespaces.presence and node ~= namespaces.messages;
|
||||
end
|
||||
|
||||
function Channel:may_retrieve_items(actor, node)
|
||||
return node ~= namespaces.presence and node ~= namespaces.messages;
|
||||
end
|
||||
|
||||
function Channel:may_join(actor)
|
||||
-- TODO(MIX-ADMIN): Check the allowed and banned node
|
||||
return true;
|
||||
end
|
||||
|
||||
return {
|
||||
Channel = Channel,
|
||||
Participant = Participant,
|
||||
default_channel_configuration = default_channel_configuration,
|
||||
default_participant_configuration = default_participant_configuration,
|
||||
Channel = Channel;
|
||||
Participant = Participant;
|
||||
get_node_access_model = get_node_access_model;
|
||||
get_node_max_items = get_node_max_items;
|
||||
channel_not_found = channel_not_found;
|
||||
};
|
||||
|
@ -1,6 +1,5 @@
|
||||
-- TODO: Channel info's Contacts is empty
|
||||
-- TODO: Maybe reset affiliations to none instead of outcast
|
||||
-- TODO: Handle creation and deletion of avatar nodes when publishing to :config
|
||||
-- TODO: Somehow make the hosts aware of our "presence"
|
||||
|
||||
local host = module:get_host();
|
||||
if module:get_host_type() ~= "component" then
|
||||
@ -16,15 +15,15 @@ local time = require("util.time");
|
||||
local dataforms = require("util.dataforms");
|
||||
local array = require("util.array");
|
||||
local set = require("util.set");
|
||||
local pep = module:depends("pep");
|
||||
local lib_pubsub = module:require("pubsub");
|
||||
|
||||
local helpers = module:require("mix/helpers");
|
||||
local namespaces = module:require("mix/namespaces");
|
||||
local anon = module:require("mix/anon");
|
||||
local lib_forms = module:require("mix/forms");
|
||||
local lib_mix = module:require("mix/mix");
|
||||
|
||||
local mixlib = module:require("mix/mix");
|
||||
local Channel = mixlib.Channel;
|
||||
local Participant = mixlib.Participant;
|
||||
local Channel = lib_mix.Channel;
|
||||
local Participant = lib_mix.Participant;
|
||||
|
||||
-- Persistent data
|
||||
local persistent_channels = module:open_store("mix_channels", "keyval");
|
||||
@ -38,84 +37,15 @@ local restrict_channel_creation = module:get_option("restrict_local_channels", "
|
||||
local service_name = module:get_option("service_name", "Prosody MIX service");
|
||||
|
||||
-- MIX configuration
|
||||
local default_mix_nodes = array { namespaces.info, namespaces.participants, namespaces.messages, namespaces.config };
|
||||
|
||||
-- Dataforms
|
||||
-- MAM
|
||||
local mam_query_form = dataforms.new({
|
||||
{ name = "FORM_TYPE", type = "hidden", value = namespaces.mam },
|
||||
{ name = "with", type = "jid-single" },
|
||||
{ name = "start", type = "text-single" },
|
||||
{ name = "end", type = "text-single" },
|
||||
});
|
||||
-- MIX Core
|
||||
local mix_info_form = dataforms.new({
|
||||
{ name = "FORM_TYPE", type = "hidden", value = namespaces.mix_core },
|
||||
{ name = "Name", type = "text-single" },
|
||||
{ name = "Description", type = "text-single" },
|
||||
{ name = "Contact", type = "jid-multi" }
|
||||
});
|
||||
-- MIX-ADMIN stuff
|
||||
local mix_config_form = dataforms.new({
|
||||
{ name = "FORM_TYPE", type = "hidden", value = namespaces.mix_admin },
|
||||
{ name = "Last Change Made By", type = "jid-single" },
|
||||
{ name = "Owner", type = "jid-multi" },
|
||||
{ name = "Administrator", type = "jid-multi" },
|
||||
{ name = "End of Life", type = "text-single" },
|
||||
{ name = "Nodes Present", type = "list-multi" },
|
||||
{ name = "Message Node Subscription", type = "list-single" },
|
||||
{ name = "Presence Node Subscription", type = "list-single" },
|
||||
{ name = "Participants Node Subscription", type = "list-single" },
|
||||
{ name = "Information Node Subscription", type = "list-single" },
|
||||
{ name = "Allowed Node Subscription", type = "list-single" },
|
||||
{ name = "Banned Node Subscription", type = "list-single" },
|
||||
{ name = "Configuration Node Access", type = "list-single" },
|
||||
{ name = "Information Node Update Rights", type = "list-single" },
|
||||
{ name = "Avatar Node Update Rights", type = "list-single" },
|
||||
{ name = "Open Presence", type = "boolean" },
|
||||
{ name = "Participants Must Provide Presence", type = "boolean" },
|
||||
{ name = "User Message Retraction", type = "boolean" },
|
||||
{ name = "Administrator Message Retraction Rights", type = "list-single" },
|
||||
{ name = "Participation Addition by Invitation from Participant", type = "boolean" },
|
||||
{ name = "Private Messages", type = "boolean" },
|
||||
{ name = "Mandatory Nicks", type = "boolean "},
|
||||
});
|
||||
|
||||
local function mix_admin_node_to_value(node)
|
||||
local map = {
|
||||
[namespaces.messages] = "messages";
|
||||
[namespaces.participants] = "participants";
|
||||
[namespaces.info] = "info";
|
||||
[namespaces.allowed] = "allowed";
|
||||
[namespaces.banned] = "banned";
|
||||
};
|
||||
|
||||
return "'"..map[node].."'";
|
||||
end
|
||||
|
||||
local function mix_admin_value_to_node(value)
|
||||
local map = {
|
||||
["'messages'"] = namespaces.messages;
|
||||
["'participants'"] = namespaces.participants;
|
||||
["'info'"] = namespaces.info;
|
||||
["'allowed'"] = namespaces.allowed;
|
||||
["'banned'"] = namespaces.banned;
|
||||
};
|
||||
|
||||
return map[value]
|
||||
end
|
||||
|
||||
module:depends("disco");
|
||||
module:add_identity("conference", "mix", service_name);
|
||||
module:add_feature("http://jabber.org/protocol/disco#info");
|
||||
module:add_feature(namespaces.mix_core);
|
||||
local default_mix_nodes = array { namespaces.info, namespaces.participants, namespaces.messages };
|
||||
|
||||
local channels = {};
|
||||
|
||||
local function get_channel(channel_jid)
|
||||
local function find_channel(channel_jid)
|
||||
-- Return the channel object from the channels array for which the
|
||||
-- JID matches. If none is found, returns -1, nil
|
||||
return helpers.find(channels, function(c) return c.jid == channel_jid; end);
|
||||
local _, channel = helpers.find(channels, function(c) return c.jid == channel_jid; end);
|
||||
return channel;
|
||||
end
|
||||
|
||||
local function save_channels()
|
||||
@ -154,56 +84,77 @@ function module.load()
|
||||
module:log("info", "Loading MIX channels done.");
|
||||
end
|
||||
|
||||
local function channel_not_found(stanza)
|
||||
-- Wrapper for returning a "Channel-not-found" error stanza
|
||||
return st.error_reply(stanza,
|
||||
"cancel",
|
||||
"item-not-found",
|
||||
"The MIX channel was not found");
|
||||
-- PubSub logic
|
||||
local function pubsub_not_implemented(stanza, feature)
|
||||
local reply = st.error_reply(stanza, "cancel", "feature-not-implemented");
|
||||
reply:tag("unsupported", {
|
||||
xmlns = namespaces.pubsub_error;
|
||||
feature = feature;
|
||||
});
|
||||
|
||||
return reply;
|
||||
end
|
||||
|
||||
module:hook("host-disco-items", function(event)
|
||||
module:log("debug", "host-disco-items called");
|
||||
local reply = event.reply;
|
||||
for _, channel in pairs(channels) do
|
||||
-- Adhoc channels are supposed to be invisible
|
||||
if not channel.adhoc then
|
||||
reply:tag("item", { jid = channel.jid }):up();
|
||||
end
|
||||
end
|
||||
end);
|
||||
local function handle_pubsub_iq(event)
|
||||
local stanza, origin = event.stanza, event.origin;
|
||||
local from = jid.bare(stanza.attr.from);
|
||||
|
||||
module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", function(event)
|
||||
module:log("debug", "IQ-GET disco#items");
|
||||
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
if stanza:get_child("query", "http://jabber.org/protocol/disco#items").attr.node ~= "mix" then
|
||||
origin.send(st.error_reply(stanza, "modify", "bad-request"));
|
||||
return true;
|
||||
end
|
||||
|
||||
-- TODO: Maybe here we should check if the user has permissions to get infos
|
||||
-- about the channel before saying that it doesn't exist to prevent creating
|
||||
-- an oracle.
|
||||
local _, channel = get_channel(stanza.attr.to);
|
||||
local channel = find_channel(stanza.attr.to);
|
||||
if not channel then
|
||||
origin.send(channel_not_found(stanza));
|
||||
module:log("error", "PubSub was used for unknown channel");
|
||||
origin:send(st.error_reply(stanza, "cancel", "item-not-found"));
|
||||
return;
|
||||
end;
|
||||
|
||||
-- Certain actions we do not want the user to perform, so we need to
|
||||
-- catch them here.
|
||||
local pubsub = stanza:get_child("pubsub", namespaces.pubsub);
|
||||
if pubsub then
|
||||
local items = pubsub:get_child("items");
|
||||
if items then
|
||||
if not channel:may_retrieve_items(from, items.attr.node) then
|
||||
origin:send(pubsub_not_implemented(stanza, "retrieve-items"));
|
||||
return true;
|
||||
end
|
||||
|
||||
if not channel:is_participant(jid.bare(stanza.attr.from)) then
|
||||
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
|
||||
local reply = st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = "mix" });
|
||||
for _, node in pairs(channel.nodes) do
|
||||
reply:tag("item", { jid = channel.jid, node = node }):up();
|
||||
local publish = pubsub:get_child("publish");
|
||||
if publish then
|
||||
if not channel:may_publish(from, publish.attr.node) then
|
||||
origin:send(pubsub_not_implemented(stanza, "publish"));
|
||||
return true;
|
||||
end
|
||||
end
|
||||
|
||||
origin.send(reply);
|
||||
local retract = pubsub:get_child("retract");
|
||||
if retract then
|
||||
if not channel:may_retract(from, retract.attr.node) then
|
||||
origin:send(pubsub_not_implemented(stanza, "delete-items"));
|
||||
return true;
|
||||
end);
|
||||
end
|
||||
end
|
||||
|
||||
-- We generally do not allow deletion, creation or configuration of
|
||||
-- nodes. (Un)Subscribing is not allowed as this is managed via
|
||||
-- interaction with the MIX host.
|
||||
-- NOTE: Checking for <delete> is not needed as no user is ever set as
|
||||
-- owner
|
||||
if pubsub:get_child("configure") then
|
||||
origin:send(pubsub_not_implemented(stanza, "config-node"));
|
||||
return true;
|
||||
end
|
||||
if pubsub:get_child("unsubscibe") or pubsub:get_child("subscribe") then
|
||||
origin:send(st.error_reply(stanza, "auth", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: Process publishing to :config
|
||||
|
||||
local service = channel:get_pubsub_service();
|
||||
return lib_pubsub.handle_pubsub_iq(event, service);
|
||||
end
|
||||
module:hook("iq/bare/"..namespaces.pubsub..":pubsub", handle_pubsub_iq, 1000);
|
||||
|
||||
local function can_create_channels(user)
|
||||
-- Returns true when the jid is allowed to create MIX channels. False otherwise.
|
||||
@ -234,6 +185,52 @@ local function can_create_channels(user)
|
||||
return true;
|
||||
end
|
||||
|
||||
|
||||
-- Disco related functionality
|
||||
module:hook("host-disco-items", function(event)
|
||||
module:log("debug", "host-disco-items called");
|
||||
local reply = event.reply;
|
||||
for _, channel in pairs(channels) do
|
||||
-- Adhoc channels are supposed to be invisible
|
||||
if not channel.adhoc then
|
||||
reply:tag("item", { jid = channel.jid }):up();
|
||||
end
|
||||
end
|
||||
end);
|
||||
|
||||
local function handle_channel_disco_items(event)
|
||||
module:log("debug", "IQ-GET disco#items");
|
||||
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
if stanza:get_child("query", "http://jabber.org/protocol/disco#items").attr.node ~= "mix" then
|
||||
origin.send(st.error_reply(stanza, "modify", "bad-request"));
|
||||
return true;
|
||||
end
|
||||
|
||||
-- TODO: Maybe here we should check if the user has permissions to get infos
|
||||
-- about the channel before saying that it doesn't exist to prevent creating
|
||||
-- an oracle.
|
||||
local channel = find_channel(stanza.attr.to);
|
||||
if not channel then
|
||||
origin.send(lib_mix.channel_not_found(stanza));
|
||||
return true;
|
||||
end
|
||||
|
||||
if not channel:is_participant(jid.bare(stanza.attr.from)) then
|
||||
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
|
||||
local reply = st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#items", node = "mix" });
|
||||
for _, node in pairs(channel.nodes) do
|
||||
reply:tag("item", { jid = channel.jid, node = node }):up();
|
||||
end
|
||||
|
||||
origin.send(reply);
|
||||
return true;
|
||||
end
|
||||
module:hook("iq/bare/http://jabber.org/protocol/disco#items:query", handle_channel_disco_items);
|
||||
|
||||
module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(event)
|
||||
module:log("debug", "IQ-GET host disco#info");
|
||||
|
||||
@ -250,15 +247,15 @@ module:hook("iq-get/host/http://jabber.org/protocol/disco#info:query", function(
|
||||
end
|
||||
origin.send(reply);
|
||||
return true;
|
||||
end);
|
||||
end, 1000);
|
||||
|
||||
module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", function(event)
|
||||
local function handle_channel_disco_info(event)
|
||||
module:log("debug", "IQ-GET disco#info");
|
||||
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local _, channel = get_channel(stanza.attr.to);
|
||||
local channel = find_channel(stanza.attr.to);
|
||||
if not channel then
|
||||
origin.send(channel_not_found(stanza));
|
||||
origin.send(lib_mix.channel_not_found(stanza));
|
||||
return true;
|
||||
end
|
||||
local reply = st.reply(stanza):tag("query", { xmlns = "http://jabber.org/protocol/disco#info" });
|
||||
@ -270,15 +267,16 @@ module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", function(
|
||||
|
||||
origin.send(reply);
|
||||
return true;
|
||||
end);
|
||||
end
|
||||
module:hook("iq-get/bare/http://jabber.org/protocol/disco#info:query", handle_channel_disco_info);
|
||||
|
||||
module:hook("iq-set/bare/"..namespaces.mam..":query", function(event)
|
||||
local stanza, origin = event.stanza, event.origin;
|
||||
local channel_jid = stanza.attr.to;
|
||||
local j, channel = get_channel(channel_jid);
|
||||
if j == -1 then
|
||||
local channel = find_channel(channel_jid);
|
||||
if not channel then
|
||||
-- TODO: Is this correct?
|
||||
origin.send(channel_not_found(stanza));
|
||||
origin.send(lib_mix.channel_not_found(stanza));
|
||||
return true;
|
||||
end
|
||||
|
||||
@ -294,7 +292,7 @@ module:hook("iq-set/bare/"..namespaces.mam..":query", function(event)
|
||||
local x = query:get_child("x", "jabber:x:data");
|
||||
if x ~= nil then
|
||||
-- TODO: Error handling
|
||||
local form, err = mam_query_form:data(x);
|
||||
local form, err = lib_forms.mam_query:data(x);
|
||||
|
||||
-- Validate
|
||||
if (form["start"] and not form["end"]) or (not form["start"] and form["end"]) then
|
||||
@ -351,14 +349,14 @@ module:hook("iq-set/bare/"..namespaces.mix_core..":leave", function(event)
|
||||
module:log("debug", "MIX leave received");
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local from = jid.bare(stanza.attr.from);
|
||||
local _, channel = get_channel(stanza.attr.to);
|
||||
local channel = find_channel(stanza.attr.to);
|
||||
if not channel then
|
||||
origin.send(channel_not_found(stanza));
|
||||
origin.send(lib_mix.channel_not_found(stanza));
|
||||
return true;
|
||||
end
|
||||
|
||||
local j, participant = channel:find_participant(from);
|
||||
if j == -1 then
|
||||
local participant = channel:find_participant(from);
|
||||
if not participant then
|
||||
origin.send(st.error_reply(stanza,
|
||||
"cancel",
|
||||
"forbidden",
|
||||
@ -381,21 +379,21 @@ module:hook("iq-set/bare/"..namespaces.mix_core..":join", function(event)
|
||||
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local from = jid.bare(stanza.attr.from);
|
||||
local _, channel = get_channel(stanza.attr.to);
|
||||
local channel = find_channel(stanza.attr.to);
|
||||
if not channel then
|
||||
origin:send(channel_not_found(stanza));
|
||||
origin:send(lib_mix.channel_not_found(stanza));
|
||||
return true;
|
||||
end
|
||||
|
||||
-- Prevent the user from joining multiple times
|
||||
local j, _ = channel:find_participant(from);
|
||||
if j ~= -1 then
|
||||
local participant = channel:find_participant(from);
|
||||
if participant then
|
||||
module:send(st.error_reply(stanza, "cancel", "bad-request", "User already joined"));
|
||||
return true;
|
||||
end
|
||||
|
||||
-- Is the user allowed to join?
|
||||
if not channel:may(from, "join", nil) then
|
||||
if not channel:may_join(from) then
|
||||
origin:send(st.error_reply(stanza, "cancel", "forbidden", "User or host is banned"));
|
||||
return true;
|
||||
end
|
||||
@ -406,11 +404,7 @@ module:hook("iq-set/bare/"..namespaces.mix_core..":join", function(event)
|
||||
local join = stanza:get_child("join", namespaces.mix_core);
|
||||
local nick_tag = join:get_child("nick");
|
||||
|
||||
-- Check if the channel has mandatory nicks
|
||||
if channel.config["Mandatory Nicks"] and not nick_tag then
|
||||
origin:send(st.error_reply(stanza, "modify", "forbidden", "Nicks are mandatory"));
|
||||
return true;
|
||||
end
|
||||
-- TODO: Check if the channel has mandatory nicks
|
||||
|
||||
local nick;
|
||||
if not nick_tag then
|
||||
@ -420,7 +414,7 @@ module:hook("iq-set/bare/"..namespaces.mix_core..":join", function(event)
|
||||
end
|
||||
module:log("debug", "User joining as nick %s", nick);
|
||||
|
||||
local srv = pep.get_pep_service(jid.node(channel.jid));
|
||||
local srv = channel:get_pubsub_service(jid.node(channel.jid));
|
||||
local nodes = {};
|
||||
local has_subscribed_once = false;
|
||||
local first_error = nil;
|
||||
@ -429,15 +423,6 @@ module:hook("iq-set/bare/"..namespaces.mix_core..":join", function(event)
|
||||
-- May the user subscribe to the node?
|
||||
module:log("debug", "Subscribing user to node %s", subscribe.attr.node);
|
||||
if channel:may_subscribe(from, subscribe.attr.node, true) then
|
||||
-- Set the correct affiliation
|
||||
local affiliation = "";
|
||||
if owner_or_admin then
|
||||
affiliation = "publisher";
|
||||
else
|
||||
affiliation = "member";
|
||||
end
|
||||
srv:set_affiliation(subscribe.attr.node, true, from, affiliation);
|
||||
|
||||
local ok, err = srv:add_subscription(subscribe.attr.node, true, from);
|
||||
if not ok then
|
||||
module:log("debug", "Error during subscription: %s", err);
|
||||
@ -447,15 +432,14 @@ module:hook("iq-set/bare/"..namespaces.mix_core..":join", function(event)
|
||||
if first_error ~= nil then
|
||||
first_error = err;
|
||||
end
|
||||
|
||||
-- Reset affiliation
|
||||
srv:set_affiliation(subscribe.attr.node, true, from, "outcast");
|
||||
|
||||
else
|
||||
table.insert(nodes, subscribe.attr.node);
|
||||
reply:tag("subscribe", { node = subscribe.attr.node }):up();
|
||||
has_subscribed_once = true;
|
||||
end
|
||||
|
||||
-- Set the correct affiliation
|
||||
channel:set_affiliation(subscribe.attr.node, from, "member");
|
||||
else
|
||||
module:log("debug", "Error during subscription: may_subscribe returned false");
|
||||
if first_error ~= nil then
|
||||
@ -470,27 +454,9 @@ module:hook("iq-set/bare/"..namespaces.mix_core..":join", function(event)
|
||||
return true;
|
||||
end
|
||||
|
||||
-- TODO: Make the default configurable
|
||||
local config = mixlib.default_participant_configuration;
|
||||
local x = join:get_child("x");
|
||||
if x ~= nil then
|
||||
-- TODO: Error handling?
|
||||
local form, err = anon.form:data(x);
|
||||
if form["JID Visibility"] then
|
||||
config["JID Visibility"] = form["JID Visibility"];
|
||||
end
|
||||
if form["Private Messages"] then
|
||||
config["Private Messages"] = form["Private Messages"];
|
||||
end
|
||||
if form["Presence"] then
|
||||
config["Presence"] = form["Presence"];
|
||||
end
|
||||
if form["vCard"] then
|
||||
config["vCard"] = form["vCard"];
|
||||
end
|
||||
end
|
||||
-- TODO: Participant configuration
|
||||
|
||||
local participant = Participant:new(jid.bare(from), nick, config);
|
||||
local participant = Participant:new(jid.bare(from), nick, {});
|
||||
channel.subscriptions[from] = nodes;
|
||||
table.insert(channel.participants, participant)
|
||||
channel:set_spid(jid.bare(stanza.attr.from), spid);
|
||||
@ -510,14 +476,14 @@ module:hook("iq-set/bare/"..namespaces.mix_core..":setnick", function(event)
|
||||
module:log("debug", "MIX setnick received");
|
||||
local origin, stanza = event.origin, event.stanza;
|
||||
local from = jid.bare(stanza.attr.from);
|
||||
local _, channel = get_channel(stanza.attr.to);
|
||||
local channel = find_channel(stanza.attr.to);
|
||||
if not channel then
|
||||
origin.send(channel_not_found(stanza));
|
||||
origin.send(lib_mix.channel_not_found(stanza));
|
||||
return true;
|
||||
end
|
||||
|
||||
local j, participant = channel:find_participant(from);
|
||||
if j == -1 then
|
||||
local participant = channel:find_participant(from);
|
||||
if not participant then
|
||||
channel:debug_print();
|
||||
module:log("debug", "%s is not a participant in %s", from, channel.jid);
|
||||
return true;
|
||||
@ -532,7 +498,7 @@ module:hook("iq-set/bare/"..namespaces.mix_core..":setnick", function(event)
|
||||
end
|
||||
|
||||
-- Change the nick
|
||||
channel.participants[j].nick = nick:get_text();
|
||||
participant.nick = nick:get_text();
|
||||
-- Inform all other members
|
||||
channel:publish_participant(channel:get_spid(participant.jid), participant);
|
||||
|
||||
@ -545,19 +511,6 @@ module:hook("iq-set/bare/"..namespaces.mix_core..":setnick", function(event)
|
||||
return true;
|
||||
end);
|
||||
|
||||
function Channel:publish_info(srv)
|
||||
local timestamp = datetime.datetime(time.now());
|
||||
-- TODO: Check if this is correct
|
||||
local info = st.stanza("item", { id = timestamp, xmlns = "http://jabber.org/protocol/pubsub" })
|
||||
:add_child(mix_info_form:form({
|
||||
["FORM_TYPE"] = "hidden",
|
||||
["Name"] = self.name,
|
||||
["Description"] = self.description,
|
||||
["Contacts"] = self.contacts
|
||||
}, "result"));
|
||||
srv:publish(namespaces.info, true, timestamp, info);
|
||||
end
|
||||
|
||||
local function create_channel(node, creator, adhoc)
|
||||
-- TODO: Now all properties from the admin dataform are covered
|
||||
local channel = Channel:new(string.format("%s@%s", node, host), -- Channel JID
|
||||
@ -572,54 +525,23 @@ local function create_channel(node, creator, adhoc)
|
||||
adhoc, -- Is channel an AdHoc channel
|
||||
{}, -- Allowed
|
||||
{}, -- Banned
|
||||
mixlib.default_channel_configuration, -- Channel config
|
||||
lib_mix.default_channel_configuration, -- Channel config
|
||||
{}); -- Present nodes
|
||||
|
||||
-- Create the PEP nodes
|
||||
local srv = pep.get_pep_service(node);
|
||||
-- MIX-CORE
|
||||
-- Create the PubSub nodes
|
||||
local srv = channel:get_pubsub_service();
|
||||
for _, psnode in ipairs(default_mix_nodes) do
|
||||
srv:create(psnode, true, {
|
||||
["access_model"] = "whitelist",
|
||||
["persist_items"] = true,
|
||||
-- NOTE: Our custom PubSub service is persistent only, so we don't
|
||||
-- need to explicitly set it
|
||||
["access_model"] = lib_mix.get_node_access_model(psnode, adhoc);
|
||||
["max_items"] = lib_mix.get_node_max_items(psnode);
|
||||
});
|
||||
channel:set_affiliation(psnode, creator, "creator");
|
||||
table.insert(channel.nodes, psnode);
|
||||
end
|
||||
|
||||
channel:publish_info(srv);
|
||||
|
||||
-- MIX-ADMIN
|
||||
local admin_nodes = array { namespaces.banned };
|
||||
if adhoc then
|
||||
admin_nodes:push(namespaces.allowed);
|
||||
end
|
||||
for _, psnode in pairs(admin_nodes) do
|
||||
srv:create(psnode, true, {
|
||||
["access_model"] = "whitelist",
|
||||
["persist_items"] = true,
|
||||
});
|
||||
srv:set_affiliation(namespaces.allowed, true, creator, "publish");
|
||||
end
|
||||
if adhoc then
|
||||
-- Allow the creator to join
|
||||
channel:allow_jid(creator);
|
||||
end
|
||||
|
||||
-- Some bookkeeping
|
||||
local nodes = default_mix_nodes + admin_nodes;
|
||||
local node_names = array(nodes):filter(function (element)
|
||||
return element ~= namespaces.config;
|
||||
end):map(mix_admin_node_to_value):push("'avatar'");
|
||||
-- NOTE: This is to prevent cycles during serialization (apparently)
|
||||
channel.nodes = {table.unpack(nodes)};
|
||||
|
||||
local config = mixlib.default_channel_configuration;
|
||||
config["Owner"] = { creator };
|
||||
config["Nodes Present"] = node_names;
|
||||
channel.config = config;
|
||||
srv:publish(namespaces.config,
|
||||
true,
|
||||
"",
|
||||
st.stanza("item", { xmlns = "http://jabber.org/protocol/pubsub" })
|
||||
:add_child(mix_config_form:form(config, "result")));
|
||||
table.insert(channels, channel);
|
||||
end
|
||||
|
||||
@ -640,9 +562,8 @@ module:hook("iq-set/host/"..namespaces.mix_core..":create", function(event)
|
||||
-- Create non-adhoc channel
|
||||
module:log("debug", "Attempting to create channel %s", create.attr.channel);
|
||||
node = create.attr.channel;
|
||||
local i, _ = get_channel(create.attr.channel.."@"..stanza.attr.to);
|
||||
module:log("debug", "Channel index %s", i);
|
||||
if i ~= -1 then
|
||||
local channel = find_channel(create.attr.channel.."@"..stanza.attr.to);
|
||||
if channel then
|
||||
origin.send(st.error_reply(stanza,
|
||||
"cancel",
|
||||
"conflict",
|
||||
@ -655,8 +576,8 @@ module:hook("iq-set/host/"..namespaces.mix_core..":create", function(event)
|
||||
-- Create adhoc channel
|
||||
while (true) do
|
||||
node = id.short();
|
||||
local i, _ = get_channel(string.format("%s@%s", node, host));
|
||||
if i == -1 then
|
||||
local ch = find_channel(string.format("%s@%s", node, host));
|
||||
if not ch then
|
||||
break;
|
||||
end
|
||||
end
|
||||
@ -680,25 +601,31 @@ module:hook("iq-set/host/"..namespaces.mix_core..":destroy", function(event)
|
||||
local destroy = stanza:get_child("destroy", namespaces.mix_core);
|
||||
local node = destroy.attr.channel;
|
||||
local node_jid = string.format("%s@%s", node, host);
|
||||
local i, channel = get_channel(node_jid);
|
||||
local channel = find_channel(node_jid);
|
||||
if not channel then
|
||||
origin.send(channel_not_found(stanza));
|
||||
origin.send(lib_mix.channel_not_found(stanza));
|
||||
return true;
|
||||
end
|
||||
|
||||
if not channel:is_owner(from) then
|
||||
-- TODO(MIX-ADMIN): Check if the user is the owner of the channel
|
||||
-- Until then, we just check if the user is in the contacts
|
||||
if helpers.find_str(channel.contacts, from) == -1 then
|
||||
origin.send(st.error_reply(stanza, "cancel", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
|
||||
-- Remove all registered nodes
|
||||
local srv = pep.get_pep_service(node);
|
||||
for _, pep_node in pairs(channel.nodes) do
|
||||
srv:delete(pep_node, true);
|
||||
if module:fire_event("mix-destroy-channel", { channel = channel }) then
|
||||
return true;
|
||||
end
|
||||
table.remove(channels, i);
|
||||
|
||||
module:fire_event("mix-destroy-channel", { channel = channel });
|
||||
-- Remove all registered nodes
|
||||
local srv = channel:get_pubsub_service();
|
||||
for _, psnode in pairs(channel.nodes) do
|
||||
srv:delete(psnode, true);
|
||||
end
|
||||
channels = array.filter(channels, function (c) return c.jid ~= node_jid end);
|
||||
|
||||
module:fire_event("mix-channel-destroyed", { channel = channel });
|
||||
module:log("debug", "Channel %s destroyed", node);
|
||||
|
||||
origin.send(st.reply(stanza));
|
||||
@ -715,14 +642,14 @@ module:hook("message/bare", function(event)
|
||||
end
|
||||
|
||||
local from = jid.bare(stanza.attr.from);
|
||||
local _, channel = get_channel(stanza.attr.to);
|
||||
local channel = find_channel(stanza.attr.to);
|
||||
if not channel then
|
||||
origin.send(channel_not_found(stanza));
|
||||
origin.send(lib_mix.channel_not_found(stanza));
|
||||
return true;
|
||||
end
|
||||
|
||||
local j, participant = channel:find_participant(from);
|
||||
if j == -1 then
|
||||
local participant = channel:find_participant(from);
|
||||
if not participant then
|
||||
origin.send(st.error_reply(stanza, "cancel", "forbidden", "Not a participant"));
|
||||
return true;
|
||||
end
|
||||
@ -733,274 +660,3 @@ module:hook("message/bare", function(event)
|
||||
channel:broadcast_message(stanza, participant, message_archive);
|
||||
return true;
|
||||
end);
|
||||
|
||||
local function handle_new_node(srv, channel, node)
|
||||
if node == namespaces.allowed then
|
||||
-- If the allowed node is created, make sure to add the owners
|
||||
-- as allowed
|
||||
|
||||
channel.allowed = {};
|
||||
for _, owner in pairs(channel.owners) do
|
||||
array.push(channel.allowed, owner)
|
||||
srv:publish(namespaces.allowed, true, owner, nil);
|
||||
end
|
||||
|
||||
-- Remove all non-allowed participants
|
||||
for _, participant in channel.participants do
|
||||
if not channel:is_allowed(participant.jid) then
|
||||
channel:remove_participant(participant.jid);
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- MIX Admin events
|
||||
local function handle_pubsub_publish(event, publish, channel)
|
||||
local node = publish.attr.node;
|
||||
local from = jid.bare(event.stanza.attr.from);
|
||||
local item = publish:get_child("item");
|
||||
|
||||
-- TODO: Deal with avatars
|
||||
-- TODO: Should we trust the clients to behave?
|
||||
-- local srv = pep.get_pep_service(jid.node(channel.jid));
|
||||
if node == namespaces.config then
|
||||
if not channel:may(from, "update", namespaces.config) then
|
||||
-- TODO: This does not work
|
||||
event.origin:send(st.error_reply(publish, "auth", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
|
||||
local config, err = mix_config_form:data(item);
|
||||
-- TODO: Error handling?
|
||||
|
||||
local srv = pep.get_pep_service(jid.node(channel.jid));
|
||||
-- TODO: Check this over
|
||||
local removed_admins = set.new(channel.administrators):difference(set.new(config["Administrator"]));
|
||||
local removed_owners = set.new(channel.owners):difference(set.new(config["Owner"]));
|
||||
local new_node_names = set.new(config["Nodes Present"]):difference(set.new(channel.nodes));
|
||||
local removed_node_names = set.new(channel.nodes):difference(set.new(config["Nodes Present"]));
|
||||
local new_nodes = array.map(new_node_names, mix_admin_value_to_node); -- TODO
|
||||
local removed_nodes = array.map(removed_node_names, mix_admin_value_to_node); -- TODO
|
||||
|
||||
-- TODO: Verify that this is actually safe
|
||||
for _, jid_ in pairs(array.append(removed_admins, removed_owners)) do
|
||||
for _, psnode in pairs(channel.nodes) do
|
||||
srv:set_affiliation(psnode, true, jid_, "member");
|
||||
end
|
||||
end
|
||||
|
||||
-- TODO: Apply this to the PubSub publish
|
||||
config["Last Change Made By"] = jid.bare(event.stanza.attr.from);
|
||||
channel.owners = config["Owner"];
|
||||
channel.administrators = config["Administrator"];
|
||||
channel.config = config;
|
||||
channel.nodes = config["Nodes Present"]
|
||||
|
||||
-- Remove all nodes that are not existant anymore...
|
||||
for _, removed_node in pairs(removed_nodes) do
|
||||
for participant, subscriptions in channel.participants do
|
||||
local j, _ = helpers.find(subscriptions, removed_node);
|
||||
if j ~= -1 then
|
||||
srv:remove_subscription(removed_node, true, from);
|
||||
table.remove(participant.subscriptions, j);
|
||||
end
|
||||
end
|
||||
|
||||
if removed_node == namespaces.allowed then
|
||||
channel.allowed = nil;
|
||||
elseif removed_node == namespaces.banned then
|
||||
channel.banned = nil;
|
||||
end
|
||||
end
|
||||
-- ... and create all new ones.
|
||||
for _, new_node in pairs(new_nodes) do
|
||||
srv:create(new_node, true, {
|
||||
["access_model"] = "whitelist",
|
||||
["persist_items"] = true,
|
||||
});
|
||||
for participant, _ in channel.participants do
|
||||
local affiliation = "";
|
||||
if channel:is_admin(participant.jid) or channel:is_owner(participant.jid) then
|
||||
affiliation = "publisher";
|
||||
else
|
||||
affiliation = "member";
|
||||
end
|
||||
|
||||
srv:set_affiliation(new_node, true, affiliation);
|
||||
end
|
||||
|
||||
handle_new_node(srv, channel, new_node);
|
||||
end
|
||||
|
||||
for _, jid_ in pairs(array.append(channel.owners, channel.administrators)) do
|
||||
for _, psnode in pairs(channel.nodes) do
|
||||
srv:set_affiliation(psnode, true, jid_, "publisher");
|
||||
end
|
||||
end
|
||||
elseif node == namespaces.info then
|
||||
if not channel:may(from, "update", namespaces.info) then
|
||||
-- TODO: This does not work
|
||||
event.origin:send(st.error_reply(publish, "auth", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
|
||||
local info, err = mix_info_form:data(item);
|
||||
-- TODO: Error handling
|
||||
if info["Name"] then
|
||||
channel.name = info["Name"];
|
||||
end
|
||||
if info["Description"] then
|
||||
channel.description = info["Description"];
|
||||
end
|
||||
if info["Contact"] then
|
||||
channel.contacts = info["Contact"];
|
||||
end
|
||||
|
||||
module:log("debug", "Channel info updated");
|
||||
elseif node == namespaces.banned then
|
||||
channel:ban_jid(publish:get_child("item").attr.id);
|
||||
elseif node == namespaces.allowed then
|
||||
channel:allow_jid(publish:get_child("item").attr.id);
|
||||
elseif node == namespaces.participants or node == namespaces.messages then
|
||||
-- We don't want admins or owners to publish to these nodes as they
|
||||
-- are managed by the module
|
||||
local reply = st.error_reply(event.stanza, "cancel", "feature-not-implemented");
|
||||
reply:tag("unsupported", {
|
||||
xmlns = "http://jabber.org/protocol/pubsub#errors",
|
||||
feature = "publish",
|
||||
});
|
||||
event.origin:send(reply);
|
||||
return true;
|
||||
end
|
||||
|
||||
-- Let the actual PubSub implementation handle the rest
|
||||
channel:save_state();
|
||||
end
|
||||
|
||||
local function handle_pubsub_retract(event, retract, channel)
|
||||
local node = retract.attr.node;
|
||||
local item = retract:get_child("item");
|
||||
if node == namespaces.banned then
|
||||
channel.banned = array.filter(channel.banned, function (element)
|
||||
return element ~= item.attr.id;
|
||||
end);
|
||||
elseif node == namespaces.allowed then
|
||||
channel.allowed = array.filter(channel.allowed, function (element)
|
||||
return element ~= item.attr.id;
|
||||
end);
|
||||
elseif node == namespaces.participants then
|
||||
local reply = st.error_reply(event.stanza, "cancel", "feature-not-implemented");
|
||||
reply:tag("unsupported", {
|
||||
xmlns = "http://jabber.org/protocol/pubsub#errors",
|
||||
feature = "delete-items"
|
||||
});
|
||||
event.origin:send(reply);
|
||||
return true;
|
||||
end
|
||||
|
||||
channel:save_state();
|
||||
end
|
||||
|
||||
local function handle_pubsub_subscribe(event, subscribe, channel)
|
||||
local from = jid.bare(event.stanza.attr.from);
|
||||
local origin = event.origin;
|
||||
local node = subscribe.attr.node;
|
||||
|
||||
-- Check for node existance
|
||||
local i, _ = helpers.find(channel.nodes, node);
|
||||
if i == -1 then
|
||||
-- Let the actual PubSub implementation handle it
|
||||
return;
|
||||
end
|
||||
|
||||
if not channel:may(from, "subscribe", node) then
|
||||
origin:send(st.error_reply(event.stanza, "auth", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
|
||||
table.insert(channel.subscriptions[from], node);
|
||||
channel:save_state();
|
||||
-- The rest is handled by the actual PubSub implementation
|
||||
end
|
||||
|
||||
local function handle_pubsub_unsubscribe(event, subscribe, channel)
|
||||
local from = jid.bare(event.stanza.attr.from);
|
||||
local origin = event.origin;
|
||||
local node = subscribe.attr.node;
|
||||
|
||||
-- Check for node existance
|
||||
local i, _ = helpers.find(channel.nodes, node);
|
||||
if i == -1 then
|
||||
-- Let the actual PubSub implementation handle it
|
||||
return;
|
||||
end
|
||||
|
||||
if not channel:may(from, "subscribe", node) then
|
||||
origin:send(st.error_reply(event.stanza, "auth", "forbidden"));
|
||||
return true;
|
||||
end
|
||||
|
||||
local j, _ = helpers.find(channel.subscriptions[from], node);
|
||||
if j == -1 then
|
||||
local errstanza = st.error_reply(event.stanza, "cancel", "unexpected-request");
|
||||
errstanza:tag("not-subscribed", { xmlns = "http://jabber.org/protocol/pubsub#errors" });
|
||||
origin:send(errstanza);
|
||||
return true;
|
||||
end
|
||||
table.remove(channel.subscriptions[from], j);
|
||||
channel:save_state();
|
||||
-- The rest is handled by the actual PubSub implementation
|
||||
end
|
||||
|
||||
local function handle_pubsub_items(event, items, channel)
|
||||
local node = items.attr.node;
|
||||
local from = jid.bare(event.stanza.attr.from);
|
||||
if node == namespaces.config then
|
||||
if not channel:may(from, "access", node) then
|
||||
return true;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module:hook("iq-get/bare/http://jabber.org/protocol/pubsub:pubsub", function(event)
|
||||
local stanza = event.stanza;
|
||||
local _, channel = get_channel(stanza.attr.to);
|
||||
if not channel then
|
||||
return;
|
||||
end
|
||||
|
||||
local pubsub = stanza:get_child("pubsub", "http://jabber.org/protocol/pubsub");
|
||||
local items = pubsub:get_child("items");
|
||||
if items then
|
||||
return handle_pubsub_items(event, items, channel);
|
||||
end
|
||||
end, 1000);
|
||||
|
||||
module:hook("iq-set/bare/http://jabber.org/protocol/pubsub:pubsub", function(event)
|
||||
local stanza = event.stanza;
|
||||
local _, channel = get_channel(stanza.attr.to);
|
||||
if not channel then
|
||||
return;
|
||||
end
|
||||
|
||||
local pubsub = stanza:get_child("pubsub", "http://jabber.org/protocol/pubsub");
|
||||
local publish = pubsub:get_child("publish");
|
||||
if publish then
|
||||
return handle_pubsub_publish(event, publish, channel);
|
||||
end
|
||||
|
||||
local subscribe = pubsub:get_child("subscribe");
|
||||
if subscribe then
|
||||
return handle_pubsub_subscribe(event, subscribe, channel);
|
||||
end
|
||||
|
||||
local unsubscribe = pubsub:get_child("unsubscribe");
|
||||
if unsubscribe then
|
||||
return handle_pubsub_unsubscribe(event, subscribe, channel);
|
||||
end
|
||||
|
||||
local retract = pubsub:get_child("retract");
|
||||
if retract then
|
||||
return handle_pubsub_retract(event, retract, channel);
|
||||
end
|
||||
end, 1000);
|
||||
|
@ -4,6 +4,7 @@ return {
|
||||
mix_core = "urn:xmpp:mix:core:1";
|
||||
mix_anon = "urn:xmpp:mix:anon:0";
|
||||
mix_admin = "urn:xmpp:mix:admin:0";
|
||||
mix_presence = "urn:xmpp:mix:presence:0";
|
||||
-- MAM
|
||||
mam = "urn:xmpp:mam:2";
|
||||
-- User Avatar
|
||||
@ -11,9 +12,14 @@ return {
|
||||
avatar_metadata = "urn:xmpp:avatar:metadata";
|
||||
-- MIX PubSub nodes
|
||||
messages = "urn:xmpp:mix:nodes:messages";
|
||||
presence = "urn:xmpp:mix:nodes:presence";
|
||||
participants = "urn:xmpp:mix:nodes:participants";
|
||||
info = "urn:xmpp:mix:nodes:info";
|
||||
allowed = "urn:xmpp:mix:nodes:allowed";
|
||||
banned = "urn:xmpp:mix:nodes:banned";
|
||||
config = "urn:xmpp:mix:nodes:config";
|
||||
-- PubSub
|
||||
pubsub = "http://jabber.org/protocol/pubsub";
|
||||
pubsub_event = "http://jabber.org/protocol/pubsub#event";
|
||||
pubsub_error = "http://jabber.org/protocol/pubsub#errors";
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user