diff --git a/roles/consul_bin/defaults/main.yml b/roles/consul_bin/defaults/main.yml index bfafaaf..bacf3e2 100644 --- a/roles/consul_bin/defaults/main.yml +++ b/roles/consul_bin/defaults/main.yml @@ -1,8 +1,8 @@ --- # Version of consul to deploy -consul_version: 1.21.2 +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: 07a738cc8f7937f28d1418e8f289b28445e82d5cbd0aabdbcf2b5a0cacf26a29 +consul_archive_sha256: ba20631037a5f63f70b0351c0875887a66c0a0d3feac2d255a768c9eb8c95e8b diff --git a/roles/consul_template/defaults/main.yml b/roles/consul_template/defaults/main.yml index 9adae52..be77bdd 100644 --- a/roles/consul_template/defaults/main.yml +++ b/roles/consul_template/defaults/main.yml @@ -1,11 +1,11 @@ --- # Version of consul-template to install -consul_tpl_version: 0.41.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: 64e732cdd75a778ea6a5e16b32792a1effc88963d37e73f0088a115ea790938f +consul_tpl_archive_sha256: ab68e09642437dcc5b6e9a572a1924d3969e4fe131f50a1a3a4f782d7a21f530 # Root dir where consul-template will be installed consul_tpl_root_dir: /opt/consul_template diff --git a/roles/httpd_common/templates/vector.yml.j2 b/roles/httpd_common/templates/vector.yml.j2 index ad54953..c22895e 100644 --- a/roles/httpd_common/templates/vector.yml.j2 +++ b/roles/httpd_common/templates/vector.yml.j2 @@ -16,5 +16,6 @@ transforms: 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" diff --git a/roles/jitsi/defaults/main.yml b/roles/jitsi/defaults/main.yml index bc1848f..9084fda 100644 --- a/roles/jitsi/defaults/main.yml +++ b/roles/jitsi/defaults/main.yml @@ -9,16 +9,16 @@ jitsi_user: jitsi jitsi_web_src_ip: - 0.0.0.0/0 -jitsi_version: 10314 +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: 0e081653d525462bfa1358ff6a25b091636792c4ac4a4fbf0b6235951d7cc4ac +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: 04770928b232fb794206083f9f4cdfe24112ce8dd57e84c253380afb39ebc5d3 +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 diff --git a/roles/jitsi/files/prosody/modules/mod_av_moderation.lua b/roles/jitsi/files/prosody/modules/mod_av_moderation.lua index 4dce74e..817fdb7 100644 --- a/roles/jitsi/files/prosody/modules/mod_av_moderation.lua +++ b/roles/jitsi/files/prosody/modules/mod_av_moderation.lua @@ -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'); diff --git a/roles/jitsi/files/prosody/modules/mod_av_moderation_component.lua b/roles/jitsi/files/prosody/modules/mod_av_moderation_component.lua index fa65670..3af314a 100644 --- a/roles/jitsi/files/prosody/modules/mod_av_moderation_component.lua +++ b/roles/jitsi/files/prosody/modules/mod_av_moderation_component.lua @@ -5,6 +5,7 @@ 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'; @@ -15,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 @@ -84,7 +91,7 @@ function notify_whitelist_change(jid, moderators, room, mediaType, removed) 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'}) do + 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 @@ -145,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) @@ -185,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 @@ -202,26 +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{}; - - -- 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 = 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; + start_av_moderation(room, mediaType, occupant); end else enabled = false; @@ -251,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; @@ -311,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); @@ -326,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 @@ -336,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 @@ -357,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); diff --git a/roles/jitsi/files/prosody/modules/mod_end_conference.lua b/roles/jitsi/files/prosody/modules/mod_end_conference.lua index 5ebda32..ffe95ae 100644 --- a/roles/jitsi/files/prosody/modules/mod_end_conference.lua +++ b/roles/jitsi/files/prosody/modules/mod_end_conference.lua @@ -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); diff --git a/roles/jitsi/files/prosody/modules/mod_features_identity.lua b/roles/jitsi/files/prosody/modules/mod_features_identity.lua new file mode 100644 index 0000000..9a82d1a --- /dev/null +++ b/roles/jitsi/files/prosody/modules/mod_features_identity.lua @@ -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); diff --git a/roles/jitsi/files/prosody/modules/mod_filesharing_component.lua b/roles/jitsi/files/prosody/modules/mod_filesharing_component.lua new file mode 100644 index 0000000..6268c4f --- /dev/null +++ b/roles/jitsi/files/prosody/modules/mod_filesharing_component.lua @@ -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); diff --git a/roles/jitsi/files/prosody/modules/mod_filter_iq_jibri.lua b/roles/jitsi/files/prosody/modules/mod_filter_iq_jibri.lua index 8df669f..0a36c47 100644 --- a/roles/jitsi/files/prosody/modules/mod_filter_iq_jibri.lua +++ b/roles/jitsi/files/prosody/modules/mod_filter_iq_jibri.lua @@ -38,7 +38,6 @@ module:hook("pre-iq/full", function(event) local is_allowed = is_feature_allowed( feature, session.jitsi_meet_context_features, - session.granted_jitsi_meet_context_features, occupant.role == 'moderator'); if jibri.attr.action == 'start' or jibri.attr.action == 'stop' then diff --git a/roles/jitsi/files/prosody/modules/mod_filter_iq_rayo.lua b/roles/jitsi/files/prosody/modules/mod_filter_iq_rayo.lua index dbeb0a8..2f6f875 100644 --- a/roles/jitsi/files/prosody/modules/mod_filter_iq_rayo.lua +++ b/roles/jitsi/files/prosody/modules/mod_filter_iq_rayo.lua @@ -107,7 +107,6 @@ module:hook("pre-iq/full", function(event) local is_session_allowed = is_feature_allowed( feature, session.jitsi_meet_context_features, - session.granted_jitsi_meet_context_features, room:get_affiliation(stanza.attr.from) == 'owner'); if roomName == nil diff --git a/roles/jitsi/files/prosody/modules/mod_fmuc.lua b/roles/jitsi/files/prosody/modules/mod_fmuc.lua index 9753c3e..d2c9228 100644 --- a/roles/jitsi/files/prosody/modules/mod_fmuc.lua +++ b/roles/jitsi/files/prosody/modules/mod_fmuc.lua @@ -16,6 +16,7 @@ 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; @@ -27,6 +28,7 @@ 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'; @@ -672,6 +674,29 @@ local function iq_from_main_handler(event) 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; }); diff --git a/roles/jitsi/files/prosody/modules/mod_jitsi_permissions.lua b/roles/jitsi/files/prosody/modules/mod_jitsi_permissions.lua index 7a761ff..46f48b1 100644 --- a/roles/jitsi/files/prosody/modules/mod_jitsi_permissions.lua +++ b/roles/jitsi/files/prosody/modules/mod_jitsi_permissions.lua @@ -16,7 +16,7 @@ local muc_domain_prefix = module:get_option_string('muc_mapper_domain_prefix', ' 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.'); + module:log('warn', 'No "muc_domain_base" option set, disabling module.'); return ; end @@ -71,15 +71,16 @@ function process_set_affiliation(event) end if previous_affiliation == 'none' and affiliation == 'owner' then - occupant_session.granted_jitsi_meet_context_features = actor_session.jitsi_meet_context_features; + 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_features = nil; occupant_session.granted_jitsi_meet_context_user_id = nil; occupant_session.granted_jitsi_meet_context_group_id = nil; @@ -154,22 +155,16 @@ function filter_stanza(stanza, session) session.force_permissions_update = false; - local permissions_to_send - = session.jitsi_meet_context_features or session.granted_jitsi_meet_context_features or default_permissions; + if not session.jitsi_meet_context_features then + session.jitsi_meet_context_features = default_permissions; + end room.send_default_permissions_to[bare_to] = nil; - if not session.granted_jitsi_meet_context_features and not session.jitsi_meet_context_features then - session.jitsi_meet_context_features = {}; - end - stanza:tag('permissions', { xmlns='http://jitsi.org/jitmeet' }); - for k, v in pairs(permissions_to_send) do + for k, v in pairs(session.jitsi_meet_context_features) do local val = tostring(v); stanza:tag('p', { name = k, val = val }):up(); - if session.jitsi_meet_context_features then - session.jitsi_meet_context_features[k] = val; - end end stanza:up(); diff --git a/roles/jitsi/files/prosody/modules/mod_muc_allowners.lua b/roles/jitsi/files/prosody/modules/mod_muc_allowners.lua index dbb4572..03e35bd 100644 --- a/roles/jitsi/files/prosody/modules/mod_muc_allowners.lua +++ b/roles/jitsi/files/prosody/modules/mod_muc_allowners.lua @@ -23,7 +23,7 @@ load_config(); -- 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; diff --git a/roles/jitsi/files/prosody/modules/mod_muc_displayname.lua b/roles/jitsi/files/prosody/modules/mod_muc_displayname.lua new file mode 100644 index 0000000..a881329 --- /dev/null +++ b/roles/jitsi/files/prosody/modules/mod_muc_displayname.lua @@ -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 diff --git a/roles/jitsi/files/prosody/modules/mod_muc_meeting_id.lua b/roles/jitsi/files/prosody/modules/mod_muc_meeting_id.lua index 50e3d97..7287db8 100644 --- a/roles/jitsi/files/prosody/modules/mod_muc_meeting_id.lua +++ b/roles/jitsi/files/prosody/modules/mod_muc_meeting_id.lua @@ -184,7 +184,8 @@ module:hook('message/bare', function(event) return; end - local json_message = stanza:get_child_text('json-message', 'http://jitsi.org/jitmeet'); + 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 diff --git a/roles/jitsi/files/prosody/modules/mod_muc_wait_for_host.lua b/roles/jitsi/files/prosody/modules/mod_muc_wait_for_host.lua index b368109..733ebe1 100644 --- a/roles/jitsi/files/prosody/modules/mod_muc_wait_for_host.lua +++ b/roles/jitsi/files/prosody/modules/mod_muc_wait_for_host.lua @@ -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 diff --git a/roles/jitsi/files/prosody/modules/mod_room_metadata.lua b/roles/jitsi/files/prosody/modules/mod_room_metadata.lua index a31abc5..a024965 100644 --- a/roles/jitsi/files/prosody/modules/mod_room_metadata.lua +++ b/roles/jitsi/files/prosody/modules/mod_room_metadata.lua @@ -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"); diff --git a/roles/jitsi/files/prosody/modules/mod_room_metadata_component.lua b/roles/jitsi/files/prosody/modules/mod_room_metadata_component.lua index a026bc8..34c6773 100644 --- a/roles/jitsi/files/prosody/modules/mod_room_metadata_component.lua +++ b/roles/jitsi/files/prosody/modules/mod_room_metadata_component.lua @@ -1,18 +1,12 @@ -- 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'; @@ -25,6 +19,7 @@ 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'; @@ -37,9 +32,9 @@ if muc_component_host == nil then 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.'); +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 @@ -85,24 +80,25 @@ function send_metadata(occupant, room, json_msg) -- we want to send the main meeting participants only to jicofo if is_admin(occupant.bare_jid) then - local participants = {}; + local participants; + local moderators = array(); - if room._data.mainMeetingParticipants then - table_add(participants, room._data.mainMeetingParticipants); + if room._data.participants then + participants = array(); + participants:append(room._data.participants); end if room._data.moderator_id then - table.insert(participants, room._data.moderator_id); + moderators:push(room._data.moderator_id); end if room._data.moderators then - table_add(participants, room._data.moderators); + moderators:append(room._data.moderators); end - if #participants > 0 then - metadata_to_send = table_shallow_copy(metadata_to_send); - metadata_to_send.mainMeetingParticipants = participants; - 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); @@ -182,7 +178,7 @@ function on_message(event) 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(muc_domain_base):fire_event('jitsi-metadata-allow-moderation', + 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; }); if not res then @@ -193,12 +189,15 @@ function on_message(event) jsonData.data = res; end - room.jitsiMetadata[jsonData.key] = jsonData.data; + local old_value = room.jitsiMetadata[jsonData.key]; + if not table_equals(old_value, jsonData.data) then + room.jitsiMetadata[jsonData.key] = jsonData.data; - broadcastMetadata(room); + broadcastMetadata(room); - -- fire and event for the change - main_muc_module:fire_event('jitsi-metadata-updated', { room = room; actor = occupant; key = jsonData.key; }); + -- 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 @@ -243,12 +242,24 @@ function process_main_muc_loaded(main_muc, host_module) local startMutedMetadata = room.jitsiMetadata.startMuted or {}; - startMutedMetadata.audio = startMuted.attr.audio == 'true'; - startMutedMetadata.video = startMuted.attr.video == 'true'; + local audioNewValue = startMuted.attr.audio == 'true'; + local videoNewValue = startMuted.attr.video == 'true'; + local send_update = false; - room.jitsiMetadata.startMuted = startMutedMetadata; + if startMutedMetadata.audio ~= audioNewValue then + startMutedMetadata.audio = audioNewValue; + send_update = true; + end + if startMutedMetadata.video ~= videoNewValue then + startMutedMetadata.video = videoNewValue; + send_update = true; + end - host_module:fire_event('room-metadata-changed', { room = room; }); + if send_update then + room.jitsiMetadata.startMuted = startMutedMetadata; + + host_module:fire_event('room-metadata-changed', { room = room; }); + end end); end @@ -342,3 +353,9 @@ 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); diff --git a/roles/jitsi/files/prosody/modules/mod_short_lived_token.lua b/roles/jitsi/files/prosody/modules/mod_short_lived_token.lua index bc40ed4..115b5ca 100644 --- a/roles/jitsi/files/prosody/modules/mod_short_lived_token.lua +++ b/roles/jitsi/files/prosody/modules/mod_short_lived_token.lua @@ -62,19 +62,19 @@ function generateToken(session, audience, room, occupant) 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, + 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 or session.granted_jitsi_meet_context_features + 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, + 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 }; @@ -115,6 +115,8 @@ module:hook('external_service/credentials', function (event) password = generateToken(session, host, room, occupant); expires = os.time() + options.ttl_seconds; restricted = true; + transport = 'https'; + port = 443; }); end end diff --git a/roles/jitsi/files/prosody/modules/mod_speakerstats.lua b/roles/jitsi/files/prosody/modules/mod_speakerstats.lua index bac15c4..380fde4 100644 --- a/roles/jitsi/files/prosody/modules/mod_speakerstats.lua +++ b/roles/jitsi/files/prosody/modules/mod_speakerstats.lua @@ -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'); diff --git a/roles/jitsi/files/prosody/modules/mod_speakerstats_component.lua b/roles/jitsi/files/prosody/modules/mod_speakerstats_component.lua index 2e5e1c1..521cc47 100644 --- a/roles/jitsi/files/prosody/modules/mod_speakerstats_component.lua +++ b/roles/jitsi/files/prosody/modules/mod_speakerstats_component.lua @@ -20,13 +20,13 @@ 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); @@ -376,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); diff --git a/roles/jitsi/files/prosody/modules/mod_visitors.lua b/roles/jitsi/files/prosody/modules/mod_visitors.lua index b0719e0..ebeef30 100644 --- a/roles/jitsi/files/prosody/modules/mod_visitors.lua +++ b/roles/jitsi/files/prosody/modules/mod_visitors.lua @@ -12,10 +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'; + +-- Debug flag +local DEBUG = false; local MUC_NS = 'http://jabber.org/protocol/muc'; @@ -78,6 +83,17 @@ local function send_visitors_iq(conference_service, room, type) 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(); @@ -85,6 +101,25 @@ local function send_visitors_iq(conference_service, room, type) 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 local function connect_vnode(event) local room, vnode = event.room, event.vnode; @@ -111,7 +146,7 @@ local function connect_vnode(event) 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; @@ -194,7 +229,8 @@ 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 @@ -215,7 +251,8 @@ 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 @@ -258,7 +295,8 @@ 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 @@ -282,7 +320,8 @@ 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] then @@ -302,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); @@ -370,23 +410,17 @@ process_host_module(main_muc_component_config, function(host_module, host) 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); diff --git a/roles/jitsi/files/prosody/modules/mod_visitors_component.lua b/roles/jitsi/files/prosody/modules/mod_visitors_component.lua index deba8bf..5528b23 100644 --- a/roles/jitsi/files/prosody/modules/mod_visitors_component.lua +++ b/roles/jitsi/files/prosody/modules/mod_visitors_component.lua @@ -23,6 +23,9 @@ local new_id = require 'util.id'.medium; 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; @@ -63,6 +66,17 @@ local visitors_promotion_requests = {}; local cache = require 'util.cache'; local sent_iq_cache = cache.new(200); +-- 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 -- @param to_jid the destination jid -- @param json_message the message content to send @@ -73,9 +87,14 @@ function send_json_message(to_jid, json_message) end 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; + 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) @@ -231,16 +250,6 @@ function get_visitors_languages(room) return count, languages:sort():concat(','); end -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 - -- 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) @@ -250,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; @@ -367,6 +380,11 @@ local function process_promotion_response(room, id, approved) 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] = { @@ -400,13 +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 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 @@ -420,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 @@ -461,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 @@ -472,12 +508,20 @@ 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 @@ -514,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 @@ -525,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 diff --git a/roles/jitsi/files/prosody/modules/util.lib.lua b/roles/jitsi/files/prosody/modules/util.lib.lua index 32fde74..5d8d529 100644 --- a/roles/jitsi/files/prosody/modules/util.lib.lua +++ b/roles/jitsi/files/prosody/modules/util.lib.lua @@ -259,13 +259,10 @@ 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 are missing but we have granted_features check that -- if features are missing from the token we check whether it is moderator -function is_feature_allowed(ft, features, granted_features, is_moderator) +function is_feature_allowed(ft, features, is_moderator) if features then return features[ft] == "true" or features[ft] == true; - elseif granted_features then - return granted_features[ft] == "true" or granted_features[ft] == true; else return is_moderator; end @@ -585,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 @@ -600,7 +597,7 @@ function table_shallow_copy(t) end local function table_find(tab, val) - if not tab then + if not tab or val == nil then return nil end @@ -619,6 +616,44 @@ local function table_add(t1, t2) 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; @@ -666,11 +701,35 @@ local function is_admin(_jid) 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; @@ -700,6 +759,8 @@ return { 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; }; diff --git a/roles/jitsi_videobridge/defaults/main.yml b/roles/jitsi_videobridge/defaults/main.yml index bfa01f3..5524676 100644 --- a/roles/jitsi_videobridge/defaults/main.yml +++ b/roles/jitsi_videobridge/defaults/main.yml @@ -3,9 +3,9 @@ jitsi_root_dir: /opt/jitsi jitsi_user: jitsi -jitsi_videobridge_version: "{{ jitsi_version | default('10314') }}" +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: 55ffb62de63c7280b4e2a6c25045da9c6f3e07c20f28d1b697510231ae56f8bf +jitsi_videobridge_archive_sha256: 57d29c7d24c5f0657efbadabd54d3efa34c587a05c7a2196dca5e3c56141b44a jitsi_videobridge_rtp_port: 10000 jitsi_videobridge_src_ip: diff --git a/roles/metabase/defaults/main.yml b/roles/metabase/defaults/main.yml index ed792e9..442e238 100644 --- a/roles/metabase/defaults/main.yml +++ b/roles/metabase/defaults/main.yml @@ -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. diff --git a/roles/metabase/tasks/install.yml b/roles/metabase/tasks/install.yml index fd85d75..68788aa 100644 --- a/roles/metabase/tasks/install.yml +++ b/roles/metabase/tasks/install.yml @@ -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 diff --git a/roles/mkdir/tasks/main.yml b/roles/mkdir/tasks/main.yml index 5626946..0af722e 100644 --- a/roles/mkdir/tasks/main.yml +++ b/roles/mkdir/tasks/main.yml @@ -34,4 +34,4 @@ - name: Create vector config dir file: path=/etc/vector/conf.d state=directory - tags: log,mkdir + tags: log,mkdir,vector diff --git a/roles/nginx/templates/vector.yml.j2 b/roles/nginx/templates/vector.yml.j2 index 165db16..2499fe9 100644 --- a/roles/nginx/templates/vector.yml.j2 +++ b/roles/nginx/templates/vector.yml.j2 @@ -18,5 +18,6 @@ transforms: } 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" diff --git a/roles/repo_nodejs/defaults/main.yml b/roles/repo_nodejs/defaults/main.yml index 51f5eb7..dc5483b 100644 --- a/roles/repo_nodejs/defaults/main.yml +++ b/roles/repo_nodejs/defaults/main.yml @@ -1,3 +1,3 @@ --- -nodejs_major_version: 18 +nodejs_major_version: 22 diff --git a/roles/repo_nodejs/templates/nodejs.repo.j2 b/roles/repo_nodejs/templates/nodejs.repo.j2 index 538c8ec..bb2da6b 100644 --- a/roles/repo_nodejs/templates/nodejs.repo.j2 +++ b/roles/repo_nodejs/templates/nodejs.repo.j2 @@ -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 diff --git a/roles/samba/templates/vector.yml b/roles/samba/templates/vector.yml index 6467c59..e75e5eb 100644 --- a/roles/samba/templates/vector.yml +++ b/roles/samba/templates/vector.yml @@ -17,4 +17,5 @@ transforms: .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() } diff --git a/roles/vault/tasks/main.yml b/roles/vault/tasks/main.yml index ca166ab..4b0753a 100644 --- a/roles/vault/tasks/main.yml +++ b/roles/vault/tasks/main.yml @@ -25,3 +25,5 @@ - include_tasks: cleanup.yml tags: always +- include_tasks: vector.yml + tags: always diff --git a/roles/vault/tasks/vector.yml b/roles/vault/tasks/vector.yml new file mode 100644 index 0000000..79e95e3 --- /dev/null +++ b/roles/vault/tasks/vector.yml @@ -0,0 +1,5 @@ +--- + +- name: Deploy vector configuration + template: src=vector.yml dest=/etc/vector/conf.d/vault.yml + tags: log,vault,vector diff --git a/roles/vault/templates/vector.yml b/roles/vault/templates/vector.yml new file mode 100644 index 0000000..ef1535a --- /dev/null +++ b/roles/vault/templates/vector.yml @@ -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() + } diff --git a/roles/vault_bin/defaults/main.yml b/roles/vault_bin/defaults/main.yml index c447986..559d1ba 100644 --- a/roles/vault_bin/defaults/main.yml +++ b/roles/vault_bin/defaults/main.yml @@ -1,7 +1,7 @@ # Version of Vault to install -vault_version: 1.20.0 +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: 25e9f1f9a6dd9866219d6a37c6d1af1d26d0e73aa95a4e755188751de133dea7 +vault_archive_sha256: e3ce3e678421c0d56f726952ab100875168c2e1eb1db751ed5a2b25b6b2ea96f