Package spade :: Package xmppd :: Package modules :: Module pubsub
[hide private]
[frames] | no frames]

Source Code for Module spade.xmppd.modules.pubsub

  1  # -*- coding: UTF-8 -*- 
  2   
  3  from pprint import pprint 
  4  from uuid import uuid4 
  5  from datetime import datetime 
  6   
  7  from xmpp import * 
  8  from xmpp.protocol import * 
  9   
10 -class PSNode(object):
11 """ 12 Publish-Subscribe node. 13 14 TODO: Items must retain info about its publisher. We have only implemented 15 owners and subscribes, not publishers. So publisher is always the onwer. 16 """ 17
18 - def __init__(self, id, owner, type='leaf', parent=None, children=[], members={}, access_model='presence'):
19 """ 20 Constructs a PSNode. 21 22 @type id: string 23 @param m: node id 24 """ 25 self.id = id 26 self.owner = owner 27 self.type = type 28 self.parent = parent 29 self.children = children 30 self.members = members 31 self.access_model = access_model 32 self.item_ids = [] 33 self.items_timestamp = {} 34 self.items = {}
35
36 - def addItem(self, id, content):
37 try: 38 #TODO: Error if ID aready exists. 39 if id not in self.item_ids: 40 self.item_ids.append(id) 41 self.items[id] = content 42 self.items_timestamp[id] = datetime.utcnow().isoformat().split('.')[0] + 'Z' 43 #print self.items_timestamp[id], self.items[id], id 44 except Exception,e: 45 self.DEBUG('Exception in addItem: '+str(e),"error")
46
47 - def __repr__(self):
48 return 'PSNode(%s, %s)' % (self.id, self.type)
49
50 - def __str__(self):
51 return self.__repr__()
52
53 -class PubSubServer(PlugIn):
54 55 NS = NS_PUBSUB 56
57 - def plugin(self,server):
58 self.name = self._owner.servernames[0] 59 self.nodes = {} 60 61 for ns in (NS_PUBSUB, NS_PUBSUB_ERRORS, NS_PUBSUB_EVENTS, NS_PUBSUB_OWNER): 62 server.Dispatcher.RegisterHandler('iq', self.PubSubIqHandler, typ='set', ns=ns, xmlns=NS_CLIENT) 63 server.Dispatcher.RegisterHandler('iq', self.PubSubIqHandler, typ='set', ns=ns, xmlns=NS_COMPONENT_ACCEPT)
64
65 - def _getIqError(self, iq, name, specific=None):
66 if specific is None: 67 return Error(iq, NS_STANZAS + ' ' + name) 68 else: 69 error_node = ErrorNode(name) 70 error_node.addChild(name=specific, attrs={'xmlns':NS_PUBSUB_ERRORS}) 71 iq = iq.buildReply('error') 72 iq.addChild(node=error_node) 73 return iq
74
75 - def _sendItem(self, node_id, item_id, frm=None):
76 #FIXME: items contents have incorrect xmlns 77 try: 78 node = self.nodes[node_id] 79 80 #print node.members 81 82 for jid in node.members.keys(): 83 #print 'Enviando %s a %s' % (item_id,jid) 84 msg = Message(frm=self.name, to=jid) 85 msg.setID(str(uuid4())) #TODO: Do this automatically in xmpp.protocol.Protocol 86 event_node = Node(tag='event', attrs={'xmlns':NS_PUBSUB_EVENTS}) 87 items_node = Node(tag='items', attrs={'node':node_id}) 88 item_node = Node(tag='item', attrs={'id':item_id, 'timestamp':node.items_timestamp[item_id]}) 89 if frm is not None: 90 item_node['publisher'] = frm 91 item_node.addChild(node=node.items[item_id]) 92 items_node.addChild(node=item_node) 93 event_node.addChild(node=items_node) 94 msg.addChild(node=event_node) 95 s = self._owner.getsession(jid) 96 s.send(msg) 97 except Exception,e: 98 self.DEBUG('Exception in sendItem: '+str(e),"error")
99 100 101 #TODO: If we had a maximum, we should remove the first item here. Doing a FIFO. 102 103
104 - def PubSubIqHandler(self, session, stanza):
105 """ 106 XXX: We do not validate. We just get what we want and that is enough. 107 """ 108 109 try: 110 111 self.DEBUG('PubSub Iq handler called','info') 112 113 #pprint(self._owner.DB.db) 114 115 #print stanza.__str__(fancy=True) 116 #if stanza.getType() == 'set': 117 pubsub_node = stanza.getTag('pubsub') 118 119 # If no pubsub node, this should have arrive here. 120 # TODO: Return an error to the user? 121 if pubsub_node is None: 122 self.DEBUG('Bad message: %s' % stanza, 'error') 123 raise NodeProcessed 124 125 # CREATE NODE 126 if pubsub_node.getTag('create') is not None: 127 create_node = pubsub_node.getTag('create') 128 node_id = create_node.getAttr('node') 129 130 #TODO: If no name, this is an instant node. 131 132 if False: #TODO: Registro 133 session.send(self._getIqError(stanza, 'registration-required')) 134 raise NodeProcessed 135 136 if False: #TODO: cuando tengamos privilegios 137 session.send(self._getIqError(stanza, 'forbidden')) 138 raise NodeProcessed 139 140 if node_id in self.nodes: 141 iq = self._getIqError(stanza, 'conflict') 142 session.send(iq) 143 raise NodeProcessed 144 145 if False: #TODO: Access model 146 session.send(self._getIqError(stanza, 'not-acceptable', 'unsupported-access-model')) 147 raise NodeProcessed 148 149 self.nodes[node_id] = PSNode(id=node_id, owner=stanza.getFrom()) 150 151 # Add node 152 #print self.nodes 153 self.DEBUG('Creating node: %s' % create_node, 'info') 154 155 id = stanza.getAttr('id') 156 if isinstance(stanza,Protocol): stanza = Iq(node=stanza) 157 158 iq = stanza.buildReply('result') 159 if id: iq.setID(id) 160 pubsub_node = Node(tag='pubsub', attrs={'xmlns':NS_PUBSUB}) 161 pubsub_node.addChild(node=create_node) 162 iq.addChild(node=pubsub_node) 163 164 # SUCCESS 165 session.send(iq) 166 167 # DELETE NODE 168 elif pubsub_node.getTag('delete') is not None: 169 170 node_id = pubsub_node.getTag('delete').getAttr('node') 171 172 if node_id is None: #FIXME 173 self.DEBUG('Node is Non', 'error') 174 session.send(self._getIqError(stanza, 'item-not-found')) 175 raise NodeProcessed 176 177 if node_id not in self.nodes: 178 self.DEBUG('Node does not exists: %s' % node_id, 'error') 179 session.send(self._getIqError(stanza, 'item-not-found')) 180 raise NodeProcessed 181 182 node = self.nodes[node_id] 183 184 if not node.owner.bareMatch(stanza.getFrom()): 185 session.send(self._getIqError(stanza, 'forbidden')) 186 raise NodeProcessed 187 188 # Delete node 189 # Keep in mind that associated items are (and must be) deleted. 190 del self.nodes[node_id] 191 192 # SUCCESS 193 session.send(stanza.buildReply('result')) 194 195 # Notify no all subscribers 196 for jid in node.members.keys(): 197 msg = Message(frm=self.name, to=jid) 198 msg.setID(str(uuid4())) #TODO: Do this automatically in xmpp.protocol.Protocol 199 event_node = Node(tag='event', attrs={'xmlns':NS_PUBSUB + '#event'}) 200 event_node.addChild(name='delete', attrs={'node':node_id}) 201 msg.addChild(node=event_node) 202 s = self._owner.getsession(jid) 203 s.send(msg) 204 205 206 # SUBSCRIBE 207 elif pubsub_node.getTag('subscribe') is not None: 208 209 #TODO: We do not do multiple subscribe. 210 # So be careful if we duplicate subscriptions. 211 212 subscribe_node = pubsub_node.getTag('subscribe') 213 node_id = subscribe_node.getAttr('node') 214 215 jid = JID(subscribe_node.getAttr('jid')) 216 217 #print 'nodes: ', self.nodes 218 if node_id is None or jid is None: #TODO: Que enviar aquí?, además, jid no es None, peta antes 219 self.DEBUG('No node id or jid in subscribe message.', 'error') 220 raise NodeProcessed 221 222 if node_id not in self.nodes: 223 self.DEBUG('Node does not exists: %s' % node_id, 'error') 224 session.send(self._getIqError(stanza, 'item-not-found')) 225 raise NodeProcessed 226 227 if not stanza.getFrom().bareMatch(jid): # Trying to subscribe a JID different from the real one 228 session.send(self._getIqError(stanza, 'bad-request', 'invalid-jid')) 229 raise NodeProcessed 230 231 node = self.nodes[node_id] 232 access_model = node.access_model 233 #print 'access_model: ', 'presence' 234 235 if access_model == 'presence': 236 owner_roster = self._owner.DB.db[self.name][node.owner.getNode()]['roster'] 237 if jid in owner_roster: 238 if owner_roster[jid.getStripped()]['subscription'] not in ('from', 'both'): 239 if owner_roster[jid.getStripped()]['status'] == 'pending_in': #XXX: pending_out too? 240 session.send(self._getIqError(stanza, 'not-authorized', 'pending-subscription')) 241 raise NodeProcessed 242 else: 243 session.send(self._getIqError(stanza, 'not-authorized', 'presence-subscription-required')) 244 raise NodeProcessed 245 else: 246 session.send(self._getIqError(stanza, 'not-authorized', 'presence-subscription-required')) 247 raise NodeProcessed 248 249 if False: #TODO: Roster access_model 250 #TODO: WE DO NOT IMPLEMENT ROSTER ACCESS MODEL EVEN IF IT IS REQUIRED BY XEP-163 251 pass 252 253 if False: #TODO: Whitelist access_model 254 #TODO: WE DO NOT IMPLEMENT WHITELIST ACCESS MODEL EVEN IF IT IS REQUIRED BY XEP-163 255 pass 256 257 if False: #TODO: Do not allow subscription from blocked people, even with 'open' access model 258 #TODO: WE DO NOT IMPLEMENT BLOCKING 259 pass 260 261 if False: #TODO: Establish a max_subscription threshold. 262 #TODO: Let's people of the future care about security. 263 pass 264 265 # SUCESS 266 267 268 # Add new member to node, with its subid (an UUID) 269 node.members[jid] = str(uuid4()) 270 271 #TODO: Send last published item (as required by XEP-163 by default 272 iq = stanza.buildReply('result') 273 pubsub_node = Node(tag='pubsub', attrs={'xmlns': NS_PUBSUB}) 274 pubsub_node.addChild(name='subscription', attrs={ 275 'node':node_id, 276 'jid': jid, 277 'subid': node.members[jid], 278 'subscription': 'subscribed' 279 }) 280 iq.addChild(node=pubsub_node) 281 session.send(iq) 282 283 284 # UNSUBSCRIBE 285 elif pubsub_node.getTag('unsubscribe') is not None: 286 unsubscribe_node = pubsub_node.getTag('unsubscribe') 287 node_id = unsubscribe_node.getAttr('node') 288 jid = unsubscribe_node.getAttr('jid') 289 290 if node_id is None or jid is None: #TODO: Que enviar aquí? 291 self.DEBUG('No node id or jid in subscribe message.', 'error') 292 raise NodeProcessed 293 294 if node_id not in self.nodes: 295 self.DEBUG('Node does not exists: %s' % node_id, 'error') 296 session.send(self._getIqError(stanza, 'item-not-found')) 297 raise NodeProcessed 298 299 300 if not stanza.getFrom().bareMatch(JID(jid)): # Trying to subscribe a JID different from the real one 301 session.send(self._getIqError(stanza, 'bad-request', 'invalid-jid')) 302 raise NodeProcessed 303 304 node = self.nodes[node_id] 305 306 if jid not in node.members: 307 #XXX: Owner is not a member, so he would trigger this error too if he is stupid. 308 session.send(self._getIqError(stanza, 'unexpected-request', 'not-subscribed')) 309 raise NodeProcessed 310 311 del node.members[jid] 312 313 # SUCESS 314 session.send(stanza.buildReply('result')) 315 316 317 # PUBLISH ITEM 318 elif pubsub_node.getTag('publish') is not None: 319 publish_node = pubsub_node.getTag('publish') 320 node_id = publish_node['node'] 321 322 if node_id is None: #TODO: Que enviar aquí? 323 self.DEBUG('No node id or jid in subscribe message.', 'error') 324 raise NodeProcessed 325 326 item_node = publish_node.getTag('item') 327 328 if item_node is None: #TODO: Que enviar aquí? 329 self.DEBUG('No item', 'error') 330 raise NodeProcessed 331 332 if node_id not in self.nodes: 333 # XEP-60 defines auto-create, XEP-163 enforces it. 334 self.nodes[node_id] = PSNode(id=node_id, owner=stanza.getFrom()) 335 336 node = self.nodes[node_id] 337 338 # TODO: Change once we implement additional publishers (not only owner). 339 if not node.owner.bareMatch(stanza.getFrom()): 340 session.send(self._getIqError(stanza, 'forbidden')) 341 raise NodeProcessed 342 343 item_id = item_node['id'] 344 if item_id is None: 345 item_id = str(uuid4()) 346 347 348 349 #print item_node.getChildren() 350 self.nodes[node_id].addItem(item_id, item_node.getChildren()[0]) 351 352 # SUCESS 353 354 iq = stanza.buildReply('result') 355 pubsub_node = Node(tag='pubsub', attrs={'xmlns':NS_PUBSUB}) 356 publish_node = Node(tag='publish', attrs={'node':node_id}) 357 publish_node.addChild(name='item', attrs={'id':item_id}) 358 pubsub_node.addChild(node=publish_node) 359 iq.addChild(node=pubsub_node) 360 session.send(iq) 361 362 # Send item to all subscriptors. 363 self._sendItem(node_id, item_id, stanza.getFrom()) 364 365 366 # NON-IMPLEMENTED ACTION 367 else: 368 self.DEBUG('Not implemented: %s' % create_node, 'info') 369 370 raise NodeProcessed 371 except NodeProcessed: 372 raise NodeProcessed 373 except Exception,e: 374 self.DEBUG("Exception in PubSub Handler: " + str(e),"error")
375