Mercurial > 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 |
| parents | 385346b6c81d |
| children | 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); |
