Merge branch 'main' into delivery_recipient_pre_sort

This commit is contained in:
tobi 2025-01-23 18:18:48 +01:00
commit 01adf8f307
23 changed files with 534 additions and 183 deletions

View file

@ -24,11 +24,11 @@ In case the rate limit is exceeded, an [HTTP 429 Too Many Requests](https://deve
### My rate limit keeps being exceeded! Why?
If you find that your rate limit is regularly being exceeded (both for yourself and other callers) during normal use of your instance, it may be that GoToSocial can't tell the clients apart by IP address. You can investigate this by viewing the logs of your instance. If (almost) all logged IP addresses appear to be the same IP address (something like `172.x.x.x`), then the rate limiting will cause problems.
If you find that your rate limit is regularly being exceeded (both for yourself and other callers) during normal use of your instance, it may be that GoToSocial can't tell the clients apart by IP address. You can investigate this by viewing the logs of your instance. If (almost) all logged client IP addresses appear to be the same IP address (something like `172.x.x.x`), then the rate limiting will cause problems.
This happens when your server is running inside NAT (port forwarding), or behind an HTTP proxy without the correct configuration, causing your instance to see all incoming IP addresses as the same address: namely, the IP address of your reverse proxy or gateway. This means that all incoming requests are *sharing the same rate limit*, rather than being split correctly per IP.
If you are using an HTTP proxy then it's likely that your `trusted-proxies` is not correctly configured. If this is the case, try adding the IP address of your reverse proxy to the list of `trusted-proxies`, and restarting your instance.
If you are using an HTTP proxy then it's likely that your `trusted-proxies` is not correctly configured. See the [trusted-proxies](../configuration/trusted_proxies.md) documentation for more info on how to resolve this.
If you don't have an HTTP proxy, then it's likely caused by NAT. In this case you should disable rate limiting altogether.

View file

@ -1,8 +1,6 @@
# General
The top-level configuration for GoToSocial, including basic things like host, port, bind address and transport protocol.
The only things you *really* need to set here are `host`, which should be the hostname where your instance is reachable, and probably `port`.
The top-level configuration for GoToSocial, including basic things like host, port, bind address, and trusted-proxies.
## Settings

View file

@ -0,0 +1,71 @@
# Trusted Proxies
To correctly enforce [rate limiting](../api/ratelimiting.md), GoToSocial relies on the concept of "trusted proxies" in order to accurately determine the IP address of clients accessing your server.
A "trusted proxy" is an intermediate network hop that GoToSocial can be instructed to trust to provide a correct client IP address.
For example, if you are running in a reverse proxy configuration with Docker + Nginx, then the Docker network address of Nginx should be configured as a trusted proxy, since all traffic from the wider internet will come into GoToSocial via Nginx.
Without setting `trusted-proxies` correctly, GoToSocial will see all incoming client IP addresses as the same address, which leads to rate limiting issues, since GoToSocial uses client IP addresses to bucket rate limits.
## tl;dr: How to set `trusted-proxies` correctly
If your `trusted-proxies` setting is not correctly configured, you may see the following warning on the web view of your instance (v0.18.0 and above):
> Warning! It looks like trusted-proxies is not set correctly in this instance's configuration. This may cause rate-limiting issues and, by extension, federation issues.
>
> If you are the instance admin, you should fix this by adding `SUGGESTED_IP_RANGE` to your trusted-proxies.
To resolve this, copy the IP range in the message, and edit your `config.yaml` file to add the IP range to your `trusted-proxies`.
!!! tip "You may be getting rate limited even if you don't see the above warning!"
If you're on a version of GoToSocial below v0.18.0, or you're running behind a CDN such as Cloudflare (not recommended), you won't see a warning message. Instead, you'll see in your GoToSocial logs that all client IPs are the same address. In this case, take the recurring client IP value as `SUGGESTED_IP_RANGE`.
In this example, we assume `SUGGESTED_IP_RANGE` to be `172.17.0.1/16` (the default Docker bridge network subnet).
Before (default config):
```yaml
trusted-proxies:
- "127.0.0.1/32"
- "::1"
```
After (new config):
```yaml
trusted-proxies:
- "172.17.0.1/16"
- "127.0.0.1/32"
- "::1"
```
If you are using [environment variables](../configuration/index.md#environment-variables) to configure your instance, you can configure `trusted-proxies` by setting the environment variable `GTS_TRUSTED_PROXIES` to a comma-separated list of IP ranges, like so:
```env
GTS_TRUSTED_PROXIES="172.17.0.1/16,127.0.0.1/32,::1"
```
If you are using docker compose, your docker-compose.yaml file should look something like this after the change (note that yaml uses `: ` and not `=`):
```yaml
################################
# BLAH BLAH OTHER CONFIG STUFF #
################################
environment:
############################
# BLAH BLAH OTHER ENV VARS #
############################
## For reverse proxy setups:
GTS_TRUSTED_PROXIES: "172.17.0.1/16,127.0.0.1/32,::1"
################################
# BLAH BLAH OTHER CONFIG STUFF #
################################
```
Once you have made the necessary configuration changes, restart your instance and refresh the home page. If the message is gone, then the problem is resolved!
If you still see the warning message but with a different suggested IP range to add to `trusted-proxies`, then follow the same steps as above again, including the new suggested IP range in your config in addition to the one you just added.
!!! tip "Cloudflare IP Addresses"
If you are running with a CDN/proxy such as Cloudflare in front of your GoToSocial instance (not recommended), then you may need to add one or more of the Cloudflare IP addresses to your `trusted-proxies` in order to have rate limiting work properly. You can find a list of Cloudflare IP addresses here: https://www.cloudflare.com/ips/

View file

@ -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).

View file

@ -41,3 +41,7 @@ We have guides available for the following servers:
When using a reverse-proxy, special care must be taken to allow WebSockets to work too. This is necessary as many client applications use WebSockets to stream your timeline. WebSockets is not used as part of federation.
Make sure you read the [WebSocket](websocket.md) documentation and configure your reverse proxy accordingly.
## Trusted Proxies
When using a reverse-proxy, you may run into issues with rate limiting and `trusted-proxies`. Check the [trusted proxies](../../configuration/trusted_proxies.md) documentation if you have any problems.

View file

@ -1,5 +1,3 @@
version: "3.3"
services:
gotosocial:
image: superseriousbusiness/gotosocial:latest
@ -24,7 +22,7 @@ services:
# Wazero compilation cache will be stored.
GTS_WAZERO_COMPILATION_CACHE: /gotosocial/.cache
## For reverse proxy setups:
# GTS_TRUSTED_PROXIES: "172.x.x.x"
GTS_TRUSTED_PROXIES: "172.18.0.1/16"
## Set the timezone of your server:
#TZ: UTC
ports:
@ -47,3 +45,6 @@ networks:
gotosocial:
ipam:
driver: default
config:
- subnet: "172.18.0.0/16"
gateway: "172.18.0.1"

View file

@ -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

View file

@ -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"
@ -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.
@ -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.
@ -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.
@ -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)
@ -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)

View file

@ -188,6 +188,7 @@ type Accountable interface {
WithTag
WithPublished
WithUpdated
WithImage
}
// Statusable represents the minimum activitypub interface for representing a 'status'.
@ -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

View file

@ -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()
@ -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
@ -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!"
)
@ -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 := suite.newUpdatePerson(asAccount, targetAccount.URI, activityID)
update, err := suite.tc.WrapAccountableInUpdate(accountable)
if err != nil {
suite.FailNow(err.Error())
}
// 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(
@ -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,

View file

@ -18,6 +18,7 @@
package util
import (
"net"
"net/http"
"github.com/gin-gonic/gin"
@ -63,6 +64,11 @@ type WebPage struct {
// ogMeta, stylesheets, javascript, and any extra
// properties will be provided to the template if
// set, but can all be nil.
//
// TemplateWebPage also checks whether the requesting
// clientIP is 127.0.0.1 or within a private IP range.
// If so, it injects a suggestion into the page header
// about setting trusted-proxies correctly.
func TemplateWebPage(
c *gin.Context,
page WebPage,
@ -74,13 +80,86 @@ func TemplateWebPage(
"javascript": page.Javascript,
}
// Add extras to template object.
for k, v := range page.Extra {
obj[k] = v
}
// Inject trustedProxiesRec to template
// object (or noop if not necessary).
injectTrustedProxiesRec(c, obj)
templatePage(c, page.Template, http.StatusOK, obj)
}
func injectTrustedProxiesRec(
c *gin.Context,
obj map[string]any,
) {
clientIP := c.ClientIP()
if clientIP == "127.0.0.1" {
// Suggest precise 127.0.0.1/32.
trustedProxiesRec := clientIP + "/32"
obj["trustedProxiesRec"] = trustedProxiesRec
return
}
// True if "X-Forwarded-For"
// or "X-Real-IP" were set.
var hasRemoteIPHeader bool
for _, k := range []string{
"X-Forwarded-For",
"X-Real-IP",
} {
if v := c.GetHeader(k); v != "" {
hasRemoteIPHeader = true
break
}
}
if !hasRemoteIPHeader {
// Upstream hasn't set a
// remote IP header, bail.
return
}
ip := net.ParseIP(clientIP)
if !ip.IsPrivate() {
// Upstream set a remote IP
// header but final clientIP
// isn't private, so upstream
// is probably already trusted.
// Don't inject suggestion.
return
}
// Private IP, guess if Docker.
if dockerSubnet.Contains(ip) {
// Suggest a CIDR that likely
// covers this Docker subnet,
// eg., 172.17.0.0 -> 172.17.255.255.
trustedProxiesRec := clientIP + "/16"
obj["trustedProxiesRec"] = trustedProxiesRec
return
}
// Private IP but we don't know
// what it is. Suggest precise CIDR.
trustedProxiesRec := clientIP + "/32"
obj["trustedProxiesRec"] = trustedProxiesRec
}
// dockerSubnet is a CIDR that lets one make hazy guesses
// as to whether an address is within the ranges Docker
// uses for subnets, ie., 172.16.0.0 -> 172.31.255.255.
var dockerSubnet = func() *net.IPNet {
_, subnet, err := net.ParseCIDR("172.16.0.0/12")
if err != nil {
panic(err)
}
return subnet
}()
// templateErrorPage renders the given
// HTTP code, error, and request ID
// within the standard error template.

View file

@ -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()

View file

@ -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"
@ -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)
@ -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.
@ -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)
}

View file

@ -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"
@ -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()
@ -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.
@ -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)
}
@ -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()
@ -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(

View file

@ -36,12 +36,24 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/uris"
"github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/internal/util/xslices"
)
// AccountToAS converts a gts model account into an activity streams person, suitable for federation
func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) {
person := streams.NewActivityStreamsPerson()
// AccountToAS converts a gts model account
// into an activity streams person or service.
func (c *Converter) AccountToAS(
ctx context.Context,
a *gtsmodel.Account,
) (ap.Accountable, error) {
// accountable is a service if this
// is a bot account, otherwise a person.
var accountable ap.Accountable
if util.PtrOrZero(a.Bot) {
accountable = streams.NewActivityStreamsService()
} else {
accountable = streams.NewActivityStreamsPerson()
}
// id should be the activitypub URI of this user
// something like https://example.org/users/example_user
@ -51,7 +63,13 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
idProp := streams.NewJSONLDIdProperty()
idProp.SetIRI(profileIDURI)
person.SetJSONLDId(idProp)
accountable.SetJSONLDId(idProp)
// published
// The moment when the account was created.
publishedProp := streams.NewActivityStreamsPublishedProperty()
publishedProp.Set(a.CreatedAt)
accountable.SetActivityStreamsPublished(publishedProp)
// following
// The URI for retrieving a list of accounts this user is following
@ -61,7 +79,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
followingProp := streams.NewActivityStreamsFollowingProperty()
followingProp.SetIRI(followingURI)
person.SetActivityStreamsFollowing(followingProp)
accountable.SetActivityStreamsFollowing(followingProp)
// followers
// The URI for retrieving a list of this user's followers
@ -71,7 +89,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
followersProp := streams.NewActivityStreamsFollowersProperty()
followersProp.SetIRI(followersURI)
person.SetActivityStreamsFollowers(followersProp)
accountable.SetActivityStreamsFollowers(followersProp)
// inbox
// the activitypub inbox of this user for accepting messages
@ -81,7 +99,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
inboxProp := streams.NewActivityStreamsInboxProperty()
inboxProp.SetIRI(inboxURI)
person.SetActivityStreamsInbox(inboxProp)
accountable.SetActivityStreamsInbox(inboxProp)
// shared inbox -- only add this if we know for sure it has one
if a.SharedInboxURI != nil && *a.SharedInboxURI != "" {
@ -95,7 +113,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
sharedInboxProp.SetIRI(sharedInboxURI)
endpoints.SetActivityStreamsSharedInbox(sharedInboxProp)
endpointsProp.AppendActivityStreamsEndpoints(endpoints)
person.SetActivityStreamsEndpoints(endpointsProp)
accountable.SetActivityStreamsEndpoints(endpointsProp)
}
// outbox
@ -106,7 +124,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
outboxProp := streams.NewActivityStreamsOutboxProperty()
outboxProp.SetIRI(outboxURI)
person.SetActivityStreamsOutbox(outboxProp)
accountable.SetActivityStreamsOutbox(outboxProp)
// featured posts
// Pinned posts.
@ -116,7 +134,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
featuredProp := streams.NewTootFeaturedProperty()
featuredProp.SetIRI(featuredURI)
person.SetTootFeatured(featuredProp)
accountable.SetTootFeatured(featuredProp)
// featuredTags
// NOT IMPLEMENTED
@ -125,7 +143,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty()
preferredUsernameProp.SetXMLSchemaString(a.Username)
person.SetActivityStreamsPreferredUsername(preferredUsernameProp)
accountable.SetActivityStreamsPreferredUsername(preferredUsernameProp)
// name
// Used as profile display name.
@ -135,14 +153,14 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
} else {
nameProp.AppendXMLSchemaString(a.Username)
}
person.SetActivityStreamsName(nameProp)
accountable.SetActivityStreamsName(nameProp)
// summary
// Used as profile bio.
if a.Note != "" {
summaryProp := streams.NewActivityStreamsSummaryProperty()
summaryProp.AppendXMLSchemaString(a.Note)
person.SetActivityStreamsSummary(summaryProp)
accountable.SetActivityStreamsSummary(summaryProp)
}
// url
@ -153,19 +171,19 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
}
urlProp := streams.NewActivityStreamsUrlProperty()
urlProp.AppendIRI(profileURL)
person.SetActivityStreamsUrl(urlProp)
accountable.SetActivityStreamsUrl(urlProp)
// manuallyApprovesFollowers
// Will be shown as a locked account.
manuallyApprovesFollowersProp := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
manuallyApprovesFollowersProp.Set(*a.Locked)
person.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp)
accountable.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp)
// discoverable
// Will be shown in the profile directory.
discoverableProp := streams.NewTootDiscoverableProperty()
discoverableProp.Set(*a.Discoverable)
person.SetTootDiscoverable(discoverableProp)
accountable.SetTootDiscoverable(discoverableProp)
// devices
// NOT IMPLEMENTED, probably won't implement
@ -183,7 +201,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
alsoKnownAsURIs[i] = uri
}
ap.SetAlsoKnownAs(person, alsoKnownAsURIs)
ap.SetAlsoKnownAs(accountable, alsoKnownAsURIs)
}
// movedTo
@ -194,7 +212,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
return nil, err
}
ap.SetMovedTo(person, movedTo)
ap.SetMovedTo(accountable, movedTo)
}
// publicKey
@ -235,7 +253,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey)
// set the public key property on the Person
person.SetW3IDSecurityV1PublicKey(publicKeyProp)
accountable.SetW3IDSecurityV1PublicKey(publicKeyProp)
// tags
tagProp := streams.NewActivityStreamsTagProperty()
@ -263,7 +281,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
// tag -- hashtags
// TODO
person.SetActivityStreamsTag(tagProp)
accountable.SetActivityStreamsTag(tagProp)
// attachment
// Used for profile fields.
@ -284,7 +302,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
attachmentProp.AppendSchemaPropertyValue(propertyValue)
}
person.SetActivityStreamsAttachment(attachmentProp)
accountable.SetActivityStreamsAttachment(attachmentProp)
}
// endpoints
@ -320,7 +338,7 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
iconImage.SetActivityStreamsUrl(avatarURLProperty)
iconProperty.AppendActivityStreamsImage(iconImage)
person.SetActivityStreamsIcon(iconProperty)
accountable.SetActivityStreamsIcon(iconProperty)
}
}
@ -354,20 +372,32 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
headerImage.SetActivityStreamsUrl(headerURLProperty)
headerProperty.AppendActivityStreamsImage(headerImage)
person.SetActivityStreamsImage(headerProperty)
accountable.SetActivityStreamsImage(headerProperty)
}
}
return person, nil
return accountable, nil
}
// AccountToASMinimal converts a gts model account into an activity streams person, suitable for federation.
// AccountToASMinimal converts a gts model account
// into an activity streams person or service.
//
// The returned account will just have the Type, Username, PublicKey, and ID properties set. This is
// suitable for serving to requesters to whom we want to give as little information as possible because
// we don't trust them (yet).
func (c *Converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) {
person := streams.NewActivityStreamsPerson()
// The returned account will just have the Type, Username,
// PublicKey, and ID properties set. This is suitable for
// serving to requesters to whom we want to give as little
// information as possible because we don't trust them (yet).
func (c *Converter) AccountToASMinimal(
ctx context.Context,
a *gtsmodel.Account,
) (ap.Accountable, error) {
// accountable is a service if this
// is a bot account, otherwise a person.
var accountable ap.Accountable
if util.PtrOrZero(a.Bot) {
accountable = streams.NewActivityStreamsService()
} else {
accountable = streams.NewActivityStreamsPerson()
}
// id should be the activitypub URI of this user
// something like https://example.org/users/example_user
@ -377,13 +407,13 @@ func (c *Converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account)
}
idProp := streams.NewJSONLDIdProperty()
idProp.SetIRI(profileIDURI)
person.SetJSONLDId(idProp)
accountable.SetJSONLDId(idProp)
// preferredUsername
// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty()
preferredUsernameProp.SetXMLSchemaString(a.Username)
person.SetActivityStreamsPreferredUsername(preferredUsernameProp)
accountable.SetActivityStreamsPreferredUsername(preferredUsernameProp)
// publicKey
// Required for signatures.
@ -423,9 +453,9 @@ func (c *Converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account)
publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey)
// set the public key property on the Person
person.SetW3IDSecurityV1PublicKey(publicKeyProp)
accountable.SetW3IDSecurityV1PublicKey(publicKeyProp)
return person, nil
return accountable, nil
}
// StatusToAS converts a gts model status into an ActivityStreams Statusable implementation, suitable for federation

View file

@ -27,6 +27,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/testrig"
)
@ -38,10 +39,10 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
testAccount := &gtsmodel.Account{}
*testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := ap.Serialize(asPerson)
ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -86,6 +87,7 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
"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"
},
"published": "2022-05-20T11:09:18Z",
"summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"tag": [],
"type": "Person",
@ -93,14 +95,80 @@ func (suite *InternalToASTestSuite) TestAccountToAS() {
}`, string(bytes))
}
func (suite *InternalToASTestSuite) TestAccountToASBot() {
testAccount := &gtsmodel.Account{}
*testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
// Update zork to be a bot.
testAccount.Bot = util.Ptr(true)
if err := suite.state.DB.UpdateAccount(context.Background(), testAccount); err != nil {
suite.FailNow(err.Error())
}
accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
suite.NoError(err)
suite.Equal(`{
"@context": [
"https://w3id.org/security/v1",
"https://www.w3.org/ns/activitystreams",
{
"discoverable": "toot:discoverable",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#"
}
],
"discoverable": 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.jpg"
},
"id": "http://localhost:8080/users/the_mighty_zork",
"image": {
"mediaType": "image/jpeg",
"type": "Image",
"url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
},
"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"
},
"published": "2022-05-20T11:09:18Z",
"summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"tag": [],
"type": "Service",
"url": "http://localhost:8080/@the_mighty_zork"
}`, string(bytes))
}
func (suite *InternalToASTestSuite) TestAccountToASWithFields() {
testAccount := &gtsmodel.Account{}
*testAccount = *suite.testAccounts["local_account_2"]
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := ap.Serialize(asPerson)
ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -150,6 +218,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithFields() {
"owner": "http://localhost:8080/users/1happyturtle",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtTc6Jpg6LrRPhVQG4KLz\n2+YqEUUtZPd4YR+TKXuCnwEG9ZNGhgP046xa9h3EWzrZXaOhXvkUQgJuRqPrAcfN\nvc8jBHV2xrUeD8pu/MWKEabAsA/tgCv3nUC47HQ3/c12aHfYoPz3ufWsGGnrkhci\nv8PaveJ3LohO5vjCn1yZ00v6osMJMViEZvZQaazyE9A8FwraIexXabDpoy7tkHRg\nA1fvSkg4FeSG1XMcIz2NN7xyUuFACD+XkuOk7UqzRd4cjPUPLxiDwIsTlcgGOd3E\nUFMWVlPxSGjY2hIKa3lEHytaYK9IMYdSuyCsJshd3/yYC9LqxZY2KdlKJ80VOVyh\nyQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"published": "2022-06-04T13:12:00Z",
"summary": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
"tag": [],
"type": "Person",
@ -174,10 +243,10 @@ func (suite *InternalToASTestSuite) TestAccountToASAliasedAndMoved() {
suite.FailNow(err.Error())
}
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := ap.Serialize(asPerson)
ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -231,6 +300,7 @@ func (suite *InternalToASTestSuite) TestAccountToASAliasedAndMoved() {
"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"
},
"published": "2022-05-20T11:09:18Z",
"summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"tag": [],
"type": "Person",
@ -243,10 +313,10 @@ func (suite *InternalToASTestSuite) TestAccountToASWithOneField() {
*testAccount = *suite.testAccounts["local_account_2"]
testAccount.Fields = testAccount.Fields[0:1] // Take only one field.
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := ap.Serialize(asPerson)
ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -292,6 +362,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithOneField() {
"owner": "http://localhost:8080/users/1happyturtle",
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtTc6Jpg6LrRPhVQG4KLz\n2+YqEUUtZPd4YR+TKXuCnwEG9ZNGhgP046xa9h3EWzrZXaOhXvkUQgJuRqPrAcfN\nvc8jBHV2xrUeD8pu/MWKEabAsA/tgCv3nUC47HQ3/c12aHfYoPz3ufWsGGnrkhci\nv8PaveJ3LohO5vjCn1yZ00v6osMJMViEZvZQaazyE9A8FwraIexXabDpoy7tkHRg\nA1fvSkg4FeSG1XMcIz2NN7xyUuFACD+XkuOk7UqzRd4cjPUPLxiDwIsTlcgGOd3E\nUFMWVlPxSGjY2hIKa3lEHytaYK9IMYdSuyCsJshd3/yYC9LqxZY2KdlKJ80VOVyh\nyQIDAQAB\n-----END PUBLIC KEY-----\n"
},
"published": "2022-06-04T13:12:00Z",
"summary": "\u003cp\u003ei post about things that concern me\u003c/p\u003e",
"tag": [],
"type": "Person",
@ -304,10 +375,10 @@ func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() {
*testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
testAccount.Emojis = []*gtsmodel.Emoji{suite.testEmojis["rainbow"]}
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := ap.Serialize(asPerson)
ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -353,6 +424,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() {
"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"
},
"published": "2022-05-20T11:09:18Z",
"summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"tag": {
"icon": {
@ -376,10 +448,10 @@ func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() {
sharedInbox := "http://localhost:8080/sharedInbox"
testAccount.SharedInboxURI = &sharedInbox
asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
suite.NoError(err)
ser, err := ap.Serialize(asPerson)
ser, err := ap.Serialize(accountable)
suite.NoError(err)
bytes, err := json.MarshalIndent(ser, "", " ")
@ -427,6 +499,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() {
"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"
},
"published": "2022-05-20T11:09:18Z",
"summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"tag": [],
"type": "Person",

View file

@ -18,68 +18,45 @@
package typeutils
import (
"net/url"
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/uris"
)
// WrapPersonInUpdate ...
func (c *Converter) WrapPersonInUpdate(person vocab.ActivityStreamsPerson, originAccount *gtsmodel.Account) (vocab.ActivityStreamsUpdate, error) {
// WrapAccountableInUpdate wraps the given accountable
// in an Update activity with the accountable as the object.
//
// The Update will be addressed to Public and bcc followers.
func (c *Converter) WrapAccountableInUpdate(accountable ap.Accountable) (vocab.ActivityStreamsUpdate, error) {
update := streams.NewActivityStreamsUpdate()
// set the actor
actorURI, err := url.Parse(originAccount.URI)
if err != nil {
return nil, gtserror.Newf("error parsing url %s: %w", originAccount.URI, err)
}
actorProp := streams.NewActivityStreamsActorProperty()
actorProp.AppendIRI(actorURI)
update.SetActivityStreamsActor(actorProp)
// Set actor IRI to this accountable's IRI.
ap.AppendActorIRIs(update, ap.GetJSONLDId(accountable))
// set the ID
newID, err := id.NewRandomULID()
if err != nil {
return nil, err
}
// Set the update ID
updateURI := uris.GenerateURIForUpdate(ap.ExtractPreferredUsername(accountable), id.NewULID())
ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(update), updateURI)
idString := uris.GenerateURIForUpdate(originAccount.Username, newID)
idURI, err := url.Parse(idString)
if err != nil {
return nil, gtserror.Newf("error parsing url %s: %w", idString, err)
}
idProp := streams.NewJSONLDIdProperty()
idProp.SetIRI(idURI)
update.SetJSONLDId(idProp)
// set the person as the object here
// Set the accountable as the object of the update.
objectProp := streams.NewActivityStreamsObjectProperty()
objectProp.AppendActivityStreamsPerson(person)
switch t := accountable.(type) {
case vocab.ActivityStreamsPerson:
objectProp.AppendActivityStreamsPerson(t)
case vocab.ActivityStreamsService:
objectProp.AppendActivityStreamsService(t)
default:
log.Panicf(nil, "%T was neither person nor service", t)
}
update.SetActivityStreamsObject(objectProp)
// to should be public
toURI, err := url.Parse(pub.PublicActivityPubIRI)
if err != nil {
return nil, gtserror.Newf("error parsing url %s: %w", pub.PublicActivityPubIRI, err)
}
toProp := streams.NewActivityStreamsToProperty()
toProp.AppendIRI(toURI)
update.SetActivityStreamsTo(toProp)
// to should be public.
ap.AppendTo(update, ap.PublicURI())
// bcc followers
followersURI, err := url.Parse(originAccount.FollowersURI)
if err != nil {
return nil, gtserror.Newf("error parsing url %s: %w", originAccount.FollowersURI, err)
}
bccProp := streams.NewActivityStreamsBccProperty()
bccProp.AppendIRI(followersURI)
update.SetActivityStreamsBcc(bccProp)
// bcc should be followers.
ap.AppendBcc(update, ap.GetFollowers(accountable))
return update, nil
}

View file

@ -139,6 +139,86 @@ func (suite *WrapTestSuite) TestWrapNoteInCreate() {
}`, string(bytes))
}
func (suite *WrapTestSuite) TestWrapAccountableInUpdate() {
testAccount := suite.testAccounts["local_account_1"]
accountable, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
if err != nil {
suite.FailNow(err.Error())
}
create, err := suite.typeconverter.WrapAccountableInUpdate(accountable)
if err != nil {
suite.FailNow(err.Error())
}
createI, err := ap.Serialize(create)
if err != nil {
suite.FailNow(err.Error())
}
// Get the ID as it's not determinate.
createID := ap.GetJSONLDId(create)
bytes, err := json.MarshalIndent(createI, "", " ")
if err != nil {
suite.FailNow(err.Error())
}
suite.Equal(`{
"@context": [
"https://w3id.org/security/v1",
"https://www.w3.org/ns/activitystreams",
{
"discoverable": "toot:discoverable",
"featured": {
"@id": "toot:featured",
"@type": "@id"
},
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
"toot": "http://joinmastodon.org/ns#"
}
],
"actor": "http://localhost:8080/users/the_mighty_zork",
"bcc": "http://localhost:8080/users/the_mighty_zork/followers",
"id": "`+createID.String()+`",
"object": {
"discoverable": 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.jpg"
},
"id": "http://localhost:8080/users/the_mighty_zork",
"image": {
"mediaType": "image/jpeg",
"type": "Image",
"url": "http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/header/original/01PFPMWK2FF0D9WMHEJHR07C3Q.jpg"
},
"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"
},
"published": "2022-05-20T11:09:18Z",
"summary": "\u003cp\u003ehey yo this is my profile!\u003c/p\u003e",
"tag": [],
"type": "Person",
"url": "http://localhost:8080/@the_mighty_zork"
},
"to": "https://www.w3.org/ns/activitystreams#Public",
"type": "Update"
}`, string(bytes))
}
func TestWrapTestSuite(t *testing.T) {
suite.Run(t, new(WrapTestSuite))
}

View file

@ -98,6 +98,7 @@ nav:
- "Configuration":
- "configuration/index.md"
- "configuration/general.md"
- "configuration/trusted_proxies.md"
- "configuration/database.md"
- "configuration/web.md"
- "configuration/instance.md"

View file

@ -2853,7 +2853,7 @@ func NewTestActivities(accounts map[string]*gtsmodel.Account) map[string]Activit
"this is a public status, please forward it!",
"",
URLMustParse("http://example.org/users/Some_User"),
[]*url.URL{URLMustParse(pub.PublicActivityPubIRI)},
[]*url.URL{ap.PublicURI()},
nil,
false,
[]vocab.ActivityStreamsMention{},
@ -3207,7 +3207,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"this is a public status, please forward it!",
"",
URLMustParse("http://example.org/users/Some_User"),
[]*url.URL{URLMustParse(pub.PublicActivityPubIRI)},
[]*url.URL{ap.PublicURI()},
nil,
false,
[]vocab.ActivityStreamsMention{},
@ -3228,7 +3228,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"",
URLMustParse("https://unknown-instance.com/users/brand_new_person"),
[]*url.URL{
URLMustParse(pub.PublicActivityPubIRI),
ap.PublicURI(),
},
[]*url.URL{},
false,
@ -3244,7 +3244,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"",
URLMustParse("https://unknown-instance.com/users/brand_new_person"),
[]*url.URL{
URLMustParse(pub.PublicActivityPubIRI),
ap.PublicURI(),
},
[]*url.URL{},
false,
@ -3265,7 +3265,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"",
URLMustParse("https://unknown-instance.com/users/brand_new_person"),
[]*url.URL{
URLMustParse(pub.PublicActivityPubIRI),
ap.PublicURI(),
},
[]*url.URL{},
false,
@ -3286,7 +3286,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"",
URLMustParse("https://turnip.farm/users/turniplover6969"),
[]*url.URL{
URLMustParse(pub.PublicActivityPubIRI),
ap.PublicURI(),
},
[]*url.URL{},
false,
@ -3309,7 +3309,7 @@ func NewTestFediStatuses() map[string]vocab.ActivityStreamsNote {
"",
URLMustParse("http://fossbros-anonymous.io/users/foss_satan"),
[]*url.URL{
URLMustParse(pub.PublicActivityPubIRI),
ap.PublicURI(),
},
[]*url.URL{},
false,

View file

@ -29,6 +29,7 @@ import (
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
"github.com/superseriousbusiness/gotosocial/internal/federation"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@ -81,7 +82,7 @@ type MockHTTPClient struct {
// to customize how the client is mocked.
//
// Note that you should never ever make ACTUAL http calls with this thing.
func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error), relativeMediaPath string, extraPeople ...vocab.ActivityStreamsPerson) *MockHTTPClient {
func NewMockHTTPClient(do func(req *http.Request) (*http.Response, error), relativeMediaPath string, extraPeople ...ap.Accountable) *MockHTTPClient {
mockHTTPClient := &MockHTTPClient{}
if do != nil {

View file

@ -42,7 +42,26 @@
padding: 1.5rem;
gap: 1rem;
a {
.trusted-proxies-rec {
color: $info-fg;
background: $info-bg;
max-width: fit-content;
padding-left: 1rem;
padding-right: 1rem;
border-radius: $br;
text-align: center;
align-self: center;
code {
background: $info-bg;
}
a {
color: $info-fg;
}
}
& > a {
display: flex;
flex-wrap: wrap;
gap: 1rem;

View file

@ -17,6 +17,29 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ -}}
{{- define "trustedProxiesRec" -}}
{{- .with }}
<div class="trusted-proxies-rec">
<p>
<strong>Warning!</strong> It looks like <code>trusted-proxies</code> is not set correctly in this instance's configuration.
This may cause rate-limiting issues and, by extension, federation issues.
</p>
<p>
If you are the instance admin, you should fix this by adding <code>{{- .trustedProxiesRec -}}</code> to your <code>trusted-proxies</code>.
</p>
<p>
For more information, see
<a
href="https://docs.gotosocial.org/en/latest/configuration/trusted_proxies/"
rel="nofollow noreferrer noopener"
target="_blank"
>
the documentation
</a>.
</p>
</div>
{{- end -}}
{{- define "thumbnailDescription" -}}
{{- if .instance.ThumbnailDescription -}}
{{- .instance.ThumbnailDescription -}}
@ -56,6 +79,9 @@ Instance Logo
{{- end -}}
{{- with . }}
{{- if .trustedProxiesRec }}
{{- template "trustedProxiesRec" . }}
{{- end }}
<a aria-label="{{- .instance.Title -}}. Go to instance homepage" href="/" class="nounderline">
<picture>
{{- if .instance.ThumbnailStatic }}