summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorLibravatar tobi <31960611+tsmethurst@users.noreply.github.com>2022-09-23 21:27:35 +0200
committerLibravatar GitHub <noreply@github.com>2022-09-23 20:27:35 +0100
commit69a193dae543641a2db6823fa6493c02f56fafbd (patch)
treec1a0c71d64642db12a17c6770642c3e0af859960 /internal
parent[docs] NLnet follow up questions (#846) (diff)
downloadgotosocial-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.go30
-rw-r--r--internal/ap/interfaces.go6
-rw-r--r--internal/cache/account.go1
-rw-r--r--internal/config/config.go5
-rw-r--r--internal/config/defaults.go5
-rw-r--r--internal/config/flags.go1
-rw-r--r--internal/config/helpers.gen.go25
-rw-r--r--internal/db/bundb/migrations/20220922142408_shared_inbox_delivery.go46
-rw-r--r--internal/federation/dereferencing/account.go42
-rw-r--r--internal/federation/dereferencing/account_test.go18
-rw-r--r--internal/federation/federatingactor_test.go2
-rw-r--r--internal/federation/federatingdb/inbox.go20
-rw-r--r--internal/federation/federatingdb/inbox_test.go21
-rw-r--r--internal/gtsmodel/account.go1
-rw-r--r--internal/processing/account_test.go2
-rw-r--r--internal/processing/fromfederator_test.go2
-rw-r--r--internal/typeutils/astointernal.go14
-rw-r--r--internal/typeutils/astointernal_test.go25
-rw-r--r--internal/typeutils/internaltoas.go15
-rw-r--r--internal/typeutils/internaltoas_test.go21
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()