Mercurial > prosody-modules
view mod_csi_battery_saver/mod_csi_battery_saver.lua @ 5193:2bb29ece216b
mod_http_oauth2: Implement stateless dynamic client registration
Replaces previous explicit registration that required either the
additional module mod_adhoc_oauth2_client or manually editing the
database. That method was enough to have something to test with, but
would not probably not scale easily.
Dynamic client registration allows creating clients on the fly, which
may be even easier in theory.
In order to not allow basically unauthenticated writes to the database,
we implement a stateless model here.
per_host_key := HMAC(config -> oauth2_registration_key, hostname)
client_id := JWT { client metadata } signed with per_host_key
client_secret := HMAC(per_host_key, client_id)
This should ensure everything we need to know is part of the client_id,
allowing redirects etc to be validated, and the client_secret can be
validated with only the client_id and the per_host_key.
A nonce injected into the client_id JWT should ensure nobody can submit
the same client metadata and retrieve the same client_secret
author | Kim Alvefur <zash@zash.se> |
---|---|
date | Fri, 03 Mar 2023 21:14:19 +0100 |
parents | 889e1695e935 |
children | f6a2602129c8 |
line wrap: on
line source
-- Copyright (C) 2016 Kim Alvefur -- Copyright (C) 2017 Thilo Molitor -- local filter_muc = module:get_option_boolean("csi_battery_saver_filter_muc", false); local queue_size = module:get_option_number("csi_battery_saver_queue_size", 256); module:depends"csi" if filter_muc then module:depends"track_muc_joins"; end -- only depend on this module if we actually use it local s_match = string.match; local s_sub = string.sub; local jid = require "util.jid"; local new_queue = require "util.queue".new; local datetime = require "util.datetime"; local st = require "util.stanza"; local xmlns_delay = "urn:xmpp:delay"; -- a log id for this module instance local id = s_sub(require "util.hashes".sha256(datetime.datetime(), true), 1, 4); -- Returns a forwarded message, and either "in" or "out" depending on the direction -- Returns nil if the message is not a carbon local function extract_carbon(stanza) local carbon = stanza:child_with_ns("urn:xmpp:carbons:2") or stanza:child_with_ns("urn:xmpp:carbons:1"); if not carbon then return; end local direction = carbon.name == "sent" and "out" or "in"; local forward = carbon:get_child("forwarded", "urn:xmpp:forward:0"); local message = forward and forward:child_with_name("message") or nil; if not message then return; end return message, direction; end local function new_pump(session, output, ...) -- luacheck: ignore 212/self local q = new_queue(...); local flush = true; function q:pause() flush = false; end function q:resume() flush = true; return q:flush(); end local push = q.push; function q:push(item) local ok = push(self, item); if not ok then session.log("debug", "mod_csi_battery_saver(%s): Queue full (%d items), forcing flush...", id, q:count()); q:flush(); output(item, self); elseif flush then return q:flush(); end return true; end function q:flush(alternative_output) local out = alternative_output or output; local item = self:pop(); while item do out(item, self); item = self:pop(); end return true; end return q; end local function is_stamp_needed(stanza, session) local st_name = stanza and stanza.name or nil; if st_name == "presence" then return true; elseif st_name == "message" then if stanza:get_child("delay", xmlns_delay) then return false; end if stanza.attr.type == "chat" or stanza.attr.type == "groupchat" then return true; end end return false; end local function add_stamp(stanza, session) local bare_jid = jid.bare(session.full_jid or session.host); stanza = stanza:tag("delay", { xmlns = xmlns_delay, from = bare_jid, stamp = datetime.datetime()}); return stanza; end local function is_important(stanza, session) local st_name = stanza and stanza.name or nil; if not st_name then return true; end -- nonzas are always important if st_name == "presence" then -- TODO check for MUC status codes? return false; elseif st_name == "message" then -- unpack carbon copies local carbon, stanza_direction = extract_carbon(stanza); --session.log("debug", "mod_csi_battery_saver(%s): stanza_direction = %s, carbon = %s, stanza = %s", id, stanza_direction, carbon and "true" or "false", tostring(stanza)); if carbon then stanza = carbon; end local st_type = stanza.attr.type; -- headline message are always not important if st_type == "headline" then return false; end -- chat markers (XEP-0333) are important, too, because some clients use them to update their notifications if stanza:child_with_ns("urn:xmpp:chat-markers:0") then return true; end; -- XEP-0353: Jingle Message Initiation incoming call messages if stanza:child_with_ns("urn:xmpp:jingle-message:0") then return true; end if stanza:child_with_ns("urn:xmpp:jingle-message:1") then return true; end -- carbon copied outgoing messages are important (some clients update their notifications upon receiving those) --> don't return false here --if carbon and stanza_direction == "out" then return false; end -- We can't check for body contents in encrypted messages, so let's treat them as important -- Some clients don't even set a body or an empty body for encrypted messages -- check omemo https://xmpp.org/extensions/inbox/omemo.html if stanza:get_child("encrypted", "eu.siacs.conversations.axolotl") or stanza:get_child("encrypted", "urn:xmpp:omemo:0") then return true; end -- check xep27 pgp https://xmpp.org/extensions/xep-0027.html if stanza:get_child("x", "jabber:x:encrypted") then return true; end -- check xep373 pgp (OX) https://xmpp.org/extensions/xep-0373.html if stanza:get_child("openpgp", "urn:xmpp:openpgp:0") then return true; end -- check eme if stanza:get_child("encryption", "urn:xmpp:eme:0") then return true; end local body = stanza:get_child_text("body"); if st_type == "groupchat" then if stanza:get_child_text("subject") then return true; end if body == nil or body == "" then return false; end -- body contains text, let's see if we want to process it further if not filter_muc then -- default case local stanza_important = module:fire_event("csi-is-stanza-important", { stanza = stanza, session = session }); if stanza_important ~= nil then return stanza_important; end return true; -- deemed unknown/high priority by mod_csi_muc_priorities or some other module else if body:find(session.username, 1, true) then return true; end local rooms = session.rooms_joined; if not rooms then return false; end local room_nick = rooms[jid.bare(stanza_direction == "in" and stanza.attr.from or stanza.attr.to)]; if room_nick and body:find(room_nick, 1, true) then return true; end return false; end end return body ~= nil and body ~= ""; end return true; end module:hook("csi-client-inactive", function (event) local session = event.origin; if not session.resource then session.log("warn", "Ignoring csi if no resource is bound!"); return; end if session.pump then session.log("debug", "mod_csi_battery_saver(%s): Client is inactive, buffering unimportant outgoing stanzas", id); session.pump:pause(); else session.log("debug", "mod_csi_battery_saver(%s): Client is inactive the first time, initializing module for this session", id); local pump = new_pump(session, session.send, queue_size); pump:pause(); session.pump = pump; session._pump_orig_send = session.send; function session.send(stanza) session.log("debug", "mod_csi_battery_saver(%s): Got outgoing stanza: <%s>", id, tostring(stanza.name or stanza)); local important = is_important(stanza, session); -- clone stanzas before adding delay stamp and putting them into the queue if st.is_stanza(stanza) then stanza = st.clone(stanza); end -- add delay stamp to unimportant (buffered) stanzas that can/need be stamped if not important and is_stamp_needed(stanza, session) then stanza = add_stamp(stanza, session); end -- add stanza to outgoing queue and flush the buffer if needed pump:push(stanza); if important then session.log("debug", "mod_csi_battery_saver(%s): Encountered important stanza, flushing buffer: <%s>", id, tostring(stanza.name or stanza)); pump:flush(); end return true; end end end); module:hook("csi-client-active", function (event) local session = event.origin; if not session.resource then session.log("warn", "Ignoring csi if no resource is bound!"); return; end if session.pump then session.log("debug", "mod_csi_battery_saver(%s): Client is active, resuming direct delivery", id); session.pump:resume(); end end); -- clean up this session on hibernation end -- but don't change resumed.send(), it is already overwritten with session.send() by the smacks module module:hook("smacks-hibernation-end", function (event) local session = event.resumed; if session.pump then session.log("debug", "mod_csi_battery_saver(%s): Hibernation ended, flushing buffer and afterwards disabling for this session", id); session.pump:flush(session.send); -- use the fresh session.send() introduced by the smacks resume -- don't reset session.send() because this is not the send previously overwritten by this module, but a fresh one -- session.send = session._pump_orig_send; session.pump = nil; session._pump_orig_send = nil; end end, 1000); -- high priority to prevent message reordering on resumption (we want to flush our buffers *first*) function module.unload() module:log("info", "%s: Unloading module, flushing all buffers", id); local host_sessions = prosody.hosts[module.host].sessions; for _, user in pairs(host_sessions) do for _, session in pairs(user.sessions) do if session.pump then session.log("debug", "mod_csi_battery_saver(%s): Flushing buffer and restoring to original session.send()", id); session.pump:flush(); session.send = session._pump_orig_send; session.pump = nil; session._pump_orig_send = nil; end end end end module:log("info", "%s: Successfully loaded module", id);