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