Software /
code /
prosody-modules
Comparison
mod_client_management/mod_client_management.lua @ 5301:8ef197cccd74
mod_client_management: Add XMPP and shell interfaces to fetch client list
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Sat, 01 Apr 2023 13:56:53 +0100 |
parent | 5294:385346b6c81d |
child | 5304:717ff9468464 |
comparison
equal
deleted
inserted
replaced
5300:fa97de0b0961 | 5301:8ef197cccd74 |
---|---|
1 local modulemanager = require "core.modulemanager"; | 1 local modulemanager = require "core.modulemanager"; |
2 local usermanager = require "core.usermanager"; | 2 local usermanager = require "core.usermanager"; |
3 | 3 |
4 local array = require "util.array"; | |
5 local dt = require "util.datetime"; | |
4 local id = require "util.id"; | 6 local id = require "util.id"; |
7 local it = require "util.iterators"; | |
5 local jid = require "util.jid"; | 8 local jid = require "util.jid"; |
6 local st = require "util.stanza"; | 9 local st = require "util.stanza"; |
7 | 10 |
8 local strict = module:get_option_boolean("enforce_client_ids", false); | 11 local strict = module:get_option_boolean("enforce_client_ids", false); |
9 | 12 |
185 | 188 |
186 -- Check for an active token grant that has been previously used by this client | 189 -- Check for an active token grant that has been previously used by this client |
187 if client.auth_token_id then | 190 if client.auth_token_id then |
188 local grant = tokenauth.get_grant_info(client.auth_token_id); | 191 local grant = tokenauth.get_grant_info(client.auth_token_id); |
189 if grant then | 192 if grant then |
190 status.active_grant = grant; | 193 status.grant = grant; |
191 end | 194 end |
192 end | 195 end |
193 | 196 |
194 -- Check for active FAST tokens | 197 -- Check for active FAST tokens |
195 if client.fast_auth then | 198 if client.fast_auth then |
196 if mod_fast.is_client_fast(username, client.id, last_password_change) then | 199 if mod_fast.is_client_fast(username, client.id, last_password_change) then |
197 status.active_fast = client.fast_auth; | 200 status.fast = client.fast_auth; |
198 end | 201 end |
199 end | 202 end |
200 | 203 |
201 -- Client has access if any password-based SASL mechanisms have been used since last password change | 204 -- Client has access if any password-based SASL mechanisms have been used since last password change |
202 for mech, mech_last_used in pairs(client.mechanisms) do | 205 for mech, mech_last_used in pairs(client.mechanisms) do |
203 if is_password_mechanism(mech) and mech_last_used >= last_password_change then | 206 if is_password_mechanism(mech) and mech_last_used >= last_password_change then |
204 status.active_password = mech_last_used; | 207 status.password = mech_last_used; |
205 end | 208 end |
206 end | 209 end |
207 | 210 |
208 if prosody.full_sessions[client.full_jid] then | 211 if prosody.full_sessions[client.full_jid] then |
209 status.active_connected = true; | 212 status.connected = true; |
210 end | 213 end |
211 | 214 |
212 if next(status) == nil then | 215 if next(status) == nil then |
213 return nil; | 216 return nil; |
214 end | 217 end |
227 local active = is_client_active(client); | 230 local active = is_client_active(client); |
228 if active then | 231 if active then |
229 client.type = "session"; | 232 client.type = "session"; |
230 client.active = active; | 233 client.active = active; |
231 table.insert(active_clients, client); | 234 table.insert(active_clients, client); |
232 if active.active_grant then | 235 if active.grant then |
233 used_grants[active.active_grant.id] = true; | 236 used_grants[active.grant.id] = true; |
234 end | 237 end |
235 end | 238 end |
236 end | 239 end |
237 | 240 |
238 -- Next, account for any grants that have been issued, but never actually logged in | 241 -- Next, account for any grants that have been issued, but never actually logged in |
242 id = grant_id; | 245 id = grant_id; |
243 type = "access"; | 246 type = "access"; |
244 first_seen = grant.created; | 247 first_seen = grant.created; |
245 last_seen = grant.accessed; | 248 last_seen = grant.accessed; |
246 active = { | 249 active = { |
247 active_grant = grant; | 250 grant = grant; |
248 }; | 251 }; |
249 user_agent = get_user_agent(nil, grant); | 252 user_agent = get_user_agent(nil, grant); |
250 }); | 253 }); |
251 end | 254 end |
252 end | 255 end |
266 return a.id < b.id; | 269 return a.id < b.id; |
267 end); | 270 end); |
268 | 271 |
269 return active_clients; | 272 return active_clients; |
270 end | 273 end |
274 | |
275 -- Protocol | |
276 | |
277 local xmlns_manage_clients = "xmpp:prosody.im/protocol/manage-clients"; | |
278 | |
279 module:hook("iq-get/self/xmpp:prosody.im/protocol/manage-clients:list", function (event) | |
280 local origin, stanza = event.origin, event.stanza; | |
281 | |
282 if not module:may(":list-clients", event) then | |
283 origin.send(st.error_reply(stanza, "auth", "forbidden")); | |
284 return true; | |
285 end | |
286 | |
287 local reply = st.reply(stanza) | |
288 :tag("clients", { xmlns = xmlns_manage_clients }); | |
289 | |
290 local active_clients = get_active_clients(event.origin.username); | |
291 for _, client in ipairs(active_clients) do | |
292 local auth_type = st.stanza("auth"); | |
293 if client.active then | |
294 if client.active.password then | |
295 auth_type:text_tag("password"); | |
296 end | |
297 if client.active.grant then | |
298 auth_type:text_tag("bearer-token"); | |
299 end | |
300 if client.active.fast then | |
301 auth_type:text_tag("fast"); | |
302 end | |
303 end | |
304 | |
305 local user_agent = st.stanza("user-agent"); | |
306 if client.user_agent then | |
307 if client.user_agent.software then | |
308 user_agent:text_tag("software", client.user_agent.software); | |
309 end | |
310 if client.user_agent.device then | |
311 user_agent:text_tag("device", client.user_agent.device); | |
312 end | |
313 if client.user_agent.uri then | |
314 user_agent:text_tag("uri", client.user_agent.uri); | |
315 end | |
316 end | |
317 | |
318 local connected = client.active and client.active.connected; | |
319 reply:tag("client", { id = client.id, connected = connected and "true" or "false" }) | |
320 :text_tag("first-seen", dt.datetime(client.first_seen)) | |
321 :text_tag("last-seen", dt.datetime(client.last_seen)) | |
322 :add_child(auth_type) | |
323 :add_child(user_agent) | |
324 :up(); | |
325 end | |
326 reply:up(); | |
327 | |
328 origin.send(reply); | |
329 return true; | |
330 end); | |
331 | |
332 -- Command | |
333 | |
334 module:once(function () | |
335 local console_env = module:shared("/*/admin_shell/env"); | |
336 if not console_env.user then return; end -- admin_shell probably not loaded | |
337 | |
338 function console_env.user:clients(username) | |
339 local clients = get_active_clients(username); | |
340 if not clients or #clients == 0 then | |
341 return true, "No clients associated with this account"; | |
342 end | |
343 | |
344 local colspec = { | |
345 { title = "Software", key = "software" }; | |
346 { title = "Last seen", key = "last_seen" }; | |
347 { title = "Authentication", key = "auth_methods" }; | |
348 }; | |
349 | |
350 local row = require "util.human.io".table(colspec, self.session.width); | |
351 | |
352 local print = self.session.print; | |
353 print(row()); | |
354 for _, client in ipairs(clients) do | |
355 print(row({ | |
356 software = client.user_agent.software; | |
357 last_seen = os.date("%Y-%m-%d", client.last_seen); | |
358 auth_methods = array.collect(it.keys(client.active)):sort(); | |
359 })); | |
360 end | |
361 print(("%d clients"):format(#clients)); | |
362 end | |
363 end); |