Mercurial > libervia-pubsub
comparison idavoll/pubsub.py @ 159:6fe78048baf9
Rework error handling, depend on Twisted Words 0.4.0.
Twisted Words 0.4.0 introduced support for stanza error handling, much better
than the custom error handling in Idavoll. Also, all protocol-level errors
were examined and brought up to date with version 1.8 of JEP-0060.
As a result of the error examination, the retrieval of default configuration
options using <default/> is now supported properly.
author | Ralph Meijer <ralphm@ik.nu> |
---|---|
date | Wed, 06 Sep 2006 12:38:47 +0000 |
parents | 5191ba7c4df8 |
children | 40d931ed15b9 |
comparison
equal
deleted
inserted
replaced
158:b2149e448465 | 159:6fe78048baf9 |
---|---|
1 # Copyright (c) 2003-2006 Ralph Meijer | 1 # Copyright (c) 2003-2006 Ralph Meijer |
2 # See LICENSE for details. | 2 # See LICENSE for details. |
3 | 3 |
4 from twisted.words.protocols.jabber import component,jid | 4 from twisted.words.protocols.jabber import component, jid, error |
5 from twisted.words.xish import utility, domish | 5 from twisted.words.xish import utility, domish |
6 from twisted.python import components | 6 from twisted.python import components |
7 from twisted.internet import defer | 7 from twisted.internet import defer |
8 from zope.interface import implements | 8 from zope.interface import implements |
9 | 9 |
10 import backend | 10 import backend |
11 import storage | 11 import storage |
12 import xmpp_error | |
13 import disco | 12 import disco |
14 import data_form | 13 import data_form |
15 | 14 |
16 if issubclass(domish.SerializedXML, str): | 15 if issubclass(domish.SerializedXML, str): |
17 # Work around bug # in twisted Xish | 16 # Work around bug # in twisted Xish |
38 PUBSUB_PUBLISH = PUBSUB_SET + '/publish' | 37 PUBSUB_PUBLISH = PUBSUB_SET + '/publish' |
39 PUBSUB_SUBSCRIBE = PUBSUB_SET + '/subscribe' | 38 PUBSUB_SUBSCRIBE = PUBSUB_SET + '/subscribe' |
40 PUBSUB_UNSUBSCRIBE = PUBSUB_SET + '/unsubscribe' | 39 PUBSUB_UNSUBSCRIBE = PUBSUB_SET + '/unsubscribe' |
41 PUBSUB_OPTIONS_GET = PUBSUB_GET + '/options' | 40 PUBSUB_OPTIONS_GET = PUBSUB_GET + '/options' |
42 PUBSUB_OPTIONS_SET = PUBSUB_SET + '/options' | 41 PUBSUB_OPTIONS_SET = PUBSUB_SET + '/options' |
42 PUBSUB_DEFAULT = PUBSUB_OWNER_GET + '/default' | |
43 PUBSUB_CONFIGURE_GET = PUBSUB_OWNER_GET + '/configure' | 43 PUBSUB_CONFIGURE_GET = PUBSUB_OWNER_GET + '/configure' |
44 PUBSUB_CONFIGURE_SET = PUBSUB_OWNER_SET + '/configure' | 44 PUBSUB_CONFIGURE_SET = PUBSUB_OWNER_SET + '/configure' |
45 PUBSUB_SUBSCRIPTIONS = PUBSUB_GET + '/subscriptions' | 45 PUBSUB_SUBSCRIPTIONS = PUBSUB_GET + '/subscriptions' |
46 PUBSUB_AFFILIATIONS = PUBSUB_GET + '/affiliations' | 46 PUBSUB_AFFILIATIONS = PUBSUB_GET + '/affiliations' |
47 PUBSUB_ITEMS = PUBSUB_GET + '/items' | 47 PUBSUB_ITEMS = PUBSUB_GET + '/items' |
48 PUBSUB_RETRACT = PUBSUB_SET + '/retract' | 48 PUBSUB_RETRACT = PUBSUB_SET + '/retract' |
49 PUBSUB_PURGE = PUBSUB_OWNER_SET + '/purge' | 49 PUBSUB_PURGE = PUBSUB_OWNER_SET + '/purge' |
50 PUBSUB_DELETE = PUBSUB_OWNER_SET + '/delete' | 50 PUBSUB_DELETE = PUBSUB_OWNER_SET + '/delete' |
51 | 51 |
52 class Error(Exception): | 52 class BadRequest(error.StanzaError): |
53 pubsub_error = None | 53 def __init__(self): |
54 stanza_error = None | 54 error.StanzaError.__init__(self, 'bad-request') |
55 msg = '' | 55 |
56 | 56 class PubSubError(error.StanzaError): |
57 class NotImplemented(Error): | 57 def __init__(self, condition, pubsubCondition, feature=None, text=None): |
58 stanza_error = 'feature-not-implemented' | 58 appCondition = domish.Element((NS_PUBSUB_ERRORS, pubsubCondition)) |
59 | 59 if feature: |
60 class BadRequest(Error): | 60 appCondition['feature'] = feature |
61 stanza_error = 'bad-request' | 61 error.StanzaError.__init__(self, condition, |
62 | 62 text=text, |
63 class OptionsUnavailable(Error): | 63 appCondition=appCondition) |
64 stanza_error = 'feature-not-implemented' | 64 |
65 pubsub_error = 'subscription-options-unavailable' | 65 class OptionsUnavailable(PubSubError): |
66 | 66 def __init__(self): |
67 class NodeNotConfigurable(Error): | 67 PubSubError.__init__(self, 'feature-not-implemented', |
68 stanza_error = 'feature-not-implemented' | 68 'unsupported', |
69 pubsub_error = 'node-not-configurable' | 69 'subscription-options-unavailable') |
70 | 70 |
71 error_map = { | 71 error_map = { |
72 storage.NodeNotFound: ('item-not-found', None), | 72 storage.NodeNotFound: ('item-not-found', None, None), |
73 storage.NodeExists: ('conflict', None), | 73 storage.NodeExists: ('conflict', None, None), |
74 storage.SubscriptionNotFound: ('not-authorized', | 74 storage.SubscriptionNotFound: ('not-authorized', 'not-subscribed', None), |
75 'not-subscribed'), | 75 backend.Forbidden: ('forbidden', None, None), |
76 backend.NotAuthorized: ('not-authorized', None), | 76 backend.ItemForbidden: ('bad-request', 'item-forbidden', None), |
77 backend.NoPayloadAllowed: ('bad-request', None), | 77 backend.ItemRequired: ('bad-request', 'item-required', None), |
78 backend.PayloadExpected: ('bad-request', None), | 78 backend.NoInstantNodes: ('not-acceptable', 'unsupported', 'instant-nodes'), |
79 backend.NoInstantNodes: ('not-acceptable', None), | 79 backend.NotSubscribed: ('not-authorized', 'not-subscribed', None), |
80 backend.NotImplemented: ('feature-not-implemented', None), | 80 backend.InvalidConfigurationOption: ('not-acceptable', None, None), |
81 backend.InvalidConfigurationOption: ('not-acceptable', None), | 81 backend.InvalidConfigurationValue: ('not-acceptable', None, None), |
82 backend.InvalidConfigurationValue: ('not-acceptable', None), | 82 backend.NodeNotPersistent: ('feature-not-implemented', 'unsupported', |
83 'persistent-node'), | |
84 backend.NoRootNode: ('bad-request', None, None), | |
83 } | 85 } |
84 | 86 |
85 class Service(component.Service): | 87 class Service(component.Service): |
86 | 88 |
87 implements(component.IService) | 89 implements(component.IService) |
89 def __init__(self, backend): | 91 def __init__(self, backend): |
90 self.backend = backend | 92 self.backend = backend |
91 | 93 |
92 def error(self, failure, iq): | 94 def error(self, failure, iq): |
93 try: | 95 try: |
94 e = failure.trap(Error, *error_map.keys()) | 96 e = failure.trap(error.StanzaError, *error_map.keys()) |
95 except: | 97 except: |
96 failure.printBriefTraceback() | 98 failure.printBriefTraceback() |
97 xmpp_error.error_from_iq(iq, 'internal-server-error') | 99 return error.StanzaError('internal-server-error').toResponse(iq) |
98 return iq | |
99 else: | 100 else: |
100 if e == Error: | 101 if e == error.StanzaError: |
101 stanza_error = failure.value.stanza_error | 102 exc = failure.value |
102 pubsub_error = failure.value.pubsub_error | |
103 msg = '' | |
104 else: | 103 else: |
105 stanza_error, pubsub_error = error_map[e] | 104 condition, pubsubCondition, feature = error_map[e] |
106 msg = failure.value.msg | 105 msg = failure.value.msg |
107 | 106 |
108 xmpp_error.error_from_iq(iq, stanza_error, msg) | 107 if pubsubCondition: |
109 if pubsub_error: | 108 exc = PubSubError(condition, pubsubCondition, feature, msg) |
110 iq.error.addElement((NS_PUBSUB_ERRORS, pubsub_error)) | 109 else: |
111 return iq | 110 exc = error.StanzaError(condition, text=msg) |
111 | |
112 return exc.toResponse(iq) | |
112 | 113 |
113 def success(self, result, iq): | 114 def success(self, result, iq): |
114 iq.swapAttributeValues("to", "from") | 115 iq.swapAttributeValues("to", "from") |
115 iq["type"] = 'result' | 116 iq["type"] = 'result' |
116 iq.children = [] | 117 iq.children = [] |
360 | 361 |
361 class ComponentServiceFromNodeCreationService(Service): | 362 class ComponentServiceFromNodeCreationService(Service): |
362 | 363 |
363 def componentConnected(self, xmlstream): | 364 def componentConnected(self, xmlstream): |
364 xmlstream.addObserver(PUBSUB_CREATE, self.onCreate) | 365 xmlstream.addObserver(PUBSUB_CREATE, self.onCreate) |
366 xmlstream.addObserver(PUBSUB_DEFAULT, self.onDefault) | |
365 xmlstream.addObserver(PUBSUB_CONFIGURE_GET, self.onConfigureGet) | 367 xmlstream.addObserver(PUBSUB_CONFIGURE_GET, self.onConfigureGet) |
366 xmlstream.addObserver(PUBSUB_CONFIGURE_SET, self.onConfigureSet) | 368 xmlstream.addObserver(PUBSUB_CONFIGURE_SET, self.onConfigureSet) |
367 | 369 |
368 def get_disco_info(self, node): | 370 def get_disco_info(self, node): |
369 info = [] | 371 info = [] |
370 | 372 |
371 if not node: | 373 if not node: |
372 info.append(disco.Feature(NS_PUBSUB + "#create-nodes")) | 374 info.append(disco.Feature(NS_PUBSUB + "#create-nodes")) |
373 info.append(disco.Feature(NS_PUBSUB + "#config-node")) | 375 info.append(disco.Feature(NS_PUBSUB + "#config-node")) |
376 info.append(disco.Feature(NS_PUBSUB + "#retrieve-default")) | |
374 | 377 |
375 if self.backend.supports_instant_nodes(): | 378 if self.backend.supports_instant_nodes(): |
376 info.append(disco.Feature(NS_PUBSUB + "#instant-nodes")) | 379 info.append(disco.Feature(NS_PUBSUB + "#instant-nodes")) |
377 | 380 |
378 return defer.succeed(info) | 381 return defer.succeed(info) |
379 | 382 |
380 def onCreate(self, iq): | 383 def onCreate(self, iq): |
384 print "onCreate" | |
381 self.handler_wrapper(self._onCreate, iq) | 385 self.handler_wrapper(self._onCreate, iq) |
382 | 386 |
383 def _onCreate(self, iq): | 387 def _onCreate(self, iq): |
384 node = iq.pubsub.create.getAttribute("node") | 388 node = iq.pubsub.create.getAttribute("node") |
385 | 389 |
395 reply = domish.Element((NS_PUBSUB, 'pubsub')) | 399 reply = domish.Element((NS_PUBSUB, 'pubsub')) |
396 entity = reply.addElement('create') | 400 entity = reply.addElement('create') |
397 entity['node'] = result | 401 entity['node'] = result |
398 return [reply] | 402 return [reply] |
399 | 403 |
404 def onDefault(self, iq): | |
405 self.handler_wrapper(self._onDefault, iq) | |
406 | |
407 def _onDefault(self, iq): | |
408 d = self.backend.get_default_configuration() | |
409 d.addCallback(self._return_default_response) | |
410 return d | |
411 | |
412 def _return_default_response(self, options): | |
413 reply = domish.Element((NS_PUBSUB_OWNER, "pubsub")) | |
414 default = reply.addElement("default") | |
415 default.addChild(self._form_from_configuration(options)) | |
416 | |
417 return [reply] | |
418 | |
400 def onConfigureGet(self, iq): | 419 def onConfigureGet(self, iq): |
401 self.handler_wrapper(self._onConfigureGet, iq) | 420 self.handler_wrapper(self._onConfigureGet, iq) |
402 | 421 |
403 def _onConfigureGet(self, iq): | 422 def _onConfigureGet(self, iq): |
404 try: | 423 node_id = iq.pubsub.configure.getAttribute("node") |
405 node_id = iq.pubsub.configure["node"] | |
406 except KeyError: | |
407 raise NodeNotConfigurable | |
408 | 424 |
409 d = self.backend.get_node_configuration(node_id) | 425 d = self.backend.get_node_configuration(node_id) |
410 d.addCallback(self._return_configuration_response, node_id) | 426 d.addCallback(self._return_configuration_response, node_id) |
411 return d | 427 return d |
412 | 428 |
413 def _return_configuration_response(self, options, node_id): | 429 def _return_configuration_response(self, options, node_id): |
414 reply = domish.Element((NS_PUBSUB_OWNER, "pubsub")) | 430 reply = domish.Element((NS_PUBSUB_OWNER, "pubsub")) |
415 configure = reply.addElement("configure") | 431 configure = reply.addElement("configure") |
416 if node_id: | 432 if node_id: |
417 configure["node"] = node_id | 433 configure["node"] = node_id |
434 configure.addChild(self._form_from_configuration(options)) | |
435 | |
436 return [reply] | |
437 | |
438 def _form_from_configuration(self, options): | |
418 form = data_form.Form(type="form", | 439 form = data_form.Form(type="form", |
419 form_type=NS_PUBSUB + "#node_config") | 440 form_type=NS_PUBSUB + "#node_config") |
420 | 441 |
421 for option in options: | 442 for option in options: |
422 form.add_field(**option) | 443 form.add_field(**option) |
423 | 444 |
424 form.parent = configure | 445 return form |
425 configure.addChild(form) | |
426 | |
427 return [reply] | |
428 | 446 |
429 def onConfigureSet(self, iq): | 447 def onConfigureSet(self, iq): |
448 print "onConfigureSet" | |
430 self.handler_wrapper(self._onConfigureSet, iq) | 449 self.handler_wrapper(self._onConfigureSet, iq) |
431 | 450 |
432 def _onConfigureSet(self, iq): | 451 def _onConfigureSet(self, iq): |
433 try: | 452 node_id = iq.pubsub.configure["node"] |
434 node_id = iq.pubsub.configure["node"] | |
435 except KeyError: | |
436 raise BadRequest | |
437 | |
438 requestor = jid.internJID(iq["from"]).userhostJID() | 453 requestor = jid.internJID(iq["from"]).userhostJID() |
439 | 454 |
440 for element in iq.pubsub.configure.elements(): | 455 for element in iq.pubsub.configure.elements(): |
441 if element.name != 'x' or element.uri != data_form.NS_X_DATA: | 456 if element.name != 'x' or element.uri != data_form.NS_X_DATA: |
442 continue | 457 continue |