snapshot of https://github.com/jitsi/jitsi-meet.git @ fd1749dd28f4138aa80234fc7f311b19aaffa0e7 (2026-09-15)
This commit is contained in:
168
resources/prosody-plugins/mod_muc_displayname.lua
Normal file
168
resources/prosody-plugins/mod_muc_displayname.lua
Normal file
@@ -0,0 +1,168 @@
|
||||
-- Manages display names and identity visibility in MUC rooms.
|
||||
--
|
||||
-- Three behaviours:
|
||||
--
|
||||
-- 1. hideDisplayNameForGuests (outbound filter on stanzas/out, priority -100):
|
||||
-- When room._data.hideDisplayNameForGuests is true, strips identity elements
|
||||
-- (<nick>, <email>, <stats-id>, and name/email inside <identity>) from
|
||||
-- presence stanzas before they are delivered to non-moderator occupants.
|
||||
-- Moderator recipients and self-presence are always passed through unchanged.
|
||||
-- Uses session.jitsi_web_query_room / jitsi_web_query_prefix to locate the room.
|
||||
--
|
||||
-- 2. name-readonly (inbound filter on stanzas/in, priority -100):
|
||||
-- When session.jitsi_meet_context_features['name-readonly'] is true, enforces
|
||||
-- the display name from the JWT token on every outgoing presence from that session:
|
||||
-- * Token has context.user.name → replaces the <nick> element with the token name
|
||||
-- * Token has no name → removes the <nick> element entirely
|
||||
-- This prevents clients from changing their display name when the token marks
|
||||
-- it as read-only.
|
||||
--
|
||||
-- 3. Message display-name handling (MUC hooks):
|
||||
-- muc-broadcast-message (priority -1): strips <nick> and <display-name> from
|
||||
-- every message before it is broadcast to occupants, so message payloads
|
||||
-- never carry identity information to live receivers.
|
||||
-- muc-add-history: injects <display-name source="token|guest"> into the
|
||||
-- history copy of each message (cloned from the broadcast stanza), sourced
|
||||
-- from session.jitsi_meet_context_user.name (token users) or the <nick>
|
||||
-- element (guests). Late-joining clients receive this annotated history.
|
||||
--
|
||||
-- Load on the 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 is_focus = util.is_focus;
|
||||
local internal_room_jid_match_rewrite = util.internal_room_jid_match_rewrite;
|
||||
local NICK_NS = 'http://jabber.org/protocol/nick';
|
||||
local DISPLAY_NAME_NS = 'http://jitsi.org/protocol/display-name';
|
||||
|
||||
local sessions = prosody.full_sessions;
|
||||
|
||||
local ignore_jwt_name = module:get_option_boolean('ignore_jwt_name', false);
|
||||
|
||||
-- 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 is_focus(stanza.attr.from) 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 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
|
||||
shouldFilter = occupant.role ~= 'moderator' and not joining_moderator_participants[occupant.bare_jid];
|
||||
end
|
||||
end
|
||||
|
||||
if shouldFilter then
|
||||
return filter_identity_from_presence(stanza);
|
||||
else
|
||||
return stanza;
|
||||
end
|
||||
end
|
||||
|
||||
-- When a participant has a name-readonly feature enabled, we need to ensure that the
|
||||
-- nick element in the presence stanza is set to the user's name, if it exists.
|
||||
-- If the user does not have a name, we remove the nick element.
|
||||
-- This is to ensure that the name is not changed by the user, as it is read-only.
|
||||
function filter_stanza_in(stanza, session)
|
||||
if not session or not session.jitsi_meet_context_features
|
||||
or session.jitsi_meet_context_features['name-readonly'] ~= true then
|
||||
-- if the name-readonly feature is not set, there is nothing we need to do
|
||||
return stanza;
|
||||
end
|
||||
|
||||
if stanza.name ~= 'presence' or stanza.attr.type == 'error'
|
||||
or stanza.attr.type == 'unavailable' or is_focus(stanza.attr.from or '') then
|
||||
return stanza;
|
||||
end
|
||||
|
||||
-- if the user does not have a name in token and name is readonly, remove any nick element
|
||||
if not session.jitsi_meet_context_user or not session.jitsi_meet_context_user.name then
|
||||
stanza:remove_children('nick', NICK_NS);
|
||||
return stanza;
|
||||
end
|
||||
|
||||
local nick_element = stanza:get_child('nick', NICK_NS);
|
||||
|
||||
if nick_element and nick_element:get_text() ~= session.jitsi_meet_context_user.name then
|
||||
stanza:remove_children('nick', NICK_NS);
|
||||
stanza:tag('nick', { xmlns = NICK_NS }):text(session.jitsi_meet_context_user.name):up();
|
||||
end
|
||||
|
||||
return stanza;
|
||||
end
|
||||
|
||||
-- 'muc-add-history' is called in 'muc-broadcast-message' with priority 0
|
||||
-- we want to clean up message after we have processed it for history, but do it for all messages
|
||||
-- not only those that are added to history (as someone can forge a message and add 'no-store')
|
||||
module:hook('muc-broadcast-message', function(event)
|
||||
-- any message that is about to be delivered, clear up nick and display-name elements
|
||||
event.stanza:remove_children('nick', NICK_NS);
|
||||
event.stanza:remove_children('display-name', DISPLAY_NAME_NS);
|
||||
end, -1);
|
||||
|
||||
module:hook('muc-add-history', function(event)
|
||||
local room, stanza = event.room, event.stanza;
|
||||
local name;
|
||||
local source;
|
||||
|
||||
local occupant = room:get_occupant_by_nick(stanza.attr.from);
|
||||
|
||||
if occupant then
|
||||
local session = sessions[occupant.jid];
|
||||
if session and session.jitsi_meet_context_user then
|
||||
if ignore_jwt_name then
|
||||
name = occupant:get_presence():get_child_text('nick', NICK_NS);
|
||||
else
|
||||
name = session.jitsi_meet_context_user.name;
|
||||
end
|
||||
|
||||
source = 'token';
|
||||
else
|
||||
name = occupant:get_presence():get_child_text('nick', NICK_NS);
|
||||
source = 'guest';
|
||||
end
|
||||
else
|
||||
-- if no occupant found it is a message from visitor let's check display-name source
|
||||
local display_name_element = stanza:get_child('display-name', DISPLAY_NAME_NS);
|
||||
if display_name_element and display_name_element.attr.source == 'visitor' then
|
||||
name = display_name_element:get_text();
|
||||
source = display_name_element.attr.source;
|
||||
end
|
||||
end
|
||||
|
||||
if name then
|
||||
-- clone the stanza, so only history has the display_name extension
|
||||
event.stanza = st.clone(stanza);
|
||||
|
||||
event.stanza:tag('display-name', {
|
||||
xmlns = DISPLAY_NAME_NS,
|
||||
source = source
|
||||
}):text(name):up();
|
||||
end
|
||||
end);
|
||||
|
||||
function filter_session(session)
|
||||
filters.add_filter(session, 'stanzas/out', filter_stanza_out, -100);
|
||||
filters.add_filter(session, 'stanzas/in', filter_stanza_in, -100);
|
||||
end
|
||||
|
||||
function module.load()
|
||||
filters.add_filter_hook(filter_session);
|
||||
end
|
||||
|
||||
function module.unload()
|
||||
filters.remove_filter_hook(filter_session);
|
||||
end
|
||||
Reference in New Issue
Block a user