Software /
code /
prosody-modules
Comparison
mod_restrict_xmpp/mod_restrict_xmpp.lua @ 5009:459a4001c1d9
mod_restrict_xmpp: XMPP-layer access control using Prosody's permissions API
author | Matthew Wild <mwild1@gmail.com> |
---|---|
date | Mon, 22 Aug 2022 20:03:23 +0100 |
child | 5010:a1f49586d28a |
comparison
equal
deleted
inserted
replaced
5008:bd63feda3704 | 5009:459a4001c1d9 |
---|---|
1 local array = require "util.array"; | |
2 local it = require "util.iterators"; | |
3 local set = require "util.set"; | |
4 local st = require "util.stanza"; | |
5 | |
6 module:default_permission("prosody:user", "xmpp:federate"); | |
7 module:hook("route/remote", function (event) | |
8 if not module:may("xmpp:federate", event) then | |
9 if event.stanza.attr.type ~= "result" and event.stanza.attr.type ~= "error" then | |
10 module:log("warn", "Access denied: xmpp:federate for %s -> %s", event.stanza.attr.from, event.stanza.attr.to); | |
11 local reply = st.error_reply(event.stanza, "auth", "forbidden"); | |
12 event.origin.send(reply); | |
13 end | |
14 return true; | |
15 end | |
16 end); | |
17 | |
18 local iq_namespaces = { | |
19 ["jabber:iq:roster"] = "contacts"; | |
20 ["jabber:iq:private"] = "storage"; | |
21 | |
22 ["vcard-temp"] = "profile"; | |
23 ["urn:xmpp:mam:0"] = "history"; | |
24 ["urn:xmpp:mam:1"] = "history"; | |
25 ["urn:xmpp:mam:2"] = "history"; | |
26 | |
27 ["urn:xmpp:carbons:0"] = "carbons"; | |
28 ["urn:xmpp:carbons:1"] = "carbons"; | |
29 ["urn:xmpp:carbons:2"] = "carbons"; | |
30 | |
31 ["urn:xmpp:blocking"] = "blocklist"; | |
32 | |
33 ["http://jabber.org/protocol/pubsub"] = "pep"; | |
34 ["http://jabber.org/protocol/disco#info"] = "disco"; | |
35 }; | |
36 | |
37 local legacy_storage_nodes = { | |
38 ["storage:bookmarks"] = "bookmarks"; | |
39 ["storage:rosternotes"] = "contacts"; | |
40 ["roster:delimiter"] = "contacts"; | |
41 ["storage:metacontacts"] = "contacts"; | |
42 }; | |
43 | |
44 local pep_nodes = { | |
45 ["storage:bookmarks"] = "bookmarks"; | |
46 ["urn:xmpp:bookmarks:1"] = "bookmarks"; | |
47 | |
48 ["urn:xmpp:avatar:data"] = "profile"; | |
49 ["urn:xmpp:avatar:metadata"] = "profile"; | |
50 ["http://jabber.org/protocol/nick"] = "profile"; | |
51 | |
52 ["eu.siacs.conversations.axolotl.devicelist"] = "omemo"; | |
53 ["urn:xmpp:omemo:1:devices"] = "omemo"; | |
54 ["urn:xmpp:omemo:1:bundles"] = "omemo"; | |
55 ["urn:xmpp:omemo:2:devices"] = "omemo"; | |
56 ["urn:xmpp:omemo:2:bundles"] = "omemo"; | |
57 }; | |
58 | |
59 module:hook("pre-iq/bare", function (event) | |
60 if not event.to_self then return; end | |
61 local origin, stanza = event.origin, event.stanza; | |
62 | |
63 local typ = stanza.attr.type; | |
64 if typ ~= "set" and typ ~= "get" then return; end | |
65 local action = typ == "get" and "read" or "write"; | |
66 | |
67 local payload = stanza.tags[1]; | |
68 local ns = payload and payload.attr.xmlns; | |
69 local proto = iq_namespaces[ns]; | |
70 if proto == "pep" then | |
71 local pubsub = payload:get_child("pubsub", "http://jabber.org/protocol/pubsub"); | |
72 local node = pubsub and #pubsub.tags == 1 and pubsub.tags[1].attr.node or nil; | |
73 proto = pep_nodes[node] or "pep"; | |
74 if proto == "pep" and node and node:match("^eu%.siacs%.conversations%.axolotl%.bundles%.%d+$") then | |
75 proto = "omemo"; -- COMPAT w/ original OMEMO | |
76 end | |
77 elseif proto == "storage" then | |
78 local data = payload.tags[1]; | |
79 proto = data and legacy_storage_nodes[data.attr.xmlns] or "legacy-storage"; | |
80 elseif proto == "carbons" then | |
81 -- This allows access to live messages | |
82 proto, action = "messages", "read"; | |
83 end | |
84 local permission_name = "xmpp:account:"..(proto and (proto..":") or "")..action; | |
85 if not module:may(permission_name, event) then | |
86 module:log("warn", "Access denied: %s ({%s}%s) for %s", permission_name, ns, payload.name, origin.full_jid or origin.id); | |
87 origin.send(st.error_reply(stanza, "auth", "forbidden", "You do not have permission to make this request ("..permission_name..")")); | |
88 return true; | |
89 end | |
90 end); | |
91 | |
92 --module:default_permission("prosody:restricted", "xmpp:account:read"); | |
93 --module:default_permission("prosody:restricted", "xmpp:account:write"); | |
94 module:default_permission("prosody:restricted", "xmpp:account:messages:read"); | |
95 module:default_permission("prosody:restricted", "xmpp:account:messages:write"); | |
96 for _, property_list in ipairs({ iq_namespaces, legacy_storage_nodes, pep_nodes }) do | |
97 for account_property in set.new(array.collect(it.values(property_list))) do | |
98 module:default_permission("prosody:restricted", "xmpp:account:"..account_property..":read"); | |
99 module:default_permission("prosody:restricted", "xmpp:account:"..account_property..":write"); | |
100 end | |
101 end | |
102 | |
103 module:default_permission("prosody:restricted", "xmpp:account:presence:write"); | |
104 module:hook("pre-presence/bare", function (event) | |
105 if not event.to_self then return; end | |
106 local stanza = event.stanza; | |
107 if not module:may("xmpp:account:presence:write", event) then | |
108 module:log("warn", "Access denied: xmpp:account:presence:write for %s", event.origin.full_jid or event.origin.id); | |
109 event.origin.send(st.error_reply(stanza, "auth", "forbidden", "You do not have permission to send account presence")); | |
110 return true; | |
111 end | |
112 local priority = stanza:get_child_text("priority"); | |
113 if priority ~= "-1" then | |
114 if not module:may("xmpp:account:messages:read", event) then | |
115 module:log("warn", "Access denied: xmpp:account:messages:read for %s", event.origin.full_jid or event.origin.id); | |
116 event.origin.send(st.error_reply(stanza, "auth", "forbidden", "You do not have permission to receive messages (use presence priority -1)")); | |
117 return true; | |
118 end | |
119 end | |
120 end); |