Skip to content

Commit

Permalink
⬆️ Migrate to SliXMPP from deprecated SleekXMPP
Browse files Browse the repository at this point in the history
  • Loading branch information
immanuelfodor committed Apr 11, 2020
1 parent 589838c commit ef1d663
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 25 deletions.
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ EXPOSE 5000
ENV LOG_LEVEL=debug

COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt

RUN set -x && \
apk add --no-cache --virtual .build-deps gcc musl-dev libffi-dev && \
pip install --no-cache-dir -r requirements.txt && \
apk del .build-deps

COPY .pyenv gateway.py gateway_helper.py xmpp_client.py ./

Expand Down
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# XMPP MUC Message Gateway

A simple Docker-based microservice that forwards a JSON object received in an HTTP POST request to an XMPP MUC room ([XEP-0045](https://xmpp.org/extensions/xep-0045.html)) through a `TLSv1.2` client connection using the [SleekXMPP](https://sleekxmpp.readthedocs.io/en/latest/) Python lib. Supports multiple rooms and sender aliases with different associated URLs, e.g, one for Grafana, another for `curl`, and so on. Can convert JSON to YAML for better message readability. Easy installation with `docker-compose`.
A simple Docker-based microservice that forwards a JSON object received in an HTTP POST request to an XMPP MUC room ([XEP-0045](https://xmpp.org/extensions/xep-0045.html))
through a `TLSv1.2` client connection using the [SliXMPP](https://github.com/poezio/slixmpp) Python lib. Supports multiple rooms and sender aliases with different
associated URLs, e.g, one for Grafana, another for `curl`, and so on. Can convert JSON to YAML for better message readability. Easy installation with `docker-compose`.

## Table of contents <!-- omit in toc -->

Expand All @@ -27,11 +29,14 @@ sudo systemctl enable docker
sudo systemctl start docker
```

Of course, you'll also need a MUC-enabled [XMPP server](https://xmpp.org/software/servers.html) up and running with at least one MUC room and two users joined in the same room (the webhook user and _you_). Explaining setting these up is way beyond the scope of this document, so please see the online docs for proper instructions. They say [Prosody](http://prosody.im/) is beginner-friendly.
Of course, you'll also need a MUC-enabled [XMPP server](https://xmpp.org/software/servers.html) up and running with at least one MUC room and two users joined
in the same room (the webhook user and _you_). Explaining setting these up is way beyond the scope of this document, so please see the online docs for proper
instructions. They say [Prosody](http://prosody.im/) is beginner-friendly.

## Configuration

You can configure the gateway as you like, but you must have at least one valid XMPP user JID, password and an associated `token:room-JID:nick` combo in a `.pyenv` file to make it work.
You can configure the gateway as you like, but you must have at least one valid XMPP user JID, password and an associated `token:room-JID:nick` combo in a
`.pyenv` file to make it work.

Create a copy of the example config file provided, and add the parameters according to the example:

Expand Down Expand Up @@ -118,9 +123,12 @@ cat /etc/hosts | jq -R -s -c 'split("\n")' | curl -K $HOME/.curlrc.xmpp -d @-

### Network topology

You can put the gateway behind a reverse proxy preferably with an SSL cert from, e.g., Let's Encrypt. You can also host the XMPP message gateway on the same domain as your XMPP server by using a _subpath configuration_, for example, matching on the `/post/...` part of the URL, see the `push.py` in this repo for details. Of course, adding another _server block_ for a new subdomain is also convenient.
You can put the gateway behind a reverse proxy preferably with an SSL cert from, e.g., Let's Encrypt. You can also host the XMPP message gateway on the same
domain as your XMPP server by using a _subpath configuration_, for example, matching on the `/post/...` part of the URL, see the `push.py` in this repo for
details. Of course, adding another _server block_ for a new subdomain is also convenient.

Malicious actors could only send spam notifications if they would know any of your long and random tokens, otherwise, the messages are not routed to a valid XMPP MUC room. However, the following setup is desirable when we don't want to expose it to the open, and it's also easier to achieve.
Malicious actors could only send spam notifications if they would know any of your long and random tokens, otherwise, the messages are not routed to a valid
XMPP MUC room. However, the following setup is desirable when we don't want to expose it to the open, and it's also easier to achieve.

If you host your message source, XMPP server and this gateway on the same network, you can use local hostnames, DNS or IP addresses to set everything up:
- Add the gateway to the data source (e.g., [Grafana webhook](https://github.com/grafana/grafana/issues/6955#issuecomment-470900383)) with the gateway's local hostname/IP + port e.g., `http://10.0.0.80:10080/post/rANd0m-tOkEN`
Expand Down Expand Up @@ -184,8 +192,9 @@ You can send test messages to see it in action, or experiment with the programma

- Tested with Grafana webhooks and Prosody as an XMPP server, but in theory, it should work with any source capable of sending HTTP POST requests with valid JSON objects (e.g., `curl`).
- The posted JSON object is formatted and indented by 2 spaces, no further processing is being made. The padded object's size is limited by your XMPP server's maximum stanza or body size.
- [OMEMO](https://omemo.top/) is not yet supported in SleekXMPP by default, but there is an interesting project out there which worth exploring: [sleekxmpp-omemo-plugin](https://gitlab.com/ecartman/sleekxmpp-omemo-plugin)
- `pyasn1` and `pyasn1-modules` are not included in the `requirements.txt` as optional SleekXMPP dependencies, so the SSL cert verification and expiration check won't work, see [this issue](https://github.com/fritzy/SleekXMPP/issues/477) for further info.
- 2020-04-11: Updated to SliXMPP, the successor of SleekXMPP, the following old notes are not validated with the new version:
- [OMEMO](https://omemo.top/) is not yet supported in SleekXMPP by default, but there is an interesting project out there which worth exploring: [sleekxmpp-omemo-plugin](https://gitlab.com/ecartman/sleekxmpp-omemo-plugin)
- `pyasn1` and `pyasn1-modules` are not included in the `requirements.txt` as optional SleekXMPP dependencies, so the SSL cert verification and expiration check won't work, see [this issue](https://github.com/fritzy/SleekXMPP/issues/477) for further info.
- If you use RocketChat, there is a similar project for that service here: [immanuelfodor/rocketchat-push-gateway](https://github.com/immanuelfodor/rocketchat-push-gateway)

## Contact
Expand Down
5 changes: 2 additions & 3 deletions gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,8 @@ def push_send(token):
try:
xmpp = MUCBot(os.environ['JID_FROM_USER'], os.environ['JID_FROM_PASS'],
known_rooms[token]['room'], known_rooms[token]['nick'], message)

if xmpp.connect(reattempt=False):
xmpp.process(threaded=False)
xmpp.connect()
xmpp.process(forever=False)

app.logger.info('Forwarded the received message as %s to %s',
known_rooms[token]['nick'], known_rooms[token]['room'])
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ urllib3==1.24.2 # requests 2.21.0 has requirement urllib3<1.25,>=1.21.1
python-dotenv
pyyaml
dnspython # sleekxmpp optional dependency
sleekxmpp
slixmpp
24 changes: 11 additions & 13 deletions xmpp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
import sys
import time

import sleekxmpp
import slixmpp


class MUCBot(sleekxmpp.ClientXMPP):
class MUCBot(slixmpp.ClientXMPP):
"""
Based on the SleekXMPP Mulit-User Chat (MUC) bot example
Based on the SliXMPP Mulit-User Chat (MUC) bot example
:see: http://sleekxmpp.com/getting_started/muc.html
:see: https://slixmpp.readthedocs.io/getting_started/muc.html
:see: https://github.com/poezio/slixmpp/blob/master/docs/getting_started/muc.rst
"""

def __init__(self, jid, password, room, nick, message):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
slixmpp.ClientXMPP.__init__(self, jid, password)

self.room = room
self.nick = nick
Expand All @@ -29,10 +30,10 @@ def __init__(self, jid, password, room, nick, message):
self.ssl_version = ssl.PROTOCOL_TLSv1_2

def start(self, event):
self.getRoster()
self.sendPresence()
self.get_roster()
self.send_presence()

self.plugin['xep_0045'].joinMUC(self.room, self.nick, wait=True)
self.plugin['xep_0045'].join_muc(self.room, self.nick, wait=True)

self.send_message(mto=self.room, mbody=self.message, mtype='groupchat')
time.sleep(1)
Expand All @@ -56,8 +57,5 @@ def start(self, event):
logging.basicConfig(level=logging.DEBUG)

xmpp = MUCBot(opts.jid, opts.password, opts.room, opts.nick, " ".join(args))

if xmpp.connect(reattempt=False):
xmpp.process(threaded=False)
else:
print("connect() failed")
xmpp.connect()
xmpp.process(forever=False)

0 comments on commit ef1d663

Please sign in to comment.