Merge 0.9->0.10
[prosody.git] / plugins / mod_pubsub / mod_pubsub.lua
1 local pubsub = require "util.pubsub";
2 local st = require "util.stanza";
3 local jid_bare = require "util.jid".bare;
4 local usermanager = require "core.usermanager";
5
6 local xmlns_pubsub = "http://jabber.org/protocol/pubsub";
7 local xmlns_pubsub_event = "http://jabber.org/protocol/pubsub#event";
8 local xmlns_pubsub_owner = "http://jabber.org/protocol/pubsub#owner";
9
10 local autocreate_on_publish = module:get_option_boolean("autocreate_on_publish", false);
11 local autocreate_on_subscribe = module:get_option_boolean("autocreate_on_subscribe", false);
12 local pubsub_disco_name = module:get_option("name");
13 if type(pubsub_disco_name) ~= "string" then pubsub_disco_name = "Prosody PubSub Service"; end
14
15 local service;
16
17 local lib_pubsub = module:require "pubsub";
18 local handlers = lib_pubsub.handlers;
19 local pubsub_error_reply = lib_pubsub.pubsub_error_reply;
20
21 module:depends("disco");
22 module:add_identity("pubsub", "service", pubsub_disco_name);
23 module:add_feature("http://jabber.org/protocol/pubsub");
24
25 function handle_pubsub_iq(event)
26         local origin, stanza = event.origin, event.stanza;
27         local pubsub = stanza.tags[1];
28         local action = pubsub.tags[1];
29         if not action then
30                 return origin.send(st.error_reply(stanza, "cancel", "bad-request"));
31         end
32         local handler = handlers[stanza.attr.type.."_"..action.name];
33         if handler then
34                 handler(origin, stanza, action, service);
35                 return true;
36         end
37 end
38
39 function simple_broadcast(kind, node, jids, item)
40         if item then
41                 item = st.clone(item);
42                 item.attr.xmlns = nil; -- Clear the pubsub namespace
43         end
44         local message = st.message({ from = module.host, type = "headline" })
45                 :tag("event", { xmlns = xmlns_pubsub_event })
46                         :tag(kind, { node = node })
47                                 :add_child(item);
48         for jid in pairs(jids) do
49                 module:log("debug", "Sending notification to %s", jid);
50                 message.attr.to = jid;
51                 module:send(message);
52         end
53 end
54
55 module:hook("iq/host/"..xmlns_pubsub..":pubsub", handle_pubsub_iq);
56 module:hook("iq/host/"..xmlns_pubsub_owner..":pubsub", handle_pubsub_iq);
57
58 local feature_map = {
59         create = { "create-nodes", "instant-nodes", "item-ids" };
60         retract = { "delete-items", "retract-items" };
61         purge = { "purge-nodes" };
62         publish = { "publish", autocreate_on_publish and "auto-create" };
63         delete = { "delete-nodes" };
64         get_items = { "retrieve-items" };
65         add_subscription = { "subscribe" };
66         get_subscriptions = { "retrieve-subscriptions" };
67 };
68
69 local function add_disco_features_from_service(service)
70         for method, features in pairs(feature_map) do
71                 if service[method] then
72                         for _, feature in ipairs(features) do
73                                 if feature then
74                                         module:add_feature(xmlns_pubsub.."#"..feature);
75                                 end
76                         end
77                 end
78         end
79         for affiliation in pairs(service.config.capabilities) do
80                 if affiliation ~= "none" and affiliation ~= "owner" then
81                         module:add_feature(xmlns_pubsub.."#"..affiliation.."-affiliation");
82                 end
83         end
84 end
85
86 module:hook("host-disco-info-node", function (event)
87         local stanza, origin, reply, node = event.stanza, event.origin, event.reply, event.node;
88         local ok, ret = service:get_nodes(stanza.attr.from);
89         if ok and not ret[node] then
90                 return;
91         end
92         if not ok then
93                 return origin.send(pubsub_error_reply(stanza, ret));
94         end
95         event.exists = true;
96         reply:tag("identity", { category = "pubsub", type = "leaf" });
97 end);
98
99 module:hook("host-disco-items-node", function (event)
100         local stanza, origin, reply, node = event.stanza, event.origin, event.reply, event.node;
101         local ok, ret = service:get_items(node, stanza.attr.from);
102         if not ok then
103                 return origin.send(pubsub_error_reply(stanza, ret));
104         end
105
106         for id, item in pairs(ret) do
107                 reply:tag("item", { jid = module.host, name = id }):up();
108         end
109         event.exists = true;
110 end);
111
112
113 module:hook("host-disco-items", function (event)
114         local stanza, origin, reply = event.stanza, event.origin, event.reply;
115         local ok, ret = service:get_nodes(event.stanza.attr.from);
116         if not ok then
117                 return origin.send(pubsub_error_reply(event.stanza, ret));
118         end
119         for node, node_obj in pairs(ret) do
120                 reply:tag("item", { jid = module.host, node = node, name = node_obj.config.name }):up();
121         end
122 end);
123
124 local admin_aff = module:get_option_string("default_admin_affiliation", "owner");
125 local function get_affiliation(jid)
126         local bare_jid = jid_bare(jid);
127         if bare_jid == module.host or usermanager.is_admin(bare_jid, module.host) then
128                 return admin_aff;
129         end
130 end
131
132 function set_service(new_service)
133         service = new_service;
134         module.environment.service = service;
135         add_disco_features_from_service(service);
136 end
137
138 function module.save()
139         return { service = service };
140 end
141
142 function module.restore(data)
143         set_service(data.service);
144 end
145
146 function module.load()
147         if module.reloading then return; end
148
149         set_service(pubsub.new({
150                 capabilities = {
151                         none = {
152                                 create = false;
153                                 publish = false;
154                                 retract = false;
155                                 get_nodes = true;
156
157                                 subscribe = true;
158                                 unsubscribe = true;
159                                 get_subscription = true;
160                                 get_subscriptions = true;
161                                 get_items = true;
162
163                                 subscribe_other = false;
164                                 unsubscribe_other = false;
165                                 get_subscription_other = false;
166                                 get_subscriptions_other = false;
167
168                                 be_subscribed = true;
169                                 be_unsubscribed = true;
170
171                                 set_affiliation = false;
172                         };
173                         publisher = {
174                                 create = false;
175                                 publish = true;
176                                 retract = true;
177                                 get_nodes = true;
178
179                                 subscribe = true;
180                                 unsubscribe = true;
181                                 get_subscription = true;
182                                 get_subscriptions = true;
183                                 get_items = true;
184
185                                 subscribe_other = false;
186                                 unsubscribe_other = false;
187                                 get_subscription_other = false;
188                                 get_subscriptions_other = false;
189
190                                 be_subscribed = true;
191                                 be_unsubscribed = true;
192
193                                 set_affiliation = false;
194                         };
195                         owner = {
196                                 create = true;
197                                 publish = true;
198                                 retract = true;
199                                 delete = true;
200                                 get_nodes = true;
201
202                                 subscribe = true;
203                                 unsubscribe = true;
204                                 get_subscription = true;
205                                 get_subscriptions = true;
206                                 get_items = true;
207
208
209                                 subscribe_other = true;
210                                 unsubscribe_other = true;
211                                 get_subscription_other = true;
212                                 get_subscriptions_other = true;
213
214                                 be_subscribed = true;
215                                 be_unsubscribed = true;
216
217                                 set_affiliation = true;
218                         };
219                 };
220
221                 autocreate_on_publish = autocreate_on_publish;
222                 autocreate_on_subscribe = autocreate_on_subscribe;
223
224                 broadcaster = simple_broadcast;
225                 get_affiliation = get_affiliation;
226
227                 normalize_jid = jid_bare;
228         }));
229 end