Package pyxmpp :: Package jabber :: Module client
[hide private]

Source Code for Module pyxmpp.jabber.client

  1  # (C) Copyright 2003-2010 Jacek Konieczny <jajcus@jajcus.net> 
  2  # 
  3  # This program is free software; you can redistribute it and/or modify 
  4  # it under the terms of the GNU Lesser General Public License Version 
  5  # 2.1 as published by the Free Software Foundation. 
  6  # 
  7  # This program is distributed in the hope that it will be useful, 
  8  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  9  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 10  # GNU Lesser General Public License for more details. 
 11  # 
 12  # You should have received a copy of the GNU Lesser General Public 
 13  # License along with this program; if not, write to the Free Software 
 14  # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 15  # 
 16  """Basic Jabber client functionality implementation. 
 17   
 18  Extends `pyxmpp.client` interface with legacy authentication 
 19  and basic Service Discovery handling. 
 20   
 21  Normative reference: 
 22    - `JEP 78 <http://www.jabber.org/jeps/jep-0078.html>`__ 
 23    - `JEP 30 <http://www.jabber.org/jeps/jep-0030.html>`__ 
 24  """ 
 25   
 26  __docformat__="restructuredtext en" 
 27   
 28  import logging 
 29   
 30  from pyxmpp.jabber.clientstream import LegacyClientStream 
 31  from pyxmpp.jabber.disco import DISCO_ITEMS_NS,DISCO_INFO_NS 
 32  from pyxmpp.jabber.disco import DiscoInfo,DiscoItems,DiscoIdentity 
 33  from pyxmpp.jabber import disco 
 34  from pyxmpp.client import Client 
 35  from pyxmpp.stanza import Stanza 
 36  from pyxmpp.cache import CacheSuite 
 37  from pyxmpp.utils import from_utf8 
 38  from pyxmpp.interfaces import IFeaturesProvider 
 39   
40 -class JabberClient(Client):
41 """Base class for a Jabber client. 42 43 :Ivariables: 44 - `disco_items`: default Disco#items reply for a query to an empty node. 45 - `disco_info`: default Disco#info reply for a query to an empty node -- 46 provides information about the client and its supported fetures. 47 - `disco_identity`: default identity of the default `disco_info`. 48 - `register`: when `True` than registration will be started instead of authentication. 49 :Types: 50 - `disco_items`: `DiscoItems` 51 - `disco_info`: `DiscoInfo` 52 - `register`: `bool` 53 """
54 - def __init__(self,jid=None, password=None, server=None, port=5222, 55 auth_methods=("sasl:DIGEST-MD5","digest"), 56 tls_settings=None, keepalive=0, 57 disco_name=u"pyxmpp based Jabber client", disco_category=u"client", 58 disco_type=u"pc"):
59 """Initialize a JabberClient object. 60 61 :Parameters: 62 - `jid`: user full JID for the connection. 63 - `password`: user password. 64 - `server`: server to use. If not given then address will be derived form the JID. 65 - `port`: port number to use. If not given then address will be derived form the JID. 66 - `auth_methods`: sallowed authentication methods. SASL authentication mechanisms 67 in the list should be prefixed with "sasl:" string. 68 - `tls_settings`: settings for StartTLS -- `TLSSettings` instance. 69 - `keepalive`: keepalive output interval. 0 to disable. 70 - `disco_name`: name of the client identity in the disco#info 71 replies. 72 - `disco_category`: category of the client identity in the disco#info 73 replies. The default of u'client' should be the right choice in 74 most cases. 75 - `disco_type`: type of the client identity in the disco#info 76 replies. Use `the types registered by Jabber Registrar <http://www.jabber.org/registrar/disco-categories.html>`__ 77 :Types: 78 - `jid`: `pyxmpp.JID` 79 - `password`: `unicode` 80 - `server`: `unicode` 81 - `port`: `int` 82 - `auth_methods`: sequence of `str` 83 - `tls_settings`: `pyxmpp.TLSSettings` 84 - `keepalive`: `int` 85 - `disco_name`: `unicode` 86 - `disco_category`: `unicode` 87 - `disco_type`: `unicode` 88 """ 89 90 Client.__init__(self,jid,password,server,port,auth_methods,tls_settings,keepalive) 91 self.stream_class = LegacyClientStream 92 self.disco_items=DiscoItems() 93 self.disco_info=DiscoInfo() 94 self.disco_identity=DiscoIdentity(self.disco_info, 95 disco_name, disco_category, disco_type) 96 self.register_feature(u"dnssrv") 97 self.register_feature(u"stringprep") 98 self.register_feature(u"urn:ietf:params:xml:ns:xmpp-sasl#c2s") 99 self.cache = CacheSuite(max_items = 1000) 100 self.__logger = logging.getLogger("pyxmpp.jabber.JabberClient")
101 102 # public methods 103
104 - def connect(self, register = False):
105 """Connect to the server and set up the stream. 106 107 Set `self.stream` and notify `self.state_changed` when connection 108 succeeds. Additionally, initialize Disco items and info of the client. 109 """ 110 Client.connect(self, register) 111 if register: 112 self.stream.registration_callback = self.process_registration_form
113
114 - def register_feature(self, feature_name):
115 """Register a feature to be announced by Service Discovery. 116 117 :Parameters: 118 - `feature_name`: feature namespace or name. 119 :Types: 120 - `feature_name`: `unicode`""" 121 self.disco_info.add_feature(feature_name)
122
123 - def unregister_feature(self, feature_name):
124 """Unregister a feature to be announced by Service Discovery. 125 126 :Parameters: 127 - `feature_name`: feature namespace or name. 128 :Types: 129 - `feature_name`: `unicode`""" 130 self.disco_info.remove_feature(feature_name)
131
132 - def submit_registration_form(self, form):
133 """Submit a registration form 134 135 :Parameters: 136 - `form`: the form to submit 137 :Types: 138 - `form`: `pyxmpp.jabber.dataforms.Form`""" 139 self.stream.submit_registration_form(form)
140 141 # private methods
142 - def __disco_info(self,iq):
143 """Handle a disco#info request. 144 145 `self.disco_get_info` method will be used to prepare the query response. 146 147 :Parameters: 148 - `iq`: the IQ stanza received. 149 :Types: 150 - `iq`: `pyxmpp.iq.Iq`""" 151 q=iq.get_query() 152 if q.hasProp("node"): 153 node=from_utf8(q.prop("node")) 154 else: 155 node=None 156 info=self.disco_get_info(node,iq) 157 if isinstance(info,DiscoInfo): 158 resp=iq.make_result_response() 159 self.__logger.debug("Disco-info query: %s preparing response: %s with reply: %s" 160 % (iq.serialize(),resp.serialize(),info.xmlnode.serialize())) 161 resp.set_content(info.xmlnode.copyNode(1)) 162 elif isinstance(info,Stanza): 163 resp=info 164 else: 165 resp=iq.make_error_response("item-not-found") 166 self.__logger.debug("Disco-info response: %s" % (resp.serialize(),)) 167 self.stream.send(resp)
168
169 - def __disco_items(self,iq):
170 """Handle a disco#items request. 171 172 `self.disco_get_items` method will be used to prepare the query response. 173 174 :Parameters: 175 - `iq`: the IQ stanza received. 176 :Types: 177 - `iq`: `pyxmpp.iq.Iq`""" 178 q=iq.get_query() 179 if q.hasProp("node"): 180 node=from_utf8(q.prop("node")) 181 else: 182 node=None 183 items=self.disco_get_items(node,iq) 184 if isinstance(items,DiscoItems): 185 resp=iq.make_result_response() 186 self.__logger.debug("Disco-items query: %s preparing response: %s with reply: %s" 187 % (iq.serialize(),resp.serialize(),items.xmlnode.serialize())) 188 resp.set_content(items.xmlnode.copyNode(1)) 189 elif isinstance(items,Stanza): 190 resp=items 191 else: 192 resp=iq.make_error_response("item-not-found") 193 self.__logger.debug("Disco-items response: %s" % (resp.serialize(),)) 194 self.stream.send(resp)
195
196 - def _session_started(self):
197 """Called when session is started. 198 199 Activates objects from `self.interface_provides` by installing 200 their disco features.""" 201 Client._session_started(self) 202 for ob in self.interface_providers: 203 if IFeaturesProvider.providedBy(ob): 204 for ns in ob.get_features(): 205 self.register_feature(ns)
206 207 # methods to override 208
209 - def authorized(self):
210 """Handle "authorized" event. May be overriden in derived classes. 211 By default: request an IM session and setup Disco handlers.""" 212 Client.authorized(self) 213 self.stream.set_iq_get_handler("query",DISCO_ITEMS_NS,self.__disco_items) 214 self.stream.set_iq_get_handler("query",DISCO_INFO_NS,self.__disco_info) 215 disco.register_disco_cache_fetchers(self.cache,self.stream)
216
217 - def disco_get_info(self,node,iq):
218 """Return Disco#info data for a node. 219 220 :Parameters: 221 - `node`: the node queried. 222 - `iq`: the request stanza received. 223 :Types: 224 - `node`: `unicode` 225 - `iq`: `pyxmpp.iq.Iq` 226 227 :return: self.disco_info if `node` is empty or `None` otherwise. 228 :returntype: `DiscoInfo`""" 229 to=iq.get_to() 230 if to and to!=self.jid: 231 return iq.make_error_response("recipient-unavailable") 232 if not node and self.disco_info: 233 return self.disco_info 234 return None
235
236 - def disco_get_items(self,node,iq):
237 """Return Disco#items data for a node. 238 239 :Parameters: 240 - `node`: the node queried. 241 - `iq`: the request stanza received. 242 :Types: 243 - `node`: `unicode` 244 - `iq`: `pyxmpp.iq.Iq` 245 246 :return: self.disco_info if `node` is empty or `None` otherwise. 247 :returntype: `DiscoInfo`""" 248 to=iq.get_to() 249 if to and to!=self.jid: 250 return iq.make_error_response("recipient-unavailable") 251 if not node and self.disco_items: 252 return self.disco_items 253 return None
254
255 - def process_registration_form(self, stanza, form):
256 """Fill-in the registration form provided by the server. 257 258 This default implementation fills-in "username" and "passwords" 259 fields only and instantly submits the form. 260 261 :Parameters: 262 - `stanza`: the stanza received. 263 - `form`: the registration form. 264 :Types: 265 - `stanza`: `pyxmpp.iq.Iq` 266 - `form`: `pyxmpp.jabber.dataforms.Form` 267 """ 268 _unused = stanza 269 self.__logger.debug(u"default registration callback started. auto-filling-in the form...") 270 if not 'FORM_TYPE' in form or 'jabber:iq:register' not in form['FORM_TYPE'].values: 271 raise RuntimeError, "Unknown form type: %r %r" % (form, form['FORM_TYPE']) 272 for field in form: 273 if field.name == u"username": 274 self.__logger.debug(u"Setting username to %r" % (self.jid.node,)) 275 field.value = self.jid.node 276 elif field.name == u"password": 277 self.__logger.debug(u"Setting password to %r" % (self.password,)) 278 field.value = self.password 279 elif field.required: 280 self.__logger.debug(u"Unknown required field: %r" % (field.name,)) 281 raise RuntimeError, "Unsupported required registration form field %r" % (field.name,) 282 else: 283 self.__logger.debug(u"Unknown field: %r" % (field.name,)) 284 self.submit_registration_form(form)
285 286 # vi: sts=4 et sw=4 287