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