Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic implementation of "client to server" ActivityPub protocol #499

Open
yvolk opened this issue Dec 9, 2018 · 53 comments

Comments

Projects
None yet
7 participants
@yvolk
Copy link
Member

commented Dec 9, 2018

Note on current state

Minimal "client to server" ActivityPub implementation, which tested to be able to connect to https://pleroma.site (and other Pleroma instances also!), read a timeline and post notes is available since v.47.02
On its download see #456

As currently the ActivityPub implementation is in Alpha phase, the "ActivityPub" type is not visible by default (in order not to confuse other users). Turn it on in AndStatus Settings -> Troubleshooting, then
you will be able to add a new account of the "ActivityPub" type.

Description

As defined at https://www.w3.org/TR/activitypub/ , ActivityPub provides two layers:

  • A server to server federation protocol (so decentralized websites can share information)
  • A client to server protocol (so users, including real-world users, bots, and other automated processes, can communicate with ActivityPub using their accounts on servers, from a phone or desktop or web application or whatever)

The https://test.activitypub.rocks/ site contains a test suite that checks basic feature that are required for a client app to implement #ActivityPub.

Below is a list of corresponding steps/features (for the start, copied from that site), which we are implementing in AndStatus, together with their implementation status:

AndStatus UI changes:

  • Add "ActivityPub" type of Social network.

Client: Basic submission

  • MUST Client discovers the URL of a user's outbox from their profile
  • MUST Client submits activity by sending an HTTP post request to the outbox URL with the Content-Type of application/ld+json; profile="https://www.w3.org/ns/activitystreams"
  • MUST Client submission request body is either a single Activity or a single non-Activity Object
  • Client submission request is authenticated with the credentials of the user to whom the outbox belongs (this could be using an OAuth 2.0 bearer token or something else)
    Currently we reused OAuth 2.0 implementation that works in AndStatus for Mastodon.

Client: Required properties

  • MUST Client provides the object property when submitting the following activity types to an outbox: Create, Update, Delete, Follow, Add, Remove, Like, Block, Undo.
  • MUST Client provides the target property when submitting the following activity types to an outbox: Add, Remove.

Client: Add targets on reply
Reply to a post with multiple recipients.

  • SHOULD Before submitting a new activity or object, Client infers appropriate target audience by recursively looking at certain properties (e.g. inReplyTo, See Section 7), and adds these targets to the new submission's audience. (The client suggests audience targeting based on participants in the referenced thread)
  • SHOULD The client also limits depth of recursion used to gather targeting.

Client: Accept header on object retrieval
Trigger the client to retrieve some remote object.

@yvolk yvolk added the Compatibility label Dec 9, 2018

@yvolk yvolk self-assigned this Dec 9, 2018

@yvolk yvolk changed the title Basic implementation "client to server" ActivityPub protocol Basic implementation of "client to server" ActivityPub protocol Dec 9, 2018

@ned14

This comment has been minimized.

Copy link

commented Jan 24, 2019

Due to pressing need due to imminent end of Google Plus, I have been knocking together the world's simplest client-to-server ActivityPub service which exclusively works on static HTML website generators such as Jekyll and Hugo. No databases, no subscriptions, no followers, no complexity. All I want is that an Android app can post new blog posts, possibly with photos and/or location, to my static HTML website, like I currently can do with Google Plus.

What I have so far can be found at https://github.com/ned14/static-website-activitypub. It is still missing the inbox and outbox implementations (coming soon). Can I request that your checkbox list above can be broken into a simpler "initial support"?

What I would need:

  • Client discovers the URL of a user's outbox from their profile via .well-know/webfinger?resource=acct:email@domain
  • Client submits activity by sending an HTTP post request to the outbox URL with the Content-Type of application/ld+json; profile="https://www.w3.org/ns/activitystreams"
  • Client submission request body is a single Create Activity of Post or Article object, with embedded images/videos/location appended as attachments.
  • Client submission request is authenticated with the credentials of the user to whom the outbox belongs using HTTP signatures (Mastodon, Pleroma etc all seem to use HTTP signatures for ActivityPub, not OAuth)
  • Client implements the following activity types when POSTing to an outbox: Create, Update, Delete, Remove. I don't need the others.
@yvolk

This comment has been minimized.

Copy link
Member Author

commented Jan 24, 2019

@ned I see that I need to start with at least "Add "ActivityPub" type of Social network" and adding "application/ld+json" to a couple of places as mentioned above. This could be done quickly and will allow to add other features as "compatibility improvements".
Please note that AndStatus supports pump.io for years, and pump.io API is quite close to what ActivityPub is...

@ned14

This comment has been minimized.

Copy link

commented Jan 25, 2019

Thanks for the tip. Reading through https://github.com/pump-io/pump.io/blob/master/API.md, as far as I can tell these are the main significant differences between pump.io and W3C ActivityPub:

  • Use of OAuth instead of HTTP signatures to authenticate. In fairness. the W3C spec leaves it open ended, but any of the W3C ActivityPub implementations I've curled so far use the latter.
  • pump.io appears to hardcode the REST endpoints, and with slightly different naming. The W3C spec leaves ./well-known/webfinger complete flexibility to specify the endpoints to use.
  • MIME types appear to be more specific in the W3C spec. Moreover, if you don't specify the specific W3C MIME types in the Accept: header, pleroma and Mastodon appear to try 301 redirecting you to elsewhere.

Otherwise, they do indeed look quite similar. The major blocker is the OAuth support really. I'm a bit loathe to implement it, because it's fundamentally broken, where HTTP signatures are not. But if it gets me up and running with andStatus quicker, I guess I can live with it.

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Jan 25, 2019

@ned14 Regarding restpoints. In pump.io, they are not hardcoded. Although they are usually the same, and this may lead to the hardcoded implementations in clients.
See the sample pump.io response with endpoints explicitly set: https://github.com/andstatus/andstatus/blob/master/app/src/androidTest/res/raw/pumpio_actor_lists_person.json

Recently I fixed hardcoded endpoints for pump.io in AndStatus (exactly for ActivityPub migration), see https://github.com/andstatus/andstatus/blob/master/app/src/main/java/org/andstatus/app/net/social/ActorEndpoints.java

yvolk added a commit that referenced this issue Jan 26, 2019

#499 Add "ActivityPub" type of Social network (turn it on in Settings…
… -> Troubleshooting). Content-Type "application/ld+json..."
@yvolk

This comment has been minimized.

Copy link
Member Author

commented Jan 26, 2019

@ned14 I think that now you can start testing/working at a server-side of "client to server" ActivityPub protocol. AndStatus client is just updated to allow creation of a Social Network of ActivityPub type.
See more details and a link to the prebuilt alpha version of AndStatus in the top post of this thread.

As I wrote there, I reused OAuth 2.0 implementation that works in AndStatus for Mastodon (with bearer token...). Everything else - from existing Pump.io implementation. Plus added "application/ld+json..." as required.
If you need additional changes to the Client registration + User authentication/authorization part, feel free to change existing code. Btw

  • Client Registration is implemented in subclasses of org.andstatus.app.net.http.HttpConnection#registerClient (called from org.andstatus.app.account.AccountSettingsActivity.OAuthRegisterClientTask ) - one implementation works in Mastodon, another - in Pump.io...
  • User authentication/authorisation - in org.andstatus.app.account.AccountSettingsActivity.OAuthAcquireRequestTokenTask and org.andstatus.app.account.AccountSettingsActivity.OAuthAcquireAccessTokenTask

In order to move forward, past authorisation step, I need an account on a server that successfully authenticates/authorises a User...

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Jan 26, 2019

A couple of years ago we did the same OAuth implementation exercise with @Gargron for Mastodon, please see this post #419 (comment) and below.
In short, an example/the simplest client application is needed (with one empty activity, e.g. from an Android template...), using any libraries, that can, using hard-coded username and password, connect to your server and request Home timeline for the User.
So we will know that:

  • Client registration works
  • Authentication works
  • At least one timeline request works (no need to parse it)
    I then integrate this into AndStatus, so the next steps will be easier.
    ?!
@autogestion

This comment has been minimized.

Copy link

commented Jan 26, 2019

@ned14 Mastodon, Pleroma and others uses http signatures only when interacting server-to-server. For interacting with clients Mastodon uses custom API, not related to ActivityPub. Pleroma just cloned it from Mastodon, and other servers uses custom APIs. OAuth is recommended way to authorize the client https://www.w3.org/TR/activitypub/#authorization -> https://www.w3.org/wiki/SocialCG/ActivityPub/Authentication_Authorization#Client_to_Server

When test suite on https://test.activitypub.rocks/ is trying to obtain a token (when testing client-to-server interaction with server), it follows next steps

  1. hit .well-know/webfinger?resource=acct:email@domain and get link to user profile
  2. fetch user profile and get from its "endpoints" list oauthTokenEndpoint url or oauthAuthorizationEndpoint url,
    it's described in spec https://www.w3.org/TR/activitypub/#actor-objects
  3. and then follows oauth procedure
@ned14

This comment has been minimized.

Copy link

commented Jan 26, 2019

Bah, looks like I'll have to implement OAuth 2.0 so. It looks complex and hard to get right. But I'll give it a go. Thanks for the feedback, you saved me work!

@autogestion

This comment has been minimized.

Copy link

commented Jan 26, 2019

@ned14 You can try copypasting from this repo https://github.com/autogestion/pubgate
all that steps are already implemented there) except oauthAuthorizationEndpoint. But I think token endpoint is enough

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Jan 26, 2019

@autogestion @ned14 Please note that in both Pump.io and Mastodon client-to-server OAuth implementations the Client application starts with Dynamic client registration (implemented differently though...). Something like https://tools.ietf.org/html/rfc7591#section-1.3 (see Dynamic Registration as part of https://openid.net/connect/ also)
Only after successful Client Registration, User Identification and Authorisation starts...

I think we must have Client Registration in our new solutions, because even existing social networks implemented this (formally optional) step years ago. Even Twitter has Client Registration, although it's not dynamic: application developer has to register his Client application manually at Twitter's developer's site.
As we are building bricks for a federated network with lots of servers, the Client registration should be dynamic in order to avoid a need for manual steps.

If you want a server code to be compatible with different client applications, not only with the one that You (or we) created, please make it at least look like generic OAuth 2.0 server solution (i.e. having similar public API / process implemented).
For example, at least for beginning, you can implement "Dynamic Client Registration" as a simple static stub that returns the same "client identifier" and "client secret" (and maybe other server metadata...) to all clients and doesn't persist any client registration data. This should be easy.
?!

Dynamic registration should result in at least two ids, returned from a server: "client identifier" and "client secret". They are referred to e.g. as "YOUR_API_KEY" and "YOUR_API_SECRET" in the ScribeJava client library docs: https://github.com/scribejava/scribejava
BTW, there is a PR to add OpenID Connect support to the ScribeJava scribejava/scribejava#646

And another note on dynamic registration metadata (returned by a server) for @autogestion and his Pubgate (as defined in RFC 7591) :
"grant_types" - Array of OAuth 2.0 grant type strings that the client can use at the token endpoint... If omitted, the default behavior is that the client will use only the "authorization_code" Grant Type.
This means that for the "password" grant type this metadata field should be provided explicitly.
I found another interesting reference about the "password" grant type (in an answer to https://softwareengineering.stackexchange.com/questions/297373/oauth2-ropc-vs-basic-auth-for-public-rest-apis ): "OAuth2 is much more than Resource Owner Password Credentials which, according to the specification, exists for "legacy or migration reasons", is considered "higher risk than other grant types" and the specification explicitly states that the clients and authorization servers "SHOULD minimize use of this grant type and utilize other grant types whenever possible"."

@kaniini

This comment has been minimized.

Copy link

commented Jan 27, 2019

pleroma.site (as well as any other Pleroma install) has ActivityPub C2S available through OAuth. You will need to use the Mastodon API to obtain client secrets for OAuth.

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Jan 27, 2019

@kaniini I registered at pleroma.site as andstatus@pleroma.site and then tried to adapt my current ActivityPub implementation to it:

  1. Client registration works at 'https://pleroma.site/api/v1/apps'
  2. Retrieval of request token works at 'oauth/token'
  3. Authorisation via Web View works at 'https://pleroma.site/oauth/authorize...' and access token received.
  4. Then I request "whoami" point and at this step I cannot get JSON with a User info, but getting either HTML document or even 406 server error.
    I tried these URLs:
    4.1. statusCode:OK (200); url:'https://pleroma.site/api/whoami'; response:'<html lang…'
    4.2. statusCode:OK (200); url:'https://pleroma.site/api/v1/whoami'; response:'<html lang…';
    4.3. statusCode:CLIENT_ERROR (406); url:'https://pleroma.site/api/v1/accounts/verify_credentials'; authenticated; response:'{"errors":{"detail":"Internal server error…';

What should I do, where can I get more info on correct API?

@kaniini

This comment has been minimized.

Copy link

commented Jan 30, 2019

the correct location to make AP C2S calls is at /users/:username/inbox

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Jan 30, 2019

the correct location to make AP C2S calls is at /users/:username/inbox

@kaniini Thank you, after several attempts I figured out the correct full URL:
https://pleroma.site/users/AndStatus/inbox

  • and got JSON response. Will parse it and return to You to move further, if you don't mind :-) ... oh, I see, why it was not parsed: because we expect an Actor's profile, but actually receive his "inbox" with activities... :-(

However, this URL, looking like an actor's inbox, is conceptually incorrect for the "whoami" call as even AntivityPub spec says: "The inbox is discovered through the inbox property of an actor's profile". This is why URL of the "whoami" endpoint should be the same for every user, and not the "inbox" of a concrete user/actor.
I think :-)

This is "whoami" endpoint where the authenticated Actor receives his/her profile information, including URLs of other endpoints!

?!

@Gargron

This comment has been minimized.

Copy link

commented Feb 2, 2019

You will need to use the Mastodon API to obtain client secrets for OAuth.

Ugh wait. What's the point of having a C2S standard if OAuth registration isn't even part of it? How was it intended to work? 🤔

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Feb 3, 2019

You will need to use the Mastodon API to obtain client secrets for OAuth.

Ugh wait. What's the point of having a C2S standard if OAuth registration isn't even part of it? How was it intended to work? 🤔

@Gargron Yes, ActivityPub refers to OAuth but doesn't mention the "registration" word explicitly.

  1. ActivityPub specification refers to OAuth 2.0 and OAuth 2.0 bearer tokens as to a way to authenticate client to server interactions (see descriptions of corresponding endpoints in the "Actor objects" section https://www.w3.org/TR/activitypub/#actor-objects )
  2. In the (non-normative) "B.1 Authentication and Authorization" section the specification also refers to the Social Web Community Group Authentication and Authorization best practices report ( https://www.w3.org/wiki/SocialCG/ActivityPub/Authentication_Authorization ), which in a "Client to Server" section says:

ActivityPub clients authenticate against a server using OAuth 2.0 bearer tokens. ...Client IDs may be acquired out of band for some servers. They may also be acquired using RFC7591.

The RFC7591 ( https://tools.ietf.org/html/rfc7591 ) is "OAuth 2.0 Dynamic Client Registration Protocol".

So OAuth Client Registration is at least a part of the specification's best practices :-)

We do have working OAuth 1.0 and Dynamic Client registration and the "whoami" endpoint for a federated network that's very close to ActivityPub from a Client app point of view ( pump.io https://github.com/pump-io/pump.io/blob/master/API.md ). Plus we do have OAuth 2.0 e.g. in Mastodon. So I think that we just need to go only one step further to implement OAuth 2.0 + Dynamic Client registration + "whoami" point in order to have a "bootstrap" for the "Client to Server interactions", which are actually a part of the ActivityPub spec.

This is how it will work.
?!

@autogestion

This comment has been minimized.

Copy link

commented Feb 3, 2019

Have to say, that this is a bit strange to use OAuth in federated network. Because it's a standard which allows to authorize using some centralized authority, and fediverse do not have one

@kaniini

This comment has been minimized.

Copy link

commented Feb 3, 2019

@yvolk

my current Pleroma tree has /api/ap/whoami for this purpose. I will deploy it on pleroma.site in a few hours. will this work for you?

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Feb 4, 2019

@yvolk

my current Pleroma tree has /api/ap/whoami for this purpose. I will deploy it on pleroma.site in a few hours. will this work for you?

@kaniini Yes, will test further and let you know. Thank you.

@kaniini

This comment has been minimized.

Copy link

commented Feb 4, 2019

I deployed that endpoint on pleroma.site.

@kaniini

This comment has been minimized.

Copy link

commented Feb 5, 2019

The AP C2S whoami endpoint which allows for mapping OAuth credentials to an actor endpoint is now in Pleroma upstream.

oauthAuthorizationEndpoint and oauthTokenEndpoint will be added to actors too.

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Feb 7, 2019

The AP C2S whoami endpoint which allows for mapping OAuth credentials to an actor endpoint is now in Pleroma upstream.

oauthAuthorizationEndpoint and oauthTokenEndpoint will be added to actors too.

@kaniini Frankly I don't understand, why these two endpoints may be needed, if I get them after OAuth authentication procedure.
What worries me in our growing solution, are URLs of the two quite voluntary/hardcoded endpoints:

  1. Client registration at https://pleroma.site/api/v1/apps
  2. whoami endpoint at https://pleroma.site/api/ap/whoami
    Both endpoints are unknown to a client application, visiting this host.

I think that

  1. we need to use more "standard" URL for the Client Registration, e.g.
    https://pleroma.site/register (just like in examples, although non-normative, in https://tools.ietf.org/html/rfc7591 )
  2. Whoami endpoint URL should be added as a separate "whoami" property to a metadata, returned by a server during OAuth...

?!

We need to dig more in standards: I suspect that solution for whoami endpoint is already defined...

@lanodan

This comment has been minimized.

Copy link

commented Feb 7, 2019

@autogestion

This comment has been minimized.

Copy link

commented Feb 7, 2019

@yvolk

Frankly I don't understand, why these two endpoints may be needed, if I get them after OAuth authentication procedure.

Why it is so important to perform client registration first? Client could start with fetching user profile using webfinger. It is de-facto standard for activitypub servers and is used to fetch user profiles, when user from one server searches for user from another server
https://mastodon.social/.well-known/webfinger?resource=acct:autogestion@mastodon.social
https://pleroma.site/.well-known/webfinger?resource=acct:kaniini@pleroma.site
https://pubgate.autogestion.org/.well-known/webfinger?resource=acct:dev@pubgate.autogestion.org

User profile MAY contain

oauthAuthorizationEndpoint and oauthTokenEndpoint will be added to actors too.

and it could be extended with attribute which contains url for dynamic registration. And there will be no need to standardize endpoints names

Still, this discussion looks like trying to stretch owl over the globe. OAuth is designed for cases when there is certain centralized server (which defines endpoints due to OAuth spec), not for chaotic federation

yvolk added a commit that referenced this issue Feb 17, 2019

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Feb 17, 2019

@kaniini @autogestion I made the simplest implementation of #ActivityPub C2S protocol in #AndStatus, allowing me to log into pleroma.site, read home timeline and get other user's info (i.e. read the Actor's profile based on his "id").
But I've got an error response from pleroma.site server on an attempt to send a Note, so I created another bug report in pleroma issues tracker: https://git.pleroma.social/pleroma/pleroma/issues/650

Alpha build of AndStatus is attached to this comment: #499 (comment) , source code is available in the repository.
send--activitypub-pleroma-001

@kaniini

This comment has been minimized.

Copy link

commented Feb 18, 2019

The error is that you're sending to inbox instead of outbox. Not a pleroma bug just a mistake :)

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Feb 19, 2019

yvolk added a commit that referenced this issue Feb 19, 2019

yvolk added a commit that referenced this issue Feb 21, 2019

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Feb 21, 2019

@kaniini Thank you for investigation. In #AndStatus v.47.02-alpha (attached to the first post of this thread) I can successfully post a Note to pleroma.site via #ActivityPub C2S API and see the Note published in AndStatus.
The latest page of inbox and outbox timelines is loaded successfully, (I didn't implement paged loading yet...).

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Mar 10, 2019

@kaniini As we discussed earlier here it would be good to add "whoami" ("me") endpoint to an OAuth response in order not to hardcode this endpoint.
As I see, pleroma.site doesn't send this endpoint yet: I checked both Client registration response (preferred place for the endpoint that is the same for all accounts)
and Access token response.
At least I can easily extract that endpoint from both places.
Please implement!

@kaniini

This comment has been minimized.

Copy link

commented Mar 16, 2019

yvolk added a commit that referenced this issue Mar 16, 2019

#499 Implement conversation retrieval for ActivityPub. The "conversat…
…ion id" is got from "conversation" and (if not found) from "context" properties.
@yvolk

This comment has been minimized.

Copy link
Member Author

commented Mar 16, 2019

@kaniini Thank you, I implemented corresponding change in AndStatus (since v.47.06), waiting for your change to appear live on the pleroma.site

yvolk added a commit that referenced this issue Mar 17, 2019

#499 "me" link is returned in the OAuth access token response just li…
…ke shown in https://indieauth.spec.indieweb.org/#access-token-response

It is used in ActivityPub as WhoAmI endpoint to verify account and get the Actor's metadata.

yvolk added a commit that referenced this issue Mar 17, 2019

#499 ActivityPub: Use "attributedTo" property to set Note's Author.
Fix activities order (oldest activities first!).
@kaniini

This comment has been minimized.

Copy link

commented Mar 19, 2019

This change has gone live on pleroma.site.

yvolk added a commit that referenced this issue Mar 19, 2019

#499 ActivityPub: Changed the default "whoami" endpoint to "api/whoam…
…i" (just like in Pump.io). The endpoint is used in case the "me" property isn't set in the OAuth access token response.
@yvolk

This comment has been minimized.

Copy link
Member Author

commented Mar 19, 2019

@kaniini Thank you, the "me" property works! AndStatus v.47.06 (released yesterday) uses this already.

As another step forward I raised the issue: https://git.pleroma.social/pleroma/pleroma/issues/741 "ActivityPub C2S: Request to the URL of the conversation's ID returns blank HTML page instead of JSON conversation".
Please implement.

@yvolk

This comment has been minimized.

Copy link
Member Author

commented Mar 23, 2019

@kaniini I raised another issue to pleroma: "ActivityPub C2S: No way to request newer page of the Collection" that is really a blocker even for the "Basic" ActivityPub C2S implementation: https://git.pleroma.social/pleroma/pleroma/issues/751

Please implement.

yvolk added a commit that referenced this issue Mar 23, 2019

yvolk added a commit that referenced this issue Mar 27, 2019

1. #499 ActivityPub: Collection implementation started.
2. mimeType of shared and attached media files is taken into account

yvolk added a commit that referenced this issue Mar 31, 2019

yvolk added a commit that referenced this issue Mar 31, 2019

yvolk added a commit that referenced this issue Apr 2, 2019

#499 ActivityPub: "Public" checkbox is always visible in the Note Edi…
…tor. "Open in Browser" works for ActivityPub

yvolk added a commit that referenced this issue Apr 3, 2019

yvolk added a commit that referenced this issue Apr 5, 2019

#499 ActivityPub: Construct the best possible Actor's URI to use in A…
…udience. In particular, with "acct" URI scheme

yvolk added a commit that referenced this issue May 19, 2019

@yvolk

This comment has been minimized.

Copy link
Member Author

commented May 25, 2019

Some time ago posting to pleroma.site stopped working properly due to changes at Pleroma side. I fixed this in AndStatus v.49.00, download from #456

The only critical thing to implement is posting attached images. @kaniini How to do this via "ActivityPub C2S"?
ActivityPubTimeline

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.