Mercurial > libervia-pubsub
annotate idavoll/generic_backend.py @ 150:35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Factor out authorization checking.
[1] http://ofxsuite.berlios.de/uuid.py
author | Ralph Meijer <ralphm@ik.nu> |
---|---|
date | Tue, 04 Oct 2005 12:17:01 +0000 |
parents | 5b0b3f013ccc |
children | ea8b4189ae3b |
rev | line source |
---|---|
107 | 1 import sha |
2 import time | |
150
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
3 import uuid |
107 | 4 from twisted.words.protocols.jabber import jid |
5 from twisted.application import service | |
6 from twisted.xish import utility | |
7 from twisted.internet import defer | |
8 from zope.interface import implements | |
129 | 9 import backend, storage |
107 | 10 |
11 def _get_affiliation(node, entity): | |
12 d = node.get_affiliation(entity) | |
13 d.addCallback(lambda affiliation: (node, affiliation)) | |
14 return d | |
15 | |
16 class BackendService(service.MultiService, utility.EventDispatcher): | |
17 | |
18 implements(backend.IBackendService) | |
19 | |
20 options = {"pubsub#persist_items": | |
21 {"type": "boolean", | |
22 "label": "Persist items to storage"}, | |
23 "pubsub#deliver_payloads": | |
24 {"type": "boolean", | |
25 "label": "Deliver payloads with event notifications"}, | |
26 } | |
27 | |
28 default_config = {"pubsub#persist_items": True, | |
29 "pubsub#deliver_payloads": True, | |
30 } | |
31 | |
32 def __init__(self, storage): | |
33 service.MultiService.__init__(self) | |
34 utility.EventDispatcher.__init__(self) | |
35 self.storage = storage | |
36 | |
37 def supports_publisher_affiliation(self): | |
38 return True | |
39 | |
40 def supports_outcast_affiliation(self): | |
41 return True | |
42 | |
43 def supports_persistent_items(self): | |
44 return True | |
45 | |
46 def get_node_type(self, node_id): | |
47 d = self.storage.get_node(node_id) | |
48 d.addCallback(lambda node: node.get_type()) | |
49 return d | |
50 | |
51 def get_nodes(self): | |
52 return self.storage.get_node_ids() | |
53 | |
54 def get_node_meta_data(self, node_id): | |
55 d = self.storage.get_node(node_id) | |
56 d.addCallback(lambda node: node.get_meta_data()) | |
57 d.addCallback(self._make_meta_data) | |
58 return d | |
59 | |
60 def _make_meta_data(self, meta_data): | |
61 options = [] | |
62 for key, value in meta_data.iteritems(): | |
63 if self.options.has_key(key): | |
64 option = {"var": key} | |
65 option.update(self.options[key]) | |
66 option["value"] = value | |
67 options.append(option) | |
68 | |
69 return options | |
70 | |
71 class PublishService(service.Service): | |
72 | |
73 implements(backend.IPublishService) | |
74 | |
150
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
75 def _check_auth(self, node, requestor): |
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
76 def check(affiliation, node): |
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
77 if affiliation not in ['owner', 'publisher']: |
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
78 raise backend.NotAuthorized |
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
79 return node |
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
80 |
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
81 d = node.get_affiliation(requestor) |
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
82 d.addCallback(check, node) |
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
83 return d |
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
84 |
107 | 85 def publish(self, node_id, items, requestor): |
86 d = self.parent.storage.get_node(node_id) | |
150
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
87 d.addCallback(self._check_auth, requestor) |
107 | 88 d.addCallback(self._do_publish, items, requestor) |
89 return d | |
90 | |
150
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
91 def _do_publish(self, node, items, requestor): |
107 | 92 configuration = node.get_configuration() |
93 persist_items = configuration["pubsub#persist_items"] | |
94 deliver_payloads = configuration["pubsub#deliver_payloads"] | |
95 | |
96 if items and not persist_items and not deliver_payloads: | |
97 raise backend.NoPayloadAllowed | |
98 elif not items and (persist_items or deliver_payloads): | |
99 raise backend.PayloadExpected | |
100 | |
101 if persist_items or deliver_payloads: | |
102 for item in items: | |
103 if not item.getAttribute("id"): | |
150
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
104 item["id"] = uuid.generate() |
107 | 105 |
106 if persist_items: | |
107 d = node.store_items(items, requestor) | |
108 else: | |
109 d = defer.succeed(None) | |
110 | |
111 d.addCallback(self._do_notify, node.id, items, deliver_payloads) | |
129 | 112 return d |
107 | 113 |
114 def _do_notify(self, result, node_id, items, deliver_payloads): | |
115 if items and not deliver_payloads: | |
116 for item in items: | |
117 item.children = [] | |
118 | |
119 self.parent.dispatch({ 'items': items, 'node_id': node_id }, | |
120 '//event/pubsub/notify') | |
121 | |
122 class NotificationService(service.Service): | |
123 | |
124 implements(backend.INotificationService) | |
125 | |
126 def get_notification_list(self, node_id, items): | |
127 d = self.parent.storage.get_node(node_id) | |
128 d.addCallback(lambda node: node.get_subscribers()) | |
129 d.addCallback(self._magic_filter, node_id, items) | |
130 return d | |
131 | |
132 def _magic_filter(self, subscribers, node_id, items): | |
133 list = [] | |
134 for subscriber in subscribers: | |
135 list.append((subscriber, items)) | |
136 return list | |
137 | |
138 def register_notifier(self, observerfn, *args, **kwargs): | |
139 self.parent.addObserver('//event/pubsub/notify', observerfn, | |
140 *args, **kwargs) | |
141 | |
142 class SubscriptionService(service.Service): | |
143 | |
144 implements(backend.ISubscriptionService) | |
145 | |
146 def subscribe(self, node_id, subscriber, requestor): | |
147 subscriber_entity = subscriber.userhostJID() | |
148 if subscriber_entity != requestor: | |
150
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
149 return defer.fail(backend.NotAuthorized()) |
107 | 150 |
151 d = self.parent.storage.get_node(node_id) | |
152 d.addCallback(_get_affiliation, subscriber_entity) | |
153 d.addCallback(self._do_subscribe, subscriber) | |
154 return d | |
155 | |
156 def _do_subscribe(self, result, subscriber): | |
157 node, affiliation = result | |
158 | |
159 if affiliation == 'outcast': | |
160 raise backend.NotAuthorized | |
161 | |
162 d = node.add_subscription(subscriber, 'subscribed') | |
118
7d83fe9bdb65
Change storage.INode.add_subscription() to return a Failure when a subscription
Ralph Meijer <ralphm@ik.nu>
parents:
107
diff
changeset
|
163 d.addCallback(lambda _: 'subscribed') |
129 | 164 d.addErrback(self._get_subscription, node, subscriber) |
118
7d83fe9bdb65
Change storage.INode.add_subscription() to return a Failure when a subscription
Ralph Meijer <ralphm@ik.nu>
parents:
107
diff
changeset
|
165 d.addCallback(self._return_subscription, affiliation, node.id) |
107 | 166 return d |
167 | |
129 | 168 def _get_subscription(self, failure, node, subscriber): |
169 failure.trap(storage.SubscriptionExists) | |
118
7d83fe9bdb65
Change storage.INode.add_subscription() to return a Failure when a subscription
Ralph Meijer <ralphm@ik.nu>
parents:
107
diff
changeset
|
170 return node.get_subscription(subscriber) |
7d83fe9bdb65
Change storage.INode.add_subscription() to return a Failure when a subscription
Ralph Meijer <ralphm@ik.nu>
parents:
107
diff
changeset
|
171 |
7d83fe9bdb65
Change storage.INode.add_subscription() to return a Failure when a subscription
Ralph Meijer <ralphm@ik.nu>
parents:
107
diff
changeset
|
172 def _return_subscription(self, result, affiliation, node_id): |
7d83fe9bdb65
Change storage.INode.add_subscription() to return a Failure when a subscription
Ralph Meijer <ralphm@ik.nu>
parents:
107
diff
changeset
|
173 return {'affiliation': affiliation, |
7d83fe9bdb65
Change storage.INode.add_subscription() to return a Failure when a subscription
Ralph Meijer <ralphm@ik.nu>
parents:
107
diff
changeset
|
174 'node': node_id, |
7d83fe9bdb65
Change storage.INode.add_subscription() to return a Failure when a subscription
Ralph Meijer <ralphm@ik.nu>
parents:
107
diff
changeset
|
175 'state': result} |
107 | 176 |
177 def unsubscribe(self, node_id, subscriber, requestor): | |
178 if subscriber.userhostJID() != requestor: | |
179 raise backend.NotAuthorized | |
180 | |
181 d = self.parent.storage.get_node(node_id) | |
182 d.addCallback(lambda node: node.remove_subscription(subscriber)) | |
183 return d | |
184 | |
185 class NodeCreationService(service.Service): | |
186 | |
187 implements(backend.INodeCreationService) | |
188 | |
189 def supports_instant_nodes(self): | |
190 return True | |
191 | |
192 def create_node(self, node_id, requestor): | |
193 if not node_id: | |
150
35977eb964e6
Use uuid.py module [1] for generating unique id's for nodes and items.
Ralph Meijer <ralphm@ik.nu>
parents:
144
diff
changeset
|
194 node_id = 'generic/%s' % uuid.generate() |
107 | 195 d = self.parent.storage.create_node(node_id, requestor) |
196 d.addCallback(lambda _: node_id) | |
197 return d | |
198 | |
199 def get_node_configuration(self, node_id): | |
200 if node_id: | |
201 d = self.parent.storage.get_node(node_id) | |
202 d.addCallback(lambda node: node.get_configuration()) | |
203 else: | |
204 # XXX: this is disabled in pubsub.py | |
205 d = defer.succeed(self.parent.default_config) | |
206 | |
207 d.addCallback(self._make_config) | |
208 return d | |
209 | |
210 def _make_config(self, config): | |
211 options = [] | |
212 for key, value in self.parent.options.iteritems(): | |
213 option = {"var": key} | |
214 option.update(value) | |
215 if config.has_key(key): | |
216 option["value"] = config[key] | |
217 options.append(option) | |
218 | |
219 return options | |
220 | |
221 def set_node_configuration(self, node_id, options, requestor): | |
222 for key, value in options.iteritems(): | |
223 if not self.parent.options.has_key(key): | |
224 raise backend.InvalidConfigurationOption | |
225 if self.parent.options[key]["type"] == 'boolean': | |
226 try: | |
227 options[key] = bool(int(value)) | |
228 except ValueError: | |
229 raise backend.InvalidConfigurationValue | |
230 | |
231 d = self.parent.storage.get_node(node_id) | |
232 d.addCallback(_get_affiliation, requestor) | |
233 d.addCallback(self._do_set_node_configuration, options) | |
234 return d | |
235 | |
236 def _do_set_node_configuration(self, result, options): | |
237 node, affiliation = result | |
238 | |
239 if affiliation != 'owner': | |
240 raise backend.NotAuthorized | |
241 | |
242 return node.set_configuration(options) | |
243 | |
244 class AffiliationsService(service.Service): | |
245 | |
246 implements(backend.IAffiliationsService) | |
247 | |
248 def get_affiliations(self, entity): | |
249 d1 = self.parent.storage.get_affiliations(entity) | |
250 d2 = self.parent.storage.get_subscriptions(entity) | |
251 d = defer.DeferredList([d1, d2], fireOnOneErrback=1, consumeErrors=1) | |
252 d.addErrback(lambda x: x.value[0]) | |
253 d.addCallback(self._affiliations_result, entity) | |
254 return d | |
255 | |
256 def _affiliations_result(self, result, entity): | |
257 affiliations = result[0][1] | |
258 subscriptions = result[1][1] | |
259 | |
260 new_affiliations = {} | |
261 | |
262 for node, affiliation in affiliations: | |
263 new_affiliations[(node, entity.full())] = {'node': node, | |
264 'jid': entity, | |
265 'affiliation': affiliation, | |
266 'subscription': None | |
267 } | |
268 | |
269 for node, subscriber, subscription in subscriptions: | |
270 key = node, subscriber.full() | |
271 if new_affiliations.has_key(key): | |
272 new_affiliations[key]['subscription'] = subscription | |
273 else: | |
274 new_affiliations[key] = {'node': node, | |
275 'jid': subscriber, | |
276 'affiliation': None, | |
277 'subscription': subscription} | |
278 | |
279 return new_affiliations.values() | |
280 | |
281 class ItemRetrievalService(service.Service): | |
282 | |
283 implements(backend.IItemRetrievalService) | |
284 | |
285 def get_items(self, node_id, requestor, max_items=None, item_ids=[]): | |
286 d = self.parent.storage.get_node(node_id) | |
287 d.addCallback(self._is_subscribed, requestor) | |
288 d.addCallback(self._do_get_items, max_items, item_ids) | |
289 return d | |
290 | |
291 def _is_subscribed(self, node, subscriber): | |
292 d = node.is_subscribed(subscriber) | |
293 d.addCallback(lambda subscribed: (node, subscribed)) | |
294 return d | |
295 | |
296 def _do_get_items(self, result, max_items, item_ids): | |
297 node, subscribed = result | |
298 | |
299 if not subscribed: | |
300 raise backend.NotAuthorized | |
301 | |
302 if item_ids: | |
303 return node.get_items_by_id(item_ids) | |
304 else: | |
305 return node.get_items(max_items) | |
306 | |
307 class RetractionService(service.Service): | |
308 | |
309 implements(backend.IRetractionService) | |
310 | |
311 def retract_item(self, node_id, item_ids, requestor): | |
312 d = self.parent.storage.get_node(node_id) | |
313 d.addCallback(_get_affiliation, requestor) | |
314 d.addCallback(self._do_retract, item_ids) | |
315 return d | |
316 | |
317 def _do_retract(self, result, item_ids): | |
318 node, affiliation = result | |
319 persist_items = node.get_configuration()["pubsub#persist_items"] | |
320 | |
321 if affiliation not in ['owner', 'publisher']: | |
322 raise backend.NotAuthorized | |
323 | |
324 if not persist_items: | |
325 raise backend.NodeNotPersistent | |
326 | |
327 d = node.remove_items(item_ids) | |
142
812300cdbc22
Changed behaviour of retraction of items so that only the actually deleted
Ralph Meijer <ralphm@ik.nu>
parents:
129
diff
changeset
|
328 d.addCallback(self._do_notify_retraction, node.id) |
107 | 329 return d |
330 | |
142
812300cdbc22
Changed behaviour of retraction of items so that only the actually deleted
Ralph Meijer <ralphm@ik.nu>
parents:
129
diff
changeset
|
331 def _do_notify_retraction(self, item_ids, node_id): |
129 | 332 self.parent.dispatch({ 'item_ids': item_ids, 'node_id': node_id }, |
107 | 333 '//event/pubsub/retract') |
334 | |
335 def purge_node(self, node_id, requestor): | |
336 d = self.parent.storage.get_node(node_id) | |
337 d.addCallback(_get_affiliation, requestor) | |
338 d.addCallback(self._do_purge) | |
339 return d | |
340 | |
341 def _do_purge(self, result): | |
342 node, affiliation = result | |
343 persist_items = node.get_configuration()["pubsub#persist_items"] | |
344 | |
345 if affiliation != 'owner': | |
346 raise backend.NotAuthorized | |
347 | |
348 if not persist_items: | |
349 raise backend.NodeNotPersistent | |
350 | |
351 d = node.purge() | |
352 d.addCallback(self._do_notify_purge, node.id) | |
353 return d | |
354 | |
355 def _do_notify_purge(self, result, node_id): | |
356 self.parent.dispatch(node_id, '//event/pubsub/purge') | |
357 | |
358 class NodeDeletionService(service.Service): | |
359 | |
360 implements(backend.INodeDeletionService) | |
361 | |
362 def __init__(self): | |
363 self._callback_list = [] | |
364 | |
365 def register_pre_delete(self, pre_delete_fn): | |
366 self._callback_list.append(pre_delete_fn) | |
367 | |
368 def get_subscribers(self, node_id): | |
369 d = self.parent.storage.get_node(node_id) | |
370 d.addCallback(lambda node: node.get_subscribers()) | |
371 return d | |
372 | |
373 def delete_node(self, node_id, requestor): | |
374 d = self.parent.storage.get_node(node_id) | |
375 d.addCallback(_get_affiliation, requestor) | |
376 d.addCallback(self._do_pre_delete) | |
377 return d | |
378 | |
379 def _do_pre_delete(self, result): | |
380 node, affiliation = result | |
381 | |
382 if affiliation != 'owner': | |
383 raise backend.NotAuthorized | |
384 | |
144
5b0b3f013ccc
Fixed typo. Reported by Herman Li.
Ralph Meijer <ralphm@ik.nu>
parents:
142
diff
changeset
|
385 d = defer.DeferredList([cb(node.id) for cb in self._callback_list], |
107 | 386 consumeErrors=1) |
387 d.addCallback(self._do_delete, node.id) | |
388 | |
389 def _do_delete(self, result, node_id): | |
390 dl = [] | |
391 for succeeded, r in result: | |
392 if succeeded and r: | |
393 dl.extend(r) | |
394 | |
395 d = self.parent.storage.delete_node(node_id) | |
396 d.addCallback(self._do_notify_delete, dl) | |
397 | |
398 return d | |
399 | |
400 def _do_notify_delete(self, result, dl): | |
401 for d in dl: | |
402 d.callback(None) |