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_REGISTRATION configuration 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_REGISTRATION configuration 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_REGISTRATION configuration 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:

  1. If you’re just making a simple helpdesk or community app, one of the alternative hooks might work for you.
  2. If you wish to have a full-featured XMPP server, then you can add 'xmppserver.rosterdb' to your INSTALLED_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