mirror of
https://codeberg.org/fediverse/fep.git
synced 2026-08-05 19:55:46 +00:00
FEP-4adb: Dereferencing identifiers with webfinger (#98)
Co-authored-by: Helge <helge.krueger@gmail.com> Reviewed-on: https://codeberg.org/fediverse/fep/pulls/98 Co-authored-by: helge <helge@noreply.codeberg.org> Co-committed-by: helge <helge@noreply.codeberg.org>
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
# Tests
|
||||
|
||||
Demo how to use behave + bovine to provide some tests for fep-4adb.
|
||||
|
||||
Usage
|
||||
|
||||
```bash
|
||||
poetry install
|
||||
poetry run behave
|
||||
```
|
||||
@@ -0,0 +1,48 @@
|
||||
Feature: Dereferencing URIs with webfinger as described in fep-4adb
|
||||
|
||||
Scenario: User found
|
||||
Given Webfinger response:
|
||||
"""
|
||||
{
|
||||
"links": [
|
||||
{
|
||||
"href": "https://having.examples.rocks/endpoints/mooo",
|
||||
"rel": "self",
|
||||
"type": "application/activity+json"
|
||||
}
|
||||
],
|
||||
"subject": "acct:moocow@having.examples.rocks"
|
||||
}
|
||||
"""
|
||||
When Looking up "acct:moocow@having.examples.rocks"
|
||||
Then Lookup at "https://having.examples.rocks/.well-known/webfinger?resource=acct%3Amoocow%40having.examples.rocks"
|
||||
Then ActivityPub Object Id is "https://having.examples.rocks/endpoints/mooo"
|
||||
|
||||
Scenario: User found multiple options
|
||||
Given Webfinger response:
|
||||
"""
|
||||
{
|
||||
"links": [
|
||||
{
|
||||
"href": "https://having.examples.rocks/themoocow",
|
||||
"rel": "http://webfinger.net/rel/profile-page",
|
||||
"type": "text/html"
|
||||
},
|
||||
{
|
||||
"href": "https://having.examples.rocks/endpoints/mooo",
|
||||
"rel": "self",
|
||||
"type": "application/activity+json"
|
||||
}
|
||||
],
|
||||
"subject": "acct:moocow@having.examples.rocks"
|
||||
}
|
||||
"""
|
||||
When Looking up "acct:moocow@having.examples.rocks"
|
||||
Then Lookup at "https://having.examples.rocks/.well-known/webfinger?resource=acct%3Amoocow%40having.examples.rocks"
|
||||
Then ActivityPub Object Id is "https://having.examples.rocks/endpoints/mooo"
|
||||
|
||||
Scenario: User not found
|
||||
Given Webfinger response with 404
|
||||
When Looking up "acct:moocow@having.examples.rocks"
|
||||
Then Lookup at "https://having.examples.rocks/.well-known/webfinger?resource=acct%3Amoocow%40having.examples.rocks"
|
||||
Then None is returned
|
||||
Generated
+1169
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,16 @@
|
||||
[tool.poetry]
|
||||
name = "demo"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Helge <helge.krueger@gmail.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
bovine = "^0.1.4"
|
||||
behave = "^1.2.6"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@@ -0,0 +1,57 @@
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from urllib.parse import urlencode
|
||||
from bovine.clients import lookup_account_with_webfinger
|
||||
|
||||
|
||||
from behave import given, when, then
|
||||
from behave.api.async_step import async_run_until_complete
|
||||
|
||||
|
||||
@given("Webfinger response")
|
||||
def load_webfinger_response(context):
|
||||
context.response = context.text
|
||||
context.response_status_code = 200
|
||||
|
||||
|
||||
@given("Webfinger response with {status_code:d}")
|
||||
def load_webfinger_response_with_status(context, status_code):
|
||||
context.response = ""
|
||||
context.response_status_code = status_code
|
||||
|
||||
|
||||
@when('Looking up "{account}"')
|
||||
@async_run_until_complete
|
||||
async def test_lookup(context, account):
|
||||
session = MagicMock()
|
||||
response_mock = AsyncMock()
|
||||
session.get.return_value = response_mock
|
||||
response_mock.__aenter__.return_value = response_mock
|
||||
|
||||
response_mock.text.return_value = context.response
|
||||
response_mock.status = context.response_status_code
|
||||
|
||||
# FIXME !!! bovine doesn't do what we need yet.
|
||||
account = account.removeprefix("acct:")
|
||||
|
||||
context.result = await lookup_account_with_webfinger(session, account)
|
||||
|
||||
session.get.assert_called_once()
|
||||
|
||||
args = session.get.call_args
|
||||
|
||||
context.request_url = args.args[0] + "?" + urlencode(args.kwargs["params"])
|
||||
|
||||
|
||||
@then('Lookup at "{url}"')
|
||||
def verify_lookup_url(context, url):
|
||||
assert context.request_url == url
|
||||
|
||||
|
||||
@then('ActivityPub Object Id is "{id}"')
|
||||
def verify_resulting_id(context, id):
|
||||
assert context.result == id
|
||||
|
||||
|
||||
@then("None is returned")
|
||||
def verify_result_is_none(context):
|
||||
assert context.result is None
|
||||
@@ -0,0 +1,220 @@
|
||||
---
|
||||
authors: Helge <helge.krueger@gmail.com>
|
||||
status: DRAFT
|
||||
dateReceived: 2023-05-13
|
||||
---
|
||||
# FEP-4adb: Dereferencing identifiers with webfinger
|
||||
|
||||
## Summary
|
||||
|
||||
In this FEP, we will formalize the process of derefencing an URI using webfinger in order for usage in ActivityPub. The main goal is to enable the usage of URIs of the form `acct:user@domain` or `did:example:12345` as ids for objects used in ActivityPub. While this FEP only discusses this in the context of actors, it should be applicable for general objects. In order for a smooth introduction, it is recommended to start deployment with actor objects.
|
||||
|
||||
This FEP first presents the algorithm and examples, then discusses the usage in the context of the Fediverse. This means the first two sections are for people wanting to implement this FEP, the following sections are for people wanting to decide if this FEP is a good idea.
|
||||
|
||||
## The algorithm
|
||||
|
||||
[Webfinger] allows us to associate an ActivityStreams object with
|
||||
a pair given by an URI and a domain. This is done through the following steps:
|
||||
|
||||
1. Fetch `GET https://domain/.well-known/webfinger?resource={URI}`.
|
||||
2. Determine the link property with `"type":"application/activity+json"`.
|
||||
3. Derefence this object.
|
||||
|
||||
We will denote this object by `ActivityStreams(URI, domain)`. There are now two cases to resolve an URI:
|
||||
|
||||
1. The URI determines the domain denoted by `domain(URI)`
|
||||
2. The URI doesn't determine the domain
|
||||
|
||||
In case 1, it is clear that we associate `ActivityStreams(URI, domain(URI))` to the URI. In case 2, we will use the domain associated with the `@id` of the document the document the URI appeared in. If the document was received through a POST request and doesn't contain an `id`, or the `id` is an URI, the domain the POST request originated from should be used. In this case a verification that the URI can be associated with the object MUST be performed. This can for example be achieved throuh [FEP-c390](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-c390.md).
|
||||
|
||||
## Examples
|
||||
|
||||
We start with the [acct URI Scheme](https://www.rfc-editor.org/rfc/rfc7565.html). In order to deliver the activity
|
||||
|
||||
```json
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"type": "Follow",
|
||||
"actor": "https://chatty.example/ben/",
|
||||
"object": "acct:alyssa@social.example",
|
||||
"to": "acct:alyssa@social.example",
|
||||
}
|
||||
```
|
||||
|
||||
the ActivityPub server should perform the lookup for `ActivityStreams("acct:alyssa@social.example", "social.example")`, i.e. the request
|
||||
|
||||
```http
|
||||
GET https://social.example/.well-known/webfinger?resource=acct:alyssa@social.example
|
||||
```
|
||||
|
||||
We now turn our attention to [DIDs](https://www.w3.org/TR/did-core/).
|
||||
In order to resolve the author of
|
||||
|
||||
```json
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://chatty.example/ben/456",
|
||||
"type": "Note",
|
||||
"attributedTo": "did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5",
|
||||
"to": "acct:alyssa@social.example",
|
||||
"content": "Hello"
|
||||
}
|
||||
```
|
||||
|
||||
one has to resolve the URI `did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5`. As this URI does not contain a domain, the domain from the id, i.e. `chatty.example` is used, so the request
|
||||
|
||||
```http
|
||||
GET https://chatty.example/.well-known/webfinger?resource=did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5
|
||||
```
|
||||
|
||||
is made. The resulting ActivityStreams object should contain
|
||||
|
||||
```json
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://chatty.example/bnm789",
|
||||
"attachment": {
|
||||
"type": "VerifiableIdentityStatement",
|
||||
"subject": "did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5",
|
||||
"alsoKnownAs": "https://chatty.social/bnm789",
|
||||
"proof": { ... }
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
following [FEP-c390](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-c390.md) so that the authority of ben to use the did can be verified.
|
||||
|
||||
## Background
|
||||
|
||||
Let's start with [Section 3.1](https://www.w3.org/TR/activitypub/#obj-id) of ActivityPub. It starts with the sentence
|
||||
|
||||
> All Objects in [ActivityStreams] should have unique global identifiers.
|
||||
|
||||
We first note that the word _unique_ does not appear in [ActivityStreams]. Second it is unclear why it is there and what it means:
|
||||
|
||||
- Does it mean that there is exactly one identifier associated with the object?
|
||||
- Does it mean that the global identifier is only used for one object?
|
||||
|
||||
We will assume that it is the second interpretation. In particular, this means that we can associated multiple identifiers with the same ActivityStreams object.
|
||||
|
||||
Next comes the following line in [ActivityPub]
|
||||
|
||||
> Publicly dereferencable URIs, such as HTTPS URIs, with their authority belonging to that of their originating server. (Publicly facing content SHOULD use HTTPS URIs).
|
||||
|
||||
The essential point of this FEP is to extend the range of _publicly deferencable URIs_ to contain basically any URI by using [Webfinger].
|
||||
|
||||
The current usage of Webfinger in the Fediverse is asymmetric. As discussed in [MastoGuide](https://guide.toot.as/guide/use-your-own-domain/), one can associate many URIs of the form `acct:user@domain.tld` with the same Actor, by just making webfinger return an appropriate response. However, only one acct-URI can be associated with an Actor. This is done by:
|
||||
|
||||
```uri
|
||||
acct:{preferredUsername}@{domain of actor id}
|
||||
```
|
||||
|
||||
where `preferredUsername` is from the actor object.
|
||||
|
||||
## Applications to the Actor Object
|
||||
|
||||
We now discuss applications of the approach of this FEP to the actor object.
|
||||
|
||||
### Preferred Account
|
||||
|
||||
We will follow [this suggestion](https://socialhub.activitypub.rocks/t/alsoknownas-and-acct/3132/20?u=helge) in [alsoKnownAs and acct:]. The main idea is to associate multiple accounts. For this, we will return to `alyssa@social.example`, whose Actor object would look like the following omitting irrelevant details
|
||||
|
||||
```json
|
||||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://social.example/abc123",
|
||||
"preferredUsername": "alyssa",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
By the algorithm discussed in background, we can associate the URI `acct:alyssa@social.example` with this. Suppose now that Alyssa owns the domain `alyssa.cool`, and set up Webfigner to answer to
|
||||
|
||||
```http
|
||||
GET https://alyssa.cool/.well-known/webfinger?resource=me@alyssa.cool
|
||||
```
|
||||
|
||||
with a link to the above actor. Alyssa would then be disappointed because Fediverse software is not displaying her cooler username. To remedy this, we propose the addition to the actor object
|
||||
|
||||
```json
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
{
|
||||
"xrd": "http://docs.oasis-open.org/ns/xri/xrd-1.0#",
|
||||
"aliases": {
|
||||
"@id": "xrd:Alias",
|
||||
"@type": "@id",
|
||||
"@container": "@list"
|
||||
},
|
||||
}
|
||||
],
|
||||
"id": "https://social.example/abc123",
|
||||
"preferredUsername": "alyssa",
|
||||
"aliases": ["acct:me@alyssa.cool", "acct:alyssa@social.example"],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
a new generation of Fediverse software will then be able to display the account of Alyssa as `me@alyssa.cool`.
|
||||
|
||||
### DNS Names
|
||||
|
||||
Continuing with Alyssa from above, by relying on [FEP-612d](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-612d.md), she may even associate `@alyssa.cool` with her account. Unfortunately, a proper URI format is still missing.
|
||||
|
||||
### Using did:key
|
||||
|
||||
Let's return to Ben and him wanting to use a decentralized identifier, see [did-core](https://www.w3.org/TR/did-core/) and [did-method-key](https://w3c-ccg.github.io/did-method-key/)
|
||||
|
||||
```json
|
||||
{
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
{
|
||||
"xrd": "http://docs.oasis-open.org/ns/xri/xrd-1.0#",
|
||||
"aliases": {
|
||||
"@id": "xrd:Alias",
|
||||
"@type": "@id",
|
||||
"@container": "@list"
|
||||
},
|
||||
}
|
||||
],
|
||||
"id": "https://chatty.social/bnm789",
|
||||
"preferredUsername": "ben",
|
||||
"aliases": ["did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5", "acct:ben@chatty.social"],
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The problem here is that, we do not know if the the did-key belongs to BEN. To remedy this, we will use [FEP-c390](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-c390.md). This means that we need to add
|
||||
|
||||
```json
|
||||
"attachment": {
|
||||
"type": "VerifiableIdentityStatement",
|
||||
"subject": "did:key:z6MkekwC6R9bj9ErToB7AiZJfyCSDhaZe1UxhDbCqJrhqpS5",
|
||||
"alsoKnownAs": "https://chatty.social/bnm789",
|
||||
"proof": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
with an appropriate proof to Ben's actor object.
|
||||
|
||||
## References
|
||||
|
||||
- [acct URI Scheme] P. Saint-Andre [RFC 7565](https://www.rfc-editor.org/rfc/rfc7565.html), 2015
|
||||
- [ActivityPub] Christine Lemmer Webber, Jessica Tallon, [ActivityPub](https://www.w3.org/TR/activitypub/), 2018
|
||||
- [ActivityStreams] J. Snell, E. Prodromou, [ActivityStreams](https://www.w3.org/TR/activitystreams-core/), 2017
|
||||
- [alsoKnownAs and acct:](https://socialhub.activitypub.rocks/t/alsoknownas-and-acct/3132?u=helge) on SocialHub
|
||||
- [did-core] Manu Sporny, Dave Longley, Markus Sabadell, Drummond Reed, Orie Steele, Christopher Allen, [Decentralized Identifiers](https://www.w3.org/TR/did-core/) (DIDs) v1.0, 2022
|
||||
- [did-method-key], Dave Longley, Dmitri Zagidulin, Manu Sporny, [did-method-key](https://w3c-ccg.github.io/did-method-key/) 20221
|
||||
- [FEP-c390] silverpill, [FEP-c390](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-c390.md), 2022
|
||||
- [FEP-612d] Helge, [FEP-612d](https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-612d.md) 2023
|
||||
- [MastoGuide] jippi [Use your own user @ domain for Mastodon discoverability](https://guide.toot.as/guide/use-your-own-domain/)
|
||||
- [Webfinger] P. Jones, G. Salgueiro, M. Jones, J. Smarr, [RFC 7033](https://datatracker.ietf.org/doc/html/rfc7033),2013
|
||||
|
||||
## Copyright
|
||||
|
||||
CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
|
||||
|
||||
To the extent possible under law, the authors of this Fediverse Enhancement Proposal have waived all copyright and related or neighboring rights to this work.
|
||||
Reference in New Issue
Block a user