Add support for AS2 document webfinger rel used by Mastodon et al

Also fix reference to RFC by renaming RFC3033 to RFC7033 :P
merge-requests/132/head
Jason Robinson 2018-09-30 16:20:03 +03:00
rodzic 865636c65b
commit ceb5d0446e
9 zmienionych plików z 76 dodań i 87 usunięć

Wyświetl plik

@ -16,7 +16,7 @@
* The `id` and possible `target_id` are now either URL format identifiers (ActivityPub) or a handle or GUID (Diaspora, depending on entity). Additionally a new `actor_id` has been added which for ActivityPub is an URL and for Diaspora a handle. Note, Diaspora entities always have also the `guid`, `handle`, `target_guid` and `target_handle` as before v0.15.0, depending on the entity. When creating Diaspora entities, you must pass these in for sending to work.
* The high level `fetchers.retrieve_remote_content` signature has changed. It now expects an `id` for fetching from AP protocol and `handle`, `guid` and `entity_type` to fetch from Diaspora. Additionally a `sender_key_fetcher` can be passed in as before to optimize public key fetching using a callable.
* The high level `fetchers.retrieve_remote_profile` signature has changed. It now expects an `id` for fetching from AP protocol and `handle` for fetching from Diaspora. Additionally a `sender_key_fetcher` can be passed in as before to optimize public key fetching using a callable.
* The generator class `RFC3033Webfinger` now expects instead of an `id` the `handle` and `guid` of the profile.
* The generator class `RFC7033Webfinger` now expects instead of an `id` the `handle` and `guid` of the profile.
* NodeInfo2 parser now returns the admin user in `handle` format instead of a Diaspora format URL.
* The high level inbound and outbound functions `inbound.handle_receive`, `outbound.handle_send` parameter `user` must now receive a `UserType` compatible object. This must have the attributes `id` and `private_key`. If Diaspora support is required then also `handle` and `guid` should exist. The type can be found as a class in `types.UserType`.
* The outbound function `outbound.handle_send` parameter `recipients` structure has changed. It must now for Diaspora contain either a `handle` (public delivery) or tuple of `handle, RSAPublicKey, guid` for private delivery. For AP delivery either `url ID` for public delivery or tuple of `url ID, RSAPublicKey` for private delivery.
@ -41,7 +41,7 @@
JSON encrypted payload encryption and decryption is handled by the Diaspora `EncryptedPayload` class.
* Add RFC3033 webfinger generator ([related issue](https://github.com/jaywink/federation/issues/108))
* Add RFC7033 webfinger generator ([related issue](https://github.com/jaywink/federation/issues/108))
Also provided is a Django view and url configuration for easy addition into Django projects. Django is not a hard dependency of this library, usage of the Django view obviously requires installing Django itself. For configuration details see documentation.

Wyświetl plik

@ -62,7 +62,7 @@ Generator classes
.. autoclass:: federation.hostmeta.generators.DiasporaWebFinger
.. autoclass:: federation.hostmeta.generators.DiasporaHCard
.. autoclass:: federation.hostmeta.generators.NodeInfo
.. autoclass:: federation.hostmeta.generators.RFC3033Webfinger
.. autoclass:: federation.hostmeta.generators.RFC7033Webfinger
.. autoclass:: federation.hostmeta.generators.SocialRelayWellKnown
Fetchers
@ -98,7 +98,7 @@ Some ready provided views and URL configuration exist for Django.
Note! Django is not part of the normal requirements for this library. It must be installed separately.
.. autofunction:: federation.hostmeta.django.generators.rfc3033_webfinger_view
.. autofunction:: federation.hostmeta.django.generators.rfc7033_webfinger_view
.. autofunction:: federation.hostmeta.django.generators.nodeinfo2_view
Configuration

Wyświetl plik

@ -1 +1 @@
from .generators import rfc3033_webfinger_view
from .generators import rfc7033_webfinger_view

Wyświetl plik

@ -2,7 +2,7 @@ import logging
from django.http import HttpResponseBadRequest, JsonResponse, HttpResponseNotFound
from federation.hostmeta.generators import RFC3033Webfinger, generate_nodeinfo2_document
from federation.hostmeta.generators import RFC7033Webfinger, generate_nodeinfo2_document
from federation.utils.django import get_configuration, get_function_from_config
from federation.utils.text import get_path_from_url
@ -19,9 +19,9 @@ def nodeinfo2_view(request, *args, **kwargs):
return JsonResponse(generate_nodeinfo2_document(**nodeinfo2))
def rfc3033_webfinger_view(request, *args, **kwargs):
def rfc7033_webfinger_view(request, *args, **kwargs):
"""
Django view to generate an RFC3033 webfinger.
Django view to generate an RFC7033 webfinger.
"""
resource = request.GET.get("resource")
if not resource:
@ -35,13 +35,12 @@ def rfc3033_webfinger_view(request, *args, **kwargs):
try:
profile = profile_func(handle=handle, request=request)
except Exception as exc:
logger.warning("rfc3033_webfinger_view - Failed to get profile by handle %s: %s", handle, exc)
logger.warning("rfc7033_webfinger_view - Failed to get profile by handle %s: %s", handle, exc)
return HttpResponseNotFound()
profile = profile.as_protocol("diaspora")
config = get_configuration()
webfinger = RFC3033Webfinger(
webfinger = RFC7033Webfinger(
id=profile.id,
handle=profile.handle,
guid=profile.guid,
base_url=config.get('base_url'),

Wyświetl plik

@ -1,9 +1,9 @@
from django.conf.urls import url
from federation.hostmeta.django import rfc3033_webfinger_view
from federation.hostmeta.django import rfc7033_webfinger_view
from federation.hostmeta.django.generators import nodeinfo2_view
urlpatterns = [
url(r'^.well-known/webfinger$', rfc3033_webfinger_view, name="rfc3033-webfinger"),
url(r'^.well-known/webfinger$', rfc7033_webfinger_view, name="rfc7033-webfinger"),
url(r'^.well-known/x-nodeinfo2$', nodeinfo2_view, name="nodeinfo2"),
]

Wyświetl plik

@ -8,8 +8,6 @@ from jsonschema import validate
from jsonschema.exceptions import ValidationError
from xrd import XRD, Link, Element
from federation.utils.diaspora import parse_profile_diaspora_id
def generate_host_meta(template=None, *args, **kwargs):
"""Generate a host-meta XRD document.
@ -333,13 +331,15 @@ def get_nodeinfo_well_known_document(url, document_path=None):
}
class RFC3033Webfinger:
class RFC7033Webfinger:
"""
RFC 3033 webfinger - see https://diaspora.github.io/diaspora_federation/discovery/webfinger.html
RFC 7033 webfinger - see https://tools.ietf.org/html/rfc7033
A Django view is also available, see the child ``django`` module for view and url configuration.
:param id: Diaspora ID in URI format
:param id: Profile ActivityPub ID in URL format
:param handle: Profile Diaspora handle
:param guid: Profile Diaspora guid
: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/``.
@ -347,8 +347,10 @@ class RFC3033Webfinger:
:returns: dict
"""
def __init__(
self, handle, guid, base_url, profile_path, hcard_path="/hcard/users/", atom_path=None, search_path=None,
self, id: str, handle: str, guid: str, base_url: str, profile_path: str, hcard_path: str="/hcard/users/",
atom_path: str=None, search_path: str=None,
):
self.id = id
self.handle = handle
self.guid = guid
self.base_url = base_url
@ -360,6 +362,10 @@ class RFC3033Webfinger:
def render(self):
webfinger = {
"subject": "acct:%s" % self.handle,
"aliases": [
f"{self.base_url}{self.profile_path}",
self.id,
],
"links": [
{
"rel": "http://microformats.org/profile/hcard",
@ -380,6 +386,11 @@ class RFC3033Webfinger:
"rel": "salmon",
"href": "%s/receive/users/%s" % (self.base_url, self.guid),
},
{
"rel": "self",
"href": self.id,
"type": "application/activity+json",
},
],
}
if self.atom_path:

Wyświetl plik

@ -1,22 +1,20 @@
from federation.entities.base import Profile
def get_object_function(object_id):
def dummy_profile():
return Profile(
url=f"https://example.com/profile/1234/",
atom_url=f"https://example.com/profile/1234/atom.xml",
id=f"https://example.com/profile/1234/",
id=f"https://example.com/p/1234/",
handle="foobar@example.com",
guid="1234",
name="Bob Bobértson",
)
def get_object_function(object_id):
return dummy_profile()
def get_profile(handle=None, request=None):
return Profile(
url=f"https://example.com/profile/1234/",
atom_url=f"https://example.com/profile/1234/atom.xml",
id=f"https://example.com/profile/1234/",
handle="foobar@example.com",
guid="1234",
)
return dummy_profile()

Wyświetl plik

@ -3,8 +3,7 @@ from unittest.mock import patch, Mock
from django.test import RequestFactory
from federation.entities.base import Profile
from federation.hostmeta.django import rfc3033_webfinger_view
from federation.hostmeta.django import rfc7033_webfinger_view
from federation.hostmeta.django.generators import nodeinfo2_view
from federation.utils.django import get_function_from_config
from federation.tests.fixtures.hostmeta import NODEINFO2_10_DOC
@ -29,31 +28,35 @@ class TestNodeInfo2View:
assert response.status_code == 200
class TestRFC3033WebfingerView:
class TestRFC7033WebfingerView:
def test_no_resource_returns_bad_request(self):
request = RequestFactory().get("/.well-known/webfinger")
response = rfc3033_webfinger_view(request)
response = rfc7033_webfinger_view(request)
assert response.status_code == 400
def test_invalid_resource_returns_bad_request(self):
request = RequestFactory().get("/.well-known/webfinger?resource=foobar")
response = rfc3033_webfinger_view(request)
response = rfc7033_webfinger_view(request)
assert response.status_code == 400
@patch("federation.hostmeta.django.generators.get_function_from_config")
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")
response = rfc3033_webfinger_view(request)
response = rfc7033_webfinger_view(request)
assert response.status_code == 404
def test_rendered_webfinger_returned(self):
request = RequestFactory().get("/.well-known/webfinger?resource=acct:foobar@example.com")
response = rfc3033_webfinger_view(request)
response = rfc7033_webfinger_view(request)
assert response.status_code == 200
assert response['Content-Type'] == "application/jrd+json"
assert json.loads(response.content.decode("utf-8")) == {
"subject": "acct:foobar@example.com",
"aliases": [
"https://example.com/profile/1234/",
"https://example.com/p/1234/",
],
"links": [
{
"rel": "http://microformats.org/profile/hcard",
@ -75,51 +78,9 @@ class TestRFC3033WebfingerView:
"href": "https://example.com/receive/users/1234",
},
{
"rel": "http://schemas.google.com/g/2010#updates-from",
"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}",
},
],
}
@patch("federation.hostmeta.django.generators.get_function_from_config", autospec=True)
def test_rendered_webfinger_returned__non_diaspora_id(self, mock_get_config):
mock_get_config.return_value = Mock(return_value=Profile(
url="https://example.com/profile/1234/",
atom_url="https://example.com/profile/1234/atom.xml",
handle="foobar@example.com",
guid="1234",
id="https://example.com/profile/1234",
))
request = RequestFactory().get("/.well-known/webfinger?resource=acct:foobar@example.com")
response = rfc3033_webfinger_view(request)
assert response.status_code == 200
assert response['Content-Type'] == "application/jrd+json"
assert json.loads(response.content.decode("utf-8")) == {
"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/",
},
{
"rel": "salmon",
"href": "https://example.com/receive/users/1234",
"rel": "self",
"href": "https://example.com/p/1234/",
"type": "application/activity+json",
},
{
"rel": "http://schemas.google.com/g/2010#updates-from",

Wyświetl plik

@ -4,7 +4,7 @@ import pytest
from federation.hostmeta.generators import (
generate_host_meta, generate_legacy_webfinger, generate_hcard,
SocialRelayWellKnown, NodeInfo, get_nodeinfo_well_known_document, RFC3033Webfinger,
SocialRelayWellKnown, NodeInfo, get_nodeinfo_well_known_document, RFC7033Webfinger,
generate_nodeinfo2_document)
from federation.tests.fixtures.payloads import DIASPORA_HOSTMETA, DIASPORA_WEBFINGER
@ -223,9 +223,10 @@ class TestNodeInfoGenerator:
assert wellknown["links"][0]["href"] == "https://example.com/nodeinfo/1.0"
class TestRFC3033Webfinger:
def test_rfc3033_webfinger__all_properties(self):
webfinger = RFC3033Webfinger(
class TestRFC7033Webfinger:
def test_rfc7033_webfinger__all_properties(self):
webfinger = RFC7033Webfinger(
id="https://example.com/p/1234/",
handle="foobar@example.com",
guid="1234",
base_url="https://example.com",
@ -236,6 +237,10 @@ class TestRFC3033Webfinger:
).render()
assert webfinger == {
"subject": "acct:foobar@example.com",
"aliases": [
"https://example.com/profile/1234/",
"https://example.com/p/1234/",
],
"links": [
{
"rel": "http://microformats.org/profile/hcard",
@ -256,6 +261,11 @@ class TestRFC3033Webfinger:
"rel": "salmon",
"href": "https://example.com/receive/users/1234",
},
{
"rel": "self",
"href": "https://example.com/p/1234/",
"type": "application/activity+json",
},
{
"rel": "http://schemas.google.com/g/2010#updates-from",
"type": "application/atom+xml",
@ -268,8 +278,9 @@ class TestRFC3033Webfinger:
],
}
def test_rfc3033_webfinger__minimal(self):
webfinger = RFC3033Webfinger(
def test_rfc7033_webfinger__minimal(self):
webfinger = RFC7033Webfinger(
id="https://example.com/p/1234/",
handle="foobar@example.com",
guid="1234",
base_url="https://example.com",
@ -277,6 +288,10 @@ class TestRFC3033Webfinger:
).render()
assert webfinger == {
"subject": "acct:foobar@example.com",
"aliases": [
"https://example.com/profile/1234/",
"https://example.com/p/1234/",
],
"links": [
{
"rel": "http://microformats.org/profile/hcard",
@ -297,5 +312,10 @@ class TestRFC3033Webfinger:
"rel": "salmon",
"href": "https://example.com/receive/users/1234",
},
{
"rel": "self",
"href": "https://example.com/p/1234/",
"type": "application/activity+json",
},
],
}