diff --git a/CHANGELOG.md b/CHANGELOG.md index f20c201..238c1f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## [unreleased] +### Added + +* Add `federation.hostmeta` generators for Matrix client and server well-known files. + Django views and url configuration also included for convenience. + ## [0.21.0] - 2020-12-20 ### Added diff --git a/docs/usage.rst b/docs/usage.rst index dbd5483..c0b9eb6 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -149,6 +149,8 @@ Generator classes .. autoclass:: federation.hostmeta.generators.DiasporaHostMeta .. autoclass:: federation.hostmeta.generators.DiasporaWebFinger .. autoclass:: federation.hostmeta.generators.DiasporaHCard +.. autoclass:: federation.hostmeta.generators.MatrixClientWellKnown +.. autoclass:: federation.hostmeta.generators.MatrixServerWellKnown .. autoclass:: federation.hostmeta.generators.NodeInfo .. autoclass:: federation.hostmeta.generators.RFC7033Webfinger .. autoclass:: federation.hostmeta.generators.SocialRelayWellKnown @@ -189,6 +191,8 @@ It must be installed separately. .. autofunction:: federation.entities.activitypub.django.views.activitypub_object_view .. autofunction:: federation.hostmeta.django.generators.rfc7033_webfinger_view +.. autofunction:: federation.hostmeta.django.generators.matrix_client_wellknown_view +.. autofunction:: federation.hostmeta.django.generators.matrix_server_wellknown_view .. autofunction:: federation.hostmeta.django.generators.nodeinfo2_view Configuration @@ -209,6 +213,7 @@ Some settings need to be set in Django settings. An example is below: "get_object_function": "myproject.utils.get_object", "get_private_key_function": "myproject.utils.get_private_key", "get_profile_function": "myproject.utils.get_profile", + "matrix_config_function": "myproject.utils.matrix_config_funct", "nodeinfo2_function": "myproject.utils.get_nodeinfo2_data", "process_payload_function": "myproject.utils.process_payload", "search_path": "/search/?q=", @@ -219,6 +224,23 @@ Some settings need to be set in Django settings. An example is below: * ``get_object_function`` should be the full path to a function that will return the object matching the ActivityPub ID for the request object passed to this function. * ``get_private_key_function`` should be the full path to a function that will accept a federation ID (url, handle or guid) and return the private key of the user (as an RSA object). Required for example to sign outbound messages in some cases. * ``get_profile_function`` should be the full path to a function that should return a ``Profile`` entity. The function should take one or more keyword arguments: ``fid``, ``handle``, ``guid`` or ``request``. It should look up a profile with one or more of the provided parameters. +* ``matrix_config_function`` (optional) function that returns a Matrix configuration dictionary, with the following objects: + +:: + + { + # Location of the homeserver (not server name) + "homeserver_base_url": "https://matrix.domain.tld", + # Homeserver domain and port (not server domain) + "homeserver_domain_with_port": "matrix.domain.tld:443", + # (Optional) location of identity server + "identity_server_base_url": "https://id.domain.tld", + # (Optional) other keys to include in the client well-known (must be a dictionary) + "client_wellknown_other_keys": { + "org.foo.key" "barfoo", + }, + } + * ``nodeinfo2_function`` (optional) function that returns data for generating a `NodeInfo2 document `_. Once configured the path ``/.well-known/x-nodeinfo2`` will automatically generate a NodeInfo2 document. The function should return a ``dict`` corresponding to the NodeInfo2 schema, with the following minimum items: :: diff --git a/federation/hostmeta/django/generators.py b/federation/hostmeta/django/generators.py index 54f4811..b81c2da 100644 --- a/federation/hostmeta/django/generators.py +++ b/federation/hostmeta/django/generators.py @@ -1,8 +1,11 @@ import logging +# noinspection PyPackageRequirements from django.http import HttpResponseBadRequest, JsonResponse, HttpResponseNotFound -from federation.hostmeta.generators import RFC7033Webfinger, generate_nodeinfo2_document +from federation.hostmeta.generators import ( + RFC7033Webfinger, generate_nodeinfo2_document, MatrixClientWellKnown, MatrixServerWellKnown, +) from federation.utils.django import get_configuration, get_function_from_config from federation.utils.text import get_path_from_url @@ -19,6 +22,34 @@ def nodeinfo2_view(request, *args, **kwargs): return JsonResponse(generate_nodeinfo2_document(**nodeinfo2)) +def matrix_client_wellknown_view(request, *args, **kwargs): + try: + matrix_config_func = get_function_from_config("matrix_config_function") + except AttributeError: + return HttpResponseBadRequest("Not configured") + matrix_config = matrix_config_func() + + wellknown = MatrixClientWellKnown( + homeserver_base_url=matrix_config["homeserver_base_url"], + identity_server_base_url=matrix_config.get("identity_server_base_url"), + other_keys=matrix_config.get("client_wellknown_other_keys"), + ) + return JsonResponse(wellknown.render()) + + +def matrix_server_wellknown_view(request, *args, **kwargs): + try: + matrix_config_func = get_function_from_config("matrix_config_function") + except AttributeError: + return HttpResponseBadRequest("Not configured") + matrix_config = matrix_config_func() + + wellknown = MatrixServerWellKnown( + homeserver_domain_with_port=matrix_config["homeserver_domain_with_port"], + ) + return JsonResponse(wellknown.render()) + + def rfc7033_webfinger_view(request, *args, **kwargs): """ Django view to generate an RFC7033 webfinger. diff --git a/federation/hostmeta/django/urls.py b/federation/hostmeta/django/urls.py index a71376d..1d77634 100644 --- a/federation/hostmeta/django/urls.py +++ b/federation/hostmeta/django/urls.py @@ -1,9 +1,14 @@ +# noinspection PyPackageRequirements from django.conf.urls import url from federation.hostmeta.django import rfc7033_webfinger_view -from federation.hostmeta.django.generators import nodeinfo2_view +from federation.hostmeta.django.generators import ( + nodeinfo2_view, matrix_client_wellknown_view, matrix_server_wellknown_view, +) urlpatterns = [ + url(r'^.well-known/matrix/client$', matrix_client_wellknown_view, name="matrix-client-wellknown"), + url(r'^.well-known/matrix/server$', matrix_server_wellknown_view, name="matrix-server-wellknown"), url(r'^.well-known/webfinger$', rfc7033_webfinger_view, name="rfc7033-webfinger"), url(r'^.well-known/x-nodeinfo2$', nodeinfo2_view, name="nodeinfo2"), ] diff --git a/federation/hostmeta/generators.py b/federation/hostmeta/generators.py index a185a0d..ad81250 100644 --- a/federation/hostmeta/generators.py +++ b/federation/hostmeta/generators.py @@ -3,6 +3,7 @@ import os import warnings from base64 import b64encode from string import Template +from typing import Dict from jsonschema import validate from jsonschema.exceptions import ValidationError @@ -331,6 +332,43 @@ def get_nodeinfo_well_known_document(url, document_path=None): } +class MatrixClientWellKnown: + """ + Matrix Client well-known as per https://matrix.org/docs/spec/client_server/r0.6.1#server-discovery + """ + def __init__(self, homeserver_base_url: str, identity_server_base_url: str = None, other_keys: Dict = None): + self.homeserver_base_url = homeserver_base_url + self.identity_server_base_url = identity_server_base_url + self.other_keys = other_keys + + def render(self): + doc = { + "m.homeserver": { + "base_url": self.homeserver_base_url, + } + } + if self.identity_server_base_url: + doc["m.identity_server"] = { + "base_url": self.identity_server_base_url, + } + if self.other_keys: + doc.update(self.other_keys) + return doc + + +class MatrixServerWellKnown: + """ + Matrix Server well-known as per https://matrix.org/docs/spec/server_server/r0.1.4#server-discovery + """ + def __init__(self, homeserver_domain_with_port: str): + self.homeserver_domain_with_port = homeserver_domain_with_port + + def render(self): + return { + "m.server": self.homeserver_domain_with_port, + } + + class RFC7033Webfinger: """ RFC 7033 webfinger - see https://tools.ietf.org/html/rfc7033