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
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 not ok or not ret[node] then
90                 return;
91         end
92         event.exists = true;
93         reply:tag("identity", { category = "pubsub", type = "leaf" });
94 end);
95
96 module:hook("host-disco-items-node", function (event)
97         local stanza, origin, reply, node = event.stanza, event.origin, event.reply, event.node;
98         local ok, ret = service:get_items(node, stanza.attr.from);
99         if not ok then
100                 return;
101         end
102
103         for _, id in ipairs(ret) do
104                 reply:tag("item", { jid = module.host, name = id }):up();
105         end
106         event.exists = true;
107 end);
108
109
110 module:hook("host-disco-items", function (event)
111         local stanza, origin, reply = event.stanza, event.origin, event.reply;
112         local ok, ret = service:get_nodes(event.stanza.attr.from);
113         if not ok then
114                 return;
115         end
116         for node, node_obj in pairs(ret) do
117                 reply:tag("item", { jid = module.host, node = node, name = node_obj.config.name }):up();
118         end
119 end);
120
121 local admin_aff = module:get_option_string("default_admin_affiliation", "owner");
122 local function get_affiliation(jid)
123         local bare_jid = jid_bare(jid);
124         if bare_jid == module.host or usermanager.is_admin(bare_jid, module.host) then
125                 return admin_aff;
126         end
127 end
128
129 function set_service(new_service)
130         service = new_service;
131         module.environment.service = service;
132         add_disco_features_from_service(service);
133 end
134
135 function module.save()
136         return { service = service };
137 end
138
139 function module.restore(data)
140         set_service(data.service);
141 end
142
143 function module.load()
144         if module.reloading then return; end
145
146         set_service(pubsub.new({
147                 capabilities = {
148                         none = {
149                                 create = false;
150                                 publish = false;
151                                 retract = false;
152                                 get_nodes = true;
153
154                                 subscribe = true;
155                                 unsubscribe = true;
156                                 get_subscription = true;
157                                 get_subscriptions = true;
158                                 get_items = true;
159
160                                 subscribe_other = false;
161                                 unsubscribe_other = false;
162                                 get_subscription_other = false;
163                                 get_subscriptions_other = false;
164
165                                 be_subscribed = true;
166                                 be_unsubscribed = true;
167
168                                 set_affiliation = false;
169                         };
170                         publisher = {
171                                 create = false;
172                                 publish = true;
173                                 retract = true;
174                                 get_nodes = true;
175
176                                 subscribe = true;
177                                 unsubscribe = true;
178                                 get_subscription = true;
179                                 get_subscriptions = true;
180                                 get_items = true;
181
182                                 subscribe_other = false;
183                                 unsubscribe_other = false;
184                                 get_subscription_other = false;
185                                 get_subscriptions_other = false;
186
187                                 be_subscribed = true;
188                                 be_unsubscribed = true;
189
190                                 set_affiliation = false;
191                         };
192                         owner = {
193                                 create = true;
194                                 publish = true;
195                                 retract = true;
196                                 delete = true;
197                                 get_nodes = true;
198
199                                 subscribe = true;
200                                 unsubscribe = true;
201                                 get_subscription = true;
202                                 get_subscriptions = true;
203                                 get_items = true;
204
205
206                                 subscribe_other = true;
207                                 unsubscribe_other = true;
208                                 get_subscription_other = true;
209                                 get_subscriptions_other = true;
210
211                                 be_subscribed = true;
212                                 be_unsubscribed = true;
213
214                                 set_affiliation = true;
215                         };
216                 };
217
218                 autocreate_on_publish = autocreate_on_publish;
219                 autocreate_on_subscribe = autocreate_on_subscribe;
220
221                 broadcaster = simple_broadcast;
222                 get_affiliation = get_affiliation;
223
224                 normalize_jid = jid_bare;
225         }));
226 end