Mercurial > prosody-modules
view mod_admin_web/admin_web/mod_admin_web.lua @ 5461:06640647d193
mod_http_oauth2: Fix use of arbitrary ports in loopback redirect URIs
Per draft-ietf-oauth-v2-1-08#section-8.4.2
> The authorization server MUST allow any port to be specified at the
> time of the request for loopback IP redirect URIs, to accommodate
> clients that obtain an available ephemeral port from the operating
> system at the time of the request.
Uncertain if it should normalize the host part, but it also seems
harmless to treat IPv6 and IPv4 the same here.
One thing is that "localhost" is NOT RECOMMENDED because it can
sometimes be pointed to non-loopback interfaces via DNS or hosts file.
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Wed, 17 May 2023 13:51:30 +0200 |
parents | 2825fa4504f0 |
children |
line wrap: on
line source
-- Copyright (C) 2010 Florian Zeitz -- -- This file is MIT/X11 licensed. Please see the -- COPYING file in the source package for more information. -- -- <session xmlns="http://prosody.im/streams/c2s" jid="alice@example.com/brussels"> -- <encrypted/> -- <compressed/> -- </session> -- <session xmlns="http://prosody.im/streams/s2s" jid="example.com"> -- <encrypted> -- <valid/> / <invalid/> -- </encrypted> -- <compressed/> -- <in/> / <out/> -- </session> local st = require "util.stanza"; local uuid_generate = require "util.uuid".generate; local is_admin = require "core.usermanager".is_admin; local pubsub = require "util.pubsub"; local jid_bare = require "util.jid".bare; local hosts = prosody.hosts; local incoming_s2s = prosody.incoming_s2s; module:set_global(); local service = {}; local xmlns_adminsub = "http://prosody.im/adminsub"; local xmlns_c2s_session = "http://prosody.im/streams/c2s"; local xmlns_s2s_session = "http://prosody.im/streams/s2s"; local idmap = {}; local function add_client(session, host) local name = session.full_jid; local id = idmap[name]; if not id then id = uuid_generate(); idmap[name] = id; end local item = st.stanza("item", { id = id }):tag("session", {xmlns = xmlns_c2s_session, jid = name}):up(); if session.secure then local encrypted = item:tag("encrypted"); local sock = session.conn and session.conn.socket and session.conn:socket() local info = sock and sock.info and sock:info(); for k, v in pairs(info or {}) do encrypted:tag("info", { name = k }):text(tostring(v)):up(); end end if session.compressed then item:tag("compressed"):up(); end service[host]:publish(xmlns_c2s_session, host, id, item); module:log("debug", "Added client %s", name); end local function del_client(session, host) local name = session.full_jid; local id = idmap[name]; if id then local notifier = st.stanza("retract", { id = id }); service[host]:retract(xmlns_c2s_session, host, id, notifier); end end local function add_host(session, type, host) local name = (type == "out" and session.to_host) or (type == "in" and session.from_host); local id = idmap[name.."_"..type]; if not id then id = uuid_generate(); idmap[name.."_"..type] = id; end local item = st.stanza("item", { id = id }):tag("session", {xmlns = xmlns_s2s_session, jid = name}) :tag(type):up(); if session.secure then local encrypted = item:tag("encrypted"); local sock = session.conn and session.conn.socket and session.conn:socket() local info = sock and sock.info and sock:info(); for k, v in pairs(info or {}) do encrypted:tag("info", { name = k }):text(tostring(v)):up(); end if session.cert_identity_status == "valid" then encrypted:tag("valid"); else encrypted:tag("invalid"); end end if session.compressed then item:tag("compressed"):up(); end service[host]:publish(xmlns_s2s_session, host, id, item); module:log("debug", "Added host %s s2s%s", name, type); end local function del_host(session, type, host) local name = (type == "out" and session.to_host) or (type == "in" and session.from_host); local id = idmap[name.."_"..type]; if id then local notifier = st.stanza("retract", { id = id }); service[host]:retract(xmlns_s2s_session, host, id, notifier); end end local function get_affiliation(jid, host) local bare_jid = jid_bare(jid); if is_admin(bare_jid, host) then return "member"; else return "none"; end end function module.add_host(module) -- Dependencies module:depends("bosh"); module:depends("admin_adhoc"); module:depends("http"); local serve; if not pcall(function () local http_files = require "net.http.files"; serve = http_files.serve; end) then serve = module:depends"http_files".serve; end local serve_file = serve { path = module:get_directory() .. "/www_files"; }; -- Setup HTTP server module:provides("http", { title = "Admin Interface"; name = "admin"; route = { ["GET"] = function(event) event.response.headers.location = event.request.path .. "/"; return 301; end; ["GET /*"] = serve_file; } }); -- Setup adminsub service local function simple_broadcast(kind, node, jids, item) if item then item = st.clone(item); item.attr.xmlns = nil; -- Clear the pubsub namespace end local message = st.message({ from = module.host, type = "headline" }) :tag("event", { xmlns = xmlns_adminsub .. "#event" }) :tag(kind, { node = node }) :add_child(item); for jid in pairs(jids) do module:log("debug", "Sending notification to %s", jid); message.attr.to = jid; module:send(message); end end service[module.host] = pubsub.new({ broadcaster = simple_broadcast; normalize_jid = jid_bare; get_affiliation = function(jid) return get_affiliation(jid, module.host) end; capabilities = { member = { create = false; publish = false; retract = false; get_nodes = true; subscribe = true; unsubscribe = true; get_subscription = true; get_subscriptions = true; get_items = true; subscribe_other = false; unsubscribe_other = false; get_subscription_other = false; get_subscriptions_other = false; be_subscribed = true; be_unsubscribed = true; set_affiliation = false; }; owner = { create = true; publish = true; retract = true; get_nodes = true; subscribe = true; unsubscribe = true; get_subscription = true; get_subscriptions = true; get_items = true; subscribe_other = true; unsubscribe_other = true; get_subscription_other = true; get_subscriptions_other = true; be_subscribed = true; be_unsubscribed = true; set_affiliation = true; }; }; }); -- Create node for s2s sessions local ok, err = service[module.host]:create(xmlns_s2s_session, true); if not ok then module:log("warn", "Could not create node %s: %s", xmlns_s2s_session, err); else service[module.host]:set_affiliation(xmlns_s2s_session, true, module.host, "owner") end -- Add outgoing s2s sessions for _, session in pairs(hosts[module.host].s2sout) do if session.type ~= "s2sout_unauthed" then add_host(session, "out", module.host); end end -- Add incoming s2s sessions for session in pairs(incoming_s2s) do if session.to_host == module.host then add_host(session, "in", module.host); end end -- Create node for c2s sessions ok, err = service[module.host]:create(xmlns_c2s_session, true); if not ok then module:log("warn", "Could not create node %s: %s", xmlns_c2s_session, tostring(err)); else service[module.host]:set_affiliation(xmlns_c2s_session, true, module.host, "owner") end -- Add c2s sessions for _, user in pairs(hosts[module.host].sessions or {}) do for _, session in pairs(user.sessions or {}) do add_client(session, module.host); end end -- Register adminsub handler module:hook("iq/host/http://prosody.im/adminsub:adminsub", function(event) -- luacheck: ignore 431/ok local origin, stanza = event.origin, event.stanza; local adminsub = stanza.tags[1]; local action = adminsub.tags[1]; local reply; if action.name == "subscribe" then local ok, ret = service[module.host]:add_subscription(action.attr.node, stanza.attr.from, stanza.attr.from); if ok then reply = st.reply(stanza) :tag("adminsub", { xmlns = xmlns_adminsub }); else reply = st.error_reply(stanza, "cancel", ret); end elseif action.name == "unsubscribe" then local ok, ret = service[module.host]:remove_subscription(action.attr.node, stanza.attr.from, stanza.attr.from); if ok then reply = st.reply(stanza) :tag("adminsub", { xmlns = xmlns_adminsub }); else reply = st.error_reply(stanza, "cancel", ret); end elseif action.name == "items" then local node = action.attr.node; local ok, ret = service[module.host]:get_items(node, stanza.attr.from); if not ok then origin.send(st.error_reply(stanza, "cancel", ret)); return true; end local data = st.stanza("items", { node = node }); for _, entry in pairs(ret) do data:add_child(entry); end if data then reply = st.reply(stanza) :tag("adminsub", { xmlns = xmlns_adminsub }) :add_child(data); else reply = st.error_reply(stanza, "cancel", "item-not-found"); end elseif action.name == "adminfor" then local data = st.stanza("adminfor"); for host_name in pairs(hosts) do if is_admin(stanza.attr.from, host_name) then data:tag("item"):text(host_name):up(); end end reply = st.reply(stanza) :tag("adminsub", { xmlns = xmlns_adminsub }) :add_child(data); else reply = st.error_reply(stanza, "feature-not-implemented"); end origin.send(reply); return true; end); -- Add/remove c2s sessions module:hook("resource-bind", function(event) add_client(event.session, module.host); end); module:hook("resource-unbind", function(event) del_client(event.session, module.host); service[module.host]:remove_subscription(xmlns_c2s_session, module.host, event.session.full_jid); service[module.host]:remove_subscription(xmlns_s2s_session, module.host, event.session.full_jid); end); -- Add/remove s2s sessions module:hook("s2sout-established", function(event) add_host(event.session, "out", module.host); end); module:hook("s2sin-established", function(event) add_host(event.session, "in", module.host); end); module:hook("s2sout-destroyed", function(event) del_host(event.session, "out", module.host); end); module:hook("s2sin-destroyed", function(event) del_host(event.session, "in", module.host); end); end