diff options
author | 2022-09-23 21:27:35 +0200 | |
---|---|---|
committer | 2022-09-23 20:27:35 +0100 | |
commit | 69a193dae543641a2db6823fa6493c02f56fafbd (patch) | |
tree | c1a0c71d64642db12a17c6770642c3e0af859960 /internal | |
parent | [docs] NLnet follow up questions (#846) (diff) | |
download | gotosocial-69a193dae543641a2db6823fa6493c02f56fafbd.tar.xz |
[feature] Allow delivery to sharedInboxes where possible (#847)
* update Activity
* add instance-deliver-to-shared-inboxes setting
* update activity version again
* add SharedInboxURI field to accounts
* serdes for endpoints/sharedInbox
* deliver to sharedInbox if one is available
* update tests
* only assign shared inbox if shared domain
* look for shared inbox if currently nil
* go fmt
* finger to get params.RemoteAccountID if necessary
* make comments clearer
* compare dns more consistently
Diffstat (limited to 'internal')
-rw-r--r-- | internal/ap/extract.go | 30 | ||||
-rw-r--r-- | internal/ap/interfaces.go | 6 | ||||
-rw-r--r-- | internal/cache/account.go | 1 | ||||
-rw-r--r-- | internal/config/config.go | 5 | ||||
-rw-r--r-- | internal/config/defaults.go | 5 | ||||
-rw-r--r-- | internal/config/flags.go | 1 | ||||
-rw-r--r-- | internal/config/helpers.gen.go | 25 | ||||
-rw-r--r-- | internal/db/bundb/migrations/20220922142408_shared_inbox_delivery.go | 46 | ||||
-rw-r--r-- | internal/federation/dereferencing/account.go | 42 | ||||
-rw-r--r-- | internal/federation/dereferencing/account_test.go | 18 | ||||
-rw-r--r-- | internal/federation/federatingactor_test.go | 2 | ||||
-rw-r--r-- | internal/federation/federatingdb/inbox.go | 20 | ||||
-rw-r--r-- | internal/federation/federatingdb/inbox_test.go | 21 | ||||
-rw-r--r-- | internal/gtsmodel/account.go | 1 | ||||
-rw-r--r-- | internal/processing/account_test.go | 2 | ||||
-rw-r--r-- | internal/processing/fromfederator_test.go | 2 | ||||
-rw-r--r-- | internal/typeutils/astointernal.go | 14 | ||||
-rw-r--r-- | internal/typeutils/astointernal_test.go | 25 | ||||
-rw-r--r-- | internal/typeutils/internaltoas.go | 15 | ||||
-rw-r--r-- | internal/typeutils/internaltoas_test.go | 21 |
20 files changed, 289 insertions, 13 deletions
diff --git a/internal/ap/extract.go b/internal/ap/extract.go index e1566059c..2d3a6fd9c 100644 --- a/internal/ap/extract.go +++ b/internal/ap/extract.go @@ -702,3 +702,33 @@ func ExtractSensitive(withSensitive WithSensitive) bool { return false } + +// ExtractSharedInbox extracts the sharedInbox URI properly from an Actor. +// Returns nil if this property is not set. +func ExtractSharedInbox(withEndpoints WithEndpoints) *url.URL { + endpointsProp := withEndpoints.GetActivityStreamsEndpoints() + if endpointsProp == nil { + return nil + } + + for iter := endpointsProp.Begin(); iter != endpointsProp.End(); iter = iter.Next() { + if iter.IsActivityStreamsEndpoints() { + endpoints := iter.Get() + if endpoints == nil { + return nil + } + sharedInboxProp := endpoints.GetActivityStreamsSharedInbox() + if sharedInboxProp == nil { + return nil + } + + if !sharedInboxProp.IsIRI() { + return nil + } + + return sharedInboxProp.GetIRI() + } + } + + return nil +} diff --git a/internal/ap/interfaces.go b/internal/ap/interfaces.go index 35ce937a1..803eda640 100644 --- a/internal/ap/interfaces.go +++ b/internal/ap/interfaces.go @@ -40,6 +40,7 @@ type Accountable interface { WithFollowers WithFeatured WithManuallyApprovesFollowers + WithEndpoints } // Statusable represents the minimum activitypub interface for representing a 'status'. @@ -337,3 +338,8 @@ type WithItems interface { type WithManuallyApprovesFollowers interface { GetActivityStreamsManuallyApprovesFollowers() vocab.ActivityStreamsManuallyApprovesFollowersProperty } + +// WithEndpoints represents a Person or profile with the endpoints property +type WithEndpoints interface { + GetActivityStreamsEndpoints() vocab.ActivityStreamsEndpointsProperty +} diff --git a/internal/cache/account.go b/internal/cache/account.go index 709d1ec30..f478c81d3 100644 --- a/internal/cache/account.go +++ b/internal/cache/account.go @@ -136,6 +136,7 @@ func copyAccount(account *gtsmodel.Account) *gtsmodel.Account { URL: account.URL, LastWebfingeredAt: account.LastWebfingeredAt, InboxURI: account.InboxURI, + SharedInboxURI: account.SharedInboxURI, OutboxURI: account.OutboxURI, FollowingURI: account.FollowingURI, FollowersURI: account.FollowersURI, diff --git a/internal/config/config.go b/internal/config/config.go index 7c0bf99a7..ff77bd367 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -67,8 +67,9 @@ type Configuration struct { WebTemplateBaseDir string `name:"web-template-base-dir" usage:"Basedir for html templating files for rendering pages and composing emails."` WebAssetBaseDir string `name:"web-asset-base-dir" usage:"Directory to serve static assets from, accessible at example.org/assets/"` - InstanceExposePeers bool `name:"instance-expose-peers" usage:"Allow unauthenticated users to query /api/v1/instance/peers?filter=open"` - InstanceExposeSuspended bool `name:"instance-expose-suspended" usage:"Expose suspended instances via web UI, and allow unauthenticated users to query /api/v1/instance/peers?filter=suspended"` + InstanceExposePeers bool `name:"instance-expose-peers" usage:"Allow unauthenticated users to query /api/v1/instance/peers?filter=open"` + InstanceExposeSuspended bool `name:"instance-expose-suspended" usage:"Expose suspended instances via web UI, and allow unauthenticated users to query /api/v1/instance/peers?filter=suspended"` + InstanceDeliverToSharedInboxes bool `name:"instance-deliver-to-shared-inboxes" usage:"Deliver federated messages to shared inboxes, if they're available."` AccountsRegistrationOpen bool `name:"accounts-registration-open" usage:"Allow anyone to submit an account signup request. If false, server will be invite-only."` AccountsApprovalRequired bool `name:"accounts-approval-required" usage:"Do account signups require approval by an admin or moderator before user can log in? If false, new registrations will be automatically approved."` diff --git a/internal/config/defaults.go b/internal/config/defaults.go index a0d409c5f..5eb87ce68 100644 --- a/internal/config/defaults.go +++ b/internal/config/defaults.go @@ -46,8 +46,9 @@ var Defaults = Configuration{ WebTemplateBaseDir: "./web/template/", WebAssetBaseDir: "./web/assets/", - InstanceExposePeers: false, - InstanceExposeSuspended: false, + InstanceExposePeers: false, + InstanceExposeSuspended: false, + InstanceDeliverToSharedInboxes: true, AccountsRegistrationOpen: true, AccountsApprovalRequired: true, diff --git a/internal/config/flags.go b/internal/config/flags.go index 183ed3762..f37a50c9e 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -63,6 +63,7 @@ func AddServerFlags(cmd *cobra.Command) { // Instance cmd.Flags().Bool(InstanceExposePeersFlag(), cfg.InstanceExposePeers, fieldtag("InstanceExposePeers", "usage")) cmd.Flags().Bool(InstanceExposeSuspendedFlag(), cfg.InstanceExposeSuspended, fieldtag("InstanceExposeSuspended", "usage")) + cmd.Flags().Bool(InstanceDeliverToSharedInboxesFlag(), cfg.InstanceDeliverToSharedInboxes, fieldtag("InstanceDeliverToSharedInboxes", "usage")) // Accounts cmd.Flags().Bool(AccountsRegistrationOpenFlag(), cfg.AccountsRegistrationOpen, fieldtag("AccountsRegistrationOpen", "usage")) diff --git a/internal/config/helpers.gen.go b/internal/config/helpers.gen.go index c8fd4f621..adc020aba 100644 --- a/internal/config/helpers.gen.go +++ b/internal/config/helpers.gen.go @@ -593,6 +593,31 @@ func GetInstanceExposeSuspended() bool { return global.GetInstanceExposeSuspende // SetInstanceExposeSuspended safely sets the value for global configuration 'InstanceExposeSuspended' field func SetInstanceExposeSuspended(v bool) { global.SetInstanceExposeSuspended(v) } +// GetInstanceDeliverToSharedInboxes safely fetches the Configuration value for state's 'InstanceDeliverToSharedInboxes' field +func (st *ConfigState) GetInstanceDeliverToSharedInboxes() (v bool) { + st.mutex.Lock() + v = st.config.InstanceDeliverToSharedInboxes + st.mutex.Unlock() + return +} + +// SetInstanceDeliverToSharedInboxes safely sets the Configuration value for state's 'InstanceDeliverToSharedInboxes' field +func (st *ConfigState) SetInstanceDeliverToSharedInboxes(v bool) { + st.mutex.Lock() + defer st.mutex.Unlock() + st.config.InstanceDeliverToSharedInboxes = v + st.reloadToViper() +} + +// InstanceDeliverToSharedInboxesFlag returns the flag name for the 'InstanceDeliverToSharedInboxes' field +func InstanceDeliverToSharedInboxesFlag() string { return "instance-deliver-to-shared-inboxes" } + +// GetInstanceDeliverToSharedInboxes safely fetches the value for global configuration 'InstanceDeliverToSharedInboxes' field +func GetInstanceDeliverToSharedInboxes() bool { return global.GetInstanceDeliverToSharedInboxes() } + +// SetInstanceDeliverToSharedInboxes safely sets the value for global configuration 'InstanceDeliverToSharedInboxes' field +func SetInstanceDeliverToSharedInboxes(v bool) { global.SetInstanceDeliverToSharedInboxes(v) } + // GetAccountsRegistrationOpen safely fetches the Configuration value for state's 'AccountsRegistrationOpen' field func (st *ConfigState) GetAccountsRegistrationOpen() (v bool) { st.mutex.Lock() diff --git a/internal/db/bundb/migrations/20220922142408_shared_inbox_delivery.go b/internal/db/bundb/migrations/20220922142408_shared_inbox_delivery.go new file mode 100644 index 000000000..d0449af86 --- /dev/null +++ b/internal/db/bundb/migrations/20220922142408_shared_inbox_delivery.go @@ -0,0 +1,46 @@ +/* + GoToSocial + Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +package migrations + +import ( + "context" + "strings" + + "github.com/uptrace/bun" +) + +func init() { + up := func(ctx context.Context, db *bun.DB) error { + _, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? TEXT", bun.Ident("accounts"), bun.Ident("shared_inbox_uri")) + if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) { + return err + } + return nil + } + + down := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + return nil + }) + } + + if err := Migrations.Register(up, down); err != nil { + panic(err) + } +} diff --git a/internal/federation/dereferencing/account.go b/internal/federation/dereferencing/account.go index 0fda96bf4..6a633a54a 100644 --- a/internal/federation/dereferencing/account.go +++ b/internal/federation/dereferencing/account.go @@ -29,6 +29,7 @@ import ( "sync" "time" + "github.com/miekg/dns" "github.com/superseriousbusiness/activity/streams" "github.com/superseriousbusiness/activity/streams/vocab" "github.com/superseriousbusiness/gotosocial/internal/ap" @@ -197,10 +198,12 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar accountDomain = params.RemoteAccountHost } - // to save on remote calls: only webfinger if we don't have a remoteAccount yet, or if we haven't - // fingered the remote account for at least 2 days; don't finger instance accounts + // to save on remote calls, only webfinger if: + // - we don't know the remote account ActivityPub ID yet OR + // - we haven't found the account yet in some other way OR + // - we haven't webfingered the account for two days AND the account isn't an instance account var fingered time.Time - if foundAccount == nil || (foundAccount.LastWebfingeredAt.Before(time.Now().Add(webfingerInterval)) && !instanceAccount(foundAccount)) { + if params.RemoteAccountID == nil || foundAccount == nil || (foundAccount.LastWebfingeredAt.Before(time.Now().Add(webfingerInterval)) && !instanceAccount(foundAccount)) { accountDomain, params.RemoteAccountID, err = d.fingerRemoteAccount(ctx, params.RequestingUsername, params.RemoteAccountUsername, params.RemoteAccountHost) if err != nil { err = fmt.Errorf("GetRemoteAccount: error while fingering: %s", err) @@ -279,6 +282,37 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar } } + // if SharedInboxURI is nil, that means we don't know yet if this account has + // a shared inbox available for it, so we need to check this here + var sharedInboxChanged bool + if foundAccount.SharedInboxURI == nil { + // we need the accountable for this, so get it if we don't have it yet + if accountable == nil { + accountable, err = d.dereferenceAccountable(ctx, params.RequestingUsername, params.RemoteAccountID) + if err != nil { + err = fmt.Errorf("GetRemoteAccount: error dereferencing accountable: %s", err) + return + } + } + + // This can be: + // - an empty string (we know it doesn't have a shared inbox) OR + // - a string URL (we know it does a shared inbox). + // Set it either way! + var sharedInbox string + + if sharedInboxURI := ap.ExtractSharedInbox(accountable); sharedInboxURI != nil { + // only trust shared inbox if it has at least two domains, + // from the right, in common with the domain of the account + if dns.CompareDomainName(foundAccount.Domain, sharedInboxURI.Host) >= 2 { + sharedInbox = sharedInboxURI.String() + } + } + + sharedInboxChanged = true + foundAccount.SharedInboxURI = &sharedInbox + } + // make sure the account fields are populated before returning: // the caller might want to block until everything is loaded var fieldsChanged bool @@ -293,7 +327,7 @@ func (d *deref) GetRemoteAccount(ctx context.Context, params GetRemoteAccountPar foundAccount.LastWebfingeredAt = fingered } - if fieldsChanged || fingeredChanged { + if fieldsChanged || fingeredChanged || sharedInboxChanged { foundAccount.UpdatedAt = time.Now() foundAccount, err = d.db.UpdateAccount(ctx, foundAccount) if err != nil { diff --git a/internal/federation/dereferencing/account_test.go b/internal/federation/dereferencing/account_test.go index 77ebb7cac..4f1a83a96 100644 --- a/internal/federation/dereferencing/account_test.go +++ b/internal/federation/dereferencing/account_test.go @@ -101,6 +101,24 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsRemoteURL() { suite.Empty(fetchedAccount.Domain) } +func (suite *AccountTestSuite) TestDereferenceLocalAccountAsRemoteURLNoSharedInboxYet() { + fetchingAccount := suite.testAccounts["local_account_1"] + targetAccount := suite.testAccounts["local_account_2"] + + targetAccount.SharedInboxURI = nil + if _, err := suite.db.UpdateAccount(context.Background(), targetAccount); err != nil { + suite.FailNow(err.Error()) + } + + fetchedAccount, err := suite.dereferencer.GetRemoteAccount(context.Background(), dereferencing.GetRemoteAccountParams{ + RequestingUsername: fetchingAccount.Username, + RemoteAccountID: testrig.URLMustParse(targetAccount.URI), + }) + suite.NoError(err) + suite.NotNil(fetchedAccount) + suite.Empty(fetchedAccount.Domain) +} + func (suite *AccountTestSuite) TestDereferenceLocalAccountAsUsername() { fetchingAccount := suite.testAccounts["local_account_1"] targetAccount := suite.testAccounts["local_account_2"] diff --git a/internal/federation/federatingactor_test.go b/internal/federation/federatingactor_test.go index bab38abe2..46a392d70 100644 --- a/internal/federation/federatingactor_test.go +++ b/internal/federation/federatingactor_test.go @@ -117,7 +117,7 @@ func (suite *FederatingActorTestSuite) TestSendRemoteFollower() { // because we added 1 remote follower for zork, there should be a url in sentMessage var sent [][]byte if !testrig.WaitFor(func() bool { - sentI, ok := httpClient.SentMessages.Load(testRemoteAccount.InboxURI) + sentI, ok := httpClient.SentMessages.Load(*testRemoteAccount.SharedInboxURI) if ok { sent, ok = sentI.([][]byte) if !ok { diff --git a/internal/federation/federatingdb/inbox.go b/internal/federation/federatingdb/inbox.go index b3b935bff..18f620bce 100644 --- a/internal/federation/federatingdb/inbox.go +++ b/internal/federation/federatingdb/inbox.go @@ -108,7 +108,15 @@ func (f *federatingDB) InboxesForIRI(c context.Context, iri *url.URL) (inboxIRIs follow.Account = followingAccount } - inboxIRI, err := url.Parse(follow.Account.InboxURI) + // deliver to a shared inbox if we have that option + var inbox string + if config.GetInstanceDeliverToSharedInboxes() && follow.Account.SharedInboxURI != nil && *follow.Account.SharedInboxURI != "" { + inbox = *follow.Account.SharedInboxURI + } else { + inbox = follow.Account.InboxURI + } + + inboxIRI, err := url.Parse(inbox) if err != nil { return nil, fmt.Errorf("error parsing inbox uri of following account %s: %s", follow.Account.InboxURI, err) } @@ -119,7 +127,15 @@ func (f *federatingDB) InboxesForIRI(c context.Context, iri *url.URL) (inboxIRIs // check if this is just an account IRI... if account, err := f.db.GetAccountByURI(c, iri.String()); err == nil { - inboxIRI, err := url.Parse(account.InboxURI) + // deliver to a shared inbox if we have that option + var inbox string + if config.GetInstanceDeliverToSharedInboxes() && account.SharedInboxURI != nil && *account.SharedInboxURI != "" { + inbox = *account.SharedInboxURI + } else { + inbox = account.InboxURI + } + + inboxIRI, err := url.Parse(inbox) if err != nil { return nil, fmt.Errorf("error parsing account inbox uri %s: %s", account.InboxURI, account.InboxURI) } diff --git a/internal/federation/federatingdb/inbox_test.go b/internal/federation/federatingdb/inbox_test.go index fb3b96944..dbf9d3c53 100644 --- a/internal/federation/federatingdb/inbox_test.go +++ b/internal/federation/federatingdb/inbox_test.go @@ -63,6 +63,27 @@ func (suite *InboxTestSuite) TestInboxesForAccountIRI() { suite.Contains(asStrings, suite.testAccounts["local_account_1"].InboxURI) } +func (suite *InboxTestSuite) TestInboxesForAccountIRIWithSharedInbox() { + ctx := context.Background() + testAccount := suite.testAccounts["local_account_1"] + sharedInbox := "http://some-inbox-iri/weeeeeeeeeeeee" + testAccount.SharedInboxURI = &sharedInbox + if _, err := suite.db.UpdateAccount(ctx, testAccount); err != nil { + suite.FailNow("error updating account") + } + + inboxIRIs, err := suite.federatingDB.InboxesForIRI(ctx, testrig.URLMustParse(testAccount.URI)) + suite.NoError(err) + + asStrings := []string{} + for _, i := range inboxIRIs { + asStrings = append(asStrings, i.String()) + } + + suite.Len(asStrings, 1) + suite.Contains(asStrings, "http://some-inbox-iri/weeeeeeeeeeeee") +} + func TestInboxTestSuite(t *testing.T) { suite.Run(t, &InboxTestSuite{}) } diff --git a/internal/gtsmodel/account.go b/internal/gtsmodel/account.go index 18b808a2f..20405f9ac 100644 --- a/internal/gtsmodel/account.go +++ b/internal/gtsmodel/account.go @@ -60,6 +60,7 @@ type Account struct { URL string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // Web URL for this account's profile LastWebfingeredAt time.Time `validate:"required_with=Domain" bun:"type:timestamptz,nullzero"` // Last time this account was refreshed/located with webfinger. InboxURI string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // Address of this account's ActivityPub inbox, for sending activity to + SharedInboxURI *string `validate:"-" bun:""` // Address of this account's ActivityPub sharedInbox. Gotcha warning: this is a string pointer because it has three possible states: 1. We don't know yet if the account has a shared inbox -- null. 2. We know it doesn't have a shared inbox -- empty string. 3. We know it does have a shared inbox -- url string. OutboxURI string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // Address of this account's activitypub outbox FollowingURI string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // URI for getting the following list of this account FollowersURI string `validate:"required_without=Domain,omitempty,url" bun:",nullzero,unique"` // URI for getting the followers list of this account diff --git a/internal/processing/account_test.go b/internal/processing/account_test.go index a9b492d06..8e33ccc7d 100644 --- a/internal/processing/account_test.go +++ b/internal/processing/account_test.go @@ -71,7 +71,7 @@ func (suite *AccountTestSuite) TestAccountDeleteLocal() { }) if !testrig.WaitFor(func() bool { - sentI, ok := suite.httpClient.SentMessages.Load(followingAccount.InboxURI) + sentI, ok := suite.httpClient.SentMessages.Load(*followingAccount.SharedInboxURI) if ok { sent, ok = sentI.([][]byte) if !ok { diff --git a/internal/processing/fromfederator_test.go b/internal/processing/fromfederator_test.go index 385f3b134..9337482c4 100644 --- a/internal/processing/fromfederator_test.go +++ b/internal/processing/fromfederator_test.go @@ -488,7 +488,7 @@ func (suite *FromFederatorTestSuite) TestProcessFollowRequestUnlocked() { // an accept message should be sent to satan's inbox var sent [][]byte if !testrig.WaitFor(func() bool { - sentI, ok := suite.httpClient.SentMessages.Load(originAccount.InboxURI) + sentI, ok := suite.httpClient.SentMessages.Load(*originAccount.SharedInboxURI) if ok { sent, ok = sentI.([][]byte) if !ok { diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index 7ec45335d..b69bb247e 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -23,6 +23,7 @@ import ( "errors" "fmt" + "github.com/miekg/dns" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" @@ -156,6 +157,19 @@ func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable a acct.InboxURI = accountable.GetActivityStreamsInbox().GetIRI().String() } + // SharedInboxURI + if sharedInboxURI := ap.ExtractSharedInbox(accountable); sharedInboxURI != nil { + var sharedInbox string + + // only trust shared inbox if it has at least two domains, + // from the right, in common with the domain of the account + if dns.CompareDomainName(acct.Domain, sharedInboxURI.Host) >= 2 { + sharedInbox = sharedInboxURI.String() + } + + acct.SharedInboxURI = &sharedInbox + } + // OutboxURI if accountable.GetActivityStreamsOutbox() != nil && accountable.GetActivityStreamsOutbox().GetIRI() != nil { acct.OutboxURI = accountable.GetActivityStreamsOutbox().GetIRI().String() diff --git a/internal/typeutils/astointernal_test.go b/internal/typeutils/astointernal_test.go index 7024018d6..9273405e6 100644 --- a/internal/typeutils/astointernal_test.go +++ b/internal/typeutils/astointernal_test.go @@ -45,6 +45,7 @@ func (suite *ASToInternalTestSuite) TestParsePerson() { suite.Equal("https://unknown-instance.com/users/brand_new_person/following", acct.FollowingURI) suite.Equal("https://unknown-instance.com/users/brand_new_person/followers", acct.FollowersURI) suite.Equal("https://unknown-instance.com/users/brand_new_person/inbox", acct.InboxURI) + suite.Nil(acct.SharedInboxURI) suite.Equal("https://unknown-instance.com/users/brand_new_person/outbox", acct.OutboxURI) suite.Equal("https://unknown-instance.com/users/brand_new_person/collections/featured", acct.FeaturedCollectionURI) suite.Equal("brand_new_person", acct.Username) @@ -56,6 +57,28 @@ func (suite *ASToInternalTestSuite) TestParsePerson() { suite.False(*acct.Locked) } +func (suite *ASToInternalTestSuite) TestParsePersonWithSharedInbox() { + testPerson := suite.testPeople["https://turnip.farm/users/turniplover6969"] + + acct, err := suite.typeconverter.ASRepresentationToAccount(context.Background(), testPerson, "", false) + suite.NoError(err) + + suite.Equal("https://turnip.farm/users/turniplover6969", acct.URI) + suite.Equal("https://turnip.farm/users/turniplover6969/following", acct.FollowingURI) + suite.Equal("https://turnip.farm/users/turniplover6969/followers", acct.FollowersURI) + suite.Equal("https://turnip.farm/users/turniplover6969/inbox", acct.InboxURI) + suite.Equal("https://turnip.farm/sharedInbox", *acct.SharedInboxURI) + suite.Equal("https://turnip.farm/users/turniplover6969/outbox", acct.OutboxURI) + suite.Equal("https://turnip.farm/users/turniplover6969/collections/featured", acct.FeaturedCollectionURI) + suite.Equal("turniplover6969", acct.Username) + suite.Equal("Turnip Lover 6969", acct.DisplayName) + suite.Equal("I just think they're neat", acct.Note) + suite.Equal("https://turnip.farm/@turniplover6969", acct.URL) + suite.True(*acct.Discoverable) + suite.Equal("https://turnip.farm/users/turniplover6969#main-key", acct.PublicKeyURI) + suite.False(*acct.Locked) +} + func (suite *ASToInternalTestSuite) TestParsePublicStatus() { m := make(map[string]interface{}) err := json.Unmarshal([]byte(publicStatusActivityJson), &m) @@ -109,6 +132,8 @@ func (suite *ASToInternalTestSuite) TestParseGargron() { acct, err := suite.typeconverter.ASRepresentationToAccount(context.Background(), rep, "", false) suite.NoError(err) + suite.Equal("https://mastodon.social/inbox", *acct.SharedInboxURI) + fmt.Printf("%+v", acct) // TODO: write assertions here, rn we're just eyeballing the output } diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index 6d8a22cab..a678a970f 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -85,6 +85,21 @@ func (c *converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab inboxProp.SetIRI(inboxURI) person.SetActivityStreamsInbox(inboxProp) + // shared inbox -- only add this if we know for sure it has one + if a.SharedInboxURI != nil && *a.SharedInboxURI != "" { + sharedInboxURI, err := url.Parse(*a.SharedInboxURI) + if err != nil { + return nil, err + } + endpointsProp := streams.NewActivityStreamsEndpointsProperty() + endpoints := streams.NewActivityStreamsEndpoints() + sharedInboxProp := streams.NewActivityStreamsSharedInboxProperty() + sharedInboxProp.SetIRI(sharedInboxURI) + endpoints.SetActivityStreamsSharedInbox(sharedInboxProp) + endpointsProp.AppendActivityStreamsEndpoints(endpoints) + person.SetActivityStreamsEndpoints(endpointsProp) + } + // outbox // the activitypub outbox of this user for serving messages outboxURI, err := url.Parse(a.OutboxURI) diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go index b6da422b1..83e235113 100644 --- a/internal/typeutils/internaltoas_test.go +++ b/internal/typeutils/internaltoas_test.go @@ -52,6 +52,27 @@ func (suite *InternalToASTestSuite) TestAccountToAS() { suite.Equal(`:true,"featured":"http://localhost:8080/users/the_mighty_zork/collections/featured","followers":"http://localhost:8080/users/the_mighty_zork/followers","following":"http://localhost:8080/users/the_mighty_zork/following","icon":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg"},"id":"http://localhost:8080/users/the_mighty_zork","image":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg"},"inbox":"http://localhost:8080/users/the_mighty_zork/inbox","manuallyApprovesFollowers":false,"name":"original zork (he/they)","outbox":"http://localhost:8080/users/the_mighty_zork/outbox","preferredUsername":"the_mighty_zork","publicKey":{"id":"http://localhost:8080/users/the_mighty_zork/main-key","owner":"http://localhost:8080/users/the_mighty_zork","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"},"summary":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","type":"Person","url":"http://localhost:8080/@the_mighty_zork"}`, trimmed) } +func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() { + testAccount := suite.testAccounts["local_account_1"] // take zork for this test + sharedInbox := "http://localhost:8080/sharedInbox" + testAccount.SharedInboxURI = &sharedInbox + + asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount) + suite.NoError(err) + + ser, err := streams.Serialize(asPerson) + suite.NoError(err) + + bytes, err := json.Marshal(ser) + suite.NoError(err) + + // trim off everything up to 'discoverable'; + // this is necessary because the order of multiple 'context' entries is not determinate + trimmed := strings.Split(string(bytes), "\"discoverable\"")[1] + + suite.Equal(`:true,"endpoints":{"sharedInbox":"http://localhost:8080/sharedInbox"},"featured":"http://localhost:8080/users/the_mighty_zork/collections/featured","followers":"http://localhost:8080/users/the_mighty_zork/followers","following":"http://localhost:8080/users/the_mighty_zork/following","icon":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpeg"},"id":"http://localhost:8080/users/the_mighty_zork","image":{"mediaType":"image/jpeg","type":"Image","url":"http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpeg"},"inbox":"http://localhost:8080/users/the_mighty_zork/inbox","manuallyApprovesFollowers":false,"name":"original zork (he/they)","outbox":"http://localhost:8080/users/the_mighty_zork/outbox","preferredUsername":"the_mighty_zork","publicKey":{"id":"http://localhost:8080/users/the_mighty_zork/main-key","owner":"http://localhost:8080/users/the_mighty_zork","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwXTcOAvM1Jiw5Ffpk0qn\nr0cwbNvFe/5zQ+Tp7tumK/ZnT37o7X0FUEXrxNi+dkhmeJ0gsaiN+JQGNUewvpSk\nPIAXKvi908aSfCGjs7bGlJCJCuDuL5d6m7hZnP9rt9fJc70GElPpG0jc9fXwlz7T\nlsPb2ecatmG05Y4jPwdC+oN4MNCv9yQzEvCVMzl76EJaM602kIHC1CISn0rDFmYd\n9rSN7XPlNJw1F6PbpJ/BWQ+pXHKw3OEwNTETAUNYiVGnZU+B7a7bZC9f6/aPbJuV\nt8Qmg+UnDvW1Y8gmfHnxaWG2f5TDBvCHmcYtucIZPLQD4trAozC4ryqlmCWQNKbt\n0wIDAQAB\n-----END PUBLIC KEY-----\n"},"summary":"\u003cp\u003ehey yo this is my profile!\u003c/p\u003e","type":"Person","url":"http://localhost:8080/@the_mighty_zork"}`, trimmed) +} + func (suite *InternalToASTestSuite) TestOutboxToASCollection() { testAccount := suite.testAccounts["admin_account"] ctx := context.Background() |