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

[feature] Serve bot accounts over AP as Service instead of Person #3672

Merged
merged 3 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions docs/federation/actors.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Actors and Actor Properties

## `Service` vs `Person` actors

GoToSocial serves most accounts as the ActivityStreams `Person` type described [here](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person).

Accounts that users have selected to mark as bot accounts, however, will use the `Service` type described [here](https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service).

This type distinction can be used by remote servers to distinguish between bot accounts and "regular" user accounts.

## Inbox

GoToSocial implements Inboxes for Actors following the ActivityPub specification [here](https://www.w3.org/TR/activitypub/#inbox).
Expand Down
16 changes: 16 additions & 0 deletions internal/ap/activitystreams.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@

package ap

import (
"net/url"

"github.com/superseriousbusiness/activity/pub"
)

// PublicURI returns a fresh copy of the *url.URL version of the
// magic ActivityPub URI https://www.w3.org/ns/activitystreams#Public
func PublicURI() *url.URL {
publicURI, err := url.Parse(pub.PublicActivityPubIRI)
if err != nil {
panic(err)
}
return publicURI
}

// https://www.w3.org/TR/activitystreams-vocabulary
const (
ActivityAccept = "Accept" // ActivityStreamsAccept https://www.w3.org/TR/activitystreams-vocabulary/#dfn-accept
Expand Down
11 changes: 5 additions & 6 deletions internal/ap/ap_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import (
"io"

"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
Expand Down Expand Up @@ -111,7 +110,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {

// Anyone can like.
canLikeAlwaysProp := streams.NewGoToSocialAlwaysProperty()
canLikeAlwaysProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
canLikeAlwaysProp.AppendIRI(ap.PublicURI())
canLike.SetGoToSocialAlways(canLikeAlwaysProp)

// Empty approvalRequired.
Expand All @@ -128,7 +127,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {

// Anyone can reply.
canReplyAlwaysProp := streams.NewGoToSocialAlwaysProperty()
canReplyAlwaysProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
canReplyAlwaysProp.AppendIRI(ap.PublicURI())
canReply.SetGoToSocialAlways(canReplyAlwaysProp)

// Set empty approvalRequired.
Expand All @@ -151,7 +150,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {

// Public requires approval to announce.
canAnnounceApprovalRequiredProp := streams.NewGoToSocialApprovalRequiredProperty()
canAnnounceApprovalRequiredProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
canAnnounceApprovalRequiredProp.AppendIRI(ap.PublicURI())
canAnnounce.SetGoToSocialApprovalRequired(canAnnounceApprovalRequiredProp)

// Set canAnnounce on the policy.
Expand Down Expand Up @@ -266,7 +265,7 @@ func addressable1() ap.Addressable {
note := streams.NewActivityStreamsNote()

toProp := streams.NewActivityStreamsToProperty()
toProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
toProp.AppendIRI(ap.PublicURI())

note.SetActivityStreamsTo(toProp)

Expand All @@ -288,7 +287,7 @@ func addressable2() ap.Addressable {
note.SetActivityStreamsTo(toProp)

ccProp := streams.NewActivityStreamsCcProperty()
ccProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
ccProp.AppendIRI(ap.PublicURI())

note.SetActivityStreamsCc(ccProp)

Expand Down
2 changes: 2 additions & 0 deletions internal/ap/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ type Accountable interface {
WithTag
WithPublished
WithUpdated
WithImage
}

// Statusable represents the minimum activitypub interface for representing a 'status'.
Expand Down Expand Up @@ -439,6 +440,7 @@ type WithValue interface {
// WithImage represents an activity with ActivityStreamsImageProperty
type WithImage interface {
GetActivityStreamsImage() vocab.ActivityStreamsImageProperty
SetActivityStreamsImage(vocab.ActivityStreamsImageProperty)
}

// WithSummary represents an activity with ActivityStreamsSummaryProperty
Expand Down
60 changes: 19 additions & 41 deletions internal/api/activitypub/users/inboxpost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,38 +177,6 @@ func (suite *InboxPostTestSuite) newUndo(
return undo
}

func (suite *InboxPostTestSuite) newUpdatePerson(person vocab.ActivityStreamsPerson, cc string, updateIRI string) vocab.ActivityStreamsUpdate {
// create an update
update := streams.NewActivityStreamsUpdate()

// set the appropriate actor on it
updateActor := streams.NewActivityStreamsActorProperty()
updateActor.AppendIRI(person.GetJSONLDId().Get())
update.SetActivityStreamsActor(updateActor)

// Set the person as the 'object' property.
updateObject := streams.NewActivityStreamsObjectProperty()
updateObject.AppendActivityStreamsPerson(person)
update.SetActivityStreamsObject(updateObject)

// Set the To of the update as public
updateTo := streams.NewActivityStreamsToProperty()
updateTo.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
update.SetActivityStreamsTo(updateTo)

// set the cc of the update to the receivingAccount
updateCC := streams.NewActivityStreamsCcProperty()
updateCC.AppendIRI(testrig.URLMustParse(cc))
update.SetActivityStreamsCc(updateCC)

// set some random-ass ID for the activity
updateID := streams.NewJSONLDIdProperty()
updateID.SetIRI(testrig.URLMustParse(updateIRI))
update.SetJSONLDId(updateID)

return update
}

func (suite *InboxPostTestSuite) newDelete(actorIRI string, objectIRI string, deleteIRI string) vocab.ActivityStreamsDelete {
// create a delete
delete := streams.NewActivityStreamsDelete()
Expand All @@ -225,7 +193,7 @@ func (suite *InboxPostTestSuite) newDelete(actorIRI string, objectIRI string, de

// Set the To of the delete as public
deleteTo := streams.NewActivityStreamsToProperty()
deleteTo.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
deleteTo.AppendIRI(ap.PublicURI())
delete.SetActivityStreamsTo(deleteTo)

// set some random-ass ID for the activity
Expand Down Expand Up @@ -329,7 +297,6 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
var (
requestingAccount = new(gtsmodel.Account)
targetAccount = suite.testAccounts["local_account_1"]
activityID = "http://fossbros-anonymous.io/72cc96a3-f742-4daf-b9f5-3407667260c5"
updatedDisplayName = "updated display name!"
)

Expand All @@ -348,11 +315,19 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
requestingAccount.Emojis = []*gtsmodel.Emoji{testEmoji}

// Create an update from the account.
asAccount, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
accountable, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
if err != nil {
suite.FailNow(err.Error())
}
update, err := suite.tc.WrapAccountableInUpdate(accountable)
if err != nil {
suite.FailNow(err.Error())
}
update := suite.newUpdatePerson(asAccount, targetAccount.URI, activityID)

// Set the ID to something from fossbros anonymous.
idProp := streams.NewJSONLDIdProperty()
idProp.SetIRI(testrig.URLMustParse("https://fossbros-anonymous.io/updates/waaaaaaaaaaaaaaaaa"))
update.SetJSONLDId(idProp)

// Update.
suite.inboxPost(
Expand Down Expand Up @@ -540,17 +515,20 @@ func (suite *InboxPostTestSuite) TestPostFromBlockedAccount() {
var (
requestingAccount = suite.testAccounts["remote_account_1"]
targetAccount = suite.testAccounts["local_account_2"]
activityID = requestingAccount.URI + "/some-new-activity/01FG9C441MCTW3R2W117V2PQK3"
)

person, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
// Create an update from the account.
accountable, err := suite.tc.AccountToAS(context.Background(), requestingAccount)
if err != nil {
suite.FailNow(err.Error())
}
update, err := suite.tc.WrapAccountableInUpdate(accountable)
if err != nil {
suite.FailNow(err.Error())
}

// Post an update from foss satan to turtle, who blocks him.
update := suite.newUpdatePerson(person, targetAccount.URI, activityID)

// Post an update from foss satan
// to turtle, who blocks him.
suite.inboxPost(
update,
requestingAccount,
Expand Down
4 changes: 2 additions & 2 deletions internal/federation/federator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ func (suite *FederatorStandardTestSuite) SetupTest() {

// Ensure it's possible to deref
// main key of foss satan.
fossSatanPerson, err := suite.typeconverter.AccountToAS(context.Background(), suite.testAccounts["remote_account_1"])
fossSatanAS, err := suite.typeconverter.AccountToAS(context.Background(), suite.testAccounts["remote_account_1"])
if err != nil {
suite.FailNow(err.Error())
}

suite.httpClient = testrig.NewMockHTTPClient(nil, "../../testrig/media", fossSatanPerson)
suite.httpClient = testrig.NewMockHTTPClient(nil, "../../testrig/media", fossSatanAS)
suite.httpClient.TestRemotePeople = testrig.NewTestFediPeople()
suite.httpClient.TestRemoteStatuses = testrig.NewTestFediStatuses()

Expand Down
13 changes: 6 additions & 7 deletions internal/processing/fedi/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"fmt"
"net/url"

"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
Expand Down Expand Up @@ -72,7 +71,7 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
}

// Auth passed, generate the proper AP representation.
person, err := p.converter.AccountToAS(ctx, receiver)
accountable, err := p.converter.AccountToAS(ctx, receiver)
if err != nil {
err := gtserror.Newf("error converting account: %w", err)
return nil, gtserror.NewErrorInternalError(err)
Expand All @@ -91,7 +90,7 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
// Instead, we end up in an 'I'll show you mine if you show me
// yours' situation, where we sort of agree to reveal each
// other's profiles at the same time.
return data(person)
return data(accountable)
}

// Get requester from auth.
Expand All @@ -107,13 +106,13 @@ func (p *Processor) UserGet(ctx context.Context, requestedUsername string, reque
return nil, gtserror.NewErrorForbidden(errors.New(text))
}

return data(person)
return data(accountable)
}

func data(requestedPerson vocab.ActivityStreamsPerson) (interface{}, gtserror.WithCode) {
data, err := ap.Serialize(requestedPerson)
func data(accountable ap.Accountable) (interface{}, gtserror.WithCode) {
data, err := ap.Serialize(accountable)
if err != nil {
err := gtserror.Newf("error serializing person: %w", err)
err := gtserror.Newf("error serializing accountable: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}

Expand Down
23 changes: 6 additions & 17 deletions internal/processing/workers/federate.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import (
"context"
"net/url"

"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
Expand Down Expand Up @@ -93,11 +92,6 @@ func (f *federate) DeleteAccount(ctx context.Context, account *gtsmodel.Account)
return err
}

publicIRI, err := parseURI(pub.PublicActivityPubIRI)
if err != nil {
return err
}

// Create a new delete.
// todo: tc.AccountToASDelete
delete := streams.NewActivityStreamsDelete()
Expand All @@ -121,7 +115,7 @@ func (f *federate) DeleteAccount(ctx context.Context, account *gtsmodel.Account)

// Address the delete CC public.
deleteCC := streams.NewActivityStreamsCcProperty()
deleteCC.AppendIRI(publicIRI)
deleteCC.AppendIRI(ap.PublicURI())
delete.SetActivityStreamsCc(deleteCC)

// Send the Delete via the Actor's outbox.
Expand Down Expand Up @@ -877,14 +871,14 @@ func (f *federate) UpdateAccount(ctx context.Context, account *gtsmodel.Account)
return err
}

// Convert account to ActivityStreams Person.
person, err := f.converter.AccountToAS(ctx, account)
// Convert account to Accountable.
accountable, err := f.converter.AccountToAS(ctx, account)
if err != nil {
return gtserror.Newf("error converting account to Person: %w", err)
}

// Use ActivityStreams Person as Object of Update.
update, err := f.converter.WrapPersonInUpdate(person, account)
// Use Accountable as Object of Update.
update, err := f.converter.WrapAccountableInUpdate(accountable)
if err != nil {
return gtserror.Newf("error wrapping Person in Update: %w", err)
}
Expand Down Expand Up @@ -1089,11 +1083,6 @@ func (f *federate) MoveAccount(ctx context.Context, account *gtsmodel.Account) e
return err
}

publicIRI, err := parseURI(pub.PublicActivityPubIRI)
if err != nil {
return err
}

// Create a new move.
move := streams.NewActivityStreamsMove()

Expand All @@ -1115,7 +1104,7 @@ func (f *federate) MoveAccount(ctx context.Context, account *gtsmodel.Account) e
ap.AppendTo(move, followersIRI)

// Address the move CC public.
ap.AppendCc(move, publicIRI)
ap.AppendCc(move, ap.PublicURI())

// Send the Move via the Actor's outbox.
if _, err := f.FederatingActor().Send(
Expand Down
Loading