"Fossies" - the Fresh Open Source Software Archive 
Member "jitsi-meet-7685/resources/prosody-plugins/mod_filter_iq_rayo.lua" (29 Nov 2023, 10566 Bytes) of package /linux/misc/jitsi-meet-7685.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Lua source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
1 local new_throttle = require "util.throttle".create;
2 local st = require "util.stanza";
3
4 local token_util = module:require "token/util".new(module);
5 local util = module:require 'util';
6 local room_jid_match_rewrite = util.room_jid_match_rewrite;
7 local is_feature_allowed = util.is_feature_allowed;
8 local get_room_from_jid = util.get_room_from_jid;
9 local is_healthcheck_room = util.is_healthcheck_room;
10 local jid_bare = require "util.jid".bare;
11
12 local sessions = prosody.full_sessions;
13
14 local main_muc_component_host = module:get_option_string('main_muc');
15 if main_muc_component_host == nil then
16 module:log('error', 'main_muc not configured. Cannot proceed.');
17 return;
18 end
19 local main_muc_service;
20
21 -- no token configuration but required
22 if token_util == nil then
23 module:log("error", "no token configuration but it is required");
24 return;
25 end
26
27 local um_is_admin = require 'core.usermanager'.is_admin;
28 local function is_admin(jid)
29 return um_is_admin(jid, module.host);
30 end
31
32 -- The maximum number of simultaneous calls,
33 -- and also the maximum number of new calls per minute that a session is allowed to create.
34 local limit_outgoing_calls;
35 local function load_config()
36 limit_outgoing_calls = module:get_option_number("max_number_outgoing_calls", -1);
37 end
38 load_config();
39
40 -- Header names to use to push extra data extracted from token, if any
41 local OUT_INITIATOR_USER_ATTR_NAME = "X-outbound-call-initiator-user";
42 local OUT_INITIATOR_GROUP_ATTR_NAME = "X-outbound-call-initiator-group";
43 local OUTGOING_CALLS_THROTTLE_INTERVAL = 60; -- if max_number_outgoing_calls is enabled it will be
44 -- the max number of outgoing calls a user can try for a minute
45
46 -- filters rayo iq in case of requested from not jwt authenticated sessions
47 -- or if the session has features in user context and it doesn't mention
48 -- feature "outbound-call" to be enabled
49 module:hook("pre-iq/full", function(event)
50 local stanza = event.stanza;
51 if stanza.name == "iq" then
52 local dial = stanza:get_child('dial', 'urn:xmpp:rayo:1');
53 if dial then
54 local session = event.origin;
55 local token = session.auth_token;
56
57 -- find header with attr name 'JvbRoomName' and extract its value
58 local headerName = 'JvbRoomName';
59 local roomName;
60 for _, child in ipairs(dial.tags) do
61 if (child.name == 'header'
62 and child.attr.name == headerName) then
63 roomName = child.attr.value;
64 break;
65 end
66 end
67
68 if (token == nil
69 or roomName == nil
70 or not token_util:verify_room(session, room_jid_match_rewrite(roomName))
71 or not is_feature_allowed(session.jitsi_meet_context_features,
72 (dial.attr.to == 'jitsi_meet_transcribe' and 'transcription' or 'outbound-call')))
73 -- if current user is not allowed, but was granted moderation by a user
74 -- that is allowed by its features we want to allow it
75 and not is_feature_allowed(session.granted_jitsi_meet_context_features,
76 (dial.attr.to == 'jitsi_meet_transcribe' and 'transcription' or 'outbound-call'))
77 then
78 module:log("warn",
79 "Filtering stanza dial, stanza:%s", tostring(stanza));
80 session.send(st.error_reply(stanza, "auth", "forbidden"));
81 return true;
82 end
83
84 -- we get current user_id or group, or the one from the granted one
85 -- so guests and the user that granted rights are sharing same limit, as guest can be without token
86 local user_id, group_id = nil, session.jitsi_meet_context_group;
87 if session.jitsi_meet_context_user then
88 user_id = session.jitsi_meet_context_user["id"];
89 else
90 user_id = session.granted_jitsi_meet_context_user_id;
91 group_id = session.granted_jitsi_meet_context_group_id;
92 end
93
94 -- now lets check any limits if configured
95 if limit_outgoing_calls > 0 then
96 if not session.dial_out_throttle then
97 module:log("debug", "Enabling dial-out throttle session=%s.", session);
98 session.dial_out_throttle = new_throttle(limit_outgoing_calls, OUTGOING_CALLS_THROTTLE_INTERVAL);
99 end
100
101 if not session.dial_out_throttle:poll(1) -- we first check the throttle so we can mark one incoming dial for the balance
102 or get_concurrent_outgoing_count(user_id, group_id) >= limit_outgoing_calls
103 then
104 module:log("warn",
105 "Filtering stanza dial, stanza:%s, outgoing calls limit reached", tostring(stanza));
106 session.send(st.error_reply(stanza, "cancel", "resource-constraint"));
107 return true;
108 end
109 end
110
111 -- now lets insert token information if any
112 if session and user_id then
113 -- First remove any 'header' element if it already
114 -- exists, so it cannot be spoofed by a client
115 stanza:maptags(
116 function(tag)
117 if tag.name == "header"
118 and (tag.attr.name == OUT_INITIATOR_USER_ATTR_NAME
119 or tag.attr.name == OUT_INITIATOR_GROUP_ATTR_NAME) then
120 return nil
121 end
122 return tag
123 end
124 )
125
126 local dial = stanza:get_child('dial', 'urn:xmpp:rayo:1');
127 -- adds initiator user id from token
128 dial:tag("header", {
129 xmlns = "urn:xmpp:rayo:1",
130 name = OUT_INITIATOR_USER_ATTR_NAME,
131 value = user_id });
132 dial:up();
133
134 -- Add the initiator group information if it is present
135 if session.jitsi_meet_context_group then
136 dial:tag("header", {
137 xmlns = "urn:xmpp:rayo:1",
138 name = OUT_INITIATOR_GROUP_ATTR_NAME,
139 value = session.jitsi_meet_context_group });
140 dial:up();
141 end
142 end
143 end
144 end
145 end);
146
147 --- Finds and returns the number of concurrent outgoing calls for a user
148 -- @param context_user the user id extracted from the token
149 -- @param context_group the group id extracted from the token
150 -- @return returns the count of concurrent calls
151 function get_concurrent_outgoing_count(context_user, context_group)
152 local count = 0;
153 local rooms = main_muc_service.live_rooms();
154
155 -- now lets iterate over rooms and occupants and search for
156 -- call initiated by the user
157 for room in rooms do
158 for _, occupant in room:each_occupant() do
159 for _, presence in occupant:each_session() do
160
161 local initiator = presence:get_child('initiator', 'http://jitsi.org/protocol/jigasi');
162
163 local found_user = false;
164 local found_group = false;
165
166 if initiator then
167 initiator:maptags(function (tag)
168 if tag.name == "header"
169 and tag.attr.name == OUT_INITIATOR_USER_ATTR_NAME then
170 found_user = tag.attr.value == context_user;
171 elseif tag.name == "header"
172 and tag.attr.name == OUT_INITIATOR_GROUP_ATTR_NAME then
173 found_group = tag.attr.value == context_group;
174 end
175
176 return tag;
177 end );
178 -- if found a jigasi participant initiated by the concurrent
179 -- participant, count it
180 if found_user
181 and (context_group == nil or found_group) then
182 count = count + 1;
183 end
184 end
185 end
186 end
187 end
188
189 return count;
190 end
191
192 module:hook_global('config-reloaded', load_config);
193
194 -- process a host module directly if loaded or hooks to wait for its load
195 function process_host_module(name, callback)
196 local function process_host(host)
197 if host == name then
198 callback(module:context(host), host);
199 end
200 end
201
202 if prosody.hosts[name] == nil then
203 module:log('debug', 'No host/component found, will wait for it: %s', name)
204
205 -- when a host or component is added
206 prosody.events.add_handler('host-activated', process_host);
207 else
208 process_host(name);
209 end
210 end
211
212 function process_set_affiliation(event)
213 local actor, affiliation, jid, previous_affiliation, room
214 = event.actor, event.affiliation, event.jid, event.previous_affiliation, event.room;
215 local actor_session = sessions[actor];
216
217 if is_admin(jid) or is_healthcheck_room(room.jid) or not actor or not previous_affiliation
218 or not actor_session or not actor_session.jitsi_meet_context_features then
219 return;
220 end
221
222 local occupant;
223 for _, o in room:each_occupant() do
224 if o.bare_jid == jid then
225 occupant = o;
226 end
227 end
228
229 if not occupant then
230 return;
231 end
232
233 local occupant_session = sessions[occupant.jid];
234 if not occupant_session then
235 return;
236 end
237
238 if previous_affiliation == 'none' and affiliation == 'owner' then
239 occupant_session.granted_jitsi_meet_context_features = actor_session.jitsi_meet_context_features;
240 occupant_session.granted_jitsi_meet_context_user_id = actor_session.jitsi_meet_context_user["id"];
241 occupant_session.granted_jitsi_meet_context_group_id = actor_session.jitsi_meet_context_group;
242 elseif previous_affiliation == 'owner' and ( affiliation == 'member' or affiliation == 'none' ) then
243 occupant_session.granted_jitsi_meet_context_features = nil;
244 occupant_session.granted_jitsi_meet_context_user_id = nil;
245 occupant_session.granted_jitsi_meet_context_group_id = nil;
246 end
247 end
248
249 process_host_module(main_muc_component_host, function(host_module, host)
250 main_muc_service = prosody.hosts[host].modules.muc;
251
252 host_module:hook("muc-pre-set-affiliation", process_set_affiliation);
253 end);