From 3730a3d9b85d7356897b8686072349e5e1a98562 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Wed, 21 Feb 2018 19:20:46 +0200 Subject: [PATCH 1/4] Make JSON webfinger support webfinger profile url --- docs/usage.rst | 7 +++++-- federation/hostmeta/django/generators.py | 17 +++++++++-------- federation/hostmeta/generators.py | 9 ++++++++- federation/tests/hostmeta/django/settings.py | 2 +- .../tests/hostmeta/django/test_generators.py | 13 +++++++++---- federation/tests/hostmeta/django/utils.py | 7 +++++-- federation/tests/hostmeta/test_generators.py | 6 ++++++ 7 files changed, 43 insertions(+), 18 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index a79360f..80cbf2c 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -111,11 +111,14 @@ Some settings need to be set in Django settings. An example is below: FEDERATION = { "base_url": "https://myserver.domain.tld, - "profile_id_function": "myproject.utils.get_profile_id_by_handle", + "get_profile_function": "myproject.utils.get_profile_by_handle", } * ``base_url`` is the base URL of the server, ie protocol://domain.tld. -* ``profile_id_function`` should be the full path to a function that given a handle will return the Diaspora URI format profile ID. +* ``profile_id_function`` should be the full path to a function that given a handle will return a dictionary with information that will be used to generate the webfinger document. The dict should contain the following elements: + + * ``id`` - Diaspora URI format ID. + * ``profile_path`` - profile path for generating an absolute URL to the profile page of the user. Protocols --------- diff --git a/federation/hostmeta/django/generators.py b/federation/hostmeta/django/generators.py index ccc2f5b..8c076d2 100644 --- a/federation/hostmeta/django/generators.py +++ b/federation/hostmeta/django/generators.py @@ -19,7 +19,7 @@ def get_configuration(): } configuration.update(settings.FEDERATION) if not all([ - "profile_id_function" in configuration, + "get_profile_function" in configuration, "base_url" in configuration, "hcard_path" in configuration, ]): @@ -27,12 +27,12 @@ def get_configuration(): return configuration -def get_profile_id_func(): +def get_profile_func(): """ - Import the function to get profile ID by handle. + Import the function to get profile by handle. """ config = get_configuration() - profile_func_path = config.get("profile_id_function") + profile_func_path = config.get("get_profile_function") module_path, func_name = profile_func_path.rsplit(".", 1) module = importlib.import_module(module_path) profile_func = getattr(module, func_name) @@ -50,18 +50,19 @@ def rfc3033_webfinger_view(request, *args, **kwargs): return HttpResponseBadRequest("Invalid resource") handle = resource.replace("acct:", "") - profile_id_func = get_profile_id_func() + profile_func = get_profile_func() try: - profile_id = profile_id_func(handle) + profile = profile_func(handle) except Exception as exc: - logger.warning("rfc3033_webfinger_view - Failed to get profile ID by handle %s: %s", handle, exc) + logger.warning("rfc3033_webfinger_view - Failed to get profile by handle %s: %s", handle, exc) return HttpResponseNotFound() config = get_configuration() webfinger = RFC3033Webfinger( - id=profile_id, + id=profile.get('id'), base_url=config.get('base_url'), + profile_path=profile.get('profile_path'), hcard_path=config.get('hcard_path'), ) diff --git a/federation/hostmeta/generators.py b/federation/hostmeta/generators.py index c379f83..4f22dff 100644 --- a/federation/hostmeta/generators.py +++ b/federation/hostmeta/generators.py @@ -287,13 +287,15 @@ class RFC3033Webfinger: :param id: Diaspora ID in URI format :param base_url: The base URL of the server (protocol://domain.tld) + :param profile_path: Profile path for the user (for example `/profile/johndoe/`) :param hcard_path: (Optional) hCard path, defaults to ``/hcard/users/``. :returns: dict """ - def __init__(self, id, base_url, hcard_path="/hcard/users/"): + def __init__(self, id, base_url, profile_path, hcard_path="/hcard/users/"): self.handle, self.guid = parse_profile_diaspora_id(id) self.base_url = base_url self.hcard_path = hcard_path + self.profile_path = profile_path def render(self): return { @@ -309,5 +311,10 @@ class RFC3033Webfinger: "type": "text/html", "href": self.base_url, }, + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": "%s%s" % (self.base_url, self.profile_path), + }, ], } diff --git a/federation/tests/hostmeta/django/settings.py b/federation/tests/hostmeta/django/settings.py index 0bc6327..e4930e6 100644 --- a/federation/tests/hostmeta/django/settings.py +++ b/federation/tests/hostmeta/django/settings.py @@ -4,5 +4,5 @@ INSTALLED_APPS = tuple() FEDERATION = { "base_url": "https://example.com", - "profile_id_function": "federation.tests.hostmeta.django.utils.get_profile_id_by_handle", + "get_profile_function": "federation.tests.hostmeta.django.utils.get_profile_by_handle", } diff --git a/federation/tests/hostmeta/django/test_generators.py b/federation/tests/hostmeta/django/test_generators.py index b5b147a..7b49a5a 100644 --- a/federation/tests/hostmeta/django/test_generators.py +++ b/federation/tests/hostmeta/django/test_generators.py @@ -4,11 +4,11 @@ from unittest.mock import patch, Mock from django.test import RequestFactory from federation.hostmeta.django import rfc3033_webfinger_view -from federation.hostmeta.django.generators import get_profile_id_func +from federation.hostmeta.django.generators import get_profile_func -def test_get_profile_id_func(): - func = get_profile_id_func() +def test_get_profile_func(): + func = get_profile_func() assert callable(func) @@ -23,7 +23,7 @@ class TestRFC3033WebfingerView: response = rfc3033_webfinger_view(request) assert response.status_code == 400 - @patch("federation.hostmeta.django.generators.get_profile_id_func") + @patch("federation.hostmeta.django.generators.get_profile_func") def test_unknown_handle_returns_not_found(self, mock_get_func): mock_get_func.return_value = Mock(side_effect=Exception) request = RequestFactory().get("/.well-known/webfinger?resource=acct:foobar@domain.tld") @@ -48,5 +48,10 @@ class TestRFC3033WebfingerView: "type": "text/html", "href": "https://example.com", }, + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": "https://example.com/profile/1234/", + }, ], } diff --git a/federation/tests/hostmeta/django/utils.py b/federation/tests/hostmeta/django/utils.py index e66fcb5..bb2fa26 100644 --- a/federation/tests/hostmeta/django/utils.py +++ b/federation/tests/hostmeta/django/utils.py @@ -1,5 +1,8 @@ from federation.utils.diaspora import generate_diaspora_profile_id -def get_profile_id_by_handle(handle): - return generate_diaspora_profile_id(handle, "1234") +def get_profile_by_handle(handle): + return { + "id": generate_diaspora_profile_id(handle, "1234"), + "profile_path": "/profile/1234/", + } diff --git a/federation/tests/hostmeta/test_generators.py b/federation/tests/hostmeta/test_generators.py index e39fdcf..266d2a0 100644 --- a/federation/tests/hostmeta/test_generators.py +++ b/federation/tests/hostmeta/test_generators.py @@ -182,6 +182,7 @@ def test_rfc3033_webfinger(): webfinger = RFC3033Webfinger( "diaspora://foobar@example.com/profile/1234", "https://example.com", + profile_path="/profile/1234/", ).render() assert webfinger == { "subject": "acct:foobar@example.com", @@ -196,5 +197,10 @@ def test_rfc3033_webfinger(): "type": "text/html", "href": "https://example.com", }, + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": "https://example.com/profile/1234/", + }, ], } From 0466931d053b9c4ef41959f55274503e8a39e8fe Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Wed, 21 Feb 2018 21:58:10 +0200 Subject: [PATCH 2/4] Add atom feed support to JSON webfinger --- federation/hostmeta/django/generators.py | 1 + federation/hostmeta/generators.py | 15 +++- .../tests/hostmeta/django/test_generators.py | 5 ++ federation/tests/hostmeta/django/utils.py | 1 + federation/tests/hostmeta/test_generators.py | 87 +++++++++++++------ 5 files changed, 81 insertions(+), 28 deletions(-) diff --git a/federation/hostmeta/django/generators.py b/federation/hostmeta/django/generators.py index 8c076d2..92d7820 100644 --- a/federation/hostmeta/django/generators.py +++ b/federation/hostmeta/django/generators.py @@ -64,6 +64,7 @@ def rfc3033_webfinger_view(request, *args, **kwargs): base_url=config.get('base_url'), profile_path=profile.get('profile_path'), hcard_path=config.get('hcard_path'), + atom_path=profile.get('atom_path'), ) return JsonResponse( diff --git a/federation/hostmeta/generators.py b/federation/hostmeta/generators.py index 4f22dff..e5775c4 100644 --- a/federation/hostmeta/generators.py +++ b/federation/hostmeta/generators.py @@ -289,16 +289,18 @@ class RFC3033Webfinger: :param base_url: The base URL of the server (protocol://domain.tld) :param profile_path: Profile path for the user (for example `/profile/johndoe/`) :param hcard_path: (Optional) hCard path, defaults to ``/hcard/users/``. + :param atom_path: (Optional) atom feed path :returns: dict """ - def __init__(self, id, base_url, profile_path, hcard_path="/hcard/users/"): + def __init__(self, id, base_url, profile_path, hcard_path="/hcard/users/", atom_path=None): self.handle, self.guid = parse_profile_diaspora_id(id) self.base_url = base_url self.hcard_path = hcard_path self.profile_path = profile_path + self.atom_path = atom_path def render(self): - return { + webfinger = { "subject": "acct:%s" % self.handle, "links": [ { @@ -318,3 +320,12 @@ class RFC3033Webfinger: }, ], } + if self.atom_path: + webfinger['links'].append( + { + "rel": "http://schemas.google.com/g/2010#updates-from", + "type": "application/atom+xml", + "href": "%s%s" % (self.base_url, self.atom_path), + } + ) + return webfinger diff --git a/federation/tests/hostmeta/django/test_generators.py b/federation/tests/hostmeta/django/test_generators.py index 7b49a5a..867fb3b 100644 --- a/federation/tests/hostmeta/django/test_generators.py +++ b/federation/tests/hostmeta/django/test_generators.py @@ -53,5 +53,10 @@ class TestRFC3033WebfingerView: "type": "text/html", "href": "https://example.com/profile/1234/", }, + { + "rel": "http://schemas.google.com/g/2010#updates-from", + "type": "application/atom+xml", + "href": "https://example.com/profile/1234/atom.xml", + }, ], } diff --git a/federation/tests/hostmeta/django/utils.py b/federation/tests/hostmeta/django/utils.py index bb2fa26..1f4e72a 100644 --- a/federation/tests/hostmeta/django/utils.py +++ b/federation/tests/hostmeta/django/utils.py @@ -5,4 +5,5 @@ def get_profile_by_handle(handle): return { "id": generate_diaspora_profile_id(handle, "1234"), "profile_path": "/profile/1234/", + "atom_path": "/profile/1234/atom.xml", } diff --git a/federation/tests/hostmeta/test_generators.py b/federation/tests/hostmeta/test_generators.py index 266d2a0..99f34e3 100644 --- a/federation/tests/hostmeta/test_generators.py +++ b/federation/tests/hostmeta/test_generators.py @@ -178,29 +178,64 @@ class TestNodeInfoGenerator: assert wellknown["links"][0]["href"] == "https://example.com/nodeinfo/1.0" -def test_rfc3033_webfinger(): - webfinger = RFC3033Webfinger( - "diaspora://foobar@example.com/profile/1234", - "https://example.com", - profile_path="/profile/1234/", - ).render() - assert webfinger == { - "subject": "acct:foobar@example.com", - "links": [ - { - "rel": "http://microformats.org/profile/hcard", - "type": "text/html", - "href": "https://example.com/hcard/users/1234", - }, - { - "rel": "http://joindiaspora.com/seed_location", - "type": "text/html", - "href": "https://example.com", - }, - { - "rel": "http://webfinger.net/rel/profile-page", - "type": "text/html", - "href": "https://example.com/profile/1234/", - }, - ], - } +class TestRFC3033Webfinger: + def test_rfc3033_webfinger__all_properties(self): + webfinger = RFC3033Webfinger( + "diaspora://foobar@example.com/profile/1234", + "https://example.com", + profile_path="/profile/1234/", + hcard_path="/hcard/path/", + atom_path="/profile/1234/atom.xml", + ).render() + assert webfinger == { + "subject": "acct:foobar@example.com", + "links": [ + { + "rel": "http://microformats.org/profile/hcard", + "type": "text/html", + "href": "https://example.com/hcard/path/1234", + }, + { + "rel": "http://joindiaspora.com/seed_location", + "type": "text/html", + "href": "https://example.com", + }, + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": "https://example.com/profile/1234/", + }, + { + "rel": "http://schemas.google.com/g/2010#updates-from", + "type": "application/atom+xml", + "href": "https://example.com/profile/1234/atom.xml", + }, + ], + } + + def test_rfc3033_webfinger__minimal(self): + webfinger = RFC3033Webfinger( + "diaspora://foobar@example.com/profile/1234", + "https://example.com", + profile_path="/profile/1234/", + ).render() + assert webfinger == { + "subject": "acct:foobar@example.com", + "links": [ + { + "rel": "http://microformats.org/profile/hcard", + "type": "text/html", + "href": "https://example.com/hcard/users/1234", + }, + { + "rel": "http://joindiaspora.com/seed_location", + "type": "text/html", + "href": "https://example.com", + }, + { + "rel": "http://webfinger.net/rel/profile-page", + "type": "text/html", + "href": "https://example.com/profile/1234/", + }, + ], + } From 85e0b90ebf862e3b663d6b1a13f0c1bb35d2016d Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Wed, 21 Feb 2018 22:02:30 +0200 Subject: [PATCH 3/4] Add salmon receive url to JSON webfinger --- federation/hostmeta/generators.py | 4 ++++ federation/tests/hostmeta/django/test_generators.py | 4 ++++ federation/tests/hostmeta/test_generators.py | 8 ++++++++ 3 files changed, 16 insertions(+) diff --git a/federation/hostmeta/generators.py b/federation/hostmeta/generators.py index e5775c4..e828090 100644 --- a/federation/hostmeta/generators.py +++ b/federation/hostmeta/generators.py @@ -318,6 +318,10 @@ class RFC3033Webfinger: "type": "text/html", "href": "%s%s" % (self.base_url, self.profile_path), }, + { + "rel": "salmon", + "href": "%s/receive/users/%s" % (self.base_url, self.guid), + }, ], } if self.atom_path: diff --git a/federation/tests/hostmeta/django/test_generators.py b/federation/tests/hostmeta/django/test_generators.py index 867fb3b..7c9a48f 100644 --- a/federation/tests/hostmeta/django/test_generators.py +++ b/federation/tests/hostmeta/django/test_generators.py @@ -53,6 +53,10 @@ class TestRFC3033WebfingerView: "type": "text/html", "href": "https://example.com/profile/1234/", }, + { + "rel": "salmon", + "href": "https://example.com/receive/users/1234", + }, { "rel": "http://schemas.google.com/g/2010#updates-from", "type": "application/atom+xml", diff --git a/federation/tests/hostmeta/test_generators.py b/federation/tests/hostmeta/test_generators.py index 99f34e3..fcaa899 100644 --- a/federation/tests/hostmeta/test_generators.py +++ b/federation/tests/hostmeta/test_generators.py @@ -205,6 +205,10 @@ class TestRFC3033Webfinger: "type": "text/html", "href": "https://example.com/profile/1234/", }, + { + "rel": "salmon", + "href": "https://example.com/receive/users/1234", + }, { "rel": "http://schemas.google.com/g/2010#updates-from", "type": "application/atom+xml", @@ -237,5 +241,9 @@ class TestRFC3033Webfinger: "type": "text/html", "href": "https://example.com/profile/1234/", }, + { + "rel": "salmon", + "href": "https://example.com/receive/users/1234", + }, ], } From d74aa5290c9cc469254a2ea4afe2ae4a562e0d68 Mon Sep 17 00:00:00 2001 From: Jason Robinson Date: Wed, 21 Feb 2018 22:21:55 +0200 Subject: [PATCH 4/4] Add search path to JSON webfinger --- docs/usage.rst | 4 ++++ federation/hostmeta/django/generators.py | 3 ++- federation/hostmeta/generators.py | 10 +++++++++- federation/tests/hostmeta/django/settings.py | 1 + federation/tests/hostmeta/django/test_generators.py | 4 ++++ federation/tests/hostmeta/test_generators.py | 5 +++++ 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/usage.rst b/docs/usage.rst index 80cbf2c..45a523e 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -112,6 +112,7 @@ Some settings need to be set in Django settings. An example is below: FEDERATION = { "base_url": "https://myserver.domain.tld, "get_profile_function": "myproject.utils.get_profile_by_handle", + "search_path": "/search/?q=", } * ``base_url`` is the base URL of the server, ie protocol://domain.tld. @@ -119,6 +120,9 @@ Some settings need to be set in Django settings. An example is below: * ``id`` - Diaspora URI format ID. * ``profile_path`` - profile path for generating an absolute URL to the profile page of the user. + * ``atom_path`` - (optional) atom feed path for the profile + +* ``search_path`` (optional) site search path which ends in a parameter for search input, for example "/search?q=" Protocols --------- diff --git a/federation/hostmeta/django/generators.py b/federation/hostmeta/django/generators.py index 92d7820..e8a8147 100644 --- a/federation/hostmeta/django/generators.py +++ b/federation/hostmeta/django/generators.py @@ -16,12 +16,12 @@ def get_configuration(): """ configuration = { "hcard_path": "/hcard/users/", + "search_path": None, } configuration.update(settings.FEDERATION) if not all([ "get_profile_function" in configuration, "base_url" in configuration, - "hcard_path" in configuration, ]): raise ImproperlyConfigured("Missing required FEDERATION settings, please check documentation.") return configuration @@ -65,6 +65,7 @@ def rfc3033_webfinger_view(request, *args, **kwargs): profile_path=profile.get('profile_path'), hcard_path=config.get('hcard_path'), atom_path=profile.get('atom_path'), + search_path=config.get('search_path'), ) return JsonResponse( diff --git a/federation/hostmeta/generators.py b/federation/hostmeta/generators.py index e828090..28c4969 100644 --- a/federation/hostmeta/generators.py +++ b/federation/hostmeta/generators.py @@ -292,12 +292,13 @@ class RFC3033Webfinger: :param atom_path: (Optional) atom feed path :returns: dict """ - def __init__(self, id, base_url, profile_path, hcard_path="/hcard/users/", atom_path=None): + def __init__(self, id, base_url, profile_path, hcard_path="/hcard/users/", atom_path=None, search_path=None): self.handle, self.guid = parse_profile_diaspora_id(id) self.base_url = base_url self.hcard_path = hcard_path self.profile_path = profile_path self.atom_path = atom_path + self.search_path = search_path def render(self): webfinger = { @@ -332,4 +333,11 @@ class RFC3033Webfinger: "href": "%s%s" % (self.base_url, self.atom_path), } ) + if self.search_path: + webfinger['links'].append( + { + "rel": "http://ostatus.org/schema/1.0/subscribe", + "template": "%s%s{uri}" % (self.base_url, self.search_path), + }, + ) return webfinger diff --git a/federation/tests/hostmeta/django/settings.py b/federation/tests/hostmeta/django/settings.py index e4930e6..aeeb9d9 100644 --- a/federation/tests/hostmeta/django/settings.py +++ b/federation/tests/hostmeta/django/settings.py @@ -5,4 +5,5 @@ INSTALLED_APPS = tuple() FEDERATION = { "base_url": "https://example.com", "get_profile_function": "federation.tests.hostmeta.django.utils.get_profile_by_handle", + "search_path": "/search?q=", } diff --git a/federation/tests/hostmeta/django/test_generators.py b/federation/tests/hostmeta/django/test_generators.py index 7c9a48f..707e48b 100644 --- a/federation/tests/hostmeta/django/test_generators.py +++ b/federation/tests/hostmeta/django/test_generators.py @@ -62,5 +62,9 @@ class TestRFC3033WebfingerView: "type": "application/atom+xml", "href": "https://example.com/profile/1234/atom.xml", }, + { + "rel": "http://ostatus.org/schema/1.0/subscribe", + "template": "https://example.com/search?q={uri}", + }, ], } diff --git a/federation/tests/hostmeta/test_generators.py b/federation/tests/hostmeta/test_generators.py index fcaa899..92873e5 100644 --- a/federation/tests/hostmeta/test_generators.py +++ b/federation/tests/hostmeta/test_generators.py @@ -186,6 +186,7 @@ class TestRFC3033Webfinger: profile_path="/profile/1234/", hcard_path="/hcard/path/", atom_path="/profile/1234/atom.xml", + search_path="/search?q=", ).render() assert webfinger == { "subject": "acct:foobar@example.com", @@ -214,6 +215,10 @@ class TestRFC3033Webfinger: "type": "application/atom+xml", "href": "https://example.com/profile/1234/atom.xml", }, + { + "rel": "http://ostatus.org/schema/1.0/subscribe", + "template": "https://example.com/search?q={uri}", + }, ], }