2020-11-02 16:07:50 +00:00
|
|
|
local st = require("util.stanza");
|
2021-02-28 10:29:21 +00:00
|
|
|
local array = require("util.array");
|
|
|
|
local jid_lib = require("util.jid");
|
|
|
|
local uuid = require("util.uuid");
|
|
|
|
local time = require("util.time");
|
2020-11-02 16:07:50 +00:00
|
|
|
local helpers = module:require("mix/helpers");
|
2021-02-28 10:29:21 +00:00
|
|
|
local namespaces = module:require("mix/namespaces");
|
|
|
|
local pep = module:depends("pep");
|
2020-11-02 16:07:50 +00:00
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
|
|
|
|
local Participant = {};
|
2020-11-02 16:07:50 +00:00
|
|
|
Participant.__index = Participant;
|
2021-02-28 10:29:21 +00:00
|
|
|
function Participant:new(jid, nick, config)
|
2020-11-02 16:07:50 +00:00
|
|
|
return setmetatable({
|
|
|
|
jid = jid,
|
|
|
|
nick = nick,
|
2021-02-28 10:29:21 +00:00
|
|
|
config = config,
|
2020-11-02 16:07:50 +00:00
|
|
|
}, Participant);
|
|
|
|
end
|
|
|
|
|
|
|
|
function Participant:from(config)
|
|
|
|
return setmetatable(config, Participant);
|
|
|
|
end
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
local Channel = {};
|
2020-11-02 16:07:50 +00:00
|
|
|
Channel.__index = Channel;
|
2021-02-28 10:29:21 +00:00
|
|
|
function Channel:new(jid, name, description, participants, administrators, owners, subscriptions, spid, contacts, adhoc, allowed, banned, config, nodes)
|
2020-11-02 16:07:50 +00:00
|
|
|
return setmetatable({
|
|
|
|
jid = jid,
|
|
|
|
name = name,
|
|
|
|
description = description,
|
|
|
|
participants = participants,
|
|
|
|
subscriptions = subscriptions,
|
|
|
|
spid = spid,
|
|
|
|
contacts = contacts,
|
|
|
|
adhoc = adhoc,
|
2021-02-28 10:29:21 +00:00
|
|
|
administrators = administrators,
|
|
|
|
owners = owners,
|
|
|
|
config = config,
|
|
|
|
nodes = nodes,
|
|
|
|
allowed = allowed,
|
|
|
|
banned = banned,
|
2020-11-02 16:07:50 +00:00
|
|
|
}, Channel);
|
|
|
|
end
|
|
|
|
function Channel:from(config)
|
|
|
|
-- Turn a channel into a Channel object
|
|
|
|
local o = setmetatable(config, Channel);
|
|
|
|
for i, _ in pairs(o.participants) do
|
|
|
|
o.participants[i] = Participant:from(o.participants[i]);
|
|
|
|
end
|
|
|
|
return o;
|
|
|
|
end
|
|
|
|
|
|
|
|
function Channel:get_spid(jid)
|
|
|
|
-- Returns the Stable Participant ID for the *BARE* jid
|
|
|
|
return self.spid[jid];
|
|
|
|
end
|
|
|
|
|
|
|
|
function Channel:set_spid(jid, spid)
|
|
|
|
-- Sets the Stable Participant ID for the *BARE* jid
|
|
|
|
self.spid[jid] = spid;
|
|
|
|
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);
|
|
|
|
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;
|
|
|
|
end
|
|
|
|
|
|
|
|
function Channel:is_subscribed(jid, node)
|
|
|
|
-- Returns true of JID is subscribed to node on this channel. Returns false
|
|
|
|
-- otherwise.
|
|
|
|
return helpers.find_str(self.subscriptions[jid], node) ~= -1;
|
|
|
|
end
|
|
|
|
|
|
|
|
function Channel:debug_print()
|
|
|
|
module:log("debug", "Channel %s (%s)", self.jid, self.name);
|
|
|
|
module:log("debug", "'%s'", self.description);
|
|
|
|
for _, p in pairs(self.participants) do
|
|
|
|
module:log("debug", "=> %s (%s)", p.jid, p.nick);
|
|
|
|
end
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2020-11-02 16:07:50 +00:00
|
|
|
module:log("debug", "Contacts:");
|
|
|
|
for _, c in pairs(self.contacts) do
|
|
|
|
module:log("debug", "=> %s", c);
|
|
|
|
end
|
2021-02-21 17:38:21 +00:00
|
|
|
|
2020-11-02 16:07:50 +00:00
|
|
|
if self.subscriptions then
|
|
|
|
module:log("debug", "Subscriptions:");
|
|
|
|
for user, subs in pairs(self.subscriptions) do
|
|
|
|
module:log("debug", "[%s]", user);
|
2021-02-21 17:38:21 +00:00
|
|
|
for _, sub in pairs(subs) do
|
2020-11-02 16:07:50 +00:00
|
|
|
module:log("debug", "=> %s", sub);
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-02-28 10:29:21 +00:00
|
|
|
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
|
|
|
|
local msg = st.clone(message);
|
|
|
|
msg:add_child(st.stanza("mix", { xmlns = namespaces.mix_core })
|
|
|
|
:tag("nick"):text(participant.nick):up()
|
|
|
|
:tag("jid"):text(participant.jid):up());
|
|
|
|
|
|
|
|
-- Put the message into the archive
|
|
|
|
local mam_id = uuid.generate();
|
|
|
|
msg.attr.id = mam_id;
|
|
|
|
-- NOTE: The spec says to do so
|
|
|
|
msg.attr.from = self.jid;
|
|
|
|
archive:append(message.attr.to, mam_id, msg, time.now());
|
|
|
|
msg.attr.from = self.jid.."/"..self:get_spid(participant.jid);
|
|
|
|
|
|
|
|
if module:fire_event("mix-broadcast-message", { message = msg, channel = self }) then
|
|
|
|
return;
|
|
|
|
end
|
|
|
|
|
|
|
|
for _, p in pairs(self.participants) do
|
|
|
|
-- Only users who subscribed to the messages node should receive
|
|
|
|
-- messages
|
|
|
|
if self:is_subscribed(p.jid, namespaces.messages) then
|
|
|
|
local tmp = st.clone(msg);
|
|
|
|
tmp.attr.to = p.jid;
|
|
|
|
module:send(tmp);
|
|
|
|
end
|
|
|
|
end
|
|
|
|
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
|
|
|
|
srv:set_node_config(namespaces.participants,
|
|
|
|
true,
|
|
|
|
{ ["max_items"] = #self.participants });
|
|
|
|
srv:publish(namespaces.participants,
|
|
|
|
true,
|
|
|
|
spid,
|
|
|
|
st.stanza("item", { id = spid, xmlns = "http://jabber.org/protocol/pubsub" })
|
|
|
|
:tag("participant", { xmlns = namespaces.mix_core })
|
|
|
|
:tag("nick"):text(participant["nick"]):up()
|
|
|
|
: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;
|
|
|
|
};
|
|
|
|
|
|
|
|
local default_participant_configuration = {
|
|
|
|
["JID Visibility"] = "never";
|
|
|
|
["Private Messages"] = "allow";
|
|
|
|
["Presence"] = "share";
|
|
|
|
["vCard"] = "block";
|
|
|
|
};
|
|
|
|
|
2020-11-02 16:07:50 +00:00
|
|
|
return {
|
|
|
|
Channel = Channel,
|
2021-02-28 10:29:21 +00:00
|
|
|
Participant = Participant,
|
|
|
|
default_channel_configuration = default_channel_configuration,
|
|
|
|
default_participant_configuration = default_participant_configuration,
|
2020-11-02 16:07:50 +00:00
|
|
|
};
|