Mercurial > prosody-modules
comparison mod_admin_web2/admin_web2/mod_admin_web2.lua @ 6520:7eb114975cec
mod_admin_web2: Fork of admin_web for prosody 13.0. It is a fork because it changes access rights and might be insecure. Mark as alpha.
| author | Menel <menel@snikket.de> |
|---|---|
| date | Fri, 24 Apr 2026 12:40:13 +0200 |
| parents | mod_admin_web/admin_web/mod_admin_web.lua@963c16e417d6 |
| children |
comparison
equal
deleted
inserted
replaced
| 6519:01d8bfbfc435 | 6520:7eb114975cec |
|---|---|
| 1 -- Copyright (C) 2010 Florian Zeitz | |
| 2 -- | |
| 3 -- This file is MIT/X11 licensed. Please see the | |
| 4 -- COPYING file in the source package for more information. | |
| 5 -- | |
| 6 | |
| 7 -- <session xmlns="http://prosody.im/streams/c2s" jid="alice@example.com/brussels"> | |
| 8 -- <encrypted/> | |
| 9 -- <compressed/> | |
| 10 -- </session> | |
| 11 | |
| 12 -- <session xmlns="http://prosody.im/streams/s2s" jid="example.com"> | |
| 13 -- <encrypted> | |
| 14 -- <valid/> / <invalid/> | |
| 15 -- </encrypted> | |
| 16 -- <compressed/> | |
| 17 -- <in/> / <out/> | |
| 18 -- </session> | |
| 19 | |
| 20 module:default_permission("prosody:operator", ":web-operator") | |
| 21 | |
| 22 local st = require "util.stanza"; | |
| 23 local uuid_generate = require "util.uuid".generate; | |
| 24 local pubsub = require "util.pubsub"; | |
| 25 local jid_bare = require "util.jid".bare; | |
| 26 | |
| 27 local hosts = prosody.hosts; | |
| 28 local incoming_s2s = prosody.incoming_s2s; | |
| 29 | |
| 30 module:set_global(); | |
| 31 | |
| 32 local service = {}; | |
| 33 | |
| 34 local xmlns_adminsub = "http://prosody.im/adminsub"; | |
| 35 local xmlns_c2s_session = "http://prosody.im/streams/c2s"; | |
| 36 local xmlns_s2s_session = "http://prosody.im/streams/s2s"; | |
| 37 | |
| 38 local idmap = {}; | |
| 39 | |
| 40 local function add_client(session, host) | |
| 41 local name = session.full_jid; | |
| 42 local id = idmap[name]; | |
| 43 if not id then | |
| 44 id = uuid_generate(); | |
| 45 idmap[name] = id; | |
| 46 end | |
| 47 local item = st.stanza("item", { id = id }):tag("session", {xmlns = xmlns_c2s_session, jid = name}):up(); | |
| 48 if session.secure then | |
| 49 local encrypted = item:tag("encrypted"); | |
| 50 local sock = session.conn and session.conn.socket and session.conn:socket() | |
| 51 local info = sock and sock.info and sock:info(); | |
| 52 for k, v in pairs(info or {}) do | |
| 53 encrypted:tag("info", { name = k }):text(tostring(v)):up(); | |
| 54 end | |
| 55 end | |
| 56 if session.compressed then | |
| 57 item:tag("compressed"):up(); | |
| 58 end | |
| 59 service[host]:publish(xmlns_c2s_session, host, id, item); | |
| 60 module:log("debug", "Added client %s", name); | |
| 61 end | |
| 62 | |
| 63 local function del_client(session, host) | |
| 64 local name = session.full_jid; | |
| 65 local id = idmap[name]; | |
| 66 if id then | |
| 67 local notifier = st.stanza("retract", { id = id }); | |
| 68 service[host]:retract(xmlns_c2s_session, host, id, notifier); | |
| 69 end | |
| 70 end | |
| 71 | |
| 72 local function add_host(session, type, host) | |
| 73 local name = (type == "out" and session.to_host) or (type == "in" and session.from_host); | |
| 74 local id = idmap[name.."_"..type]; | |
| 75 if not id then | |
| 76 id = uuid_generate(); | |
| 77 idmap[name.."_"..type] = id; | |
| 78 end | |
| 79 local item = st.stanza("item", { id = id }):tag("session", {xmlns = xmlns_s2s_session, jid = name}) | |
| 80 :tag(type):up(); | |
| 81 if session.secure then | |
| 82 local encrypted = item:tag("encrypted"); | |
| 83 | |
| 84 local sock = session.conn and session.conn.socket and session.conn:socket() | |
| 85 local info = sock and sock.info and sock:info(); | |
| 86 for k, v in pairs(info or {}) do | |
| 87 encrypted:tag("info", { name = k }):text(tostring(v)):up(); | |
| 88 end | |
| 89 | |
| 90 if session.cert_identity_status == "valid" then | |
| 91 encrypted:tag("valid"); | |
| 92 else | |
| 93 encrypted:tag("invalid"); | |
| 94 end | |
| 95 end | |
| 96 if session.compressed then | |
| 97 item:tag("compressed"):up(); | |
| 98 end | |
| 99 service[host]:publish(xmlns_s2s_session, host, id, item); | |
| 100 module:log("debug", "Added host %s s2s%s", name, type); | |
| 101 end | |
| 102 | |
| 103 local function del_host(session, type, host) | |
| 104 local name = (type == "out" and session.to_host) or (type == "in" and session.from_host); | |
| 105 local id = idmap[name.."_"..type]; | |
| 106 if id then | |
| 107 local notifier = st.stanza("retract", { id = id }); | |
| 108 service[host]:retract(xmlns_s2s_session, host, id, notifier); | |
| 109 end | |
| 110 end | |
| 111 | |
| 112 function module.add_host(module) | |
| 113 -- Dependencies | |
| 114 module:depends("bosh"); | |
| 115 module:depends("admin_adhoc"); | |
| 116 module:depends("http"); | |
| 117 | |
| 118 local http_files = require "net.http.files"; | |
| 119 local serve_file = http_files.serve { | |
| 120 path = module:get_directory() .. "/www_files"; | |
| 121 }; | |
| 122 | |
| 123 -- Setup HTTP server | |
| 124 module:provides("http", { | |
| 125 title = "Admin Interface"; | |
| 126 name = "admin"; | |
| 127 route = { | |
| 128 ["GET"] = function(event) | |
| 129 event.response.headers.location = event.request.path .. "/"; | |
| 130 return 301; | |
| 131 end; | |
| 132 ["GET /*"] = serve_file; | |
| 133 } | |
| 134 }); | |
| 135 | |
| 136 -- Setup adminsub service | |
| 137 local function simple_broadcast(kind, node, jids, item) | |
| 138 if item then | |
| 139 item = st.clone(item); | |
| 140 item.attr.xmlns = nil; -- Clear the pubsub namespace | |
| 141 end | |
| 142 local message = st.message({ from = module.host, type = "headline" }) | |
| 143 :tag("event", { xmlns = xmlns_adminsub .. "#event" }) | |
| 144 :tag(kind, { node = node }) | |
| 145 :add_child(item); | |
| 146 for jid in pairs(jids) do | |
| 147 module:log("debug", "Sending notification to %s", jid); | |
| 148 message.attr.to = jid; | |
| 149 module:send(message); | |
| 150 end | |
| 151 end | |
| 152 | |
| 153 service[module.host] = pubsub.new({ | |
| 154 broadcaster = simple_broadcast; | |
| 155 normalize_jid = jid_bare; | |
| 156 -- what was this for? prosody:operator has full rights everywhere, this shoud be fine? | |
| 157 -- get_affiliation = function(jid) return get_affiliation(jid, module.host) end; | |
| 158 get_affiliation = function() return "owner" end; | |
| 159 capabilities = { | |
| 160 member = { | |
| 161 create = false; | |
| 162 publish = false; | |
| 163 retract = false; | |
| 164 get_nodes = true; | |
| 165 | |
| 166 subscribe = true; | |
| 167 unsubscribe = true; | |
| 168 get_subscription = true; | |
| 169 get_subscriptions = true; | |
| 170 get_items = true; | |
| 171 | |
| 172 subscribe_other = false; | |
| 173 unsubscribe_other = false; | |
| 174 get_subscription_other = false; | |
| 175 get_subscriptions_other = false; | |
| 176 | |
| 177 be_subscribed = true; | |
| 178 be_unsubscribed = true; | |
| 179 | |
| 180 set_affiliation = false; | |
| 181 }; | |
| 182 | |
| 183 owner = { | |
| 184 create = true; | |
| 185 publish = true; | |
| 186 retract = true; | |
| 187 get_nodes = true; | |
| 188 | |
| 189 subscribe = true; | |
| 190 unsubscribe = true; | |
| 191 get_subscription = true; | |
| 192 get_subscriptions = true; | |
| 193 get_items = true; | |
| 194 | |
| 195 subscribe_other = true; | |
| 196 unsubscribe_other = true; | |
| 197 get_subscription_other = true; | |
| 198 get_subscriptions_other = true; | |
| 199 | |
| 200 be_subscribed = true; | |
| 201 be_unsubscribed = true; | |
| 202 | |
| 203 set_affiliation = true; | |
| 204 }; | |
| 205 }; | |
| 206 }); | |
| 207 | |
| 208 -- Create node for s2s sessions | |
| 209 local ok, err = service[module.host]:create(xmlns_s2s_session, true); | |
| 210 if not ok then | |
| 211 module:log("warn", "Could not create node %s: %s", xmlns_s2s_session, err); | |
| 212 else | |
| 213 service[module.host]:set_affiliation(xmlns_s2s_session, true, module.host, "owner") | |
| 214 end | |
| 215 | |
| 216 -- Add outgoing s2s sessions | |
| 217 for _, session in pairs(hosts[module.host].s2sout) do | |
| 218 if session.type ~= "s2sout_unauthed" then | |
| 219 add_host(session, "out", module.host); | |
| 220 end | |
| 221 end | |
| 222 | |
| 223 -- Add incoming s2s sessions | |
| 224 for session in pairs(incoming_s2s) do | |
| 225 if session.to_host == module.host then | |
| 226 add_host(session, "in", module.host); | |
| 227 end | |
| 228 end | |
| 229 | |
| 230 -- Create node for c2s sessions | |
| 231 ok, err = service[module.host]:create(xmlns_c2s_session, true); | |
| 232 if not ok then | |
| 233 module:log("warn", "Could not create node %s: %s", xmlns_c2s_session, tostring(err)); | |
| 234 else | |
| 235 service[module.host]:set_affiliation(xmlns_c2s_session, true, module.host, "owner") | |
| 236 end | |
| 237 | |
| 238 -- Add c2s sessions | |
| 239 for _, user in pairs(hosts[module.host].sessions or {}) do | |
| 240 for _, session in pairs(user.sessions or {}) do | |
| 241 add_client(session, module.host); | |
| 242 end | |
| 243 end | |
| 244 | |
| 245 -- Register adminsub handler | |
| 246 module:hook("iq/host/http://prosody.im/adminsub:adminsub", function(event) | |
| 247 -- luacheck: ignore 431/ok | |
| 248 local origin, stanza = event.origin, event.stanza; | |
| 249 local adminsub = stanza.tags[1]; | |
| 250 local action = adminsub.tags[1]; | |
| 251 local reply; | |
| 252 if action.name == "subscribe" then | |
| 253 local ok, ret = service[module.host]:add_subscription(action.attr.node, stanza.attr.from, stanza.attr.from); | |
| 254 if ok then | |
| 255 reply = st.reply(stanza) | |
| 256 :tag("adminsub", { xmlns = xmlns_adminsub }); | |
| 257 else | |
| 258 reply = st.error_reply(stanza, "cancel", ret); | |
| 259 end | |
| 260 elseif action.name == "unsubscribe" then | |
| 261 local ok, ret = service[module.host]:remove_subscription(action.attr.node, stanza.attr.from, stanza.attr.from); | |
| 262 if ok then | |
| 263 reply = st.reply(stanza) | |
| 264 :tag("adminsub", { xmlns = xmlns_adminsub }); | |
| 265 else | |
| 266 reply = st.error_reply(stanza, "cancel", ret); | |
| 267 end | |
| 268 elseif action.name == "items" then | |
| 269 local node = action.attr.node; | |
| 270 local ok, ret = service[module.host]:get_items(node, stanza.attr.from); | |
| 271 if not ok then | |
| 272 origin.send(st.error_reply(stanza, "cancel", ret)); | |
| 273 return true; | |
| 274 end | |
| 275 | |
| 276 local data = st.stanza("items", { node = node }); | |
| 277 for _, entry in pairs(ret) do | |
| 278 data:add_child(entry); | |
| 279 end | |
| 280 if data then | |
| 281 reply = st.reply(stanza) | |
| 282 :tag("adminsub", { xmlns = xmlns_adminsub }) | |
| 283 :add_child(data); | |
| 284 else | |
| 285 reply = st.error_reply(stanza, "cancel", "item-not-found"); | |
| 286 end | |
| 287 elseif action.name == "adminfor" then | |
| 288 local data = st.stanza("adminfor"); | |
| 289 for host_name in pairs(hosts) do | |
| 290 -- new Permission API: | |
| 291 if module:context(host_name):may(":web-operator", stanza.attr.from) then | |
| 292 data:tag("item"):text(host_name):up(); | |
| 293 end | |
| 294 end | |
| 295 reply = st.reply(stanza) | |
| 296 :tag("adminsub", { xmlns = xmlns_adminsub }) | |
| 297 :add_child(data); | |
| 298 else | |
| 299 reply = st.error_reply(stanza, "feature-not-implemented"); | |
| 300 end | |
| 301 origin.send(reply); | |
| 302 return true; | |
| 303 end); | |
| 304 | |
| 305 -- Add/remove c2s sessions | |
| 306 module:hook("resource-bind", function(event) | |
| 307 add_client(event.session, module.host); | |
| 308 end); | |
| 309 | |
| 310 module:hook("resource-unbind", function(event) | |
| 311 del_client(event.session, module.host); | |
| 312 service[module.host]:remove_subscription(xmlns_c2s_session, module.host, event.session.full_jid); | |
| 313 service[module.host]:remove_subscription(xmlns_s2s_session, module.host, event.session.full_jid); | |
| 314 end); | |
| 315 | |
| 316 -- Add/remove s2s sessions | |
| 317 module:hook("s2sout-established", function(event) | |
| 318 add_host(event.session, "out", module.host); | |
| 319 end); | |
| 320 | |
| 321 module:hook("s2sin-established", function(event) | |
| 322 add_host(event.session, "in", module.host); | |
| 323 end); | |
| 324 | |
| 325 module:hook("s2sout-destroyed", function(event) | |
| 326 del_host(event.session, "out", module.host); | |
| 327 end); | |
| 328 | |
| 329 module:hook("s2sin-destroyed", function(event) | |
| 330 del_host(event.session, "in", module.host); | |
| 331 end); | |
| 332 end |
