Compare commits

...

19 Commits

Author SHA1 Message Date
Daniel Berteaud
e749b39e13 Update to 2025-07-28 13:00 2025-07-28 13:00:23 +02:00
Daniel Berteaud
58a1a78ce5 Update to 2025-07-28 10:00 2025-07-28 10:00:34 +02:00
Daniel Berteaud
cc8300da55 Update to 2025-07-17 00:00 2025-07-17 00:00:47 +02:00
Daniel Berteaud
1d891828f0 Update to 2025-07-15 10:00 2025-07-15 10:00:16 +02:00
Daniel Berteaud
0fcd8c4297 Update to 2025-07-15 00:00 2025-07-15 00:00:22 +02:00
Daniel Berteaud
65ebcfc093 Update to 2025-07-14 23:00 2025-07-14 23:00:12 +02:00
Daniel Berteaud
9c15069b74 Update to 2025-07-04 17:00 2025-07-04 17:00:17 +02:00
Daniel Berteaud
f8d3e57155 Update to 2025-07-04 09:00 2025-07-04 09:00:19 +02:00
Daniel Berteaud
3a08150ae4 Update to 2025-06-27 13:00 2025-06-27 13:00:12 +02:00
Daniel Berteaud
eb0714be6f Update to 2025-06-16 16:00 2025-06-16 16:00:13 +02:00
Daniel Berteaud
ffe9caded5 Update to 2025-06-11 11:00 2025-06-11 11:00:12 +02:00
Daniel Berteaud
9d979b11f2 Update to 2025-06-10 10:00 2025-06-10 10:00:16 +02:00
Daniel Berteaud
7c9556a76b Update to 2025-06-04 16:00 2025-06-04 16:00:23 +02:00
Daniel Berteaud
b6c1e1bbfd Update to 2025-05-27 15:00 2025-05-27 15:00:12 +02:00
Daniel Berteaud
23f39f1115 Update to 2025-05-20 16:00 2025-05-20 16:00:15 +02:00
Daniel Berteaud
04250e0ba7 Update to 2025-05-20 14:00 2025-05-20 14:00:10 +02:00
Daniel Berteaud
0aaa7d2625 Update to 2025-05-16 17:00 2025-05-16 17:00:09 +02:00
Daniel Berteaud
93beed0a89 Update to 2025-05-13 11:00 2025-05-13 11:00:09 +02:00
Daniel Berteaud
a5bfc87852 Update to 2025-05-13 10:00 2025-05-13 10:00:10 +02:00
103 changed files with 2812 additions and 518 deletions

View File

@@ -1,8 +1,8 @@
---
# Version of consul to deploy
consul_version: 1.20.5
consul_version: 1.21.3
# URL from where the consul archive will be downloaded
consul_archive_url: https://releases.hashicorp.com/consul/{{ consul_version }}/consul_{{ consul_version }}_linux_amd64.zip
# Expected sha256 of the archive
consul_archive_sha256: 75132816072b3c7da86f04153fc58fcfcf39abadee5279b3f72bec3cce01a16b
consul_archive_sha256: ba20631037a5f63f70b0351c0875887a66c0a0d3feac2d255a768c9eb8c95e8b

View File

@@ -1,11 +1,11 @@
---
# Version of consul-template to install
consul_tpl_version: 0.40.0
consul_tpl_version: 0.41.1
# URL of the archive
consul_tpl_archive_url: https://releases.hashicorp.com/consul-template/{{ consul_tpl_version }}/consul-template_{{ consul_tpl_version }}_linux_amd64.zip
# Expected sha256 of the archive
consul_tpl_archive_sha256: f73cb36988b9aaccb0ac918df26c854ccd199e60c0df011357405672f3d934bc
consul_tpl_archive_sha256: ab68e09642437dcc5b6e9a572a1924d3969e4fe131f50a1a3a4f782d7a21f530
# Root dir where consul-template will be installed
consul_tpl_root_dir: /opt/consul_template

View File

@@ -7,6 +7,11 @@ docker_base_conf:
data-root: /opt/docker
log-driver: journald
storage-driver: overlay2
default-ulimits:
nofile:
Hard: 1048576
Soft: 1048576
Name: nofile
docker_extra_conf: {}
# docker_extra_conf:
# log-opts:

View File

@@ -1,11 +1,11 @@
---
# Version to install
gitea_version: '1.23.6'
gitea_version: '1.24.3'
# URL to the binary
gitea_bin_url: https://dl.gitea.io/gitea/{{ gitea_version }}/gitea-{{ gitea_version }}-linux-amd64
# sha256 of the binary
gitea_bin_sha256: fcb76127fec7ba9fba10bfe11d81cdc01888aacb588fc4f29b124bf2ffba883e
gitea_bin_sha256: a454176defd183788f2e1334834fbae7c16a62de142bd71ee4c9b01700e55f6d
# Handle updates. If set to false, ansible will only install
# Gitea and then won't touch an existing installation
gitea_manage_upgrade: True

View File

@@ -162,4 +162,7 @@
- include_tasks: filebeat.yml
tags: always
- include_tasks: vector.yml
tags: always
...

View File

@@ -0,0 +1,5 @@
---
- name: Deploy vector config
template: src=vector.yml.j2 dest=/etc/vector/conf.d/httpd.yml
tags: log,vector,web

View File

@@ -0,0 +1,21 @@
---
sources:
in_logs_httpd:
type: file
include: ["/var/log/httpd/access_log", "/var/log/httpd/error_log"]
transforms:
format_logs_httpd:
type: remap
inputs: ["in_logs_httpd"]
source: |
if (.file == "/var/log/httpd/access_log"){
.http = parse_grok!(.message, "%{HOSTNAME:host} %{HTTPD_COMBINEDLOG}")
}
if (.file == "/var/log/httpd/error_log"){
.http = parse_apache_log!(.message, format:"error")
}
.timestamp = parse_timestamp(del(.http.timestamp), format: "%d/%h/%Y:%H:%M:%S %z") ?? now()
.service = "httpd"
.group = "web"

View File

@@ -9,16 +9,16 @@ jitsi_user: jitsi
jitsi_web_src_ip:
- 0.0.0.0/0
jitsi_version: 10133
jitsi_version: 10431
jitsi_jicofo_archive_url: https://github.com/jitsi/jicofo/archive/refs/tags/stable/jitsi-meet_{{ jitsi_version }}.tar.gz
jitsi_jicofo_archive_sha256: a233b30fbbb41c30cdef0bbfbf971d7dcb3b0ce96a54f16a4bf9e1b7633f31fc
jitsi_jicofo_archive_sha256: 5a575f97a4f5f24b2406712cd25196083ac33535bde998d72bb0acf07d727592
# Jigasi has no release, nor tags, so use master
jitsi_jigasi_archive_url: https://github.com/jitsi/jigasi/archive/refs/heads/master.tar.gz
jitsi_meet_archive_url: https://github.com/jitsi/jitsi-meet/archive/refs/tags/stable/jitsi-meet_{{ jitsi_version }}.tar.gz
jitsi_meet_archive_sha256: c1ed6ff9546fe681ac3996b49bee22df420350f406468408acbe58491d95c0d9
jitsi_meet_archive_sha256: 7402386e8ecb77ef3f6029b169aa07e6a56011d59ac0cb7019bfa1fdb3d8fcb3
jitsi_excalidraw_version: 2025.2.2
jitsi_excalidraw_archive_url: https://github.com/jitsi/excalidraw-backend/archive/refs/tags/{{ jitsi_excalidraw_version }}.tar.gz

View File

@@ -226,7 +226,11 @@ function M.verify(token, expectedAlgo, key, acceptedIssuers, acceptedAudiences)
if body.exp and os.time() >= body.exp then
return nil, "Not acceptable by exp ("..tostring(os.time()-body.exp)..")"
local extra_msg = '';
if body.iat then
extra_msg = ", valid for:"..tostring(body.exp-body.iat).." sec";
end
return nil, "Not acceptable by exp ("..tostring(os.time()-body.exp).." sec since expired"..extra_msg..")"
end
if body.nbf and os.time() < body.nbf then

View File

@@ -48,13 +48,14 @@ function init_session(event)
-- After validating auth_token will be cleaned in case of error and few
-- other fields will be extracted from the token and set in the session
if query and params.token then
if params and params.token then
token = params.token;
end
end
-- in either case set auth_token in the session
session.auth_token = token;
session.user_agent_header = request.headers['user_agent'];
end
module:hook_global("bosh-session", init_session);
@@ -101,8 +102,9 @@ function provider.get_sasl_handler(session)
local res, error, reason = token_util:process_and_verify_token(session);
if res == false then
module:log("warn",
"Error verifying token err:%s, reason:%s tenant:%s room:%s",
error, reason, session.jitsi_web_query_prefix, session.jitsi_web_query_room);
"Error verifying token err:%s, reason:%s tenant:%s room:%s user_agent:%s",
error, reason, session.jitsi_web_query_prefix, session.jitsi_web_query_room,
session.user_agent_header);
session.auth_token = nil;
measure_verify_fail(1);
return res, error, reason;

View File

@@ -1,6 +1,6 @@
local avmoderation_component = module:get_option_string('av_moderation_component', 'avmoderation.'..module.host);
-- TODO: Remove this file after several stable releases when people update their configs
module:log('warn', 'mod_av_moderation is deprecated and will be removed in a future release. '
.. 'Please update your config by removing this module from the list of loaded modules.');
-- Advertise AV Moderation so client can pick up the address and use it
module:add_identity('component', 'av_moderation', avmoderation_component);
module:depends("jitsi_session");
module:depends('jitsi_session');
module:depends('features_identity');

View File

@@ -4,6 +4,8 @@ local is_healthcheck_room = util.is_healthcheck_room;
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
local process_host_module = util.process_host_module;
local table_shallow_copy = util.table_shallow_copy;
local is_admin = util.is_admin;
local array = require "util.array";
local json = require 'cjson.safe';
local st = require 'util.stanza';
@@ -14,6 +16,12 @@ if muc_component_host == nil then
return;
end
local main_virtual_host = module:get_option_string('muc_mapper_domain_base');
if not main_virtual_host then
module:log('warn', 'No "muc_mapper_domain_base" option set, disabling AV moderation.');
return ;
end
module:log('info', 'Starting av_moderation for %s', muc_component_host);
-- Returns the index of the given element in the table
@@ -47,7 +55,7 @@ function notify_occupants_enable(jid, enable, room, actorJid, mediaType)
body_json.type = 'av_moderation';
body_json.enabled = enable;
body_json.room = internal_room_jid_match_rewrite(room.jid);
body_json.actor = actorJid;
body_json.actor = internal_room_jid_match_rewrite(actorJid);
body_json.mediaType = mediaType;
local body_json_str, error = json.encode(body_json);
@@ -75,11 +83,20 @@ function notify_whitelist_change(jid, moderators, room, mediaType, removed)
local body_json = {};
body_json.type = 'av_moderation';
body_json.room = internal_room_jid_match_rewrite(room.jid);
body_json.whitelists = room.av_moderation;
-- we will be modifying it, so we need a copy
body_json.whitelists = table_shallow_copy(room.av_moderation);
if removed then
body_json.removed = true;
end
body_json.mediaType = mediaType;
-- sanitize, make sure we don't have an empty array as it will encode it as {} not as []
for _,mediaType in pairs({'audio', 'video', 'desktop'}) do
if body_json.whitelists[mediaType] and #body_json.whitelists[mediaType] == 0 then
body_json.whitelists[mediaType] = nil;
end
end
local moderators_body_json_str, error = json.encode(body_json);
if not moderators_body_json_str then
@@ -135,6 +152,36 @@ function notify_jid_approved(jid, from, room, mediaType)
send_json_message(jid, json_message);
end
function start_av_moderation(room, mediaType, occupant)
if not room.av_moderation then
room.av_moderation = {};
room.av_moderation_actors = {};
end
room.av_moderation[mediaType] = array();
-- add all current moderators to the new whitelist
for _, room_occupant in room:each_occupant() do
if room_occupant.role == 'moderator' and not ends_with(room_occupant.nick, '/focus') then
room.av_moderation[mediaType]:push(internal_room_jid_match_rewrite(room_occupant.nick));
end
end
-- We want to set startMuted policy in metadata, in case of new participants are joining to respect
-- it, that will be enforced by jicofo
local startMutedMetadata = room.jitsiMetadata.startMuted or {};
-- We want to keep the previous value of startMuted for this mediaType if av moderation is disabled
-- to be able to restore
local av_moderation_startMuted_restore = room.av_moderation_startMuted_restore or {};
av_moderation_startMuted_restore[mediaType] = startMutedMetadata[mediaType];
room.av_moderation_startMuted_restore = av_moderation_startMuted_restore;
startMutedMetadata[mediaType] = true;
room.jitsiMetadata.startMuted = startMutedMetadata;
room.av_moderation_actors[mediaType] = occupant.nick;
end
-- receives messages from clients to the component sending A/V moderation enable/disable commands or adding
-- jids to the whitelist
function on_message(event)
@@ -175,7 +222,7 @@ function on_message(event)
local mediaType = moderation_command.attr.mediaType;
if mediaType then
if mediaType ~= 'audio' and mediaType ~= 'video' then
if mediaType ~= 'audio' and mediaType ~= 'video' and mediaType ~= 'desktop' then
module:log('warn', 'Wrong mediaType %s for %s', mediaType, room.jid);
return false;
end
@@ -192,12 +239,7 @@ function on_message(event)
module:log('warn', 'Concurrent moderator enable/disable request or something is out of sync');
return true;
else
if not room.av_moderation then
room.av_moderation = {};
room.av_moderation_actors = {};
end
room.av_moderation[mediaType] = array{};
room.av_moderation_actors[mediaType] = occupant.nick;
start_av_moderation(room, mediaType, occupant);
end
else
enabled = false;
@@ -208,7 +250,11 @@ function on_message(event)
room.av_moderation[mediaType] = nil;
room.av_moderation_actors[mediaType] = nil;
-- clears room.av_moderation if empty
local startMutedMetadata = room.jitsiMetadata.startMuted or {};
local av_moderation_startMuted_restore = room.av_moderation_startMuted_restore or {};
startMutedMetadata[mediaType] = av_moderation_startMuted_restore[mediaType];
room.jitsiMetadata.startMuted = startMutedMetadata;
local is_empty = true;
for key,_ in pairs(room.av_moderation) do
if room.av_moderation[key] then
@@ -223,6 +269,12 @@ function on_message(event)
-- send message to all occupants
notify_occupants_enable(nil, enabled, room, occupant.nick, mediaType);
if enabled then
-- inform all moderators for the newly created whitelist
notify_whitelist_change(nil, true, room, mediaType);
end
return true;
elseif moderation_command.attr.jidToWhitelist then
local occupant_jid = moderation_command.attr.jidToWhitelist;
@@ -283,12 +335,32 @@ end
function occupant_joined(event)
local room, occupant = event.room, event.occupant;
if is_healthcheck_room(room.jid) then
if is_healthcheck_room(room.jid) or is_admin(occupant.bare_jid) then
return;
end
-- when first moderator joins if av_can_unmute from password preset is set to false, we enable av moderation for both
-- audio and video, and set the first moderator as the actor that enabled it
if room._data.av_can_unmute ~= nil
and not room._data.av_first_moderator_joined
-- occupant.role is not reflecting the actual role after set_affiliation is used in same occupant_joined event
and room:get_role(occupant.nick) == 'moderator' then
if not room._data.av_can_unmute then
for _,mediaType in pairs({'audio', 'video', 'desktop'}) do
start_av_moderation(room, mediaType, occupant);
notify_occupants_enable(nil, true, room, occupant.nick, mediaType);
end
room._data.av_first_moderator_joined = true;
return;
end
end
if room.av_moderation then
for _,mediaType in pairs({'audio', 'video'}) do
for _,mediaType in pairs({'audio', 'video', 'desktop'}) do
if room.av_moderation[mediaType] then
notify_occupants_enable(
occupant.jid, true, room, room.av_moderation_actors[mediaType], mediaType);
@@ -298,9 +370,13 @@ function occupant_joined(event)
-- NOTE for some reason event.occupant.role is not reflecting the actual occupant role (when changed
-- from allowners module) but iterating over room occupants returns the correct role
for _, room_occupant in room:each_occupant() do
-- if moderator send the whitelist
if room_occupant.nick == occupant.nick and room_occupant.role == 'moderator' then
notify_whitelist_change(room_occupant.jid, false, room);
-- if it is a moderator, send the whitelist to every moderator
if room_occupant.nick == occupant.nick and room_occupant.role == 'moderator' then
for _,mediaType in pairs({'audio', 'video', 'desktop'}) do
if room.av_moderation[mediaType] then
notify_whitelist_change(nil, true, room, mediaType);
end
end
end
end
end
@@ -308,14 +384,30 @@ end
-- when a occupant was granted moderator we need to update him with the whitelist
function occupant_affiliation_changed(event)
local room = event.room;
if not room.av_moderation or is_healthcheck_room(room.jid) or is_admin(event.jid)
or event.affiliation ~= 'owner' then
return;
end
-- in any enabled media type add the new moderator to the whitelist
for _, room_occupant in room:each_occupant() do
if room_occupant.bare_jid == event.jid then
for _,mediaType in pairs({'audio', 'video', 'desktop'}) do
if room.av_moderation[mediaType] then
room.av_moderation[mediaType]:push(internal_room_jid_match_rewrite(room_occupant.nick));
end
end
end
end
-- the actor can be nil if is coming from allowners or similar module we want to skip it here
-- as we will handle it in occupant_joined
if event.actor and event.affiliation == 'owner' and event.room.av_moderation then
local room = event.room;
-- event.jid is the bare jid of participant
for _, occupant in room:each_occupant() do
if occupant.bare_jid == event.jid then
notify_whitelist_change(occupant.jid, false, room);
if event.actor and event.affiliation == 'owner' then
-- notify all moderators for the new grant moderator and the change in whitelists
for _,mediaType in pairs({'audio', 'video', 'desktop'}) do
if room.av_moderation[mediaType] then
notify_whitelist_change(nil, true, room, mediaType);
end
end
end
@@ -329,3 +421,9 @@ process_host_module(muc_component_host, function(host_module, host)
host_module:hook('muc-occupant-joined', occupant_joined, -2); -- make sure it runs after allowners or similar
host_module:hook('muc-set-affiliation', occupant_affiliation_changed, -1);
end);
process_host_module(main_virtual_host, function(host_module)
module:context(host_module.host):fire_event('jitsi-add-identity', {
name = 'av_moderation'; host = module.host;
});
end);

View File

@@ -1,24 +1,21 @@
-- This module is added under the main virtual host domain
--
-- VirtualHost "jitmeet.example.com"
-- modules_enabled = {
-- "end_conference"
-- }
-- end_conference_component = "endconference.jitmeet.example.com"
--
-- Component "endconference.jitmeet.example.com" "end_conference"
-- muc_component = muc.jitmeet.example.com
--
local get_room_by_name_and_subdomain = module:require 'util'.get_room_by_name_and_subdomain;
local util = module:require 'util';
local get_room_by_name_and_subdomain = util.get_room_by_name_and_subdomain;
local process_host_module = util.process_host_module;
local END_CONFERENCE_REASON = 'The meeting has been terminated';
-- Since this file serves as both the host module and the component, we rely on the assumption that
-- end_conference_component var would only be define for the host and not in the end_conference component
-- TODO: Remove this if block after several stable releases when people update their configs
local end_conference_component = module:get_option_string('end_conference_component');
if end_conference_component then
-- Advertise end conference so client can pick up the address and use it
module:add_identity('component', 'end_conference', end_conference_component);
module:log('warn', 'Please update your config by removing muc_end_conference module from '
.. 'the list of loaded modules in the main virtual host.');
module:depends("features_identity");
return; -- nothing left to do if called as host module
end
@@ -32,6 +29,12 @@ if muc_component_host == nil then
return;
end
local main_virtual_host = module:get_option_string('muc_mapper_domain_base');
if not main_virtual_host then
module:log('warn', 'No "muc_mapper_domain_base" option set, disabling end conference component.');
return ;
end
module:log('info', 'Starting end_conference for %s', muc_component_host);
-- receives messages from clients to the component to end a conference
@@ -84,3 +87,9 @@ end
-- we will receive messages from the clients
module:hook('message/host', on_message);
process_host_module(main_virtual_host, function(host_module)
module:context(host_module.host):fire_event('jitsi-add-identity', {
name = 'end_conference'; host = module.host;
});
end);

View File

@@ -0,0 +1,8 @@
-- Other components can use the event 'jitsi-add-identity' to attach identity which
-- will be advertised by the main virtual host and discovered by clients.
-- With this we avoid having an almost empty module to just add identity with an extra config
module:hook('jitsi-add-identity', function(event)
module:log('info', 'Adding identity %s for host %s', event.name, event.host);
module:add_identity('component', event.name, event.host);
end);

View File

@@ -0,0 +1,245 @@
local json = require 'cjson.safe';
local jid = require 'util.jid';
local st = require 'util.stanza';
local util = module:require 'util';
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
local is_admin = util.is_admin;
local process_host_module = util.process_host_module;
local FILE_SHARING_IDENTITY_TYPE = 'file-sharing';
local JSON_TYPE_ADD_FILE = 'add';
local JSON_TYPE_REMOVE_FILE = 'remove';
local JSON_TYPE_LIST_FILES = 'list';
local NICK_NS = 'http://jabber.org/protocol/nick';
-- this is the main virtual host of the main prosody that this vnode serves
local main_domain = module:get_option_string('main_domain');
-- only the visitor prosody has main_domain setting
local is_visitor_prosody = main_domain ~= nil;
local muc_component_host = module:get_option_string('muc_component');
if muc_component_host == nil then
module:log('error', 'No muc_component specified. No muc to operate on!');
return;
end
local muc_domain_base = module:get_option_string("muc_mapper_domain_base");
if not muc_domain_base then
module:log("warn", "No 'muc_domain_base' option set, disabling file sharing component.");
return ;
end
-- receives messages from clients to the component sending file sharing commands for adding or removing files
function on_message(event)
local session, stanza = event.origin, event.stanza;
-- Check the type of the incoming stanza to avoid loops:
if stanza.attr.type == 'error' then
return; -- We do not want to reply to these, so leave.
end
if not session or not session.jitsi_web_query_room then
return false;
end
local message = stanza:get_child(FILE_SHARING_IDENTITY_TYPE, 'http://jitsi.org/jitmeet');
if not message then
return false;
end
-- get room name with tenant and find room
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if not room then
module:log('warn', 'No room found for %s/%s', session.jitsi_web_query_prefix, session.jitsi_web_query_room);
return false;
end
-- check that the participant sending the message is an occupant in the room
local from = stanza.attr.from;
local occupant = room:get_occupant_by_real_jid(from);
if not occupant then
module:log('warn', 'No occupant %s found for %s', from, room.jid);
return false;
end
if not is_feature_allowed(
'file-upload',
session.jitsi_meet_context_features,
room:get_affiliation(stanza.attr.from) == 'owner') then
session.send(st.error_reply(stanza, 'auth', 'forbidden'));
return true;
end
if message.attr.type == JSON_TYPE_ADD_FILE then
local msg_obj, error = json.decode(message:get_text());
if error then
module:log('error','Error decoding data error:%s %s', error, stanza);
return false;
end
if not msg_obj.fileId then
module:log('error', 'Error missing required field: %s', stanza);
return false;
end
-- make sure we overwrite data for sender so we avoid spoofing
msg_obj.authorParticipantId = jid.resource(occupant.nick);
msg_obj.authorParticipantJid = from;
local nick_element = occupant:get_presence():get_child('nick', NICK_NS);
if nick_element then
msg_obj.authorParticipantName = nick_element:get_text();
else
msg_obj.authorParticipantName = 'anonymous';
end
msg_obj.conferenceFullName = internal_room_jid_match_rewrite(room.jid);
module:context(muc_domain_base):fire_event('jitsi-filesharing-add', {
room = room; file = msg_obj; actor = occupant.nick;
});
module:context(muc_domain_base):fire_event('jitsi-filesharing-updated', {
room = room;
});
return true;
elseif message.attr.type == JSON_TYPE_REMOVE_FILE then
if not message.attr.fileId then
module:log('error', 'Error missing required field: %s', stanza);
return true;
end
module:context(muc_domain_base):fire_event('jitsi-filesharing-remove', {
room = room; id = message.attr.fileId; actor = occupant.nick;
});
module:context(muc_domain_base):fire_event('jitsi-filesharing-updated', {
room = room;
});
return true;
else
-- return error.
return false;
end
end
-- handles new occupants to inform them about any file shared by other participants
function occupant_joined(event)
local room, occupant = event.room, event.occupant;
-- healthcheck rooms does not have shared files
if not room.jitsi_shared_files
or is_admin(occupant.bare_jid)
or not room.jitsi_shared_files
or next(room.jitsi_shared_files) == nil then
return;
end
-- send file list to the new occupant
local json_msg, error = json.encode({
type = FILE_SHARING_IDENTITY_TYPE,
event = JSON_TYPE_LIST_FILES,
files = room.jitsi_shared_files
});
local stanza = st.message({ from = module.host; to = occupant.jid; })
:tag('json-message', { xmlns = 'http://jitsi.org/jitmeet' })
:text(json_msg):up();
module:send(stanza);
end
process_host_module(muc_component_host, function(host_module, host)
module:log('info','Hook to muc events on %s', host);
host_module:hook('muc-occupant-joined', occupant_joined, -10); -- make sure it runs after allowners or similar
end);
-- we will receive messages from the clients
module:hook('message/host', on_message);
process_host_module(muc_domain_base, function(host_module, host)
module:context(muc_domain_base):fire_event('jitsi-add-identity', {
name = FILE_SHARING_IDENTITY_TYPE; host = module.host;
});
module:context(muc_domain_base):hook('jitsi-filesharing-add', function(event)
local actor, file, room = event.actor, event.file, event.room;
if not room.jitsi_shared_files then
room.jitsi_shared_files = {};
end
room.jitsi_shared_files[file.fileId] = file;
local json_msg, error = json.encode({
type = FILE_SHARING_IDENTITY_TYPE,
event = JSON_TYPE_ADD_FILE,
file = file
});
if not json_msg then
module:log('error', 'skip sending add request room:%s error:%s', room.jid, error);
return false
end
local stanza = st.message({ from = module.host; }):tag('json-message', { xmlns = 'http://jitsi.org/jitmeet' })
:text(json_msg):up();
-- send add file to all occupants except jicofo and sender
-- if this is visitor prosody send it only to visitors
for _, room_occupant in room:each_occupant() do
local send_event = not is_admin(room_occupant.bare_jid) and room_occupant.nick ~= actor;
if is_visitor_prosody then
send_event = room_occupant.role == 'visitor';
end
if send_event then
local to_send = st.clone(stanza);
to_send.attr.to = room_occupant.jid;
module:send(to_send);
end
end
end);
module:context(muc_domain_base):hook('jitsi-filesharing-remove', function(event)
local actor, id, room = event.actor, event.id, event.room;
if not room.jitsi_shared_files then
return;
end
room.jitsi_shared_files[id] = nil;
local json_msg, error = json.encode({
type = FILE_SHARING_IDENTITY_TYPE,
event = JSON_TYPE_REMOVE_FILE,
fileId = id
});
if not json_msg then
module:log('error', 'skip sending remove request room:%s error:%s', room.jid, error);
return false
end
local stanza = st.message({ from = module.host; }):tag('json-message', { xmlns = 'http://jitsi.org/jitmeet' })
:text(json_msg):up();
-- send remove file to all occupants except jicofo and sender
-- if this is visitor prosody send it only to visitors
for _, room_occupant in room:each_occupant() do
local send_event = not is_admin(room_occupant.bare_jid) and room_occupant.nick ~= actor;
if is_visitor_prosody then
send_event = room_occupant.role == 'visitor';
end
if send_event then
local to_send = st.clone(stanza);
to_send.attr.to = room_occupant.jid;
module:send(to_send);
end
end
end);
end);

View File

@@ -1,5 +1,27 @@
-- This module is enabled under the main virtual host
local cache = require 'util.cache';
local new_throttle = require 'util.throttle'.create;
local st = require "util.stanza";
local is_feature_allowed = module:require "util".is_feature_allowed;
local jid_bare = require "util.jid".bare;
local util = module:require 'util';
local is_feature_allowed = util.is_feature_allowed;
local get_ip = util.get_ip;
local get_room_from_jid = util.get_room_from_jid;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
local limit_jibri_reach_ip_attempts;
local limit_jibri_reach_room_attempts;
local rates_per_ip;
local function load_config()
limit_jibri_reach_ip_attempts = module:get_option_number("max_number_ip_attempts_per_minute", 9);
limit_jibri_reach_room_attempts = module:get_option_number("max_number_room_attempts_per_minute", 3);
-- The size of the cache that saves state for IP addresses
cache_size = module:get_option_number("jibri_rate_limit_cache_size", 10000);
-- Maps an IP address to a util.throttle which keeps the rate of attempts to reach jibri events from that IP.
rates_per_ip = cache.new(cache_size);
end
load_config();
-- filters jibri iq in case of requested from jwt authenticated session that
-- has features in the user context, but without feature for recording
@@ -10,15 +32,34 @@ module:hook("pre-iq/full", function(event)
if jibri then
local session = event.origin;
local token = session.auth_token;
local room = get_room_from_jid(room_jid_match_rewrite(jid_bare(stanza.attr.to)));
local occupant = room:get_occupant_by_real_jid(stanza.attr.from);
local feature = jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming';
local is_allowed = is_feature_allowed(
feature,
session.jitsi_meet_context_features,
occupant.role == 'moderator');
if jibri.attr.action == 'start' then
if token == nil
or not is_feature_allowed(session.jitsi_meet_context_features,
(jibri.attr.recording_mode == 'file' and 'recording' or 'livestreaming')
) then
module:log("info",
"Filtering jibri start recording, stanza:%s", tostring(stanza));
session.send(st.error_reply(stanza, "auth", "forbidden"));
if jibri.attr.action == 'start' or jibri.attr.action == 'stop' then
if not is_allowed then
module:log('info', 'Filtering jibri start recording, stanza:%s', tostring(stanza));
session.send(st.error_reply(stanza, 'auth', 'forbidden'));
return true;
end
local ip = get_ip(session);
if not rates_per_ip:get(ip) then
rates_per_ip:set(ip, new_throttle(limit_jibri_reach_ip_attempts, 60));
end
if not room.jibri_throttle then
room.jibri_throttle = new_throttle(limit_jibri_reach_room_attempts, 60);
end
if not rates_per_ip:get(ip):poll(1) or not room.jibri_throttle:poll(1) then
module:log('warn', 'Filtering jibri start recording, ip:%s, room:%s stanza:%s',
ip, room.jid, tostring(stanza));
session.send(st.error_reply(stanza, 'wait', 'policy-violation'));
return true;
end
end

View File

@@ -1,13 +1,14 @@
-- This module is enabled under the main virtual host
local new_throttle = require "util.throttle".create;
local st = require "util.stanza";
local jid = require "util.jid";
local token_util = module:require "token/util".new(module);
local util = module:require 'util';
local is_admin = util.is_admin;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
local is_feature_allowed = util.is_feature_allowed;
local is_sip_jigasi = util.is_sip_jigasi;
local get_room_from_jid = util.get_room_from_jid;
local is_healthcheck_room = util.is_healthcheck_room;
local process_host_module = util.process_host_module;
local jid_bare = require "util.jid".bare;
@@ -22,17 +23,30 @@ if main_muc_component_host == nil then
end
local main_muc_service;
-- this is the main virtual host of the main prosody that this vnode serves
local main_domain = module:get_option_string('main_domain');
-- only the visitor prosody has main_domain setting
local is_visitor_prosody = main_domain ~= nil;
-- this is the main virtual host of this vnode
local local_domain = module:get_option_string('muc_mapper_domain_base');
local parentCtx = module:context(local_domain);
if parentCtx == nil then
log("error",
"Failed to start - unable to get parent context for host: %s",
tostring(local_domain));
return;
end
local token_util = module:require "token/util".new(parentCtx);
-- no token configuration but required
if token_util == nil then
module:log("error", "no token configuration but it is required");
return;
end
local um_is_admin = require 'core.usermanager'.is_admin;
local function is_admin(jid)
return um_is_admin(jid, module.host);
end
-- The maximum number of simultaneous calls,
-- and also the maximum number of new calls per minute that a session is allowed to create.
local limit_outgoing_calls;
@@ -44,6 +58,8 @@ load_config();
-- Header names to use to push extra data extracted from token, if any
local OUT_INITIATOR_USER_ATTR_NAME = "X-outbound-call-initiator-user";
local OUT_INITIATOR_GROUP_ATTR_NAME = "X-outbound-call-initiator-group";
local OUT_ROOM_NAME_ATTR_NAME = "JvbRoomName";
local OUTGOING_CALLS_THROTTLE_INTERVAL = 60; -- if max_number_outgoing_calls is enabled it will be
-- the max number of outgoing calls a user can try for a minute
@@ -59,30 +75,44 @@ module:hook("pre-iq/full", function(event)
local token = session.auth_token;
-- find header with attr name 'JvbRoomName' and extract its value
local headerName = 'JvbRoomName';
local roomName;
for _, child in ipairs(dial.tags) do
if (child.name == 'header'
and child.attr.name == headerName) then
roomName = child.attr.value;
break;
-- Remove any 'header' element if it already exists, so it cannot be spoofed by a client
dial:maptags(function(tag)
if tag.name == "header"
and (tag.attr.name == OUT_INITIATOR_USER_ATTR_NAME
or tag.attr.name == OUT_INITIATOR_GROUP_ATTR_NAME) then
return nil
elseif tag.name == "header" and tag.attr.name == OUT_ROOM_NAME_ATTR_NAME then
roomName = tag.attr.value;
-- we will remove it as we will add it later, modified
if is_visitor_prosody then
return nil;
end
end
return tag
end);
local room_jid = jid.bare(stanza.attr.to);
local room_real_jid = room_jid_match_rewrite(room_jid);
local room = main_muc_service.get_room_from_jid(room_real_jid);
local is_sender_in_room = room:get_occupant_jid(stanza.attr.from) ~= nil;
if not room or not is_sender_in_room then
module:log("warn", "Filtering stanza dial, stanza:%s", tostring(stanza));
session.send(st.error_reply(stanza, "auth", "forbidden"));
return true;
end
local feature = dial.attr.to == 'jitsi_meet_transcribe' and 'transcription' or 'outbound-call';
local is_session_allowed = is_feature_allowed(session.jitsi_meet_context_features, feature);
local is_session_allowed = is_feature_allowed(
feature,
session.jitsi_meet_context_features,
room:get_affiliation(stanza.attr.from) == 'owner');
-- if current user is not allowed, but was granted moderation by a user
-- that is allowed by its features we want to allow it
local is_granting_session_allowed = false;
if (session.granted_jitsi_meet_context_features) then
is_granting_session_allowed = is_feature_allowed(session.granted_jitsi_meet_context_features, feature);
end
if (token == nil
or roomName == nil
or not token_util:verify_room(session, room_jid_match_rewrite(roomName))
or not (is_session_allowed or is_granting_session_allowed))
if roomName == nil
or roomName ~= room_jid
or (token ~= nil and not token_util:verify_room(session, room_real_jid))
or not is_session_allowed
then
module:log("warn", "Filtering stanza dial, stanza:%s", tostring(stanza));
session.send(st.error_reply(stanza, "auth", "forbidden"));
@@ -99,8 +129,8 @@ module:hook("pre-iq/full", function(event)
group_id = session.granted_jitsi_meet_context_group_id;
end
-- now lets check any limits if configured
if limit_outgoing_calls > 0 then
-- now lets check any limits for outgoing calls if configured
if feature == 'outbound-call' and limit_outgoing_calls > 0 then
if not session.dial_out_throttle then
-- module:log("debug", "Enabling dial-out throttle session=%s.", session);
session.dial_out_throttle = new_throttle(limit_outgoing_calls, OUTGOING_CALLS_THROTTLE_INTERVAL);
@@ -119,25 +149,11 @@ module:hook("pre-iq/full", function(event)
-- now lets insert token information if any
if session and user_id then
-- First remove any 'header' element if it already
-- exists, so it cannot be spoofed by a client
stanza:maptags(
function(tag)
if tag.name == "header"
and (tag.attr.name == OUT_INITIATOR_USER_ATTR_NAME
or tag.attr.name == OUT_INITIATOR_GROUP_ATTR_NAME) then
return nil
end
return tag
end
)
local dial = stanza:get_child('dial', 'urn:xmpp:rayo:1');
-- adds initiator user id from token
dial:tag("header", {
xmlns = "urn:xmpp:rayo:1",
name = OUT_INITIATOR_USER_ATTR_NAME,
value = user_id });
value = tostring(user_id)});
dial:up();
-- Add the initiator group information if it is present
@@ -145,13 +161,22 @@ module:hook("pre-iq/full", function(event)
dial:tag("header", {
xmlns = "urn:xmpp:rayo:1",
name = OUT_INITIATOR_GROUP_ATTR_NAME,
value = session.jitsi_meet_context_group });
value = tostring(session.jitsi_meet_context_group) });
dial:up();
end
end
-- we want to instruct jigasi to enter the main room, so send the correct main room jid
if is_visitor_prosody then
dial:tag("header", {
xmlns = "urn:xmpp:rayo:1",
name = OUT_ROOM_NAME_ATTR_NAME,
value = string.gsub(roomName, local_domain, main_domain) });
dial:up();
end
end
end
end);
end, 1); -- make sure we run before domain mapper
--- Finds and returns the number of concurrent outgoing calls for a user
-- @param context_user the user id extracted from the token
@@ -200,49 +225,10 @@ end
module:hook_global('config-reloaded', load_config);
function process_set_affiliation(event)
local actor, affiliation, jid, previous_affiliation, room
= event.actor, event.affiliation, event.jid, event.previous_affiliation, event.room;
local actor_session = sessions[actor];
if is_admin(jid) or is_healthcheck_room(room.jid) or not actor or not previous_affiliation
or not actor_session or not actor_session.jitsi_meet_context_features then
return;
end
local occupant;
for _, o in room:each_occupant() do
if o.bare_jid == jid then
occupant = o;
end
end
if not occupant then
return;
end
local occupant_session = sessions[occupant.jid];
if not occupant_session then
return;
end
if previous_affiliation == 'none' and affiliation == 'owner' then
occupant_session.granted_jitsi_meet_context_features = actor_session.jitsi_meet_context_features;
occupant_session.granted_jitsi_meet_context_user_id = actor_session.jitsi_meet_context_user["id"];
occupant_session.granted_jitsi_meet_context_group_id = actor_session.jitsi_meet_context_group;
elseif previous_affiliation == 'owner' and ( affiliation == 'member' or affiliation == 'none' ) then
occupant_session.granted_jitsi_meet_context_features = nil;
occupant_session.granted_jitsi_meet_context_user_id = nil;
occupant_session.granted_jitsi_meet_context_group_id = nil;
end
end
function process_main_muc_loaded(main_muc, host_module)
module:log('debug', 'Main muc loaded');
main_muc_service = main_muc;
module:log("info", "Hook to muc events on %s", main_muc_component_host);
host_module:hook("muc-pre-set-affiliation", process_set_affiliation);
end
process_host_module(main_muc_component_host, function(host_module, host)
@@ -259,3 +245,34 @@ process_host_module(main_muc_component_host, function(host_module, host)
end);
end
end);
-- when recording participants may enable and backend transcriptions
-- it is possible that participant is not moderator, but has the features enabled for
-- transcribing, we need to allow that operation
module:hook('jitsi-metadata-allow-moderation', function (event)
local data, key, occupant, session = event.data, event.key, event.actor, event.session;
if key == 'recording' and data and data.isTranscribingEnabled ~= nil then
-- if it is recording we want to allow setting in metadata if not moderator but features
-- are present
if session.jitsi_meet_context_features
and occupant.role ~= 'moderator'
and is_feature_allowed('transcription', session.jitsi_meet_context_features)
and is_feature_allowed('recording', session.jitsi_meet_context_features) then
local res = {};
res.isTranscribingEnabled = data.isTranscribingEnabled;
return res;
elseif not session.jitsi_meet_context_features and occupant.role == 'moderator' then
return data;
else
return nil;
end
end
if occupant.role == 'moderator' then
return data;
end
return nil;
end);

View File

@@ -0,0 +1,42 @@
-- enable under the main muc module
-- a module that will filter group messages based on features (jitsi_meet_context_features)
-- when requested via metadata (permissions.groupChatRestricted)
local util = module:require 'util';
local get_room_from_jid = util.get_room_from_jid;
local st = require 'util.stanza';
local function on_message(event)
local stanza = event.stanza;
local body = stanza:get_child('body');
local session = event.origin;
if not body or not session then
-- we ignore messages without body - lobby, polls ...
return;
end
-- get room name with tenant and find room.
-- this should already been through domain mapper and this should be the real room jid [tenant]name format
local room = get_room_from_jid(stanza.attr.to);
if not room then
module:log('warn', 'No room found found for %s', stanza.attr.to);
return;
end
if room.jitsiMetadata and room.jitsiMetadata.permissions
and room.jitsiMetadata.permissions.groupChatRestricted
and not is_feature_allowed('send-groupchat', session.jitsi_meet_context_features) then
local reply = st.error_reply(stanza, 'cancel', 'not-allowed', 'Sending group messages not allowed');
if session.type == 's2sin' or session.type == 's2sout' then
reply.skipMapping = true;
end
module:send(reply);
-- let's filter this message
return true;
end
end
module:hook('message/bare', on_message); -- room messages
module:hook('jitsi-visitor-groupchat-pre-route', on_message); -- visitors messages

View File

@@ -14,8 +14,12 @@ local jid = require 'util.jid';
local st = require 'util.stanza';
local new_id = require 'util.id'.medium;
local filters = require 'util.filters';
local array = require 'util.array';
local set = require 'util.set';
local json = require 'cjson.safe';
local util = module:require 'util';
local is_admin = util.is_admin;
local ends_with = util.ends_with;
local is_vpaas = util.is_vpaas;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
@@ -23,6 +27,13 @@ local get_room_from_jid = util.get_room_from_jid;
local get_focus_occupant = util.get_focus_occupant;
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
local presence_check_status = util.presence_check_status;
local respond_iq_result = util.respond_iq_result;
local table_compare = util.table_compare;
local PARTICIPANT_PROP_RAISE_HAND = 'jitsi_participant_raisedHand';
local PARTICIPANT_PROP_REQUEST_TRANSCRIPTION = 'jitsi_participant_requestingTranscription';
local PARTICIPANT_PROP_TRANSLATION_LANG = 'jitsi_participant_translation_language';
local TRANSCRIPT_DEFAULT_LANG = module:get_option_string('transcriptions_default_language', 'en');
-- this is the main virtual host of this vnode
local local_domain = module:get_option_string('muc_mapper_domain_base');
@@ -43,6 +54,9 @@ local local_muc_domain = muc_domain_prefix..'.'..local_domain;
local NICK_NS = 'http://jabber.org/protocol/nick';
-- in certain cases we consider participants with token as moderators, this is the default behavior which can be turned off
local auto_promoted_with_token = module:get_option_boolean('visitors_auto_promoted_with_token', true);
-- we send stats for the total number of rooms, total number of participants and total number of visitors
local measure_rooms = module:measure('vnode-rooms', 'amount');
local measure_participants = module:measure('vnode-participants', 'amount');
@@ -52,17 +66,72 @@ local sent_iq_cache = require 'util.cache'.new(200);
local sessions = prosody.full_sessions;
local um_is_admin = require 'core.usermanager'.is_admin;
local function is_admin(jid)
return um_is_admin(jid, module.host);
local function send_transcriptions_update(room)
-- let's notify main prosody
local lang_array = array();
local count = 0;
for k, v in pairs(room._transcription_languages) do
lang_array:push(v);
count = count + 1;
end
local iq_id = new_id();
sent_iq_cache:set(iq_id, socket.gettime());
module:send(st.iq({
type = 'set',
to = 'visitors.'..main_domain,
from = local_domain,
id = iq_id })
:tag('visitors', { xmlns = 'jitsi:visitors',
room = jid.join(jid.node(room.jid), muc_domain_prefix..'.'..main_domain) })
:tag('transcription-languages', {
xmlns = 'jitsi:visitors',
langs = lang_array:unique():sort():concat(','),
count = tostring(count)
}):up());
end
local function remove_transcription(room, occupant)
local send_update = false;
if room._transcription_languages then
if room._transcription_languages[occupant.jid] then
send_update = true;
end
room._transcription_languages[occupant.jid] = nil;
end
if send_update then
send_transcriptions_update(room);
end
end
-- if lang is nil we will remove it from the list
local function add_transcription(room, occupant, lang)
if not room._transcription_languages then
room._transcription_languages = {};
end
local old = room._transcription_languages[occupant.jid];
room._transcription_languages[occupant.jid] = lang or TRANSCRIPT_DEFAULT_LANG;
if old ~= room._transcription_languages[occupant.jid] then
send_transcriptions_update(room);
end
end
-- mark all occupants as visitors
module:hook('muc-occupant-pre-join', function (event)
local occupant, room, origin, stanza = event.occupant, event.room, event.origin, event.stanza;
local node, host = jid.split(occupant.bare_jid);
local resource = jid.resource(occupant.nick);
if prosody.hosts[host] and not is_admin(occupant.bare_jid) then
if is_admin(occupant.bare_jid) then
return;
end
if prosody.hosts[host] then
-- local participants which host is defined in this prosody
if room._main_room_lobby_enabled then
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Visitors not allowed while lobby is on!')
:tag('no-visitors-lobby', { xmlns = 'jitsi:visitors' }));
@@ -70,6 +139,9 @@ module:hook('muc-occupant-pre-join', function (event)
else
occupant.role = 'visitor';
end
elseif room.moderators_list and room.moderators_list:contains(resource) then
-- remote participants, host is the main prosody
occupant.role = 'moderator';
end
end, 3);
@@ -89,7 +161,7 @@ module:hook('muc-occupant-pre-leave', function (event)
-- to main prosody
local pr = occupant:get_presence();
local raiseHand = pr:get_child_text('jitsi_participant_raisedHand');
local raiseHand = pr:get_child_text(PARTICIPANT_PROP_RAISE_HAND);
-- a promotion detected let's send it to main prosody
if raiseHand and #raiseHand > 0 then
@@ -111,6 +183,7 @@ module:hook('muc-occupant-pre-leave', function (event)
module:send(promotion_request);
end
remove_transcription(room, occupant);
end, 1); -- rate limit is 0
-- Returns the main participants count and the visitors count
@@ -136,6 +209,20 @@ local function cancel_destroy_timer(room)
end
end
local function destroy_with_conference_ended(room)
-- if the room is being destroyed, ignore
if room.destroying then
return;
end
cancel_destroy_timer(room);
local main_count, visitors_count = get_occupant_counts(room);
module:log('info', 'Will destroy:%s main_occupants:%s visitors:%s', room.jid, main_count, visitors_count);
room:destroy(nil, 'Conference ended.');
return true;
end
-- schedules a new destroy timer which will destroy the room if there are no visitors after the timeout
local function schedule_destroy_timer(room)
cancel_destroy_timer(room);
@@ -165,7 +252,9 @@ module:hook('muc-occupant-left', function (event)
if prosody.hosts[occupant_domain] and not is_admin(occupant.bare_jid) then
local focus_occupant = get_focus_occupant(room);
if not focus_occupant then
module:log('info', 'No focus found for %s', room.jid);
if not room.destroying then
module:log('warn', 'No focus found for %s', room.jid);
end
return;
end
-- Let's forward unavailable presence to the special jicofo
@@ -194,10 +283,15 @@ module:hook('muc-occupant-left', function (event)
if visitors_count == 0 then
schedule_destroy_timer(room);
end
if main_count == 0 then
destroy_with_conference_ended(room);
end
end);
-- forward visitor presences to jicofo
-- detects raise hand in visitors presence, this is request for promotion
-- detects the requested transcription and its language to send updates for it
module:hook('muc-broadcast-presence', function (event)
local occupant = event.occupant;
@@ -227,10 +321,11 @@ module:hook('muc-broadcast-presence', function (event)
full_p.attr.to = focus_occupant.jid;
room:route_to_occupant(focus_occupant, full_p);
local raiseHand = full_p:get_child_text('jitsi_participant_raisedHand');
local raiseHand = full_p:get_child_text(PARTICIPANT_PROP_RAISE_HAND);
-- a promotion detected let's send it to main prosody
if raiseHand then
local user_id;
local group_id;
local is_moderator;
local session = sessions[occupant.jid];
local identity = session and session.jitsi_meet_context_user;
@@ -246,14 +341,14 @@ module:hook('muc-broadcast-presence', function (event)
-- so we can be auto promoted
if identity and identity.id then
user_id = session.jitsi_meet_context_user.id;
group_id = session.jitsi_meet_context_group;
if room._data.moderator_id then
if room._data.moderator_id == user_id then
if session.auth_token and auto_promoted_with_token then
if not session.jitsi_meet_tenant_mismatch or session.jitsi_web_query_prefix == '' then
-- non-vpaas and having a token is considered a moderator, and if it is not in '/' tenant
-- the tenant from url and token should match
is_moderator = true;
end
elseif session.auth_token then
-- non-vpass and having a token is considered a moderator
is_moderator = true;
end
end
end
@@ -272,6 +367,7 @@ module:hook('muc-broadcast-presence', function (event)
jid = occupant.jid,
time = raiseHand,
userId = user_id,
groupId = group_id,
forcePromote = is_moderator and 'true' or 'false';
}):up();
@@ -283,6 +379,18 @@ module:hook('muc-broadcast-presence', function (event)
module:send(promotion_request);
end
local requestTranscriptionValue = full_p:get_child_text(PARTICIPANT_PROP_REQUEST_TRANSCRIPTION);
local hasTranscriptionEnabled = room._transcription_languages and room._transcription_languages[occupant.jid];
-- detect transcription
if requestTranscriptionValue == 'true' then
local lang = full_p:get_child_text(PARTICIPANT_PROP_TRANSLATION_LANG);
add_transcription(room, occupant, lang);
elseif hasTranscriptionEnabled then
remove_transcription(room, occupant, nil);
end
return;
end);
@@ -317,7 +425,6 @@ local function stanza_handler(event)
local room = get_room_from_jid(room_jid_match_rewrite(room_jid));
if not room then
module:log('warn', 'No room found %s', room_jid);
return;
end
@@ -327,12 +434,7 @@ local function stanza_handler(event)
end
-- respond with successful receiving the iq
origin.send(st.iq({
type = 'result';
from = stanza.attr.to;
to = stanza.attr.from;
id = stanza.attr.id
}));
respond_iq_result(origin, stanza);
local req_jid = request_promotion.attr.jid;
-- now let's find the occupant and forward the response
@@ -469,7 +571,15 @@ module:hook('jicofo-unlock-room', function(e)
return true;
end);
-- handles incoming iq connect stanzas
-- handles incoming iq visitors stanzas
-- connect - sent after sending all main participant's presences
-- disconnect - sent when main room is destroyed or when we receive a 'disconnect-vnode' iq from jicofo
-- update - sent on:
-- * room secret is changed
-- * lobby enabled or disabled
-- * initially before connect to report currently joined moderators
-- * moderator participant joins main room
-- * a participant has been granted moderator rights
local function iq_from_main_handler(event)
local origin, stanza = event.origin, event.stanza;
@@ -500,7 +610,7 @@ local function iq_from_main_handler(event)
local room = get_room_from_jid(room_jid_match_rewrite(room_jid));
if not room then
module:log('warn', 'No room found %s', room_jid);
module:log('warn', 'No room found %s in iq_from_main_handler for:%s', room_jid, visitors_iq);
return;
end
@@ -523,27 +633,16 @@ local function iq_from_main_handler(event)
end
-- respond with successful receiving the iq
origin.send(st.iq({
type = 'result';
from = stanza.attr.to;
to = stanza.attr.from;
id = stanza.attr.id
}));
respond_iq_result(origin, stanza);
if process_disconnect then
cancel_destroy_timer(room);
local main_count, visitors_count = get_occupant_counts(room);
module:log('info', 'Will destroy:%s main_occupants:%s visitors:%s', room.jid, main_count, visitors_count);
room:destroy(nil, 'Conference ended.');
return true;
return destroy_with_conference_ended(room);
end
-- if there is password supplied use it
-- if this is update it will either set or remove the password
room:set_password(node.attr.password);
room._data.meetingId = node.attr.meetingId;
room._data.moderator_id = node.attr.moderatorId;
local createdTimestamp = node.attr.createdTimestamp;
room.created_timestamp = createdTimestamp and tonumber(createdTimestamp) or nil;
@@ -553,6 +652,51 @@ local function iq_from_main_handler(event)
room._main_room_lobby_enabled = false;
end
-- read the moderators list
room.moderators_list = room.moderators_list or set.new();
local moderators = node:get_child('moderators');
if moderators then
for _, child in ipairs(moderators.tags) do
if child.name == 'item' then
room.moderators_list:add(child.attr.epId);
end
end
-- let's check current occupants roles and promote them if needed
-- we change only main participants which are not moderators, but participant
for _, o in room:each_occupant() do
if not is_admin(o.bare_jid)
and o.role == 'participant'
and room.moderators_list:contains(jid.resource(o.nick)) then
room:set_affiliation(true, o.bare_jid, 'owner');
end
end
end
local files = node:get_child('files');
if files then
local received_files = {};
for _, child in ipairs(files.tags) do
if child.name == 'file' then
received_files[child.attr.id] = json.decode(child:get_text());
end
end
-- fire events so file sharing component will add/remove files and will notify clients
local removed, added = table_compare(room.jitsi_shared_files or {}, received_files)
for _, id in ipairs(removed) do
module:context(local_domain):fire_event('jitsi-filesharing-remove', {
room = room; id = id;
});
end
for _, id in ipairs(added) do
module:context(local_domain):fire_event('jitsi-filesharing-add', {
room = room; file = received_files[id];
});
end
end
if fire_jicofo_unlock then
-- everything is connected allow participants to join
module:fire_event('jicofo-unlock-room', { room = room; fmuc_fired = true; });

View File

@@ -44,10 +44,12 @@ local stanza = event.stanza;
if session.jitsi_meet_context_user ~= nil then
initiator.id = session.jitsi_meet_context_user.id;
else
initiator.id = session.granted_jitsi_meet_context_user_id;
end
if session.jitsi_meet_context_group ~= nil then
initiator.group = session.jitsi_meet_context_group;
end
initiator.group
= session.jitsi_meet_context_group or session.granted_jitsi_meet_context_group_id;
app_data.file_recording_metadata.initiator = initiator
update_app_data = true;

View File

@@ -0,0 +1,196 @@
-- this is auto loaded by meeting_id
local filters = require 'util.filters';
local jid = require 'util.jid';
local util = module:require 'util';
local is_admin = util.is_admin;
local get_room_from_jid = util.get_room_from_jid;
local is_healthcheck_room = util.is_healthcheck_room;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
local ends_with = util.ends_with;
local presence_check_status = util.presence_check_status;
local MUC_NS = 'http://jabber.org/protocol/muc';
local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', 'conference');
local muc_domain_base = module:get_option_string('muc_mapper_domain_base');
if not muc_domain_base then
module:log('warn', 'No "muc_domain_base" option set, disabling module.');
return ;
end
-- only the visitor prosody has main_domain setting
local is_visitor_prosody = module:get_option_string('main_domain') ~= nil;
-- load it only on the main muc component as it is loaded by muc_meeting_id which is loaded and for the breakout room muc
if muc_domain_prefix..'.'..muc_domain_base ~= module.host or is_visitor_prosody then
return;
end
local sessions = prosody.full_sessions;
local default_permissions;
local function load_config()
default_permissions = module:get_option('jitsi_default_permissions', {
livestreaming = true;
recording = true;
transcription = true;
['outbound-call'] = true;
['create-polls'] = true;
['send-groupchat'] = true;
flip = true;
});
end
load_config();
function process_set_affiliation(event)
local actor, affiliation, jid, previous_affiliation, room
= event.actor, event.affiliation, event.jid, event.previous_affiliation, event.room;
local actor_session = sessions[actor];
if is_admin(jid) or is_healthcheck_room(room.jid) or not actor or not previous_affiliation
or not actor_session or not actor_session.jitsi_meet_context_features then
return;
end
local occupant;
for _, o in room:each_occupant() do
if o.bare_jid == jid then
occupant = o;
end
end
if not occupant then
return;
end
local occupant_session = sessions[occupant.jid];
if not occupant_session then
return;
end
if previous_affiliation == 'none' and affiliation == 'owner' then
occupant_session.jitsi_meet_context_features = actor_session.jitsi_meet_context_features;
if actor_session.jitsi_meet_context_user then
occupant_session.granted_jitsi_meet_context_user_id = actor_session.jitsi_meet_context_user['id']
or actor_session.granted_jitsi_meet_context_user_id;
end
occupant_session.granted_jitsi_meet_context_group_id = actor_session.jitsi_meet_context_group
or actor_session.granted_jitsi_meet_context_group_id;
-- even if token and features are set we may want to re-send permissions
occupant_session.force_permissions_update = true;
elseif previous_affiliation == 'owner' and ( affiliation == 'member' or affiliation == 'none' ) then
occupant_session.granted_jitsi_meet_context_user_id = nil;
occupant_session.granted_jitsi_meet_context_group_id = nil;
-- on revoke
if not session.auth_token then
occupant_session.jitsi_meet_context_features = nil;
end
end
end
-- Detects when sending self-presence because of role change
-- we can end up here because of the following cases:
-- 1. user joins the room and is granted moderator by another moderator or jicofo
-- 2. Some module changes the role of the user by using set_affiliation method
-- In cases where authentication is 'anonymous', 'jitsi-anonymous', 'internal_hashed', 'internal_plain', 'cyrus' we
-- want to send default permissions all to indicate UI that everything is allowed (to not relay on the UI to check
-- is participant moderator or not), to allow finer control over the permissions.
-- In case the authentication is 'token' based we want to send permissions only if the token of the user does not include
-- features in the user.context.
-- In case of allowners we want to send the permissions, no matter of the authentication method.
-- In case permissions were granted we want to send the granted permissions in all cases except when the user is
-- using token that has features pre-defined (authentication is 'token').
function filter_stanza(stanza, session)
if not stanza.attr or not stanza.attr.to or stanza.name ~= 'presence'
or stanza.attr.type == 'unavailable' or ends_with(stanza.attr.from, '/focus') then
return stanza;
end
local bare_to = jid.bare(stanza.attr.to);
if is_admin(bare_to) then
return stanza;
end
local muc_x = stanza:get_child('x', MUC_NS..'#user');
if not muc_x or not presence_check_status(muc_x, '110') then
return stanza;
end
local room = get_room_from_jid(room_jid_match_rewrite(jid.bare(stanza.attr.from)));
if not room or is_healthcheck_room(room.jid) then
return stanza;
end
if not room.send_default_permissions_to then
room.send_default_permissions_to = {};
end
if not session.force_permissions_update then
if session.auth_token and session.jitsi_meet_context_features then -- token and features are set so skip
room.send_default_permissions_to[bare_to] = nil;
return stanza;
end
-- we are sending permissions only when becoming a member
local is_moderator = false;
for item in muc_x:childtags('item') do
if item.attr.role == 'moderator' then
is_moderator = true;
break;
end
end
if not is_moderator then
return stanza;
end
if not room.send_default_permissions_to[bare_to] then
return stanza;
end
end
session.force_permissions_update = false;
if not session.jitsi_meet_context_features then
session.jitsi_meet_context_features = default_permissions;
end
room.send_default_permissions_to[bare_to] = nil;
stanza:tag('permissions', { xmlns='http://jitsi.org/jitmeet' });
for k, v in pairs(session.jitsi_meet_context_features) do
local val = tostring(v);
stanza:tag('p', { name = k, val = val }):up();
end
stanza:up();
return stanza;
end
-- we need to indicate that we will send permissions if we need to
-- we need to handle granted features and stuff in the pre-set hook so they are unavailable
-- when the self presence is set, so we can update the client, the checks
-- whether the actor is allowed to set the affiliation are done before pre-set hook is fired
module:hook('muc-pre-set-affiliation', function(event)
local jid, room = event.jid, event.room;
if not room.send_default_permissions_to then
room.send_default_permissions_to = {};
end
room.send_default_permissions_to[jid] = true;
process_set_affiliation(event);
end);
function filter_session(session)
-- domain mapper is filtering on default priority 0
-- allowners is -1 and we need it after that
filters.add_filter(session, 'stanzas/out', filter_stanza, -2);
end
-- enable filtering presences
filters.add_filter_hook(filter_session);

View File

@@ -3,6 +3,7 @@
module:set_global();
local formdecode = require "util.http".formdecode;
local region_header_name = module:get_option_string('region_header_name', 'x_proxy_region');
-- Extract the following parameters from the URL and set them in the session:
-- * previd: for session resumption
@@ -24,6 +25,8 @@ function init_session(event)
session.jitsi_web_query_room = params.room;
session.jitsi_web_query_prefix = params.prefix or "";
end
session.user_region = request.headers[region_header_name];
end
module:hook_global("bosh-session", init_session, 1);

View File

@@ -1,13 +1,15 @@
--- activate under the main muc component
local filters = require 'util.filters';
local jid = require "util.jid";
local jid_bare = require "util.jid".bare;
local jid_host = require "util.jid".host;
local st = require "util.stanza";
local um_is_admin = require "core.usermanager".is_admin;
local util = module:require "util";
local is_admin = util.is_admin;
local is_healthcheck_room = util.is_healthcheck_room;
local is_moderated = util.is_moderated;
local get_room_from_jid = util.get_room_from_jid;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
local presence_check_status = util.presence_check_status;
local MUC_NS = 'http://jabber.org/protocol/muc';
@@ -18,14 +20,18 @@ local function load_config()
end
load_config();
local function is_admin(_jid)
return um_is_admin(_jid, module.host);
end
-- List of the bare_jids of all occupants that are currently joining (went through pre-join) and will be promoted
-- as moderators. As pre-join (where added) and joined event (where removed) happen one after another this list should
-- have length of 1
local joining_moderator_participants = {};
local joining_moderator_participants = module:shared('moderators/joining_moderator_participants');
module:hook("muc-room-created", function(event)
local room = event.room;
if room.jitsiMetadata then
room.jitsiMetadata.allownersEnabled = true;
end
end, -2); -- room_metadata should run before this module on -1
module:hook("muc-occupant-pre-join", function (event)
local room, occupant = event.room, event.occupant;
@@ -87,7 +93,7 @@ function filter_stanza(stanza)
end
-- we want to filter presences only on this host for allowners and skip anything like lobby etc.
local host_from = jid_host(stanza.attr.from);
local host_from = jid_host(room_jid_match_rewrite(stanza.attr.from));
if host_from ~= module.host then
return stanza;
end

View File

@@ -200,8 +200,14 @@ end
-- Managing breakout rooms
function create_breakout_room(room_jid, subject)
local main_room, main_room_jid = get_main_room(room_jid);
function create_breakout_room(orig_room, subject)
local main_room, main_room_jid = get_main_room(orig_room.jid);
if orig_room ~= main_room then
module:log('warn', 'Invalid create breakout room request for %s', orig_room.jid);
return;
end
local breakout_room_jid = uuid_gen() .. '@' .. breakout_rooms_muc_component_config;
if not main_room._data.breakout_rooms then
@@ -219,13 +225,18 @@ function create_breakout_room(room_jid, subject)
broadcast_breakout_rooms(main_room_jid);
end
function destroy_breakout_room(room_jid, message)
function destroy_breakout_room(orig_room, room_jid, message)
local main_room, main_room_jid = get_main_room(room_jid);
if room_jid == main_room_jid then
return;
end
if orig_room ~= main_room then
module:log('warn', 'Invalid destroy breakout room request for %s', orig_room.jid);
return;
end
local breakout_room = breakout_rooms_muc_service.get_room_from_jid(room_jid);
if breakout_room then
@@ -244,13 +255,18 @@ function destroy_breakout_room(room_jid, message)
end
function rename_breakout_room(room_jid, name)
function rename_breakout_room(orig_room, room_jid, name)
local main_room, main_room_jid = get_main_room(room_jid);
if room_jid == main_room_jid then
return;
end
if orig_room ~= main_room then
module:log('warn', 'Invalid rename breakout room request for %s', orig_room.jid);
return;
end
if main_room then
if main_room._data.breakout_rooms then
main_room._data.breakout_rooms[room_jid] = name;
@@ -322,18 +338,25 @@ function on_message(event)
end
if message.attr.type == JSON_TYPE_ADD_BREAKOUT_ROOM then
create_breakout_room(room.jid, message.attr.subject);
create_breakout_room(room, message.attr.subject);
return true;
elseif message.attr.type == JSON_TYPE_REMOVE_BREAKOUT_ROOM then
destroy_breakout_room(message.attr.breakoutRoomJid);
destroy_breakout_room(room, message.attr.breakoutRoomJid);
return true;
elseif message.attr.type == JSON_TYPE_RENAME_BREAKOUT_ROOM then
rename_breakout_room(message.attr.breakoutRoomJid, message.attr.subject);
rename_breakout_room(room, message.attr.breakoutRoomJid, message.attr.subject);
return true;
elseif message.attr.type == JSON_TYPE_MOVE_TO_ROOM_REQUEST then
local participant_jid = message.attr.participantJid;
local target_room_jid = message.attr.roomJid;
if not room._data.breakout_rooms or not (
room._data.breakout_rooms[target_room_jid] or target_room_jid == internal_room_jid_match_rewrite(room.jid))
then
module:log('warn', 'Invalid breakout room %s for %s', target_room_jid, room.jid);
return false
end
local json_msg, error = json.encode({
type = BREAKOUT_ROOMS_IDENTITY_TYPE,
event = JSON_TYPE_MOVE_TO_ROOM_REQUEST,
@@ -342,6 +365,7 @@ function on_message(event)
if not json_msg then
module:log('error', 'skip sending request room:%s error:%s', room.jid, error);
return false
end
send_json_msg(participant_jid, json_msg)
@@ -416,6 +440,16 @@ function exist_occupants_in_rooms(main_room)
return false;
end
function on_occupant_pre_leave(event)
local room, occupant, session, stanza = event.room, event.occupant, event.origin, event.stanza;
local main_room = get_main_room(room.jid);
prosody.events.fire_event('jitsi-breakout-occupant-leaving', {
room = room; main_room = main_room; occupant = occupant; stanza = stanza; session = session;
});
end
function on_occupant_left(event)
local room_jid = event.room.jid;
@@ -481,7 +515,7 @@ function on_main_room_destroyed(event)
end
for breakout_room_jid in pairs(main_room._data.breakout_rooms or {}) do
destroy_breakout_room(breakout_room_jid, event.reason)
destroy_breakout_room(main_room, breakout_room_jid, event.reason)
end
end
@@ -510,6 +544,7 @@ function process_breakout_rooms_muc_loaded(breakout_rooms_muc, host_module)
host_module:hook('muc-occupant-joined', on_occupant_joined);
host_module:hook('muc-occupant-left', on_occupant_left);
host_module:hook('muc-room-pre-create', on_breakout_room_pre_create);
host_module:hook('muc-occupant-pre-leave', on_occupant_pre_leave);
host_module:hook('muc-disco#info', function (event)
local room = event.room;
@@ -526,7 +561,7 @@ function process_breakout_rooms_muc_loaded(breakout_rooms_muc, host_module)
name = 'muc#roominfo_breakout_main_room';
label = 'The main room associated with this breakout room';
});
event.formdata['muc#roominfo_breakout_main_room'] = main_room_jid;
event.formdata['muc#roominfo_breakout_main_room'] = internal_room_jid_match_rewrite(main_room_jid);
-- If the main room has a lobby, make it so this breakout room also uses it.
if (main_room and main_room._data.lobbyroom and main_room:get_members_only()) then
@@ -553,7 +588,7 @@ function process_breakout_rooms_muc_loaded(breakout_rooms_muc, host_module)
table.insert(event.form, {
name = 'muc#roominfo_breakout_main_room';
label = 'The main room associated with this breakout room';
value = main_room_jid;
value = internal_room_jid_match_rewrite(main_room_jid);
});
end);

View File

@@ -0,0 +1,56 @@
--- This module removes identity information from presence stanzas when the
--- hideDisplayNameForAll or hideDisplayNameForGuests options are enabled
--- for a room.
--- To be enabled under the main muc component
local filters = require 'util.filters';
local st = require 'util.stanza';
local util = module:require 'util';
local filter_identity_from_presence = util.filter_identity_from_presence;
local get_room_by_name_and_subdomain = util.get_room_by_name_and_subdomain;
local is_admin = util.is_admin;
local ends_with = util.ends_with;
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
-- we need to get the shared resource for joining moderators, as participants are marked as moderators
-- after joining which is after the filter for stanza/out, but we need to know will this participant be a moderator
local joining_moderator_participants = module:shared('moderators/joining_moderator_participants');
--- Filter presence sent to non-moderator members of a room when the hideDisplayNameForGuests option is set.
function filter_stanza_out(stanza, session)
if stanza.name ~= 'presence' or stanza.attr.type == 'error'
or stanza.attr.type == 'unavailable' or ends_with(stanza.attr.from, '/focus') then
return stanza;
end
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
local shouldFilter = false;
if room and (room._data.hideDisplayNameForGuests == true or room._data.hideDisplayNameForAll == true) then
local occupant = room:get_occupant_by_real_jid(stanza.attr.to);
-- don't touch self-presence
if occupant and stanza.attr.from ~= internal_room_jid_match_rewrite(occupant.nick) then
local isModerator = (occupant.role == 'moderator' or joining_moderator_participants[occupant.bare_jid]);
shouldFilter = room._data.hideDisplayNameForAll or not isModerator;
end
end
if shouldFilter then
return filter_identity_from_presence(stanza);
else
return stanza;
end
end
function filter_session(session)
filters.add_filter(session, 'stanzas/out', filter_stanza_out, -100);
end
function module.load()
filters.add_filter_hook(filter_session);
end
function module.unload()
filters.remove_filter_hook(filter_session);
end

View File

@@ -4,9 +4,9 @@
-- Copyright (C) 2023-present 8x8, Inc.
local oss_util = module:require "util";
local is_admin = oss_util.is_admin;
local is_healthcheck_room = oss_util.is_healthcheck_room;
local process_host_module = oss_util.process_host_module;
local um_is_admin = require "core.usermanager".is_admin;
local inspect = require('inspect');
local jid_bare = require "util.jid".bare;
local jid = require "util.jid";
@@ -21,10 +21,6 @@ if lobby_muc_component_config == nil then
return ;
end
local function is_admin(occupant_jid)
return um_is_admin(occupant_jid, module.host);
end
local function remove_flip_tag(stanza)
stanza:maptags(function(tag)
if tag and tag.name == "flip_device" then

View File

@@ -1,6 +1,30 @@
-- This module makes all MUCs in Prosody unavailable on disco#items query
-- Copyright (C) 2023-present 8x8, Inc.
local jid = require 'util.jid';
local st = require 'util.stanza';
module:hook("muc-room-pre-create", function(event)
local util = module:require 'util';
local get_room_from_jid = util.get_room_from_jid;
module:hook('muc-room-pre-create', function(event)
event.room:set_hidden(true);
end, -1);
for _, event_name in pairs {
'iq-get/bare/http://jabber.org/protocol/disco#info:query';
'iq-get/host/http://jabber.org/protocol/disco#info:query';
} do
module:hook(event_name, function (event)
local origin, stanza = event.origin, event.stanza;
local room_jid = jid.bare(stanza.attr.to);
local room = get_room_from_jid(room_jid);
if room then
if not room:get_occupant_by_real_jid(stanza.attr.from) then
origin.send(st.error_reply(stanza, 'auth', 'forbidden'));
return true;
end
end
-- prosody will send item-not-found
end, 1) -- make sure we handle it before prosody that uses priority -2 for this
end

View File

@@ -0,0 +1,171 @@
-- http endpoint to kick participants, access is based on provided jwt token
-- the correct jigasi we fined based on the display name and the number provided
-- Copyright (C) 2023-present 8x8, Inc.
local util = module:require "util";
local async_handler_wrapper = util.async_handler_wrapper;
local is_sip_jigasi = util.is_sip_jigasi;
local starts_with = util.starts_with;
local formdecode = require "util.http".formdecode;
local urlencode = require "util.http".urlencode;
local jid = require "util.jid";
local json = require 'cjson.safe';
local muc_domain_prefix = module:get_option_string("muc_mapper_domain_prefix", "conference");
local muc_domain_base = module:get_option_string("muc_mapper_domain_base");
if not muc_domain_base then
module:log("warn", "No 'muc_domain_base' option set, disabling kick check endpoint.");
return ;
end
local json_content_type = "application/json";
local token_util = module:require "token/util".new(module);
local asapKeyServer = module:get_option_string('prosody_password_public_key_repo_url', '');
if asapKeyServer == '' then
module:log('warn', 'No "prosody_password_public_key_repo_url" option set, disabling kick endpoint.');
return ;
end
token_util:set_asap_key_server(asapKeyServer);
--- Verifies the token
-- @param token the token we received
-- @param room_address the full room address jid
-- @return true if values are ok or false otherwise
function verify_token(token, room_address)
if token == nil then
module:log("warn", "no token provided for %s", room_address);
return false;
end
local session = {};
session.auth_token = token;
local verified, reason, msg = token_util:process_and_verify_token(session);
if not verified then
module:log("warn", "not a valid token %s %s for %s", tostring(reason), tostring(msg), room_address);
return false;
end
return true;
end
-- Validates the request by checking for required url param room and
-- validates the token provided with the request
-- @param request - The request to validate.
-- @return [error_code, room]
local function validate_and_get_room(request)
if not request.url.query then
module:log("warn", "No query");
return 400, nil;
end
local params = formdecode(request.url.query);
local room_name = urlencode(params.room) or "";
local subdomain = urlencode(params.prefix) or "";
if not room_name then
module:log("warn", "Missing room param for %s", room_name);
return 400, nil;
end
local room_address = jid.join(room_name, muc_domain_prefix.."."..muc_domain_base);
if subdomain and subdomain ~= "" then
room_address = "["..subdomain.."]"..room_address;
end
-- verify access
local token = request.headers["authorization"]
if token and starts_with(token,'Bearer ') then
token = token:sub(8,#token)
end
if not verify_token(token, room_address) then
return 403, nil;
end
local room = get_room_from_jid(room_address);
if not room then
module:log("warn", "No room found for %s", room_address);
return 404, nil;
else
return 200, room;
end
end
function handle_kick_participant (event)
local request = event.request;
if request.headers.content_type ~= json_content_type
or (not request.body or #request.body == 0) then
module:log("warn", "Wrong content type: %s", request.headers.content_type);
return { status_code = 400; }
end
local params, error = json.decode(request.body);
if not params then
module:log("warn", "Missing params error:%s", error);
return { status_code = 400; }
end
local number = params["number"];
local participantId = params["participantId"];
if (not number and not participantId) or (number and participantId) then
module:log("warn", "Invalid parameters: exactly one of 'number' or 'participantId' must be provided.");
return { status_code = 400; };
end
local error_code, room = validate_and_get_room(request);
if error_code and error_code ~= 200 then
module:log("error", "Error validating %s", error_code);
return { error_code = 400; }
end
if not room then
return { status_code = 404; }
end
for _, occupant in room:each_occupant() do
local pr = occupant:get_presence();
if is_participant_match(pr, number, participantId) then
room:set_role(true, occupant.nick, nil);
module:log('info', 'Occupant kicked %s from %s', occupant.nick, room.jid);
return { status_code = 200; }
end
end
-- not found participant to kick
return { status_code = 404; };
end
function is_participant_match(pr, number, participantId)
if number then
local displayName = pr:get_child_text('nick', 'http://jabber.org/protocol/nick');
return is_sip_jigasi(pr) and displayName and starts_with(displayName, number);
elseif participantId then
local from = pr.attr.from;
local _, _, from_resource = jid.split(from);
if from_resource then
return from_resource == participantId;
end
end
return false;
end
module:log("info","Adding http handler for /kick-participant on %s", module.host);
module:depends("http");
module:provides("http", {
default_path = "/";
route = {
["PUT kick-participant"] = function (event) return async_handler_wrapper(event, handle_kick_participant) end;
};
});

View File

@@ -193,6 +193,10 @@ function filter_stanza(stanza)
end
end
if not from_real_jid then
return nil;
end
local is_from_moderator = lobby_room:get_affiliation(from_real_jid) == 'owner';
if is_to_moderator or is_from_moderator then

View File

@@ -1,21 +1,29 @@
local jid = require 'util.jid';
local json = require 'cjson.safe';
local queue = require "util.queue";
local uuid_gen = require "util.uuid".generate;
local main_util = module:require "util";
local is_admin = main_util.is_admin;
local ends_with = main_util.ends_with;
local get_room_from_jid = main_util.get_room_from_jid;
local is_healthcheck_room = main_util.is_healthcheck_room;
local internal_room_jid_match_rewrite = main_util.internal_room_jid_match_rewrite;
local presence_check_status = main_util.presence_check_status;
local um_is_admin = require 'core.usermanager'.is_admin;
local function is_admin(jid)
return um_is_admin(jid, module.host);
end
local extract_subdomain = main_util.extract_subdomain;
local QUEUE_MAX_SIZE = 500;
-- Module that generates a unique meetingId, attaches it to the room
-- and adds it to all disco info form data (when room is queried or in the
-- initial room owner config)
module:depends("jitsi_permissions");
-- Common module for all logic that can be loaded under the conference muc component.
--
-- This module:
-- a) Generates a unique meetingId, attaches it to the room and adds it to all disco info form data
-- (when room is queried or in the initial room owner config).
-- b) Updates user region (obtain it from the incoming http headers) in the occupant's presence on pre-join.
-- c) Avoids any participant joining the room in the interval between creating the room and jicofo entering the room.
-- d) Removes any nick that maybe set to messages being sent to the room.
-- e) Fires event for received endpoint messages (optimization to decode them once).
-- Hook to assign meetingId for new rooms
module:hook("muc-room-created", function(event)
@@ -75,17 +83,35 @@ module:hook('muc-broadcast-presence', function (event)
end
end);
--- Avoids any participant joining the room in the interval between creating the room
--- and jicofo entering the room
module:hook('muc-occupant-pre-join', function (event)
local room, stanza = event.room, event.stanza;
-- we skip processing only if jicofo_lock is set to false
if room._data.jicofo_lock == false or is_healthcheck_room(room.jid) then
local function process_region(session, stanza)
if not session.user_region then
return;
end
local region = stanza:get_child_text('jitsi_participant_region');
if region then
return;
end
stanza:tag('jitsi_participant_region'):text(session.user_region):up();
end
--- Avoids any participant joining the room in the interval between creating the room
--- and jicofo entering the room
module:hook('muc-occupant-pre-join', function (event)
local occupant, room, stanza = event.occupant, event.room, event.stanza;
local is_health_room = is_healthcheck_room(room.jid);
-- check for region
if not is_admin(occupant.bare_jid) and not is_health_room then
process_region(event.origin, stanza);
end
-- we skip processing only if jicofo_lock is set to false
if room._data.jicofo_lock == false or is_health_room then
return;
end
local occupant = event.occupant;
if ends_with(occupant.nick, '/focus') then
module:fire_event('jicofo-unlock-room', { room = room; });
else
@@ -131,3 +157,87 @@ module:hook('jicofo-unlock-room', handle_jicofo_unlock);
module:hook("muc-occupant-groupchat", function(event)
event.stanza:remove_children('nick', 'http://jabber.org/protocol/nick');
end, 45); -- prosody check is prio 50, we want to run after it
module:hook('message/bare', function(event)
local stanza = event.stanza;
if stanza.attr.type ~= 'groupchat' then
return nil;
end
-- we are interested in all messages without a body
local body = stanza:get_child('body')
if body then
return;
end
local room = get_room_from_jid(stanza.attr.to);
if not room then
module:log('warn', 'No room found found for %s', stanza.attr.to);
return;
end
local occupant_jid = stanza.attr.from;
local occupant = room:get_occupant_by_real_jid(occupant_jid);
if not occupant then
module:log("error", "Occupant sending msg %s was not found in room %s", occupant_jid, room.jid)
return;
end
local json_message = stanza:get_child_text('json-message', 'http://jitsi.org/jitmeet')
or stanza:get_child_text('json-message');
if not json_message then
return;
end
-- TODO: add optimization by moving type and certain fields like is_interim as attribute on 'json-message'
-- using string find is roughly 70x faster than json decode for checking the value
if string.find(json_message, '"is_interim":true', 1, true) then
return;
end
local msg_obj, error = json.decode(json_message);
if error then
module:log('error', 'Error decoding data error:%s Sender: %s to:%s', error, stanza.attr.from, stanza.attr.to);
return true;
end
if msg_obj.transcript ~= nil then
local transcription = msg_obj;
-- in case of the string matching optimization above failed
if transcription.is_interim then
return;
end
-- TODO what if we have multiple alternative transcriptions not just 1
local text_message = transcription.transcript[1].text;
--do not send empty messages
if text_message == '' then
return;
end
local user_id = transcription.participant.id;
local who = room:get_occupant_by_nick(jid.bare(room.jid)..'/'..user_id);
transcription.jid = who and who.jid;
transcription.session_id = room._data.meetingId;
local tenant, conference_name, id = extract_subdomain(jid.node(room.jid));
if tenant then
transcription.fqn = tenant..'/'..conference_name;
else
transcription.fqn = conference_name;
end
transcription.customer_id = id;
return module:fire_event('jitsi-transcript-received', {
room = room, occupant = occupant, transcription = transcription, stanza = stanza });
end
return module:fire_event('jitsi-endpoint-message-received', {
room = room, occupant = occupant, message = msg_obj,
origin = event.origin,
stanza = stanza, raw_message = json_message });
end);

View File

@@ -6,9 +6,9 @@
-- the guest domain which is anonymous.
-- The module has the option to set participants to moderators when connected via token/when they are authenticated
-- This module depends on mod_persistent_lobby.
local um_is_admin = require 'core.usermanager'.is_admin;
local jid = require 'util.jid';
local util = module:require "util";
local is_admin = util.is_admin;
local is_healthcheck_room = util.is_healthcheck_room;
local is_moderated = util.is_moderated;
local process_host_module = util.process_host_module;
@@ -17,7 +17,7 @@ local disable_auto_owners = module:get_option_boolean('wait_for_host_disable_aut
local muc_domain_base = module:get_option_string('muc_mapper_domain_base');
if not muc_domain_base then
module:log('warn', "No 'muc_mapper_domain_base' option set, disabling muc_mapper plugin inactive");
module:log('warn', "No 'muc_mapper_domain_base' option set, disabling module");
return
end
@@ -43,10 +43,6 @@ if not disable_auto_owners then
end, 2);
end
local function is_admin(jid)
return um_is_admin(jid, module.host);
end
-- if not authenticated user is trying to join the room we enable lobby in it
-- and wait for the moderator to join
module:hook('muc-occupant-pre-join', function (event)

View File

@@ -11,26 +11,8 @@ local muc = module:depends("muc");
local NS_NICK = 'http://jabber.org/protocol/nick';
local is_healthcheck_room = util.is_healthcheck_room;
-- Checks if the given stanza contains a JSON message,
-- and that the message type pertains to the polls feature.
-- If yes, returns the parsed message. Otherwise, returns nil.
local function get_poll_message(stanza)
if stanza.attr.type ~= "groupchat" then
return nil;
end
local json_data = stanza:get_child_text("json-message", "http://jitsi.org/jitmeet");
if json_data == nil then
return nil;
end
local data, error = json.decode(json_data);
if not data or (data.type ~= "new-poll" and data.type ~= "answer-poll") then
if error then
module:log('error', 'Error decoding data error:%s', error);
end
return nil;
end
return data;
end
local POLLS_LIMIT = 128;
local POLL_PAYLOAD_LIMIT = 1024;
-- Logs a warning and returns true if a room does not
-- have poll data associated with it.
@@ -72,6 +54,7 @@ module:hook("muc-room-created", function(event)
room.polls = {
by_id = {};
order = {};
count = 0;
};
end);
@@ -79,27 +62,46 @@ end);
-- by listening to "new-poll" and "answer-poll" messages,
-- and updating the room poll data accordingly.
-- This mirrors the client-side poll update logic.
module:hook("message/bare", function(event)
local data = get_poll_message(event.stanza);
if data == nil then return end
module:hook('jitsi-endpoint-message-received', function(event)
local data, error, occupant, room, origin, stanza
= event.message, event.error, event.occupant, event.room, event.origin, event.stanza;
local room = muc.get_room_from_jid(event.stanza.attr.to);
if not data or (data.type ~= "new-poll" and data.type ~= "answer-poll") then
return;
end
if string.len(event.raw_message) >= POLL_PAYLOAD_LIMIT then
module:log('error', 'Poll payload too large, discarding. Sender: %s to:%s', stanza.attr.from, stanza.attr.to);
return true;
end
if data.type == "new-poll" then
if check_polls(room) then return end
local occupant_jid = event.stanza.attr.from;
local occupant = room:get_occupant_by_real_jid(occupant_jid);
if not occupant then
module:log("error", "Occupant %s was not found in room %s", occupant_jid, room.jid)
return
end
local poll_creator = get_occupant_details(occupant)
if not poll_creator then
module:log("error", "Cannot retrieve poll creator id and name for %s from %s", occupant.jid, room.jid)
return
end
if room.polls.count >= POLLS_LIMIT then
module:log("error", "Too many polls created in %s", room.jid)
return true;
end
if room.polls.by_id[data.pollId] ~= nil then
module:log("error", "Poll already exists: %s", data.pollId);
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Poll already exists'));
return true;
end
if room.jitsiMetadata and room.jitsiMetadata.permissions
and room.jitsiMetadata.permissions.pollCreationRestricted
and not is_feature_allowed('create-polls', origin.jitsi_meet_context_features) then
origin.send(st.error_reply(stanza, 'cancel', 'not-allowed', 'Creation of polls not allowed for user'));
return true;
end
local answers = {}
local compact_answers = {}
for i, name in ipairs(data.answers) do
@@ -117,6 +119,7 @@ module:hook("message/bare", function(event)
room.polls.by_id[data.pollId] = poll
table.insert(room.polls.order, poll)
room.polls.count = room.polls.count + 1;
local pollData = {
event = event,
@@ -130,16 +133,9 @@ module:hook("message/bare", function(event)
}
}
module:fire_event("poll-created", pollData);
elseif data.type == "answer-poll" then
if check_polls(room) then return end
local occupant_jid = event.stanza.attr.from;
local occupant = room:get_occupant_by_real_jid(occupant_jid);
if not occupant then
module:log("error", "Occupant %s does not exists for room %s", occupant_jid, room.jid)
return
end
local poll = room.polls.by_id[data.pollId];
if poll == nil then
module:log("warn", "answering inexistent poll");

View File

@@ -5,16 +5,13 @@ local update_presence_identity = module:require "util".update_presence_identity;
-- values are set in the session, then insert them into the presence messages
-- for that session.
function on_message(event)
if event and event["stanza"] then
if event.origin and event.origin.jitsi_meet_context_user then
local stanza, session = event.stanza, event.origin;
if stanza and session then
update_presence_identity(
event.stanza,
event.origin.jitsi_meet_context_user,
event.origin.jitsi_meet_context_group
stanza,
session.jitsi_meet_context_user,
session.jitsi_meet_context_group
);
end
end
end

View File

@@ -14,6 +14,7 @@ local ip_util = require "util.ip";
local new_ip = ip_util.new_ip;
local match_ip = ip_util.match;
local parse_cidr = ip_util.parse_cidr;
local get_ip = module:require "util".get_ip;
local config = {};
local limits_resolution = 1;
@@ -76,14 +77,6 @@ local function is_whitelisted_host(h)
return config.whitelist_hosts:contains(h);
end
-- Discover real remote IP of a session
-- Note: http_server.get_request_from_conn() was added in Prosody 0.12.3,
-- this code provides backwards compatibility with older versions
local get_request_from_conn = http_server.get_request_from_conn or function (conn)
local response = conn and conn._http_open_response;
return response and response.request or nil;
end;
-- Add an IP to the set of limied IPs
local function limit_ip(ip)
module:log("info", "Limiting %s due to login/join rate exceeded.", ip);
@@ -192,9 +185,8 @@ local function filter_hook(session)
return;
end
local request = get_request_from_conn(session.conn);
local ip = request and request.ip or session.ip;
module:log("debug", "New session from %s", ip);
local ip = get_ip(session);
module:log("debug", "New session from %s", ip);
if is_whitelisted(ip) or is_whitelisted_host(session.host) then
return;
end

View File

@@ -1,10 +1,6 @@
-- Generic room metadata
-- See mod_room_metadata_component.lua
local COMPONENT_IDENTITY_TYPE = 'room_metadata';
local room_metadata_component_host = module:get_option_string('room_metadata_component', 'metadata.'..module.host);
-- TODO: Remove this file after several stable releases when people update their configs
module:log('warn', 'mod_room_metadata is deprecated and will be removed in a future release. '
.. 'Please update your config by removing this module from the list of loaded modules.');
module:depends("jitsi_session");
-- Advertise the component so clients can pick up the address and use it
module:add_identity('component', COMPONENT_IDENTITY_TYPE, room_metadata_component_host);
module:depends("features_identity");

View File

@@ -1,34 +1,40 @@
-- This module implements a generic metadata storage system for rooms.
--
-- VirtualHost "jitmeet.example.com"
-- modules_enabled = {
-- "room_metadata"
-- }
-- room_metadata_component = "metadata.jitmeet.example.com"
-- main_muc = "conference.jitmeet.example.com"
--
-- Component "metadata.jitmeet.example.com" "room_metadata_component"
-- muc_component = "conference.jitmeet.example.com"
-- breakout_rooms_component = "breakout.jitmeet.example.com"
local array = require 'util.array';
local filters = require 'util.filters';
local jid_node = require 'util.jid'.node;
local json = require 'cjson.safe';
local json = require 'util.json';
local st = require 'util.stanza';
local jid = require 'util.jid';
local util = module:require 'util';
local is_admin = util.is_admin;
local is_healthcheck_room = util.is_healthcheck_room;
local get_room_from_jid = util.get_room_from_jid;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
local process_host_module = util.process_host_module;
local table_shallow_copy = util.table_shallow_copy;
local table_add = util.table_add;
local table_equals = util.table_equals;
local MUC_NS = 'http://jabber.org/protocol/muc';
local COMPONENT_IDENTITY_TYPE = 'room_metadata';
local FORM_KEY = 'muc#roominfo_jitsimetadata';
local muc_component_host = module:get_option_string('muc_component');
if muc_component_host == nil then
module:log("error", "No muc_component specified. No muc to operate on!");
module:log('error', 'No muc_component specified. No muc to operate on!');
return;
end
local main_virtual_host = module:get_option_string('muc_mapper_domain_base');
if not main_virtual_host then
module:log('warn', 'No muc_mapper_domain_base option set.');
return;
end
@@ -40,10 +46,13 @@ local main_muc_module;
-- Utility functions
function getMetadataJSON(room)
-- Returns json string with the metadata for the room.
-- @param room The room object.
-- @param metadata Optional metadata to use instead of the room's jitsiMetadata.
function getMetadataJSON(room, metadata)
local res, error = json.encode({
type = COMPONENT_IDENTITY_TYPE,
metadata = room.jitsiMetadata or {}
metadata = metadata or room.jitsiMetadata or {}
});
if not res then
@@ -53,28 +62,53 @@ function getMetadataJSON(room)
return res;
end
-- Putting the information on the config form / disco-info allows us to save
-- an extra message to users who join later.
function getFormData(room)
return {
name = FORM_KEY;
type = 'text-multi';
label = 'Room metadata';
value = getMetadataJSON(room);
};
end
function broadcastMetadata(room)
local json_msg = getMetadataJSON(room);
if not json_msg then
return;
end
for _, occupant in room:each_occupant() do
send_json_msg(occupant.jid, internal_room_jid_match_rewrite(room.jid), json_msg)
send_metadata(occupant, room, json_msg)
end
end
function send_json_msg(to_jid, room_jid, json_msg)
local stanza = st.message({ from = module.host; to = to_jid; })
:tag('json-message', { xmlns = 'http://jitsi.org/jitmeet', room = room_jid }):text(json_msg):up();
function send_metadata(occupant, room, json_msg)
if not json_msg or is_admin(occupant.bare_jid) then
local metadata_to_send = room.jitsiMetadata or {};
-- we want to send the main meeting participants only to jicofo
if is_admin(occupant.bare_jid) then
local participants;
local moderators = array();
if room._data.participants then
participants = array();
participants:append(room._data.participants);
end
if room._data.moderator_id then
moderators:push(room._data.moderator_id);
end
if room._data.moderators then
moderators:append(room._data.moderators);
end
metadata_to_send = table_shallow_copy(metadata_to_send);
metadata_to_send.participants = participants;
metadata_to_send.moderators = moderators;
end
json_msg = getMetadataJSON(room, metadata_to_send);
end
local stanza = st.message({ from = module.host; to = occupant.jid; })
:tag('json-message', {
xmlns = 'http://jitsi.org/jitmeet',
room = internal_room_jid_match_rewrite(room.jid)
}):text(json_msg):up();
module:send(stanza);
end
@@ -84,12 +118,14 @@ function room_created(event)
local room = event.room;
if is_healthcheck_room(room.jid) then
return ;
return;
end
if not room.jitsiMetadata then
room.jitsiMetadata = {};
end
room.sent_initial_metadata = {};
end
function on_message(event)
@@ -129,11 +165,6 @@ function on_message(event)
return false;
end
if occupant.role ~= 'moderator' then
module:log('warn', 'Occupant %s is not moderator and not allowed this operation for %s', from, room.jid);
return false;
end
local jsonData, error = json.decode(messageText);
if jsonData == nil then -- invalid JSON
module:log("error", "Invalid JSON message: %s error:%s", messageText, error);
@@ -145,12 +176,28 @@ function on_message(event)
return false;
end
room.jitsiMetadata[jsonData.key] = jsonData.data;
if occupant.role ~= 'moderator' then
-- will return a non nil filtered data to use, if it is nil, it is not allowed
local res = module:context(main_virtual_host):fire_event('jitsi-metadata-allow-moderation',
{ room = room; actor = occupant; key = jsonData.key ; data = jsonData.data; session = session; });
broadcastMetadata(room);
if not res then
module:log('warn', 'Occupant %s is not moderator and not allowed this operation for %s', from, room.jid);
return false;
end
-- fire and event for the change
main_muc_module:fire_event('jitsi-metadata-updated', { room = room; actor = occupant; key = jsonData.key; });
jsonData.data = res;
end
local old_value = room.jitsiMetadata[jsonData.key];
if not table_equals(old_value, jsonData.data) then
room.jitsiMetadata[jsonData.key] = jsonData.data;
broadcastMetadata(room);
-- fire and event for the change
main_muc_module:fire_event('jitsi-metadata-updated', { room = room; actor = occupant; key = jsonData.key; });
end
return true;
end
@@ -169,21 +216,51 @@ function process_main_muc_loaded(main_muc, host_module)
host_module:hook("muc-room-created", room_created, -1);
host_module:hook('muc-disco#info', function (event)
local room = event.room;
table.insert(event.form, getFormData(room));
end);
host_module:hook("muc-config-form", function(event)
local room = event.room;
table.insert(event.form, getFormData(room));
end);
-- The room metadata was updated internally (from another module).
host_module:hook("room-metadata-changed", function(event)
broadcastMetadata(event.room);
end);
-- TODO: Once clients update to read/write metadata for startMuted policy we can drop this
-- this is to convert presence settings from old clients to metadata
host_module:hook('muc-broadcast-presence', function (event)
local actor, occupant, room, stanza, x = event.actor, event.occupant, event.room, event.stanza, event.x;
if is_healthcheck_room(room.jid) or occupant.role ~= 'moderator' then
return;
end
local startMuted = stanza:get_child('startmuted', 'http://jitsi.org/jitmeet/start-muted');
if not startMuted then
return;
end
if not room.jitsiMetadata then
room.jitsiMetadata = {};
end
local startMutedMetadata = room.jitsiMetadata.startMuted or {};
local audioNewValue = startMuted.attr.audio == 'true';
local videoNewValue = startMuted.attr.video == 'true';
local send_update = false;
if startMutedMetadata.audio ~= audioNewValue then
startMutedMetadata.audio = audioNewValue;
send_update = true;
end
if startMutedMetadata.video ~= videoNewValue then
startMutedMetadata.video = videoNewValue;
send_update = true;
end
if send_update then
room.jitsiMetadata.startMuted = startMutedMetadata;
host_module:fire_event('room-metadata-changed', { room = room; });
end
end);
end
-- process or waits to process the main muc component
@@ -208,18 +285,6 @@ function process_breakout_muc_loaded(breakout_muc, host_module)
module:log("info", "Hook to muc events on %s", breakout_rooms_component_host);
host_module:hook("muc-room-created", room_created, -1);
host_module:hook('muc-disco#info', function (event)
local room = event.room;
table.insert(event.form, getFormData(room));
end);
host_module:hook("muc-config-form", function(event)
local room = event.room;
table.insert(event.form, getFormData(room));
end);
end
if breakout_rooms_component_host then
@@ -238,3 +303,59 @@ if breakout_rooms_component_host then
end
end);
end
-- Send a message update for metadata before sending the first self presence
function filter_stanza(stanza, session)
if not stanza.attr or not stanza.attr.to or stanza.name ~= 'presence'
or stanza.attr.type == 'unavailable' or ends_with(stanza.attr.from, '/focus') then
return stanza;
end
local bare_to = jid.bare(stanza.attr.to);
local muc_x = stanza:get_child('x', MUC_NS..'#user');
if not muc_x or not presence_check_status(muc_x, '110') then
return stanza;
end
local room = get_room_from_jid(room_jid_match_rewrite(jid.bare(stanza.attr.from)));
if not room or not room.sent_initial_metadata or is_healthcheck_room(room.jid) then
return stanza;
end
if room.sent_initial_metadata[bare_to] then
return stanza;
end
local occupant;
for _, o in room:each_occupant() do
if o.bare_jid == bare_to then
occupant = o;
end
end
if not occupant then
module:log('warn', 'No occupant %s found for %s', bare_to, room.jid);
return stanza;
end
room.sent_initial_metadata[bare_to] = true;
send_metadata(occupant, room);
return stanza;
end
function filter_session(session)
-- domain mapper is filtering on default priority 0
-- allowners is -1 and we need it after that, permissions is -2
filters.add_filter(session, 'stanzas/out', filter_stanza, -3);
end
-- enable filtering presences
filters.add_filter_hook(filter_session);
process_host_module(main_virtual_host, function(host_module)
module:context(host_module.host):fire_event('jitsi-add-identity', {
name = 'room_metadata'; host = module.host;
});
end);

View File

@@ -0,0 +1,138 @@
-- to be enabled under the main virtual host with all required settings
-- short_lived_token = {
-- issuer = 'myissuer';
-- accepted_audiences = { 'file-sharing' };
-- key_path = '/etc/prosody/short_lived_token.key';
-- key_id = 'my_kid';
-- ttl_seconds = 30;
-- };
-- The key in key_path can be generated via: openssl genrsa -out $PRIVATE_KEY_PATH 2048
-- And you can get the public key from it, which can be used ot verify those tokens via:
-- openssl rsa -in $PRIVATE_KEY_PATH -pubout -out $PUBLIC_KEY_PATH
local jid = require 'util.jid';
local st = require 'util.stanza';
local jwt = module:require 'luajwtjitsi';
local util = module:require 'util';
local is_vpaas = util.is_vpaas;
local process_host_module = util.process_host_module;
local table_find = util.table_find;
local create_throttle = require 'prosody.util.throttle'.create;
local SERVICE_TYPE = 'short-lived-token';
local options = module:get_option('short_lived_token');
if not (options.issuer and options.accepted_audiences
and options.key_path and options.key_id and options.ttl_seconds) then
module:log('error', 'Missing required options for short_lived_token');
return;
end
local f = io.open(options.key_path, 'r');
if f then
options.key = f:read('*all');
f:close();
end
local accepted_requests = {};
for _, host in pairs(options.accepted_audiences) do
accepted_requests[string.format('%s:%s:0', SERVICE_TYPE, host)] = host;
end
local server_region_name = module:get_option_string('region_name');
local main_muc_component_host = module:get_option_string('main_muc');
if main_muc_component_host == nil then
module:log('error', 'main_muc not configured. Cannot proceed.');
return;
end
local main_muc_service;
function generateToken(session, audience, room, occupant)
local t = os.time();
local exp = t + options.ttl_seconds;
local presence = occupant:get_presence(session.full_jid);
local _, _, id = extract_subdomain(jid.node(room.jid));
local payload = {
iss = options.issuer,
aud = audience,
nbf = t,
exp = exp,
sub = session.jitsi_web_query_prefix or module.host,
context = {
group = session.jitsi_meet_context_group or session.granted_jitsi_meet_context_group_id,
user = session.jitsi_meet_context_user or {
id = session.full_jid,
name = presence:get_child_text('nick', 'http://jabber.org/protocol/nick'),
email = presence:get_child_text("email") or nil,
nick = jid.resource(occupant.nick)
},
features = session.jitsi_meet_context_features
},
room = session.jitsi_web_query_room,
meeting_id = room._data.meetingId,
granted_from = session.granted_jitsi_meet_context_user_id,
customer_id = id or session.jitsi_meet_context_group or session.granted_jitsi_meet_context_group_id,
backend_region = server_region_name,
user_region = session.user_region
};
local alg = 'RS256';
local token, err = jwt.encode(payload, options.key, alg, { kid = options.key_id });
if not err then
return token
else
module:log('error', 'Error generating token: %s', err);
return ''
end
end
module:hook('external_service/credentials', function (event)
local requested_credentials, services, session, stanza
= event.requested_credentials, event.services, event.origin, event.stanza;
local room = get_room_by_name_and_subdomain(session.jitsi_web_query_room, session.jitsi_web_query_prefix);
if not room then
session.send(st.error_reply(stanza, 'cancel', 'not-allowed'));
return;
end
local occupant = room:get_occupant_by_real_jid(session.full_jid);
if not occupant then
session.send(st.error_reply(stanza, 'cancel', 'not-allowed'));
return;
end
for request in requested_credentials do
local host = accepted_requests[request];
if host then
services:push({
type = SERVICE_TYPE;
host = host;
username = 'token';
password = generateToken(session, host, room, occupant);
expires = os.time() + options.ttl_seconds;
restricted = true;
transport = 'https';
port = 443;
});
end
end
end);
process_host_module(main_muc_component_host, function(host_module, host)
local muc_module = prosody.hosts[host].modules.muc;
if muc_module then
main_muc_service = muc_module;
else
prosody.hosts[host].events.add_handler('module-loaded', function(event)
if (event.module == 'muc') then
main_muc_service = prosody.hosts[host].modules.muc;
end
end);
end
end);

View File

@@ -1,6 +1,6 @@
local speakerstats_component
= module:get_option_string("speakerstats_component", "speakerstats."..module.host);
-- TODO: Remove this file after several stable releases when people update their configs
module:log('warn', 'mod_speakerstats is deprecated and will be removed in a future release. '
.. 'Please update your config by removing this module from the list of loaded modules.');
-- Advertise speaker stats so client can pick up the address and start sending
-- dominant speaker events
module:add_identity("component", "speakerstats", speakerstats_component);
module:depends('jitsi_session');
module:depends('features_identity');

View File

@@ -1,13 +1,15 @@
local util = module:require "util";
local is_admin = util.is_admin;
local get_room_from_jid = util.get_room_from_jid;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
local is_jibri = util.is_jibri;
local is_healthcheck_room = util.is_healthcheck_room;
local process_host_module = util.process_host_module;
local is_transcriber_jigasi = util.is_transcriber_jigasi;
local jid_resource = require "util.jid".resource;
local st = require "util.stanza";
local socket = require "socket";
local json = require 'cjson.safe';
local um_is_admin = require "core.usermanager".is_admin;
local jid_split = require 'util.jid'.split;
-- we use async to detect Prosody 0.10 and earlier
@@ -18,22 +20,18 @@ if not have_async then
end
local muc_component_host = module:get_option_string("muc_component");
local muc_domain_base = module:get_option_string("muc_mapper_domain_base");
local main_virtual_host = module:get_option_string("muc_mapper_domain_base");
if muc_component_host == nil or muc_domain_base == nil then
if muc_component_host == nil or main_virtual_host == nil then
module:log("error", "No muc_component specified. No muc to operate on!");
return;
end
local breakout_room_component_host = "breakout." .. muc_domain_base;
local breakout_room_component_host = "breakout." .. main_virtual_host;
module:log("info", "Starting speakerstats for %s", muc_component_host);
local main_muc_service;
local function is_admin(jid)
return um_is_admin(jid, module.host);
end
-- Searches all rooms in the main muc component that holds a breakout room
-- caches it if found so we don't search it again
-- we should not cache objects in _data as this is being serialized when calling room:save()
@@ -221,14 +219,15 @@ end
-- Create SpeakerStats object for the joined user
function occupant_joined(event)
local occupant, room = event.occupant, event.room;
local occupant, room, stanza = event.occupant, event.room, event.stanza;
if is_healthcheck_room(room.jid) or is_admin(occupant.bare_jid) then
if is_healthcheck_room(room.jid)
or is_admin(occupant.bare_jid)
or is_transcriber_jigasi(stanza)
or is_jibri(occupant) then
return;
end
local occupant = event.occupant;
local nick = jid_resource(occupant.nick);
if room.speakerStats then
@@ -377,3 +376,9 @@ process_host_module(breakout_room_component_host, function(host_module, host)
end);
end
end);
process_host_module(main_virtual_host, function(host_module)
module:context(host_module.host):fire_event('jitsi-add-identity', {
name = 'speakerstats'; host = module.host;
});
end);

View File

@@ -15,7 +15,6 @@ local get_room_from_jid = util.get_room_from_jid;
local st = require "util.stanza";
local json = require "cjson.safe";
local muc_domain_base = module:get_option_string("muc_mapper_domain_base");
local asapKeyServer = module:get_option_string("prosody_password_public_key_repo_url", "");
if asapKeyServer then

View File

@@ -4,19 +4,17 @@
local log = module._log;
local host = module.host;
local st = require "util.stanza";
local um_is_admin = require "core.usermanager".is_admin;
local jid_split = require 'util.jid'.split;
local jid_bare = require 'util.jid'.bare;
local util = module:require 'util';
local is_admin = util.is_admin;
local DEBUG = false;
local measure_success = module:measure('success', 'counter');
local measure_fail = module:measure('fail', 'counter');
local function is_admin(jid)
return um_is_admin(jid, host);
end
local parentHostName = string.gmatch(tostring(host), "%w+.(%w.+)")();
if parentHostName == nil then
module:log("error", "Failed to start - unable to get parent hostname");

View File

@@ -12,13 +12,15 @@ local st = require 'util.stanza';
local jid = require 'util.jid';
local new_id = require 'util.id'.medium;
local util = module:require 'util';
local filter_identity_from_presence = util.filter_identity_from_presence;
local is_admin = util.is_admin;
local presence_check_status = util.presence_check_status;
local process_host_module = util.process_host_module;
local is_transcriber_jigasi = util.is_transcriber_jigasi;
local json = require 'cjson.safe';
local um_is_admin = require 'core.usermanager'.is_admin;
local function is_admin(jid)
return um_is_admin(jid, module.host);
end
-- Debug flag
local DEBUG = false;
local MUC_NS = 'http://jabber.org/protocol/muc';
@@ -57,7 +59,7 @@ local function send_visitors_iq(conference_service, room, type)
-- send iq informing the vnode that the connect is done and it will allow visitors to join
local iq_id = new_id();
sent_iq_cache:set(iq_id, socket.gettime());
local connect_done = st.iq({
local visitors_iq = st.iq({
type = 'set',
to = conference_service,
from = module.host,
@@ -68,11 +70,54 @@ local function send_visitors_iq(conference_service, room, type)
password = type ~= 'disconnect' and room:get_password() or '',
lobby = room._data.lobbyroom and 'true' or 'false',
meetingId = room._data.meetingId,
moderatorId = room._data.moderator_id, -- can be used from external modules to set single moderator for meetings
createdTimestamp = room.created_timestamp and tostring(room.created_timestamp) or nil
}):up();
});
module:send(connect_done);
if type == 'update' then
visitors_iq:tag('moderators', { xmlns = 'jitsi:visitors' });
for _, o in room:each_occupant() do
if not is_admin(o.bare_jid) and o.role == 'moderator' then
visitors_iq:tag('item', { epId = jid.resource(o.nick) }):up();
end
end
visitors_iq:up();
-- files that are shared in the room
if room.jitsi_shared_files then
visitors_iq:tag('files', { xmlns = 'jitsi:visitors' });
for k, v in pairs(room.jitsi_shared_files) do
visitors_iq:tag('file', {
id = k
}):text(json.encode(v)):up();
end
visitors_iq:up();
end
end
visitors_iq:up();
module:send(visitors_iq);
end
-- Filter out identity information (nick name, email, etc) from a presence stanza,
-- if the hideDisplayNameForGuests option for the room is set (note that the
-- hideDisplayNameForAll option is implemented in a diffrent way and does not
-- require filtering here)
-- This is applied to presence of main room participants before it is sent out to
-- vnodes.
local function filter_stanza_nick_if_needed(stanza, room)
if not stanza or stanza.name ~= 'presence' or stanza.attr.type == 'error' or stanza.attr.type == 'unavailable' then
return stanza;
end
-- if hideDisplayNameForGuests we want to drop any display name from the presence stanza
if room and (room._data.hideDisplayNameForGuests or room._data.hideDisplayNameForAll) then
return filter_identity_from_presence(stanza);
end
return stanza;
end
-- an event received from visitors component, which receives iqs from jicofo
@@ -96,9 +141,12 @@ local function connect_vnode(event)
local sent_main_participants = 0;
-- send update initially so we can report the moderators that will join
send_visitors_iq(conference_service, room, 'update');
for _, o in room:each_occupant() do
if not is_admin(o.bare_jid) then
local fmuc_pr = st.clone(o:get_presence());
local fmuc_pr = filter_stanza_nick_if_needed(st.clone(o:get_presence()), room);
local user, _, res = jid.split(o.nick);
fmuc_pr.attr.to = jid.join(user, conference_service , res);
fmuc_pr.attr.from = o.jid;
@@ -181,11 +229,12 @@ end, 900);
process_host_module(main_muc_component_config, function(host_module, host)
-- detects presence change in a main participant and propagate it to the used visitor nodes
host_module:hook('muc-occupant-pre-change', function (event)
local room, stanza, occupant = event.room, event.stanza, event.dest_occupant;
local room, stanzaEv, occupant = event.room, event.stanza, event.dest_occupant;
local stanza = filter_stanza_nick_if_needed(stanzaEv, room);
-- filter focus and configured domains (used for jibri and transcribers)
if is_admin(stanza.attr.from) or visitors_nodes[room.jid] == nil
or ignore_list:contains(jid.host(occupant.bare_jid)) then
or (ignore_list:contains(jid.host(occupant.bare_jid)) and not is_transcriber_jigasi(stanza)) then
return;
end
@@ -202,11 +251,12 @@ process_host_module(main_muc_component_config, function(host_module, host)
-- when a main participant leaves inform the visitor nodes
host_module:hook('muc-occupant-left', function (event)
local room, stanza, occupant = event.room, event.stanza, event.occupant;
local room, stanzaEv, occupant = event.room, event.stanza, event.occupant;
local stanza = filter_stanza_nick_if_needed(stanzaEv, room);
-- ignore configured domains (jibri and transcribers)
if is_admin(occupant.bare_jid) or visitors_nodes[room.jid] == nil or visitors_nodes[room.jid].nodes == nil
or ignore_list:contains(jid.host(occupant.bare_jid)) then
or (ignore_list:contains(jid.host(occupant.bare_jid)) and not is_transcriber_jigasi(stanza)) then
return;
end
@@ -245,11 +295,12 @@ process_host_module(main_muc_component_config, function(host_module, host)
-- detects new participants joining main room and sending them to the visitor nodes
host_module:hook('muc-occupant-joined', function (event)
local room, stanza, occupant = event.room, event.stanza, event.occupant;
local room, stanzaEv, occupant = event.room, event.stanza, event.occupant;
local stanza = filter_stanza_nick_if_needed(stanzaEv, room);
-- filter focus, ignore configured domains (jibri and transcribers)
if is_admin(stanza.attr.from) or visitors_nodes[room.jid] == nil
or ignore_list:contains(jid.host(occupant.bare_jid)) then
or (ignore_list:contains(jid.host(occupant.bare_jid)) and not is_transcriber_jigasi(stanza)) then
return;
end
@@ -257,6 +308,10 @@ process_host_module(main_muc_component_config, function(host_module, host)
local user, _, res = jid.split(occupant.nick);
-- a main participant we need to update all active visitor nodes
for k in pairs(vnodes) do
if occupant.role == 'moderator' then
-- first send that the participant is a moderator
send_visitors_iq(k, room, 'update');
end
local fmuc_pr = st.clone(stanza);
fmuc_pr.attr.to = jid.join(user, k, res);
fmuc_pr.attr.from = occupant.jid;
@@ -265,10 +320,11 @@ process_host_module(main_muc_component_config, function(host_module, host)
end);
-- forwards messages from main participants to vnodes
host_module:hook('muc-occupant-groupchat', function(event)
local room, stanza, occupant = event.room, event.stanza, event.occupant;
local room, stanzaEv, occupant = event.room, event.stanza, event.occupant;
local stanza = filter_stanza_nick_if_needed(stanzaEv, room);
-- filter sending messages from transcribers/jibris to visitors
if not visitors_nodes[room.jid] or ignore_list:contains(jid.host(occupant.bare_jid)) then
if not visitors_nodes[room.jid] then
return;
end
@@ -285,7 +341,8 @@ process_host_module(main_muc_component_config, function(host_module, host)
-- receiving messages from visitor nodes and forward them to local main participants
-- and forward them to the rest of visitor nodes
host_module:hook('muc-occupant-groupchat', function(event)
local occupant, room, stanza = event.occupant, event.room, event.stanza;
local occupant, room, stanzaEv = event.occupant, event.room, event.stanza;
local stanza = filter_stanza_nick_if_needed(stanzaEv, room);
local to = stanza.attr.to;
local from = stanza.attr.from;
local from_vnode = jid.host(from);
@@ -296,6 +353,11 @@ process_host_module(main_muc_component_config, function(host_module, host)
return;
end
if host_module:fire_event('jitsi-visitor-groupchat-pre-route', event) then
-- message filtered
return;
end
-- a message from visitor occupant of known visitor node
stanza.attr.from = to;
for _, o in room:each_occupant() do
@@ -331,25 +393,34 @@ process_host_module(main_muc_component_config, function(host_module, host)
end
end
end, -100); -- we want to run last in order to check is the status code 104
host_module:hook('muc-set-affiliation', function (event)
if event.actor and not is_admin(event.actor) and event.affiliation == 'owner' then
local room = event.room;
if not visitors_nodes[room.jid] then
return;
end
-- we need to update all vnodes
local vnodes = visitors_nodes[room.jid].nodes;
for conference_service in pairs(vnodes) do
send_visitors_iq(conference_service, room, 'update');
end
end
end, -2);
end);
module:hook('jitsi-lobby-enabled', function(event)
local function update_vnodes_for_room(event)
local room = event.room;
if visitors_nodes[room.jid] then
-- we need to update all vnodes
local vnodes = visitors_nodes[room.jid].nodes;
for conference_service in pairs(vnodes) do
send_visitors_iq(conference_service, room, 'update');
if visitors_nodes[room.jid] then
-- we need to update all vnodes
local vnodes = visitors_nodes[room.jid].nodes;
for conference_service in pairs(vnodes) do
send_visitors_iq(conference_service, room, 'update');
end
end
end
end);
module:hook('jitsi-lobby-disabled', function(event)
local room = event.room;
if visitors_nodes[room.jid] then
-- we need to update all vnodes
local vnodes = visitors_nodes[room.jid].nodes;
for conference_service in pairs(vnodes) do
send_visitors_iq(conference_service, room, 'update');
end
end
end);
end
module:hook('jitsi-lobby-enabled', update_vnodes_for_room);
module:hook('jitsi-lobby-disabled', update_vnodes_for_room);
module:hook('jitsi-filesharing-updated', update_vnodes_for_room);

View File

@@ -1,9 +1,11 @@
module:log('info', 'Starting visitors_component at %s', module.host);
local array = require "util.array";
local http = require 'net.http';
local jid = require 'util.jid';
local st = require 'util.stanza';
local util = module:require 'util';
local is_admin = util.is_admin;
local is_healthcheck_room = util.is_healthcheck_room;
local is_sip_jigasi = util.is_sip_jigasi;
local room_jid_match_rewrite = util.room_jid_match_rewrite;
@@ -11,14 +13,19 @@ local get_room_from_jid = util.get_room_from_jid;
local get_focus_occupant = util.get_focus_occupant;
local get_room_by_name_and_subdomain = util.get_room_by_name_and_subdomain;
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
local table_find = util.table_find;
local is_vpaas = util.is_vpaas;
local is_sip_jibri_join = util.is_sip_jibri_join;
local process_host_module = util.process_host_module;
local respond_iq_result = util.respond_iq_result;
local split_string = util.split_string;
local new_id = require 'util.id'.medium;
local um_is_admin = require 'core.usermanager'.is_admin;
local json = require 'cjson.safe';
local inspect = require 'inspect';
-- Debug flag
local DEBUG = false;
-- will be initialized once the main virtual host module is initialized
local token_util;
@@ -47,10 +54,6 @@ local http_headers = {
["Accept"] = "application/json"
};
local function is_admin(jid)
return um_is_admin(jid, module.host);
end
-- This is a map to keep data for room and the jids that were allowed to join after visitor mode is enabled
-- automatically allowed or allowed by a moderator
local visitors_promotion_map = {};
@@ -63,15 +66,15 @@ local visitors_promotion_requests = {};
local cache = require 'util.cache';
local sent_iq_cache = cache.new(200);
-- send iq result that the iq was received and will be processed
local function respond_iq_result(origin, stanza)
-- respond with successful receiving the iq
origin.send(st.iq({
type = 'result';
from = stanza.attr.to;
to = stanza.attr.from;
id = stanza.attr.id
}));
-- Function to get visitors room metadata
local function get_visitors_room_metadata(room)
if not room.jitsiMetadata then
room.jitsiMetadata = {};
end
if not room.jitsiMetadata.visitors then
room.jitsiMetadata.visitors = {};
end
return room.jitsiMetadata.visitors;
end
-- Sends a json-message to the destination jid
@@ -83,13 +86,38 @@ function send_json_message(to_jid, json_message)
module:send(stanza);
end
local function request_promotion_received(room, from_jid, from_vnode, nick, time, user_id, force_promote)
local function request_promotion_received(room, from_jid, from_vnode, nick, time, user_id, group_id, force_promote_requested)
if DEBUG then
module:log('debug', 'Received promotion request from %s for room %s, nick: %s, time: %s, user_id: %s, group_id: %s, force_promote_requested: %s',
from_jid, room.jid, nick, time, user_id, group_id, force_promote_requested);
end
-- if visitors is enabled for the room
if visitors_promotion_map[room.jid] then
local force_promote = auto_allow_promotion or get_visitors_room_metadata(room).autoPromote;
if not force_promote and force_promote_requested == 'true' then
-- Let's do the force_promote checks if requested
-- if it is vpaas meeting we trust the moderator computation from visitor node (value of force_promote_requested)
-- if it is not vpaas we need to check further settings only if they exist
if is_vpaas(room) or (not room._data.moderator_id and not room._data.moderators)
-- _data.moderator_id can be used from external modules to set single moderator for a meeting
-- or a whole group of moderators
or (room._data.moderator_id
and room._data.moderator_id == user_id or room._data.moderator_id == group_id)
-- all moderators are allowed to auto promote, the fact that user_id and force_promote_requested are set
-- means that the user has token and is moderator on visitor node side
or room._data.allModerators
-- can be used by external modules to set multiple moderator ids (table of values)
or table_find(room._data.moderators, user_id)
then
force_promote = true;
end
end
-- only for raise hand, ignore lowering the hand
if time and time > 0 and (
auto_allow_promotion
or force_promote == 'true') then
if time and time > 0 and force_promote then
-- we are in auto-allow mode, let's reply with accept
-- we store where the request is coming from so we can send back the response
local username = new_id():lower();
@@ -179,7 +207,7 @@ local function connect_vnode_received(room, vnode)
room._connected_vnodes = cache.new(16); -- we up to 16 vnodes for this prosody
end
room._connected_vnodes:set(vnode..'.meet.jitsi', 'connected');
room._connected_vnodes:set(vnode..'.meet.jitsi', {});
end
local function disconnect_vnode_received(room, vnode)
@@ -194,6 +222,34 @@ local function disconnect_vnode_received(room, vnode)
end
end
-- returns the accumulated data for visitors nodes, count all visitors requesting transcriptions
-- and accumulated languages requested
-- @returns count, languages
function get_visitors_languages(room)
if not room._connected_vnodes then
return;
end
local count = 0;
local languages = array();
-- iterate over visitor nodes we are connected to and accumulate data if we have it
for k, v in room._connected_vnodes:items() do
if v.count then
count = count + v.count;
end
if v.langs then
for k in pairs(v.langs) do
local val = v.langs[k]
if not languages[val] then
languages:push(val);
end
end
end
end
return count, languages:sort():concat(',');
end
-- listens for iq request for promotion and forward it to moderators in the meeting for approval
-- or auto-allow it if such the config is set enabling it
local function stanza_handler(event)
@@ -203,6 +259,10 @@ local function stanza_handler(event)
return;
end
if DEBUG then
module:log('debug', 'Received stanza %s from %s', stanza, origin.full_jid);
end
if stanza.attr.type == 'result' and sent_iq_cache:get(stanza.attr.id) then
sent_iq_cache:set(stanza.attr.id, nil);
return true;
@@ -229,15 +289,20 @@ local function stanza_handler(event)
if not room then
-- this maybe as we receive the iq from jicofo after the room is already destroyed
module:log('debug', 'No room found %s', room_jid);
return;
return true;
end
local from_vnode;
if room._connected_vnodes then
from_vnode = room._connected_vnodes:get(stanza.attr.from);
end
local processed;
-- promotion request is coming from visitors and is a set and is over the s2s connection
local request_promotion = visitors_iq:get_child('promotion-request');
if request_promotion then
if not (room._connected_vnodes and room._connected_vnodes:get(stanza.attr.from)) then
module:log('warn', 'Received forged promotion-request: %s %s %s', stanza, inspect(room._connected_vnodes), room._connected_vnodes:get(stanza.attr.from));
if not from_vnode then
module:log('warn', 'Received forged request_promotion message: %s %s',stanza, inspect(room._connected_vnodes));
return true; -- stop processing
end
@@ -249,6 +314,7 @@ local function stanza_handler(event)
display_name,
tonumber(request_promotion.attr.time),
request_promotion.attr.userId,
request_promotion.attr.groupId,
request_promotion.attr.forcePromote
);
end
@@ -266,6 +332,40 @@ local function stanza_handler(event)
end
end
-- request to update metadata service for jigasi languages
local transcription_languages = visitors_iq:get_child('transcription-languages');
if transcription_languages
and (transcription_languages.attr.langs or transcription_languages.attr.count) then
if not from_vnode then
module:log('warn', 'Received forged transcription_languages message: %s %s',stanza, inspect(room._connected_vnodes));
return true; -- stop processing
end
local metadata = get_visitors_room_metadata(room);
-- we keep the split by languages array to optimize accumulating languages
from_vnode.langs = split_string(transcription_languages.attr.langs, ',');
from_vnode.count = transcription_languages.attr.count;
local count, languages = get_visitors_languages(room);
if metadata.transcribingLanguages ~= languages then
metadata.transcribingLanguages = languages;
processed = true;
end
if metadata.transcribingCount ~= count then
metadata.transcribingCount = count;
processed = true;
end
if processed then
module:context(muc_domain_prefix..'.'..muc_domain_base)
:fire_event('room-metadata-changed', { room = room; });
end
end
if not processed then
module:log('warn', 'Unknown iq received for %s: %s', module.host, stanza);
end
@@ -275,6 +375,16 @@ local function stanza_handler(event)
end
local function process_promotion_response(room, id, approved)
if not approved then
module:log('debug', 'promotion not approved %s, %s', room.jid, id);
return;
end
if DEBUG then
module:log('debug', 'Processing promotion response for room %s, id %s, approved %s',
room.jid, id, approved);
end
-- lets reply to participant that requested promotion
local username = new_id():lower();
visitors_promotion_map[room.jid][username] = {
@@ -308,11 +418,24 @@ end
-- if room metadata does not have visitors.live set to `true` and there are no occupants in the meeting
-- it will skip calling goLive endpoint
local function go_live(room)
if DEBUG then
module:log('debug', 'Checking if room %s is live', room.jid);
end
if room._jitsi_go_live_sent then
if DEBUG then
module:log('debug', 'Room %s already sent go live request, skipping', room.jid);
end
return;
end
if not (room.jitsiMetadata and room.jitsiMetadata.visitors and room.jitsiMetadata.visitors.live) then
-- if missing we assume room is live, only skip if it is marked explicitly as false
if room.jitsiMetadata and room.jitsiMetadata.visitors
and room.jitsiMetadata.visitors.live ~= nil and room.jitsiMetadata.visitors.live == false then
if DEBUG then
module:log('debug', 'Room %s is not live, skipping go live request', room.jid);
end
return;
end
@@ -326,6 +449,9 @@ local function go_live(room)
-- when there is an occupant then go live
if not has_occupant then
if DEBUG then
module:log('debug', 'Room %s has no occupants, skipping go live request', room.jid);
end
return;
end
@@ -367,6 +493,10 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
local room, stanza, occupant, session = event.room, event.stanza, event.occupant, event.origin;
if is_healthcheck_room(room.jid) or is_admin(occupant.bare_jid) then
if DEBUG then
module:log('debug', 'Skipping visitor checks for healthcheck room %s or admin %s',
room.jid, occupant.bare_jid);
end
return;
end
@@ -378,19 +508,40 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
join:tag('password', { xmlns = MUC_NS }):text(room:get_password());
end
-- we skip any checks when auto-allow is enabled
if auto_allow_promotion
local is_live = get_visitors_room_metadata(room).live;
-- we skip any checks when auto-allow is enabled and room is live
if (auto_allow_promotion or get_visitors_room_metadata(room).autoPromote and (is_live or is_live == nil))
or ignore_list:contains(jid.host(stanza.attr.from)) -- jibri or other domains to ignore
or is_sip_jigasi(stanza)
or is_sip_jibri_join(stanza) then
return;
or is_sip_jibri_join(stanza)
or table_find(room._data.moderators, session.jitsi_meet_context_user and session.jitsi_meet_context_user.id)
or (room._data.moderator_id and room._data.moderator_id == (session.jitsi_meet_context_user and session.jitsi_meet_context_user.id))
or table_find(room._data.participants, session.jitsi_meet_context_user and session.jitsi_meet_context_user.id) then
if DEBUG then
module:log('debug', 'Auto-allowing visitor %s in room %s', stanza.attr.from, room.jid);
end
return;
end
if visitors_promotion_map[room.jid] then
local in_ignore_list = ignore_list:contains(jid.host(stanza.attr.from));
-- now let's check for jid
if visitors_promotion_map[room.jid][jid.node(stanza.attr.from)] -- promotion was approved
or ignore_list:contains(jid.host(stanza.attr.from)) then -- jibri or other domains to ignore
or in_ignore_list then -- jibri or other domains to ignore
-- allow join
if not in_ignore_list then
-- let's update metadata
local metadata = get_visitors_room_metadata(room);
if not metadata.promoted then
metadata.promoted = {};
end
metadata.promoted[jid.resource(occupant.nick)] = true;
module:context(muc_domain_prefix..'.'..muc_domain_base)
:fire_event('room-metadata-changed', { room = room; });
end
return;
end
module:log('error', 'Visitor needs to be allowed by a moderator %s', stanza.attr.from);
@@ -407,6 +558,16 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
:tag('no-main-participants', { xmlns = 'jitsi:visitors' }));
return true;
end
elseif room._data.participants then
-- This is non jaas room which has a list of participants allowed to participate in the main room
-- but this occupant is not one of them and the room is either not live or has no participants joined
session.log('warn',
'Deny user join in the main not live meeting, not in the list of main participants');
session.send(st.error_reply(
stanza, 'cancel', 'not-allowed',
'Tried to join the main (not live or without main participants) room')
:tag('not-live-room', { xmlns = 'jitsi:visitors' }));
return true;
end
end, 7); -- after muc_meeting_id, the logic for not joining before jicofo
@@ -418,8 +579,16 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
host_module:hook('muc-occupant-joined', function (event)
local room, occupant = event.room, event.occupant;
if DEBUG then
module:log('debug', 'Occupant %s joined room %s', occupant.jid, room.jid);
end
if is_healthcheck_room(room.jid) or is_admin(occupant.bare_jid) or occupant.role ~= 'moderator' -- luacheck: ignore
or not visitors_promotion_requests[event.room.jid] then
if DEBUG then
module:log('debug', 'Skipping visitor checks for healthcheck room %s or admin %s or not moderator %s',
room.jid, occupant.bare_jid, occupant.role);
end
return;
end
@@ -446,17 +615,11 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
end
end
end);
host_module:hook("message/bare", function(event)
local stanza = event.stanza;
if stanza.attr.type ~= "groupchat" then
return;
end
local json_data = stanza:get_child_text("json-message", "http://jitsi.org/jitmeet");
if json_data == nil then
return;
end
local data, error = json.decode(json_data);
host_module:hook('jitsi-endpoint-message-received', function(event)
local data, error, occupant, room, stanza
= event.message, event.error, event.occupant, event.room, event.stanza;
if not data or data.type ~= 'visitors'
or (data.action ~= "promotion-response" and data.action ~= "demote-request") then
if error then
@@ -465,17 +628,9 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
return;
end
local room = get_room_from_jid(event.stanza.attr.to);
local occupant_jid = event.stanza.attr.from;
local occupant = room:get_occupant_by_real_jid(occupant_jid);
if not occupant then
module:log("error", "Occupant %s was not found in room %s", occupant_jid, room.jid)
return
end
if occupant.role ~= 'moderator' then
module:log('error', 'Occupant %s sending response message but not moderator in room %s',
occupant_jid, room.jid);
occupant.jid, room.jid);
return false;
end
@@ -501,7 +656,6 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
end
end
end
else
if data.id then
process_promotion_response(room, data.id, data.approved and 'true' or 'false');
@@ -515,6 +669,7 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
return true; -- halt processing, but return true that we handled it
end);
if visitors_queue_service then
host_module:hook('muc-room-created', function (event)
local room = event.room;
@@ -535,7 +690,13 @@ process_host_module(muc_domain_prefix..'.'..muc_domain_base, function(host_modul
go_live(event.room);
end);
host_module:hook('muc-occupant-joined', function (event)
go_live(event.room);
local room = event.room;
if is_healthcheck_room(room.jid) then
return;
end
go_live(room);
end);
end
@@ -571,3 +732,22 @@ prosody.events.add_handler('pre-jitsi-authentication', function(session)
return session.customusername;
end
end);
-- when occupant is leaving breakout to join the main room and visitors are enabled
-- make sure we will allow that participant to join as it is already part of the main room
function handle_occupant_leaving_breakout(event)
local main_room, occupant, stanza = event.main_room, event.occupant, event.stanza;
local presence_status = stanza:get_child_text('status');
if presence_status ~= 'switch_room' or not visitors_promotion_map[main_room.jid] then
return;
end
local node = jid.node(occupant.bare_jid);
visitors_promotion_map[main_room.jid][node] = {
from = 'none';
jid = occupant.bare_jid;
};
end
module:hook_global('jitsi-breakout-occupant-leaving', handle_occupant_leaving_breakout);

View File

@@ -80,7 +80,7 @@ function Util.new(module)
These setups relay on configuration 'muc_domain_base' which holds
the main domain and we use it to subtract subdomains from the
virtual addresses.
The following confgurations are for multidomain setups and domain name
The following configurations are for multidomain setups and domain name
verification:
--]]
@@ -225,7 +225,7 @@ function Util:get_public_key(keyId)
self.cache:set(keyId, content);
else
if code == nil then
-- this is timout after nr_retries retries
-- this is timeout after nr_retries retries
module:log('warn', 'Timeout retrieving %s from %s', keyId, keyurl);
end
end

View File

@@ -1,7 +1,15 @@
local http_server = require "net.http.server";
local jid = require "util.jid";
local st = require 'util.stanza';
local timer = require "util.timer";
local http = require "net.http";
local cache = require "util.cache";
local array = require "util.array";
local is_set = require 'util.set'.is_set;
local usermanager = require 'core.usermanager';
local config_global_admin_jids = module:context('*'):get_option_set('admins', {}) / jid.prep;
local config_admin_jids = module:get_option_inherited_set('admins', {}) / jid.prep;
local http_timeout = 30;
local have_async, async = pcall(require, "util.async");
@@ -28,6 +36,8 @@ local roomless_iqs = {};
local OUTBOUND_SIP_JIBRI_PREFIXES = { 'outbound-sip-jibri@', 'sipjibriouta@', 'sipjibrioutb@' };
local INBOUND_SIP_JIBRI_PREFIXES = { 'inbound-sip-jibri@', 'sipjibriina@', 'sipjibriina@' };
local RECORDER_PREFIXES = module:get_option_inherited_set('recorder_prefixes', { 'recorder@recorder.', 'jibria@recorder.', 'jibrib@recorder.' });
local TRANSCRIBER_PREFIXES = module:get_option_inherited_set('transcriber_prefixes', { 'transcriber@recorder.', 'transcribera@recorder.', 'transcriberb@recorder.' });
local split_subdomain_cache = cache.new(1000);
local extract_subdomain_cache = cache.new(1000);
@@ -122,11 +132,7 @@ function get_room_from_jid(room_jid)
local component = hosts[host];
if component then
local muc = component.modules.muc
if muc and rawget(muc,"rooms") then
-- We're running 0.9.x or 0.10 (old MUC API)
return muc.rooms[room_jid];
elseif muc and rawget(muc,"get_room_from_jid") then
-- We're running >0.10 (new MUC API)
if muc then
return muc.get_room_from_jid(room_jid);
else
return
@@ -202,8 +208,7 @@ end
-- @param creator_group the group of the user who created the user which
-- presence we are updating (this is the poltergeist case, where a user creates
-- a poltergeist), optional.
function update_presence_identity(
stanza, user, group, creator_user, creator_group)
function update_presence_identity(stanza, user, group, creator_user, creator_group)
-- First remove any 'identity' element if it already
-- exists, so it cannot be spoofed by a client
@@ -216,7 +221,11 @@ function update_presence_identity(
end
return tag
end
)
);
if not user then
return;
end
stanza:tag("identity"):tag("user");
for k, v in pairs(user) do
@@ -250,28 +259,33 @@ end
-- Utility function to check whether feature is present and enabled. Allow
-- a feature if there are features present in the session(coming from
-- the token) and the value of the feature is true.
-- If features is not present in the token we skip feature detection and allow
-- everything.
function is_feature_allowed(features, ft)
if (features == nil or features[ft] == "true" or features[ft] == true) then
return true;
-- if features are missing from the token we check whether it is moderator
function is_feature_allowed(ft, features, is_moderator)
if features then
return features[ft] == "true" or features[ft] == true;
else
return false;
return is_moderator;
end
end
--- Extracts the subdomain and room name from internal jid node [foo]room1
-- @return subdomain(optional, if extracted or nil), the room name
-- @return subdomain(optional, if extracted or nil), the room name, the customer_id in case of vpaas
function extract_subdomain(room_node)
local ret = extract_subdomain_cache:get(room_node);
if ret then
return ret.subdomain, ret.room;
return ret.subdomain, ret.room, ret.customer_id;
end
local subdomain, room_name = room_node:match("^%[([^%]]+)%](.+)$");
local cache_value = {subdomain=subdomain, room=room_name};
if not subdomain then
room_name = room_node;
end
local _, customer_id = subdomain and subdomain:match("^(vpaas%-magic%-cookie%-)(.*)$") or nil, nil;
local cache_value = { subdomain=subdomain, room=room_name, customer_id=customer_id };
extract_subdomain_cache:set(room_node, cache_value);
return subdomain, room_name;
return subdomain, room_name, customer_id;
end
function starts_with(str, start)
@@ -282,19 +296,33 @@ function starts_with(str, start)
end
function starts_with_one_of(str, prefixes)
if not str then
if not str or not prefixes then
return false;
end
for i=1,#prefixes do
if starts_with(str, prefixes[i]) then
return prefixes[i];
if is_set(prefixes) then
-- set is a table with keys and value of true
for k, _ in prefixes:items() do
if starts_with(str, k) then
return k;
end
end
else
for _, v in pairs(prefixes) do
if starts_with(str, v) then
return v;
end
end
end
return false
end
function ends_with(str, ending)
if not str then
return false;
end
return ending == "" or str:sub(-#ending) == ending
end
@@ -468,9 +496,38 @@ end
-- Returns the initiator extension if the stanza is coming from a sip jigasi
function is_sip_jigasi(stanza)
if not stanza then
return false;
end
return stanza:get_child('initiator', 'http://jitsi.org/protocol/jigasi');
end
-- This requires presence stanza being passed
function is_transcriber_jigasi(stanza)
if not stanza then
return false;
end
local features = stanza:get_child('features');
if not features then
return false;
end
for i = 1, #features do
local feature = features[i];
if feature.attr and feature.attr.var and feature.attr.var == 'http://jitsi.org/protocol/transcriber' then
return true;
end
end
return false;
end
function is_transcriber(jid)
return starts_with_one_of(jid, TRANSCRIBER_PREFIXES);
end
function get_sip_jibri_email_prefix(email)
if not email then
return nil;
@@ -508,6 +565,10 @@ function is_sip_jibri_join(stanza)
return false
end
function is_jibri(occupant)
return starts_with_one_of(type(occupant) == "string" and occupant or occupant.jid, RECORDER_PREFIXES)
end
-- process a host module directly if loaded or hooks to wait for its load
function process_host_module(name, callback)
local function process_host(host)
@@ -521,7 +582,7 @@ function process_host_module(name, callback)
module:log('info', 'No host/component found, will wait for it: %s', name)
-- when a host or component is added
prosody.events.add_handler('host-activated', process_host);
prosody.events.add_handler('host-activated', process_host, -100); -- make sure everything is loaded
else
process_host(name);
end
@@ -535,30 +596,171 @@ function table_shallow_copy(t)
return t2
end
local function table_find(tab, val)
if not tab or val == nil then
return nil
end
for i, v in ipairs(tab) do
if v == val then
return i
end
end
return nil
end
-- Adds second table values to the first table
local function table_add(t1, t2)
for _,v in ipairs(t2) do
table.insert(t1, v);
end
end
-- Returns as a first result the removed items and as a second the added items
local function table_compare(old_table, new_table)
local removed = {}
local added = {}
local modified = {}
-- Find removed items (in old but not in new)
for id, value in pairs(old_table) do
if new_table[id] == nil then
table.insert(removed, id)
elseif new_table[id] ~= value then
table.insert(modified, id)
end
end
-- Find added items (in new but not in old)
for id, _ in pairs(new_table) do
if old_table[id] == nil then
table.insert(added, id)
end
end
return removed, added, modified
end
local function table_equals(t1, t2)
if t1 == nil then
return t2 == nil;
end
if t2 == nil then
return t1 == nil;
end
local removed, added, modified = table_compare(t1, t2);
return next(removed) == nil and next(added) == nil and next(modified) == nil
end
-- Splits a string using delimiter
function split_string(str, delimiter)
str = str .. delimiter;
local result = array();
for w in str:gmatch("(.-)" .. delimiter) do
result:push(w);
end
return result;
end
-- send iq result that the iq was received and will be processed
function respond_iq_result(origin, stanza)
-- respond with successful receiving the iq
origin.send(st.iq({
type = 'result';
from = stanza.attr.to;
to = stanza.attr.from;
id = stanza.attr.id
}));
end
-- Note: http_server.get_request_from_conn() was added in Prosody 0.12.3,
-- this code provides backwards compatibility with older versions
local get_request_from_conn = http_server.get_request_from_conn or function (conn)
local response = conn and conn._http_open_response;
return response and response.request or nil;
end;
-- Discover real remote IP of a session
function get_ip(session)
local request = get_request_from_conn(session.conn);
return request and request.ip or session.ip;
end
-- Checks whether the provided jid is in the list of admins
-- we are not using the new permissions and roles api as we have few global modules which need to be
-- refactored into host modules, as that api needs to be executed in host context
local function is_admin(_jid)
local bare_jid = jid.bare(_jid);
if config_global_admin_jids:contains(bare_jid) or config_admin_jids:contains(bare_jid) then
return true;
end
return false;
end
-- Filter out identity information (nick name, email, etc) from a presence stanza.
local function filter_identity_from_presence(orig_stanza)
local stanza = st.clone(orig_stanza);
stanza:remove_children('nick', 'http://jabber.org/protocol/nick');
stanza:remove_children('email');
stanza:remove_children('stats-id');
local identity = stanza:get_child('identity');
if identity then
local user = identity:get_child('user');
local name = identity:get_child('name');
if user then
user:remove_children('email');
user:remove_children('name');
end
if name then
name:remove_children('name'); -- Remove name with no namespace
end
end
return stanza;
end
return {
OUTBOUND_SIP_JIBRI_PREFIXES = OUTBOUND_SIP_JIBRI_PREFIXES;
INBOUND_SIP_JIBRI_PREFIXES = INBOUND_SIP_JIBRI_PREFIXES;
RECORDER_PREFIXES = RECORDER_PREFIXES;
extract_subdomain = extract_subdomain;
filter_identity_from_presence = filter_identity_from_presence;
is_admin = is_admin;
is_feature_allowed = is_feature_allowed;
is_jibri = is_jibri;
is_healthcheck_room = is_healthcheck_room;
is_moderated = is_moderated;
is_sip_jibri_join = is_sip_jibri_join;
is_sip_jigasi = is_sip_jigasi;
is_transcriber = is_transcriber;
is_transcriber_jigasi = is_transcriber_jigasi;
is_vpaas = is_vpaas;
get_focus_occupant = get_focus_occupant;
get_ip = get_ip;
get_room_from_jid = get_room_from_jid;
get_room_by_name_and_subdomain = get_room_by_name_and_subdomain;
get_sip_jibri_email_prefix = get_sip_jibri_email_prefix;
async_handler_wrapper = async_handler_wrapper;
presence_check_status = presence_check_status;
process_host_module = process_host_module;
respond_iq_result = respond_iq_result;
room_jid_match_rewrite = room_jid_match_rewrite;
room_jid_split_subdomain = room_jid_split_subdomain;
internal_room_jid_match_rewrite = internal_room_jid_match_rewrite;
update_presence_identity = update_presence_identity;
http_get_with_retry = http_get_with_retry;
ends_with = ends_with;
split_string = split_string;
starts_with = starts_with;
starts_with_one_of = starts_with_one_of;
table_add = table_add;
table_compare = table_compare;
table_shallow_copy = table_shallow_copy;
table_find = table_find;
table_equals = table_equals;
};

View File

@@ -3,6 +3,7 @@ plugin_paths = { "{{ jitsi_root_dir }}/prosody/modules" }
muc_mapper_domain_base = "{{ jitsi_domain }}";
admins = { "{{ jitsi_jicofo_xmpp_user }}@{{ jitsi_auth_domain }}" };
component_admins_as_room_owners = true;
http_default_host = "{{ jitsi_domain }}";
-- Enable use of native prosody 0.11 support for epoll over select

View File

@@ -3,9 +3,9 @@
jitsi_root_dir: /opt/jitsi
jitsi_user: jitsi
jitsi_videobridge_version: "{{ jitsi_version | default('10133') }}"
jitsi_videobridge_version: "{{ jitsi_version | default('10431') }}"
jitsi_videobridge_archive_url: https://github.com/jitsi/jitsi-videobridge/archive/refs/tags/stable/jitsi-meet_{{ jitsi_videobridge_version }}.tar.gz
jitsi_videobridge_archive_sha256: 79e7d50c3eb8c2528830b32ca8428235c02f6049f5601c4c0e7bfb772d980ec6
jitsi_videobridge_archive_sha256: 57d29c7d24c5f0657efbadabd54d3efa34c587a05c7a2196dca5e3c56141b44a
jitsi_videobridge_rtp_port: 10000
jitsi_videobridge_src_ip:

View File

@@ -1,13 +1,13 @@
---
# The version of ldap2pg to deploy
ldap2pg_version: 6.2
ldap2pg_version: '6.4.0'
# The URL where the ldap2pg archive can be fetched
ldap2pg_archive_url: https://github.com/dalibo/ldap2pg/releases/download/v{{ ldap2pg_version }}/ldap2pg_{{ ldap2pg_version }}_linux_amd64.tar.gz
# The expected sha256 checksum of the archive
ldap2pg_archive_sha256: 875fbee44c3831907e84fbc24cb80f6badccc33310c7b4cbe6346d9ac405f565
ldap2pg_archive_sha256: 038301980136629ad6be4f7e0e5cb74bc41a8448d2320edaa7c9905fcc3e39e5
# How often ldap2pg will sync (eg hourly, '*:0:15', systemd timer syntaxe)
ldap2pg_sync_freq: hourly

View File

@@ -1,15 +1,15 @@
---
# Version to deploy
metabase_version: 0.52.5
metabase_version: 0.54.8
# URL to fetch the jar
metabase_jar_url: https://downloads.metabase.com/v{{ metabase_version }}.x/metabase.jar
# Expected sha256 of the jar
metabase_jar_sha256: 64fbbe0f1358dfcf7c2146270ca1e65dfc8a8972ab053df935340f6229009f91
metabase_jar_sha256: c427ed7d45759f1a2c398c46fe73a9f1a67e6ba3c21ef67ab4a2e0e4231e6ab1
# When building from source
metabase_archive_url: https://github.com/metabase/metabase/archive/refs/tags/v{{ metabase_version }}.tar.gz
# Expected sha256 of the archive
metabase_archive_sha256: f30b43c208e0aaafa49e0b0172e1d78122ef0e7bad841400461018807cd55c5b
metabase_archive_sha256: 399fb345e8eff956734721573769a866454ec3b2661e858a96d8f7ba115e33e5
# Should ansible handle upgrades ? If set to false, only the initial install (and the config) will be handled
metabase_manage_upgrade: True
@@ -31,6 +31,8 @@ metabase_db_server: "{{ (metabase_db_engine == 'mysql') | ternary(mysql_server,
metabase_db_port: "{{ (metabase_db_engine == 'mysql') | ternary('3306', '5432') }}"
metabase_db_name: metabase
metabase_db_user: metabase
# Should ansible try to create user and db on the server. If false, the user and the database must already exist
metabase_db_create: true
# A random pass will be generated and stored in the meta dir if not defined
# metabase_db_pass: S3cr3t.

View File

@@ -92,10 +92,14 @@
- db_user: "{{ metabase_db_user }}"
- db_server: "{{ metabase_db_server }}"
- db_pass: "{{ metabase_db_pass }}"
when: metabase_db_engine == 'mysql'
when:
- metabase_db_engine == 'mysql'
- metabase_db_create
tags: metabase
- when: metabase_db_engine == 'postgres'
- when:
- metabase_db_engine == 'postgres'
- metabase_db_create
block:
- name: Install postgresql client
package: name=postgresql16

View File

@@ -7,7 +7,7 @@ Type=simple
User={{ metabase_user }}
WorkingDirectory={{ metabase_root_dir }}/app
EnvironmentFile={{ metabase_root_dir }}/etc/env
ExecStart=/usr/bin/java -Djava.net.preferIPv4Stack=true \
ExecStart=/usr/lib/jvm/jre-21/bin/java -Djava.net.preferIPv4Stack=true \
-Djava.io.tmpdir={{ metabase_root_dir }}/tmp \
{% if system_proxy is defined and system_proxy != '' %}
-Dhttp.proxyHost={{ system_proxy | urlsplit('hostname') }} -Dhttp.proxyPort={{ system_proxy | urlsplit('port') }} \

View File

@@ -1,9 +1,9 @@
---
metabase_packages:
- java-11-openjdk
- java-21-openjdk
metabase_build_packages:
- java-11-openjdk-devel
- java-21-openjdk-devel
- patch
- nodejs

View File

@@ -1,12 +1,5 @@
---
- name: Create filebeat snippet direcories
file: path=/etc/filebeat/ansible_{{ item }}.d state=directory
loop:
- inputs
- modules
tags: log,mkdir
- name: Create journald directory
file: path=/var/log/journal state=directory
notify: restart journald
@@ -38,3 +31,7 @@
- name: Create bash_completion dir
file: path=/etc/bash_completion.d state=directory
tags: mkdir
- name: Create vector config dir
file: path=/etc/vector/conf.d state=directory
tags: log,mkdir,vector

View File

@@ -33,3 +33,6 @@
- include_tasks: filebeat.yml
tags: always
- include_tasks: vector.yml
tags: always

View File

@@ -0,0 +1,5 @@
---
- name: Install vector configuration
template: src=vector.yml.j2 dest=/etc/vector/conf.d/nginx.yml
tags: log,web,vector

View File

@@ -0,0 +1,23 @@
---
sources:
in_logs_nginx:
type: file
include:
- /var/log/nginx/access.log
- /var/log/nginx/error.log
transforms:
format_logs_nginx:
type: remap
inputs:
- in_logs_nginx
source: |
if (.file == "/var/log/nginx/access.log"){
.http = parse_grok!(.message, "%{HOSTNAME:host} %{HTTPD_COMBINEDLOG}")
} else if (.file == "/var/log/nginx/error.log"){
.http = parse_nginx_log!(.message, format:"error")
}
.timestamp = parse_timestamp(del(.http.timestamp), format: "%d/%h/%Y:%H:%M:%S %z") ?? now()
.service = "nginx"
.group = "web"

View File

@@ -3,11 +3,8 @@
# List of plugins to install
nomad_plugins:
podman:
archive_url: https://releases.hashicorp.com/nomad-driver-podman/0.6.2/nomad-driver-podman_0.6.2_linux_amd64.zip
sha256: 49fc4c03864e0c1db6f2fde1369b432948fd0eda249a10e34c87ec3eb6e5870d
containerd:
archive_url: https://github.com/Roblox/nomad-driver-containerd/releases/download/v0.9.4/containerd-driver
sha256: 337e1bab178071500bfbe46a59946e0e3bafc652906ed1b755d2aa4d35990982
archive_url: https://releases.hashicorp.com/nomad-driver-podman/0.6.3/nomad-driver-podman_0.6.3_linux_amd64.zip
sha256: 56b85d844385b4b5b96936fe6af06d623e87b5939f26de9fbad963f72af1e4a1
exec2:
archive_url: https://releases.hashicorp.com/nomad-driver-exec2/0.1.0/nomad-driver-exec2_0.1.0_linux_amd64.zip
sha256: 13edd022ac793f3ca8cbd413015a13d7d396141d11234faaf72bdf34b08cae50

View File

@@ -66,3 +66,7 @@
when: item.create | default(False)
tags: nomad
- name: Create host_volumes_dir directory
file: path={{ nomad_conf.client.host_volumes_dir }} state=directory owner={{ nomad_user }} group={{ nomad_user }} mode=770
when: nomad_conf.client.host_volumes_dir is defined
tags: nomad

View File

@@ -101,9 +101,11 @@ client {
# Required for alloc to be able to reach themselves through an exposed port
bridge_network_hairpin_mode = true
{% if nomad_conf.client.node_pool is defined %}
node_pool = "{{ nomad_conf.client.node_pool }}"
{% for conf in ['node_pool', 'host_volumes_dir'] %}
{% if nomad_conf.client[conf] is defined %}
{{ conf }} = "{{ nomad_conf.client[conf] }}"
{% endif %}
{% endfor %}
{% if nomad_conf.client.drain_on_shutdown is defined %}
drain_on_shutdown {

View File

@@ -35,7 +35,7 @@ TasksMax=infinity
OOMScoreAdjust=-1000
{% if nomad_conf.client.enabled %}
# Give Nomad some time to drain the node
TimeoutStopSec=3600
TimeoutStopSec=300
{% endif %}
[Install]

View File

@@ -1,9 +1,9 @@
---
# Version of Nomad to install
nomad_version: 1.10.0
nomad_version: 1.10.3
# URL of the archive
nomad_archive_url: https://releases.hashicorp.com/nomad/{{ nomad_version }}/nomad_{{ nomad_version }}_linux_amd64.zip
# Expected sha256 of the archive
nomad_archive_sha256: d0936673cfa026b87744d60ad21ba85db70fe792b0685bfce95ac06a98d30b9d
nomad_archive_sha256: a161b8d59b42555d97d37f7a75c122831be485e89dfb97d16d6b60cfaec8d88b

View File

@@ -53,3 +53,6 @@
- include_tasks: filebeat.yml
tags: always
- include_tasks: vector.yml
tags: always

View File

@@ -0,0 +1,5 @@
---
- name: Deploy vector configuration
template: src=vector.yml dest=/etc/vector/conf.d/pve.yml
tags: log,pve,vector

View File

@@ -0,0 +1,17 @@
---
sources:
in_logs_pve:
type: file
include:
- /var/log/pve-firewall.log
- /var/log/pveproxy/access.log
- /var/log/vzdump/*
- /var/log/ceph/*.log
transforms:
format_logs_pve:
type: remap
inputs: ["in_logs_pve"]
source: |
# Nothing to do

View File

@@ -1,5 +1,5 @@
[postgresql-client]
baseurl = https://download.postgresql.org/pub/repos/yum/{{ repo_pg_client_version }}/redhat/rhel-{{ ansible_distribution_version }}-$basearch
baseurl = https://download.postgresql.org/pub/repos/yum/{{ repo_pg_client_version }}/redhat/rhel-{{ ansible_distribution_major_version }}-$basearch
gpgcheck = 1
gpgkey = https://download.postgresql.org/pub/repos/yum/keys/PGDG-RPM-GPG-KEY-RHEL
name = PostgreSQL Client

View File

@@ -1,3 +1,3 @@
---
nodejs_major_version: 18
nodejs_major_version: 22

View File

@@ -1,7 +1,7 @@
[nodejs]
baseurl = https://rpm.nodesource.com/pub_{{ nodejs_major_version }}.x/el/{{ ansible_distribution_major_version }}/$basearch
baseurl = https://rpm.nodesource.com/pub_{{ nodejs_major_version }}.x/nodistro/nodejs/$basearch
gpgcheck = 1
gpgkey = https://rpm.nodesource.com/pub/el/NODESOURCE-GPG-SIGNING-KEY-EL
gpgkey = https://rpm.nodesource.com/gpgkey/ns-operations-public.key
name = Node.js Packages for Enterprise Linux
{% if ansible_os_family == 'RedHat' and ansible_distribution_major_version is version('8', '>=') %}
# Workaround a bug in dnf which would make the default module mask

View File

@@ -0,0 +1,3 @@
---
vector_major_version: 0

View File

@@ -0,0 +1,20 @@
---
- name: Add Vector repo key
apt_key:
url: "{{ item }}"
environment:
https_proxy: "{{ system_proxy | default('') }}"
loop:
- https://keys.datadoghq.com/DATADOG_APT_KEY_CURRENT.public
- https://keys.datadoghq.com/DATADOG_APT_KEY_C0962C7D.public
- https://keys.datadoghq.com/DATADOG_APT_KEY_F14F620E.public
tags: repo,log,vector
- name: Add Vector repo
apt_repository:
repo: deb https://apt.vector.dev/ stable vector-0
filename: vector
environment:
https_proxy: "{{ system_proxy | default('') }}"
tags: repo,log,vector

View File

@@ -0,0 +1,14 @@
---
- name: Configure vector repo
yum_repository:
name: vector
description: Vector
baseurl: https://yum.vector.dev/stable/vector-0/$basearch/
gpgcheck: True
gpgkey:
- https://keys.datadoghq.com/DATADOG_RPM_KEY_CURRENT.public
- https://keys.datadoghq.com/DATADOG_RPM_KEY_B01082D3.public
- https://keys.datadoghq.com/DATADOG_RPM_KEY_FD4BF915.public
priority: 1
tags: repo,vector,log

View File

@@ -0,0 +1,4 @@
---
- include_tasks: "{{ ansible_os_family }}.yml"
tags: always

View File

@@ -7,6 +7,7 @@
- el7-x86_64.cfg
- el8-x86_64.cfg
- el9-x86_64.cfg
- el10-x86_64.cfg
tags: rpm
- name: Deploy rpmmacros

View File

@@ -13,6 +13,7 @@
- dir: "{{ rpm_root_dir }}/uploads/el7"
- dir: "{{ rpm_root_dir }}/uploads/el8"
- dir: "{{ rpm_root_dir }}/uploads/el9"
- dir: "{{ rpm_root_dir }}/uploads/el10"
- dir: "{{ rpm_root_dir }}/errors"
- dir: "{{ rpm_root_dir }}/builds"
- dir: "{{ rpm_root_dir }}/etc"

View File

@@ -7,6 +7,8 @@ targets:
- x86_64
el9:
- x86_64
el10:
- x86_64
paths:
repo: {{ rpm_root_dir }}/repo

View File

@@ -1,11 +1,14 @@
module samba-dc 1.0;
module samba-dc 1.1;
require {
type ntpd_var_run_t;
type unconfined_service_t;
type chronyd_t;
class sock_file write;
class unix_stream_socket connectto;
}
#============= chronyd_t ==============
allow chronyd_t ntpd_var_run_t:sock_file write;
allow chronyd_t unconfined_service_t:unix_stream_socket connectto;

View File

@@ -16,3 +16,5 @@
tags: always
- include_tasks: filebeat.yml
tags: always
- include_tasks: vector.yml
tags: always

View File

@@ -19,6 +19,7 @@
when: samba_role == 'dc' or samba_role == 'rodc'
with_items:
- samba_domain_controller
- rsync_sys_admin
tags: samba
- name: Copy custom policy

View File

@@ -0,0 +1,5 @@
---
- name: Deploy vector configuration
template: src=vector.yml dest=/etc/vector/conf.d/samba.yml
tags: log,samba,vector

View File

@@ -0,0 +1,21 @@
---
sources:
in_logs_samba:
type: file
include:
- /var/log/samba/json/auth.log
- /var/log/samba/json/dsdb.log
- /var/log/samba/json/dsdb_password.log
- /var/log/samba/json/dsdb_transaction.log
transforms:
format_logs_samba:
type: remap
inputs: ["in_logs_samba"]
source: |
.message = string!(.message)
if (is_json(.message)) {
.samba = parse_json!(.message)
.timestamp = parse_timestamp(del(.samba.timestamp), format: "%FT%H:%M:%S%.f%z") ?? now()
}

View File

@@ -4,6 +4,7 @@
/usr/bin/systemctl stop seafile seahub
{% endif %}
/usr/bin/systemd-cat /usr/bin/sudo -u {{ seafile_user }} {{ seafile_root_dir }}/seafile-server/seaf-gc.sh
/usr/bin/systemd-cat /usr/bin/sudo -u {{ seafile_user }} {{ seafile_root_dir }}/seafile-server/seaf-gc.sh --rm-fs
{% if seafile_license is not defined %}
/usr/bin/systemctl start seafile seahub
{% endif %}

View File

@@ -10,3 +10,7 @@ almalinux.slaskdatacenter.com
almalinux.mirror.katapult.io
alma.mirror.ate.info
mirror.previder.nl
kitten.mirrors.almalinux.org
linuxsoft.cern.ch
mirrors.fibianet.dk
mirror.bahnhof.net

View File

@@ -6,6 +6,7 @@ download.dokuwiki.org
raw.githubusercontent.com
objects.githubusercontent.com
github-releases.githubusercontent.com
release-assets.githubusercontent.com
packagecloud.io
.cloudfront.net
packagist.org
@@ -424,5 +425,6 @@ s3.eu-central-1.amazonaws.com
# Vector.dev
setup.vector.dev
yum.vector.dev
apt.vector.dev
s3.amazonaws.com
keys.datadoghq.com

View File

@@ -1,4 +1,4 @@
module ufdb 1.3;
module ufdb 1.4;
require {
type initrc_tmp_t;
@@ -6,6 +6,7 @@ require {
type tmp_t;
type squid_t;
type unconfined_service_t;
type var_run_t;
class sock_file write;
class unix_stream_socket connectto;
}
@@ -15,3 +16,4 @@ allow squid_t initrc_t:unix_stream_socket connectto;
allow squid_t unconfined_service_t:unix_stream_socket connectto;
allow squid_t initrc_tmp_t:sock_file write;
allow squid_t tmp_t:sock_file write;
allow squid_t var_run_t:sock_file write;

View File

@@ -226,3 +226,6 @@
- include_tasks: filebeat.yml
tags: always
- include_tasks: vector.yml
tags: always

View File

@@ -0,0 +1,5 @@
---
- name: Deploy vector configuration
template: src=vector.yml dest=/etc/vector/conf.d/squid.yml
tags: log,proxy,vector

View File

@@ -0,0 +1,25 @@
---
sources:
in_logs_squid:
type: file
include:
- /var/log/squid/access.log
- /var/log/squid/cache.log
- /var/log/squid/ufdbgclient.log
- /var/log/ufdbguard/ufdbguardd.log
transforms:
format_logs_squid:
type: remap
inputs: ["in_logs_squid"]
source: |
.group = "proxy"
if (.file == "/var/log/squid/access.log"){
.squid = parse_grok!(
.message,
"%{HTTPDATE:timestamp}\\s+%{NUMBER:response_time} %{IPORHOST:src_ip} %{NOTSPACE:squid_request_status}/%{NUMBER:http_status_code} %{NUMBER:transfer_size} %{NOTSPACE:http_method} (%{URIPROTO:url_scheme}://)?(?<url_host>\\S+?)(:%{INT:url_port})?(/%{NOTSPACE:url_path})?\\s+%{NOTSPACE:client_identity}\\s+%{NOTSPACE:peer_code}/%{NOTSPACE:peerhost}\\s+%{NOTSPACE:content_type}"
)
.timestamp = parse_timestamp(del(.squid.timestamp), format: "%d/%h/%Y:%H:%M:%S %z") ?? now()
.service = "squid"
}

View File

@@ -25,3 +25,5 @@
- include_tasks: cleanup.yml
tags: always
- include_tasks: vector.yml
tags: always

View File

@@ -0,0 +1,5 @@
---
- name: Deploy vector configuration
template: src=vector.yml dest=/etc/vector/conf.d/vault.yml
tags: log,vault,vector

View File

@@ -0,0 +1,18 @@
---
sources:
in_logs_vault:
type: file
include:
- /opt/vault/log/audit.json
transforms:
format_logs_vault:
type: remap
inputs: ["in_logs_vault"]
source: |
.message = string!(.message)
if (is_json(.message)) {
.vault = parse_json!(.message)
.timestamp = parse_timestamp(del(.vault.time), format: "%FT%H:%M:%S%.fZ", timezone: "UTC") ?? now()
}

View File

@@ -1,7 +1,7 @@
# Version of Vault to install
vault_version: 1.19.1
vault_version: 1.20.1
# URL of the archive
vault_archive_url: https://releases.hashicorp.com/vault/{{ vault_version }}/vault_{{ vault_version }}_linux_amd64.zip
# Expected sha256 of the archive
vault_archive_sha256: a673933f5b02236b5e241e153c0d2fed15b47b48ad640ae886f8b3b567087a05
vault_archive_sha256: e3ce3e678421c0d56f726952ab100875168c2e1eb1db751ed5a2b25b6b2ea96f

View File

@@ -0,0 +1,46 @@
---
vector_base_conf:
data_dir: /var/lib/vector
wildcard_matching: relaxed
sources:
in_logs_journald:
type: journald
transforms:
route_journald:
type: route
inputs: ["in_logs_journald"]
route:
dummy: exists(.dummy) && .dummy == "true"
iptables: exists(._TRANSPORT) && ._TRANSPORT == "kernel" && starts_with(string!(.message), "Firewall:")
parse_journald_iptables:
type: remap
inputs: ["route_journald.iptables"]
source: |
msg = string!(.message)
msg = replace(msg, "Firewall: ", "")
.iptables = parse_key_value!(msg, whitespace:"strict")
format_logs_journald:
type: remap
inputs: ["route_journald._unmatched", "parse_journald_*"]
source: |
.group = "system"
logs_out:
type: remap
inputs: ['format_logs_*']
source: |
# Nothing to do
sinks:
sink_blackhole:
type: blackhole
inputs:
- logs_out
vector_extra_conf: {}
vector_host_conf: {}
vector_conf: "{{ vector_base_conf | combine(vector_extra_conf, recursive=true) | combine(vector_host_conf, recursive=true)}}"

View File

@@ -0,0 +1,7 @@
---
- name: reload vector
service: name=vector state=reloaded
- name: restart vector
service: name=vector state=restarted

View File

@@ -0,0 +1,5 @@
---
dependencies:
- role: mkdir
- role: repo_vector

View File

@@ -0,0 +1,10 @@
---
- name: Deploy config
template: src=vector.yml dest=/etc/vector/vector.yaml owner=root group=root mode=0600
notify: reload vector
tags: log,vector
- name: Remove dummy conf if present
file: path=/etc/vector/conf.d/_dummy.yml state=absent
tags: log,vector

View File

@@ -0,0 +1 @@
---

View File

@@ -0,0 +1,16 @@
---
- name: Install vector
package: name=vector
tags: log,vector
- name: Deploy custom systemd unit
template: src=vector.service.j2 dest=/etc/systemd/system/vector.service
register: vector_unit
notify: restart vector
tags: log,vector
- name: Reload systemd
systemd: daemon_reload=true
when: vector_unit.changed
tags: log,vector

View File

@@ -0,0 +1,14 @@
---
- include_tasks: facts.yml
tags: always
- include_tasks: install.yml
tags: always
- include_tasks: conf.yml
tags: always
- include_tasks: services.yml
tags: always

View File

@@ -0,0 +1,5 @@
---
- name: Start and enable vector
service: name=vector state=started enabled=true
tags: log,vector

View File

@@ -0,0 +1,21 @@
[Unit]
Description=Vector
Documentation=https://vector.dev
After=network-online.target
Requires=network-online.target
[Service]
ExecStartPre=/usr/bin/vector --config /etc/vector/vector.yaml --config-dir /etc/vector/conf.d validate
ExecStart=/usr/bin/vector --config /etc/vector/vector.yaml --config-dir /etc/vector/conf.d --watch-config
ExecReload=/usr/bin/vector --config /etc/vector/vector.yaml --config-dir /etc/vector/conf.d validate --no-environment
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
AmbientCapabilities=CAP_NET_BIND_SERVICE
EnvironmentFile=-/etc/default/vector
# Since systemd 229, should be in [Unit] but in order to support systemd <229,
# it is also supported to have it here.
StartLimitInterval=10
StartLimitBurst=5
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,2 @@
---
{{ vector_conf | to_nice_yaml(indent=2) }}

Some files were not shown because too many files have changed in this diff Show More