comparison mod_privilege/mod_privilege.lua @ 1660:d1072db4db44

mod_privilege: implemented roster set privilege
author Goffi <goffi@goffi.org>
date Thu, 02 Apr 2015 21:47:05 +0200
parents 495a093798eb
children 69aa2b54ba8a
comparison
equal deleted inserted replaced
1659:495a093798eb 1660:d1072db4db44
1 -- XEP-0356 (Privileged Entity) 1 -- XEP-0356 (Privileged Entity)
2 -- Copyright (C) 2015 Jérôme Poisson 2 -- Copyright (C) 2015 Jérôme Poisson
3 -- 3 --
4 -- This module is MIT/X11 licensed. Please see the 4 -- This module is MIT/X11 licensed. Please see the
5 -- COPYING file in the source package for more information. 5 -- COPYING file in the source package for more information.
6 --
7 -- Some parts come from mod_remote_roster (module by Waqas Hussain and Kim Alvefur, see https://code.google.com/p/prosody-modules/)
6 8
7 9
8 local jid = require("util/jid") 10 local jid = require("util/jid")
9 local set = require("util/set") 11 local set = require("util/set")
10 local st = require("util/stanza") 12 local st = require("util/stanza")
11 local roster_manager = require("core/rostermanager") 13 local roster_manager = require("core/rostermanager")
14 local user_manager = require("core/usermanager")
12 15
13 local _ALLOWED_ROSTER = set.new({'none', 'get', 'set', 'both'}) 16 local _ALLOWED_ROSTER = set.new({'none', 'get', 'set', 'both'})
14 local _ROSTER_GET_PERM = set.new({'get', 'both'}) 17 local _ROSTER_GET_PERM = set.new({'get', 'both'})
15 local _ROSTER_SET_PERM = set.new({'set', 'both'}) 18 local _ROSTER_SET_PERM = set.new({'set', 'both'})
16 local _ALLOWED_MESSAGE = set.new({'none', 'outgoing'}) 19 local _ALLOWED_MESSAGE = set.new({'none', 'outgoing'})
28 function advertise_perm(to_jid, perms) 31 function advertise_perm(to_jid, perms)
29 -- send <message/> stanza to advertise permissions 32 -- send <message/> stanza to advertise permissions
30 -- as expained in section 4.2 33 -- as expained in section 4.2
31 local message = st.message({to=to_jid}) 34 local message = st.message({to=to_jid})
32 :tag("privilege", {xmlns=_PRIV_ENT_NS}) 35 :tag("privilege", {xmlns=_PRIV_ENT_NS})
33 36
34 for _, perm in pairs({'roster', 'message', 'presence'}) do 37 for _, perm in pairs({'roster', 'message', 'presence'}) do
35 if perms[perm] then 38 if perms[perm] then
36 message:tag("perm", {access=perm, type=perms[perm]}):up() 39 message:tag("perm", {access=perm, type=perms[perm]}):up()
37 end 40 end
38 end 41 end
39 42
40 module:send(message) 43 module:send(message)
41 end 44 end
42 45
43 function on_auth(event) 46 function on_auth(event)
44 -- Check if entity is privileged according to configuration, 47 -- Check if entity is privileged according to configuration,
45 -- and set session.privileges accordingly 48 -- and set session.privileges accordingly
46 49
47 local session = event.session 50 local session = event.session
48 local bare_jid = jid.join(session.username, session.host) 51 local bare_jid = jid.join(session.username, session.host)
49 52
50 local ent_priv = privileges[bare_jid] 53 local ent_priv = privileges[bare_jid]
51 if ent_priv ~= nil then 54 if ent_priv ~= nil then
87 module:hook('presence/initial', on_presence) 90 module:hook('presence/initial', on_presence)
88 91
89 92
90 --> roster permission <-- 93 --> roster permission <--
91 94
95 -- get
92 module:hook("iq-get/bare/jabber:iq:roster:query", function(event) 96 module:hook("iq-get/bare/jabber:iq:roster:query", function(event)
93 local session, stanza = event.origin, event.stanza; 97 local session, stanza = event.origin, event.stanza;
94 if not stanza.attr.to then 98 if not stanza.attr.to then
95 -- we don't want stanzas addressed to /self 99 -- we don't want stanzas addressed to /self
96 return; 100 return;
97 end 101 end
98 102
99 if session.privileges and _ROSTER_GET_PERM:contains(session.privileges.roster) then 103 if session.privileges and _ROSTER_GET_PERM:contains(session.privileges.roster) then
100 module:log("debug", "Roster get from allowed privileged entity received") 104 module:log("debug", "Roster get from allowed privileged entity received")
101 -- following code is adapted from mod_remote_roster 105 -- following code is adapted from mod_remote_roster
102 local node, host = jid.split(stanza.attr.to); 106 local node, host = jid.split(stanza.attr.to);
103 local roster = roster_manager.load_roster(node, host); 107 local roster = roster_manager.load_roster(node, host);
104 108
105 local reply = st.reply(stanza):query("jabber:iq:roster"); 109 local reply = st.reply(stanza):query("jabber:iq:roster");
106 for entity_jid, item in pairs(roster) do 110 for entity_jid, item in pairs(roster) do
107 if entity_jid and entity_jid ~= "pending" then 111 if entity_jid and entity_jid ~= "pending" then
108 local node, host = jid.split(entity_jid); 112 local node, host = jid.split(entity_jid);
109 reply:tag("item", { 113 reply:tag("item", {
116 reply:tag("group"):text(group):up(); 120 reply:tag("group"):text(group):up();
117 end 121 end
118 reply:up(); -- move out from item 122 reply:up(); -- move out from item
119 end 123 end
120 end 124 end
125 -- end of code adapted from mod_remote_roster
121 session.send(reply); 126 session.send(reply);
122 else 127 else
123 module:log("warn", "Entity "..tostring(session.full_jid).." try to get roster without permission") 128 module:log("warn", "Entity "..tostring(session.full_jid).." try to get roster without permission")
124 session.send(st.error_reply(stanza, 'auth', 'forbidden')) 129 session.send(st.error_reply(stanza, 'auth', 'forbidden'))
125 end 130 end
126 131
127 return true 132 return true
128
129 end); 133 end);
134
135 -- set
136 module:hook("iq-set/bare/jabber:iq:roster:query", function(event)
137 local session, stanza = event.origin, event.stanza;
138 if not stanza.attr.to then
139 -- we don't want stanzas addressed to /self
140 return;
141 end
142
143 if session.privileges and _ROSTER_SET_PERM:contains(session.privileges.roster) then
144 module:log("debug", "Roster set from allowed privileged entity received")
145 -- following code is adapted from mod_remote_roster
146 local from_node, from_host = jid.split(stanza.attr.to);
147 if not(user_manager.user_exists(from_node, from_host)) then return; end
148 local roster = roster_manager.load_roster(from_node, from_host);
149 if not(roster) then return; end
150
151 local query = stanza.tags[1];
152 for i_, item in ipairs(query.tags) do
153 if item.name == "item"
154 and item.attr.xmlns == "jabber:iq:roster" and item.attr.jid
155 -- Protection against overwriting roster.pending, until we move it
156 and item.attr.jid ~= "pending" then
157
158 local item_jid = jid.prep(item.attr.jid);
159 local node, host, resource = jid.split(item_jid);
160 if not resource then
161 if item_jid ~= stanza.attr.to then -- not self-item_jid
162 if item.attr.subscription == "remove" then
163 local r_item = roster[item_jid];
164 if r_item then
165 local to_bare = node and (node.."@"..host) or host; -- bare jid
166 roster[item_jid] = nil;
167 if roster_manager.save_roster(from_node, from_host, roster) then
168 session.send(st.reply(stanza));
169 roster_manager.roster_push(from_node, from_host, item_jid);
170 else
171 roster[item_jid] = item;
172 session.send(st.error_reply(stanza, "wait", "internal-server-error", "Unable to save roster"));
173 end
174 else
175 session.send(st.error_reply(stanza, "modify", "item-not-found"));
176 end
177 else
178 local subscription = item.attr.subscription;
179 if subscription ~= "both" and subscription ~= "to" and subscription ~= "from" and subscription ~= "none" then -- TODO error on invalid
180 subscription = roster[item_jid] and roster[item_jid].subscription or "none";
181 end
182 local r_item = {name = item.attr.name, groups = {}};
183 if r_item.name == "" then r_item.name = nil; end
184 r_item.subscription = subscription;
185 if subscription ~= "both" and subscription ~= "to" then
186 r_item.ask = roster[item_jid] and roster[item_jid].ask;
187 end
188 for _, child in ipairs(item) do
189 if child.name == "group" then
190 local text = table.concat(child);
191 if text and text ~= "" then
192 r_item.groups[text] = true;
193 end
194 end
195 end
196 local olditem = roster[item_jid];
197 roster[item_jid] = r_item;
198 if roster_manager.save_roster(from_node, from_host, roster) then -- Ok, send success
199 session.send(st.reply(stanza));
200 -- and push change to all resources
201 roster_manager.roster_push(from_node, from_host, item_jid);
202 else -- Adding to roster failed
203 roster[item_jid] = olditem;
204 session.send(st.error_reply(stanza, "wait", "internal-server-error", "Unable to save roster"));
205 end
206 end
207 else -- Trying to add self to roster
208 session.send(st.error_reply(stanza, "cancel", "not-allowed"));
209 end
210 else -- Invalid JID added to roster
211 module:log("warn", "resource: %s , host: %s", tostring(resource), tostring(host))
212 session.send(st.error_reply(stanza, "modify", "bad-request")); -- FIXME what's the correct error?
213 end
214 else -- Roster set didn't include a single item, or its name wasn't 'item'
215 session.send(st.error_reply(stanza, "modify", "bad-request"));
216 end
217 end -- for loop end
218 -- end of code adapted from mod_remote_roster
219 else -- The permission is not granted
220 module:log("warn", "Entity "..tostring(session.full_jid).." try to set roster without permission")
221 session.send(st.error_reply(stanza, 'auth', 'forbidden'))
222 end
223
224 return true
225 end);