Hooks¶
You can customize certain XMPP Server behaviour by installing hooks, in the form of classes that implement the behaviour you want.
For example, say you wanted the XMPP username to be independent of the
Django username, and made a custom user model with an xmpp_username
field. Then you could install an ‘auth’ hook as follows:
from xmppserver import hooks
class MyAuthHook(hooks.DefaultAuthHook):
@staticmethod
def get_webuser_username(user):
return user.xmpp_username
@staticmethod
def get_webuser_by_username(username):
return MyUserModel.objects.get(xmpp_username=username)
hooks.set_hook('auth', MyAuthHook)
If you’re writing a reusable app, a good place to install hooks from may be your app’s
AppConfig.ready method (see the Django documentation). Otherwise, you can do
it from more or less any module that’s loaded on startup, such as your routing.py.
Only one hook can be installed for each hook type. If more than one app might
want to install a hook of the same type, they can specify a priority.
set_hook will then install the hook that has the highest priority.
Alternative hooks¶
xmppserver comes with two non-default roster hooks that may be useful
if you’re just making a simple helpdesk or community app.
The xmppserver.hooks.roster.EveryoneRosterHook returns all
Django users, and xmppserver.hooks.roster.StaffRosterHook
returns all Django staff users (i.e., users that have access to the
Django admin site), thus allowing users to chat with any of your
helpdesk staff.
The optional components xmppserver.sessiondb and xmppserver.rosterdb
also have their own hooks, which are installed automatically (with priority 1)
if you add them to your INSTALLED_APPS.
Hook functions¶
-
set_hook(type, hook, priority=1000)¶ Install new hook. Hooks are only installed if their priority is higher than the previously installed hook. The default hooks have priority 0.
Parameters: - type (str) – Hook type (either ‘auth’, ‘roster’, or ‘session’)
- hook – Hook class
- priority – Hook priority
-
get_hook(type)¶ Get current hook.
Parameters: type (str) – Hook type Returns: Hook class
Writing hooks¶
Hooks are instantiated by XMPP streams when their functionality is needed. For example, the authentication hook is instantiated when an XMPP client is ready to authenticate. Each hook instance belongs to exactly one XMPP stream, and is generally kept around until the stream is unbound (disconnected).
Most hook methods are asynchronous, but the Django ORM is synchronous.
If you need to access the Django database from an asynchronous method,
you can write an ordinary synchronous method and use
channels.db.database_sync_to_async on it, as explained in the
Django Channels documentation.
Authentication hooks¶
These hooks have type ‘auth’, and hook instances are available as
stream.auth_hook. The default hook (DefaultAuthHook) implements
authentication for user models supported by django.contrib.auth.
This should cover the need of many projects, but if it doesn’t cover
yours, you can write your own hook.
The default hook keeps track of the currently authenticated Django user
in self.user, and for the convenience of other hooks and plugins,
also stores it in stream.bound_user once the stream is bound.
-
bind(stream)¶ Prepare to bind the stream. The client has authenticated, but has not yet requested a resource identifier for the stream. The bare JID is available in
stream.boundjid.Parameters: stream – XMPP stream object
-
unbind(stream)¶ Unbind the stream. The connection to the client has been closed or lost.
Parameters: stream – XMPP stream object
-
static
get_webuser_username()¶ Get the XMPP username of a Django user. The default hook calls
user.get_username().This method must be synchronous and static. It may be called from a Django view in order to generate credentials before an XMPP stream is opened, or from a Django signal receiver when the user is being deleted.
Parameters: user – Django user object Returns: XMPP username
-
static
get_webuser_by_username()¶ Get the Django user of an XMPP username. The default hook calls
get_user_model().objects.get_by_natural_key(username).This method must be synchronous.
Parameters: username (str) – XMPP username Returns: Django user object Raises: ObjectDoesNotExist – If user does not exist
-
check_webuser(stream, user, username)¶ Authenticate using Django session cookies. The Django user identified by the session cookies is provided, but the object may be lazy, so checking it may result in a database access. The default hook checks whether the Django user is authenticated, and that the Django user’s XMPP username matches the XMPP client’s username.
Parameters: - user – Django user object
- username (str) – Username requested by XMPP client
Returns: True if authentication is successful
-
check_token(stream, username, token)¶ Authenticate using a session token. The default hook validates the token’s HMAC signature and timestamp, and checks whether the Django user is active.
Parameters: - username (str) – Username requested by XMPP client
- token (str) – Token provided by XMPP client
Returns: True if authentication is successful
-
check_password(stream, username, password)¶ Authenticate using a plaintext password. Should return True if authentication was successful. The default hook checks the password, and whether the Django user is active.
Parameters: - username (str) – Username requested by XMPP client
- password (str) – Password provided by XMPP client
Returns: True if authentication is successful
-
valid_contact(username)¶ Check whether the given XMPP username exists and could be contacted by the bound user. The default hook checks whether the Django user is active.
Parameters: username (str) – XMPP username Returns: True if the contact exists
-
create_user(username, password)¶ Register a new user. Only used if the
XMPP_ALLOW_REGISTRATIONconfiguration option is enabled.Parameters: - username (str) – Username requested by XMPP client
- password (str) – Password requested by XMPP client
Returns: True if successful, False if username already exists
Raises: XMPPError – If unsuccessful for any other reason
-
change_password(password)¶ Change the bound user’s password. Only used if the
XMPP_ALLOW_REGISTRATIONconfiguration option is enabled.Parameters: password (str) – Password requested by XMPP client Raises: XMPPError – If unsuccessful
-
delete_user()¶ Delete the bound user. Only used if the
XMPP_ALLOW_REGISTRATIONconfiguration option is enabled.Raises: XMPPError – If unsuccessful
Roster hooks¶
These hooks have type ‘roster’, and hook instances are available as
stream.roster_hook. The default hook (DefaultRosterHook) doesn’t
do anything, but xmppserver comes with two alternatives:
- If you’re just making a simple helpdesk or community app, one of the alternative hooks might work for you.
- If you wish to have a full-featured XMPP server, then you can
add
'xmppserver.rosterdb'to yourINSTALLED_APPS. It installs a full-featured roster hook, backed by your database.
Or if you want, you can write your own hook.
-
bind(stream) The stream has been bound. The client has authenticated and assigned a resource identifier to the stream, and now needs its roster. The client’s full JID is available as
stream.boundjid.Parameters: stream – XMPP stream object
-
unbind(stream) Unbind the stream. The connection to the client has been closed or lost.
Parameters: stream – XMPP stream object
-
get_contacts(user)¶ Get the roster fields of all the contacts of the given user.
The roster is represented as a sequence of (jid, values) tuples, where the values are dictionaries with XMPP roster fields, such as ‘name’, ‘groups’, and ‘subscription’.
If subscription would be ‘none’, then this method can omit the field for brevity, but the other hook methods should not do so.
Parameters: user (JID) – User JID Returns: Roster sequence, or None if roster doesn’t exist
-
get_contact(user, jid)¶ Get the roster fields of a specific contact of the given user.
Since this method may be involved in roster pushes, the ‘subscription’ field must be included, even when it is ‘none’.
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
Returns: Roster fields, or None if contact doesn’t exist
Raises: XMPPError – If unsuccessful
-
update_contact(user, jid, values)¶ Create or update a contact on behalf of the given user. The method should only change names and groups, not subscriptions.
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
- values – Roster fields requested by XMPP client
Returns: Updated roster fields
Raises: XMPPError – If unsuccessful
-
remove_contact(user, jid)¶ Delete a contact on behalf of the given user. The method should verify that there are no active subscriptions from or to the contact (and that it’s not in the Pending-Out state either), before deleting it from the roster.
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
Returns: True if successful, False if subscriptions exist, or None if contact doesn’t exist
Raises: XMPPError – If unsuccessful
-
get_pending(user)¶ Get all (potential) contacts of the given user that are currently in the Pending-In state.
Parameters: user (JID) – User JID Returns: Sequence of (JID, stanza) tuples, or None
-
is_pending(user, jid)¶ Returns whether a specific (potential) contact is currently in the Pending-In state.
Parameters: user (JID) – User JID Returns: True if pending, False if not pending, or None if contact doesn’t exist
-
inbound_subscribe(user, jid, stanza='')¶ Transition a (potential) contact into the Pending-In state, unless a subscription from the contact already exists. If the contact is pre-approved, then instead accept the ‘from’ subscription.
If the contact isn’t in the roster yet, it should not be added to it, but tracked outside of the roster until the user either adds the contact or denies the request.
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
- stanza – Presence stanza as an XML string
Returns: New roster fields if pre-approved, True if now pending, False if already pending, or None if already subscribed
Raises: XMPPError – If unsuccessful
-
inbound_subscribed(user, jid)¶ Transition a contact out of the Pending-Out state, and add the ‘to’ subscription.
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
Returns: New roster fields, or None if subscription was not pending
Raises: XMPPError – If unsuccessful
-
inbound_unsubscribe(user, jid)¶ Cancel a Pending-In state or an active ‘from’ subscription.
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
Returns: Old roster fields, or None if already unsubscribed
Raises: XMPPError – If unsuccessful
-
inbound_unsubscribed(user, jid)¶ Cancel a Pending-Out state or an active ‘to’ subscription.
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
Returns: Old roster fields, or None if already unsubscribed
Raises: XMPPError – If unsuccessful
-
outbound_subscribe(user, jid, stanza='')¶ Transition a contact into the Pending-Out state (creating it if necessary), unless a subscription to the contact already exists.
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
- stanza – Presence stanza as an XML string
Returns: New roster fields, or None if already subscribed
Raises: XMPPError – If unsuccessful
-
outbound_subscribed(user, jid)¶ Transition a contact out of the Pending-In state, and add the ‘from’ subscription (creating it if necessary). If no subscription is pending or active yet, then pre-approve the contact (creating it if necessary).
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
Returns: New roster fields, or None if already subscribed
Raises: XMPPError – If unsuccessful
-
outbound_unsubscribe(user, jid)¶ Cancel a Pending-Out state or an active ‘to’ subscription.
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
Returns: Old roster fields, or None if already unsubscribed
Raises: XMPPError – If unsuccessful
-
outbound_unsubscribed(user, jid)¶ Cancel a Pending-In state, an active ‘from’ subscription, or a pre-approval.
Parameters: - user (JID) – User JID
- jid (JID) – Contact JID
Returns: Old roster fields, or None if already unsubscribed
Raises: XMPPError – If unsuccessful
Session hooks¶
These hooks have type ‘session’, and hook instances are available as
stream.session_hook. The default hook (DefaultSessionHook) doesn’t
do much other than creating session IDs. If you use it, the server does
work, but with a somewhat reduced feature set.
Presence should mostly work (because the server will fall back to XMPP
presence probes if the session database isn’t available), but things like
resource discovery and session management might not. Currently, sending
messages to users that are offline would also not result in error messages.
If you wish to have a full-featured XMPP server, then you can add
'xmppserver.sessiondb' to your INSTALLED_APPS.
It installs a full-featured session hook, backed by your database.
HOWEVER, using your database for session data could be slow.
It may be better to use a session hook that uses something like
Redis instead, but xmppserver does not currently provide such a hook.
-
bind(stream) Bind the stream. The client has authenticated and requested a resource identifier for the stream. The requested full JID is available as
stream.boundjid. The method should verify that the requested resource is not already bound by a different stream.Parameters: stream – XMPP stream object Returns: True if successful, False if the resource is in use Raises: XMPPError – If unsuccessful
-
unbind(stream) Unbind the stream. The connection to the client has been closed or lost.
Parameters: stream – XMPP stream object
-
set_presence(priority, stanza=None)¶ Set priority and availability of the bound stream.
Parameters: - priority – Presence priority if available, or None if unavailable
- stanza – Presence stanza as an XML string, or None
-
get_presence(jid)¶ Retrieve priority and availability of a specific resource.
Parameters: jid (JID) – Full JID Returns: (priority, stanza) tuple, or None if unavailable
-
get_all_presences(username)¶ Retrieve priorities and availabilities of all resources belonging to a given user.
Parameters: username – XMPP username Returns: Sequence of (resource, priority, stanza) tuples
-
get_all_roster_presences(usernames)¶ Retrieve priorities and availabilities of all resources belonging to any of the given users. Implementing this method is optional, but may speed up roster queries.
Parameters: usernames – List of usernames Returns: Sequence of (username, resource, priority, stanza) tuples
-
get_resource(jid)¶ Retrieve priority of a specific resource.
Parameters: jid (JID) – Full JID Returns: Presence priority if available, or None if unavailable
-
get_all_resources(username)¶ Retrieve priorities of all resources belonging to a given user.
Parameters: username – XMPP username Returns: Sequence of (resource, priority) tuples
-
get_preferred_resource(username)¶ Retrieve the resource with the highest priority among all available resources with non-negative priorities that belongs to a given user.
It’s also possible to return an empty string in order to broadcast to all available resources.
Parameters: username – XMPP username Returns: Resource, or None if no such resource exists
-
kill_resource(jid)¶ Remove binding records of a specific resource. Used if the server hosting the corresponding stream seems to have crashed.
Parameters: jid (JID) – Full JID