Bumper commit for the new modulemanager API \o/ Updates all the modules, though some...
[prosody.git] / core / rostermanager.lua
1
2
3 local log = require "util.logger".init("rostermanager");
4
5 local setmetatable = setmetatable;
6 local format = string.format;
7 local loadfile, setfenv, pcall = loadfile, setfenv, pcall;
8 local pairs, ipairs = pairs, ipairs;
9
10 local hosts = hosts;
11
12 local datamanager = require "util.datamanager"
13 local st = require "util.stanza";
14
15 module "rostermanager"
16
17 function add_to_roster(session, jid, item)
18         if session.roster then
19                 local old_item = session.roster[jid];
20                 session.roster[jid] = item;
21                 if save_roster(session.username, session.host) then
22                         return true;
23                 else
24                         session.roster[jid] = old_item;
25                         return nil, "wait", "internal-server-error", "Unable to save roster";
26                 end
27         else
28                 return nil, "auth", "not-authorized", "Session's roster not loaded";
29         end
30 end
31
32 function remove_from_roster(session, jid)
33         if session.roster then
34                 local old_item = session.roster[jid];
35                 session.roster[jid] = nil;
36                 if save_roster(session.username, session.host) then
37                         return true;
38                 else
39                         session.roster[jid] = old_item;
40                         return nil, "wait", "internal-server-error", "Unable to save roster";
41                 end
42         else
43                 return nil, "auth", "not-authorized", "Session's roster not loaded";
44         end
45 end
46
47 function roster_push(username, host, jid)
48         if jid ~= "pending" and hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster then
49                 local item = hosts[host].sessions[username].roster[jid];
50                 local stanza = st.iq({type="set"});
51                 stanza:tag("query", {xmlns = "jabber:iq:roster"});
52                 if item then
53                         stanza:tag("item", {jid = jid, subscription = item.subscription, name = item.name, ask = item.ask});
54                         for group in pairs(item.groups) do
55                                 stanza:tag("group"):text(group):up();
56                         end
57                 else
58                         stanza:tag("item", {jid = jid, subscription = "remove"});
59                 end
60                 stanza:up(); -- move out from item
61                 stanza:up(); -- move out from stanza
62                 -- stanza ready
63                 for _, session in pairs(hosts[host].sessions[username].sessions) do
64                         if session.interested then
65                                 -- FIXME do we need to set stanza.attr.to?
66                                 session.send(stanza);
67                         end
68                 end
69         end
70 end
71
72 function load_roster(username, host)
73         log("debug", "load_roster: asked for: "..username.."@"..host);
74         if hosts[host] and hosts[host].sessions[username] then
75                 local roster = hosts[host].sessions[username].roster;
76                 if not roster then
77                         log("debug", "load_roster: loading for new user: "..username.."@"..host);
78                         roster = datamanager.load(username, host, "roster") or {};
79                         hosts[host].sessions[username].roster = roster;
80                 end
81                 return roster;
82         end
83         -- Attempt to load roster for non-loaded user
84         log("debug", "load_roster: loading for offline user: "..username.."@"..host);
85         return datamanager.load(username, host, "roster") or {};
86 end
87
88 function save_roster(username, host)
89         log("debug", "save_roster: saving roster for "..username.."@"..host);
90         if hosts[host] and hosts[host].sessions[username] and hosts[host].sessions[username].roster then
91                 return datamanager.store(username, host, "roster", hosts[host].sessions[username].roster);
92         end
93         return nil;
94 end
95
96 function process_inbound_subscription_approval(username, host, jid)
97         local roster = load_roster(username, host);
98         local item = roster[jid];
99         if item and item.ask then
100                 if item.subscription == "none" then
101                         item.subscription = "to";
102                 else -- subscription == from
103                         item.subscription = "both";
104                 end
105                 item.ask = nil;
106                 return datamanager.store(username, host, "roster", roster);
107         end
108 end
109
110 function process_inbound_subscription_cancellation(username, host, jid)
111         local roster = load_roster(username, host);
112         local item = roster[jid];
113         local changed = nil;
114         if is_contact_pending_out(username, host, jid) then
115                 item.ask = nil;
116                 changed = true;
117         end
118         if item then
119                 if item.subscription == "to" then
120                         item.subscription = "none";
121                         changed = true;
122                 elseif item.subscription == "both" then
123                         item.subscription = "from";
124                         changed = true;
125                 end
126         end
127         if changed then
128                 return datamanager.store(username, host, "roster", roster);
129         end
130 end
131
132 function process_inbound_unsubscribe(username, host, jid)
133         local roster = load_roster(username, host);
134         local item = roster[jid];
135         local changed = nil;
136         if is_contact_pending_in(username, host, jid) then
137                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
138                 changed = true;
139         end
140         if item then
141                 if item.subscription == "from" then
142                         item.subscription = "none";
143                         changed = true;
144                 elseif item.subscription == "both" then
145                         item.subscription = "to";
146                         changed = true;
147                 end
148         end
149         if changed then
150                 return datamanager.store(username, host, "roster", roster);
151         end
152 end
153
154 function is_contact_subscribed(username, host, jid)
155         local roster = load_roster(username, host);
156         local item = roster[jid];
157         return item and (item.subscription == "from" or item.subscription == "both");
158 end
159
160 function is_contact_pending_in(username, host, jid)
161         local roster = load_roster(username, host);
162         return roster.pending and roster.pending[jid];
163 end
164 function set_contact_pending_in(username, host, jid, pending)
165         local roster = load_roster(username, host);
166         local item = roster[jid];
167         if item and (item.subscription == "from" or item.subscription == "both") then
168                 return; -- false
169         end
170         if not roster.pending then roster.pending = {}; end
171         roster.pending[jid] = true;
172         return datamanager.store(username, host, "roster", roster);
173 end
174 function is_contact_pending_out(username, host, jid)
175         local roster = load_roster(username, host);
176         local item = roster[jid];
177         return item and item.ask;
178 end
179 function set_contact_pending_out(username, host, jid) -- subscribe
180         local roster = load_roster(username, host);
181         local item = roster[jid];
182         if item and (item.ask or item.subscription == "to" or item.subscription == "both") then
183                 return true;
184         end
185         if not item then
186                 item = {subscription = "none", groups = {}};
187                 roster[jid] = item;
188         end
189         item.ask = "subscribe";
190         log("debug", "set_contact_pending_out: saving roster; set "..username.."@"..host..".roster["..jid.."].ask=subscribe");
191         return datamanager.store(username, host, "roster", roster);
192 end
193 function unsubscribe(username, host, jid)
194         local roster = load_roster(username, host);
195         local item = roster[jid];
196         if not item then return false; end
197         if (item.subscription == "from" or item.subscription == "none") and not item.ask then
198                 return true;
199         end
200         item.ask = nil;
201         if item.subscription == "both" then
202                 item.subscription = "from";
203         elseif item.subscription == "to" then
204                 item.subscription = "none";
205         end
206         return datamanager.store(username, host, "roster", roster);
207 end
208 function subscribed(username, host, jid)
209         if is_contact_pending_in(username, host, jid) then
210                 local roster = load_roster(username, host);
211                 local item = roster[jid];
212                 if item.subscription == "none" then
213                         item.subscription = "from";
214                 else -- subscription == to
215                         item.subscription = "both";
216                 end
217                 roster.pending[jid] = nil;
218                 -- TODO maybe remove roster.pending if empty
219                 return datamanager.store(username, host, "roster", roster);
220         end -- TODO else implement optional feature pre-approval (ask = subscribed)
221 end
222 function unsubscribed(username, host, jid)
223         local roster = load_roster(username, host);
224         local item = roster[jid];
225         local pending = is_contact_pending_in(username, host, jid);
226         local changed = nil;
227         if is_contact_pending_in(username, host, jid) then
228                 roster.pending[jid] = nil; -- TODO maybe delete roster.pending if empty?
229                 changed = true;
230         end
231         if item then
232                 if item.subscription == "from" then
233                         item.subscription = "none";
234                         changed = true;
235                 elseif item.subscription == "both" then
236                         item.subscription = "to";
237                         changed = true;
238                 end
239         end
240         if changed then
241                 return datamanager.store(username, host, "roster", roster);
242         end
243 end
244
245 function process_outbound_subscription_request(username, host, jid)
246         local roster = load_roster(username, host);
247         local item = roster[jid];
248         if item and (item.subscription == "none" or item.subscription == "from") then
249                 item.ask = "subscribe";
250                 return datamanager.store(username, host, "roster", roster);
251         end
252 end
253
254 --[[function process_outbound_subscription_approval(username, host, jid)
255         local roster = load_roster(username, host);
256         local item = roster[jid];
257         if item and (item.subscription == "none" or item.subscription == "from" then
258                 item.ask = "subscribe";
259                 return datamanager.store(username, host, "roster", roster);
260         end
261 end]]
262
263
264
265 return _M;