summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/ap/interfaces.go22
-rw-r--r--internal/ap/normalize.go30
-rw-r--r--internal/ap/properties.go98
-rw-r--r--internal/ap/serialize.go12
-rw-r--r--internal/db/bundb/account.go7
-rw-r--r--internal/typeutils/astointernal.go20
-rw-r--r--internal/typeutils/astointernal_test.go1
-rw-r--r--internal/typeutils/internaltoas.go25
-rw-r--r--internal/typeutils/internaltoas_test.go66
9 files changed, 263 insertions, 18 deletions
diff --git a/internal/ap/interfaces.go b/internal/ap/interfaces.go
index 45ddbfba7..811e09125 100644
--- a/internal/ap/interfaces.go
+++ b/internal/ap/interfaces.go
@@ -160,6 +160,8 @@ type Accountable interface {
WithFollowing
WithFollowers
WithFeatured
+ WithMovedTo
+ WithAlsoKnownAs
WithManuallyApprovesFollowers
WithEndpoints
WithTag
@@ -327,7 +329,7 @@ type TypeOrIRI interface {
}
// Property represents the minimum interface for an ActivityStreams property with IRIs.
-type Property[T TypeOrIRI] interface {
+type Property[T WithIRI] interface {
Len() int
At(int) T
@@ -441,6 +443,18 @@ type WithFeatured interface {
SetTootFeatured(vocab.TootFeaturedProperty)
}
+// WithMovedTo represents an Object with ActivityStreamsMovedToProperty.
+type WithMovedTo interface {
+ GetActivityStreamsMovedTo() vocab.ActivityStreamsMovedToProperty
+ SetActivityStreamsMovedTo(vocab.ActivityStreamsMovedToProperty)
+}
+
+// WithAlsoKnownAs represents an Object with ActivityStreamsAlsoKnownAsProperty.
+type WithAlsoKnownAs interface {
+ GetActivityStreamsAlsoKnownAs() vocab.ActivityStreamsAlsoKnownAsProperty
+ SetActivityStreamsAlsoKnownAs(vocab.ActivityStreamsAlsoKnownAsProperty)
+}
+
// WithAttributedTo represents an activity with ActivityStreamsAttributedToProperty
type WithAttributedTo interface {
GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty
@@ -551,6 +565,12 @@ type WithObject interface {
SetActivityStreamsObject(vocab.ActivityStreamsObjectProperty)
}
+// WithTarget represents an activity with ActivityStreamsTargetProperty
+type WithTarget interface {
+ GetActivityStreamsTarget() vocab.ActivityStreamsTargetProperty
+ SetActivityStreamsTarget(vocab.ActivityStreamsTargetProperty)
+}
+
// WithNext represents an activity with ActivityStreamsNextProperty
type WithNext interface {
GetActivityStreamsNext() vocab.ActivityStreamsNextProperty
diff --git a/internal/ap/normalize.go b/internal/ap/normalize.go
index a27527b84..8e0a022c1 100644
--- a/internal/ap/normalize.go
+++ b/internal/ap/normalize.go
@@ -391,6 +391,36 @@ func NormalizeOutgoingAttachmentProp(item WithAttachment, rawJSON map[string]int
rawJSON["attachment"] = []interface{}{attachment}
}
+// NormalizeOutgoingAlsoKnownAsProp replaces single-entry alsoKnownAs values with
+// single-entry arrays, for better compatibility with other AP implementations.
+//
+// Ie:
+//
+// "alsoKnownAs": "https://example.org/users/some_user"
+//
+// becomes:
+//
+// "alsoKnownAs": ["https://example.org/users/some_user"]
+//
+// Noop for items with no attachments, or with attachments that are already a slice.
+func NormalizeOutgoingAlsoKnownAsProp(item WithAlsoKnownAs, rawJSON map[string]interface{}) {
+ alsoKnownAs, ok := rawJSON["alsoKnownAs"]
+ if !ok {
+ // No 'alsoKnownAs',
+ // nothing to change.
+ return
+ }
+
+ if _, ok := alsoKnownAs.([]interface{}); ok {
+ // Already slice,
+ // nothing to change.
+ return
+ }
+
+ // Coerce single-object to slice.
+ rawJSON["alsoKnownAs"] = []interface{}{alsoKnownAs}
+}
+
// NormalizeOutgoingContentProp normalizes go-fed's funky formatting of content and
// contentMap properties to a format better understood by other AP implementations.
//
diff --git a/internal/ap/properties.go b/internal/ap/properties.go
index 6103608d6..b77d20a02 100644
--- a/internal/ap/properties.go
+++ b/internal/ap/properties.go
@@ -102,7 +102,7 @@ func AppendTo(with WithTo, to ...*url.URL) {
// GetCc returns the IRIs contained in the Cc property of 'with'. Panics on entries with missing ID.
func GetCc(with WithCc) []*url.URL {
ccProp := with.GetActivityStreamsCc()
- return getIRIs[vocab.ActivityStreamsCcPropertyIterator](ccProp)
+ return extractIRIs[vocab.ActivityStreamsCcPropertyIterator](ccProp)
}
// AppendCc appends the given IRIs to the Cc property of 'with'.
@@ -120,7 +120,7 @@ func AppendCc(with WithCc, cc ...*url.URL) {
// GetBcc returns the IRIs contained in the Bcc property of 'with'. Panics on entries with missing ID.
func GetBcc(with WithBcc) []*url.URL {
bccProp := with.GetActivityStreamsBcc()
- return getIRIs[vocab.ActivityStreamsBccPropertyIterator](bccProp)
+ return extractIRIs[vocab.ActivityStreamsBccPropertyIterator](bccProp)
}
// AppendBcc appends the given IRIs to the Bcc property of 'with'.
@@ -170,7 +170,7 @@ func AppendURL(with WithURL, url ...*url.URL) {
// GetActorIRIs returns the IRIs contained in the Actor property of 'with'.
func GetActorIRIs(with WithActor) []*url.URL {
actorProp := with.GetActivityStreamsActor()
- return getIRIs[vocab.ActivityStreamsActorPropertyIterator](actorProp)
+ return extractIRIs[vocab.ActivityStreamsActorPropertyIterator](actorProp)
}
// AppendActorIRIs appends the given IRIs to the Actor property of 'with'.
@@ -188,7 +188,7 @@ func AppendActorIRIs(with WithActor, actor ...*url.URL) {
// GetObjectIRIs returns the IRIs contained in the Object property of 'with'.
func GetObjectIRIs(with WithObject) []*url.URL {
objectProp := with.GetActivityStreamsObject()
- return getIRIs[vocab.ActivityStreamsObjectPropertyIterator](objectProp)
+ return extractIRIs[vocab.ActivityStreamsObjectPropertyIterator](objectProp)
}
// AppendObjectIRIs appends the given IRIs to the Object property of 'with'.
@@ -203,10 +203,28 @@ func AppendObjectIRIs(with WithObject) {
})
}
+// GetTargetIRIs returns the IRIs contained in the Target property of 'with'.
+func GetTargetIRIs(with WithTarget) []*url.URL {
+ targetProp := with.GetActivityStreamsTarget()
+ return extractIRIs[vocab.ActivityStreamsTargetPropertyIterator](targetProp)
+}
+
+// AppendTargetIRIs appends the given IRIs to the Target property of 'with'.
+func AppendTargetIRIs(with WithTarget) {
+ appendIRIs(func() Property[vocab.ActivityStreamsTargetPropertyIterator] {
+ targetProp := with.GetActivityStreamsTarget()
+ if targetProp == nil {
+ targetProp = streams.NewActivityStreamsTargetProperty()
+ with.SetActivityStreamsTarget(targetProp)
+ }
+ return targetProp
+ })
+}
+
// GetAttributedTo returns the IRIs contained in the AttributedTo property of 'with'.
func GetAttributedTo(with WithAttributedTo) []*url.URL {
attribProp := with.GetActivityStreamsAttributedTo()
- return getIRIs[vocab.ActivityStreamsAttributedToPropertyIterator](attribProp)
+ return extractIRIs[vocab.ActivityStreamsAttributedToPropertyIterator](attribProp)
}
// AppendAttributedTo appends the given IRIs to the AttributedTo property of 'with'.
@@ -224,7 +242,7 @@ func AppendAttributedTo(with WithAttributedTo, attribTo ...*url.URL) {
// GetInReplyTo returns the IRIs contained in the InReplyTo property of 'with'.
func GetInReplyTo(with WithInReplyTo) []*url.URL {
replyProp := with.GetActivityStreamsInReplyTo()
- return getIRIs[vocab.ActivityStreamsInReplyToPropertyIterator](replyProp)
+ return extractIRIs[vocab.ActivityStreamsInReplyToPropertyIterator](replyProp)
}
// AppendInReplyTo appends the given IRIs to the InReplyTo property of 'with'.
@@ -334,6 +352,43 @@ func SetFeatured(with WithFeatured, featured *url.URL) {
featuredProp.SetIRI(featured)
}
+// GetMovedTo returns the IRI contained in the movedTo property of 'with'.
+func GetMovedTo(with WithMovedTo) *url.URL {
+ movedToProp := with.GetActivityStreamsMovedTo()
+ if movedToProp == nil || !movedToProp.IsIRI() {
+ return nil
+ }
+ return movedToProp.GetIRI()
+}
+
+// SetMovedTo sets the given IRI on the movedTo property of 'with'.
+func SetMovedTo(with WithMovedTo, movedTo *url.URL) {
+ movedToProp := with.GetActivityStreamsMovedTo()
+ if movedToProp == nil {
+ movedToProp = streams.NewActivityStreamsMovedToProperty()
+ with.SetActivityStreamsMovedTo(movedToProp)
+ }
+ movedToProp.SetIRI(movedTo)
+}
+
+// GetAlsoKnownAs returns the IRI contained in the alsoKnownAs property of 'with'.
+func GetAlsoKnownAs(with WithAlsoKnownAs) []*url.URL {
+ alsoKnownAsProp := with.GetActivityStreamsAlsoKnownAs()
+ return getIRIs[vocab.ActivityStreamsAlsoKnownAsPropertyIterator](alsoKnownAsProp)
+}
+
+// SetAlsoKnownAs sets the given IRIs on the alsoKnownAs property of 'with'.
+func SetAlsoKnownAs(with WithAlsoKnownAs, alsoKnownAs []*url.URL) {
+ appendIRIs(func() Property[vocab.ActivityStreamsAlsoKnownAsPropertyIterator] {
+ alsoKnownAsProp := with.GetActivityStreamsAlsoKnownAs()
+ if alsoKnownAsProp == nil {
+ alsoKnownAsProp = streams.NewActivityStreamsAlsoKnownAsProperty()
+ with.SetActivityStreamsAlsoKnownAs(alsoKnownAsProp)
+ }
+ return alsoKnownAsProp
+ }, alsoKnownAs...)
+}
+
// GetPublished returns the time contained in the Published property of 'with'.
func GetPublished(with WithPublished) time.Time {
publishProp := with.GetActivityStreamsPublished()
@@ -465,7 +520,12 @@ func SetManuallyApprovesFollowers(with WithManuallyApprovesFollowers, manuallyAp
mafProp.Set(manuallyApprovesFollowers)
}
-func getIRIs[T TypeOrIRI](prop Property[T]) []*url.URL {
+// extractIRIs extracts just the AP IRIs from an iterable
+// property that may contain types (with IRIs) or just IRIs.
+//
+// If you know the property contains only IRIs and no types,
+// then use getIRIs instead, since it's slightly faster.
+func extractIRIs[T TypeOrIRI](prop Property[T]) []*url.URL {
if prop == nil || prop.Len() == 0 {
return nil
}
@@ -490,7 +550,29 @@ func getIRIs[T TypeOrIRI](prop Property[T]) []*url.URL {
return ids
}
-func appendIRIs[T TypeOrIRI](getProp func() Property[T], iri ...*url.URL) {
+// getIRIs gets AP IRIs from an iterable property of IRIs.
+//
+// Types will be ignored; to extract IRIs from an iterable
+// that may contain types too, use extractIRIs.
+func getIRIs[T WithIRI](prop Property[T]) []*url.URL {
+ if prop == nil || prop.Len() == 0 {
+ return nil
+ }
+ ids := make([]*url.URL, 0, prop.Len())
+ for i := 0; i < prop.Len(); i++ {
+ at := prop.At(i)
+ if at.IsIRI() {
+ id := at.GetIRI()
+ if id != nil {
+ ids = append(ids, id)
+ continue
+ }
+ }
+ }
+ return ids
+}
+
+func appendIRIs[T WithIRI](getProp func() Property[T], iri ...*url.URL) {
if len(iri) == 0 {
return
}
diff --git a/internal/ap/serialize.go b/internal/ap/serialize.go
index 774e95f2d..b13ebb340 100644
--- a/internal/ap/serialize.go
+++ b/internal/ap/serialize.go
@@ -90,13 +90,12 @@ func serializeWithOrderedItems(t vocab.Type) (map[string]interface{}, error) {
}
// SerializeAccountable is a custom serializer for any Accountable type.
-// This serializer rewrites the 'attachment' value of the Accountable, if
-// present, to always be an array/slice.
+// This serializer rewrites certain values of the Accountable, if present,
+// to always be an array/slice.
//
-// While this is not strictly necessary in json-ld terms, most other fedi
-// implementations look for attachment to be an array of PropertyValue (field)
-// entries, and will not parse single-entry, non-array attachments on accounts
-// properly.
+// While this may not always be strictly necessary in json-ld terms, most other
+// fedi implementations look for certain fields to be an array and will not parse
+// single-entry, non-array fields on accounts properly.
//
// If the accountable is being serialized as a top-level object (eg., for serving
// in response to an account dereference request), then includeContext should be
@@ -126,6 +125,7 @@ func serializeAccountable(t vocab.Type, includeContext bool) (map[string]interfa
}
NormalizeOutgoingAttachmentProp(accountable, data)
+ NormalizeOutgoingAlsoKnownAsProp(accountable, data)
return data, nil
}
diff --git a/internal/db/bundb/account.go b/internal/db/bundb/account.go
index cdb949efa..705e1b118 100644
--- a/internal/db/bundb/account.go
+++ b/internal/db/bundb/account.go
@@ -279,7 +279,12 @@ func (a *accountDB) PopulateAccount(ctx context.Context, account *gtsmodel.Accou
}
}
- if !account.AlsoKnownAsPopulated() {
+ // Only try to populate AlsoKnownAs for local accounts,
+ // since those are the only accounts to which it's relevant.
+ //
+ // AKA from remotes might have loads of random-ass values
+ // set here, and we don't want to do lots of failing DB calls.
+ if account.IsLocal() && !account.AlsoKnownAsPopulated() {
// Account alsoKnownAs accounts are
// out-of-date with URIs, repopulate.
alsoKnownAs := make([]*gtsmodel.Account, 0)
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go
index fa2ae6a62..b262030de 100644
--- a/internal/typeutils/astointernal.go
+++ b/internal/typeutils/astointernal.go
@@ -198,7 +198,25 @@ func (c *Converter) ASRepresentationToAccount(ctx context.Context, accountable a
// TODO: FeaturedTagsURI
- // TODO: alsoKnownAs
+ // Moved and AlsoKnownAsURIs,
+ // needed for account migrations.
+ movedToURI := ap.GetMovedTo(accountable)
+ if movedToURI != nil {
+ acct.MovedToURI = movedToURI.String()
+ }
+
+ alsoKnownAsURIs := ap.GetAlsoKnownAs(accountable)
+ for i, uri := range alsoKnownAsURIs {
+ // Don't store more than
+ // 20 AKA URIs for remotes,
+ // to prevent people playing
+ // silly buggers.
+ if i >= 20 {
+ break
+ }
+
+ acct.AlsoKnownAsURIs = append(acct.AlsoKnownAsURIs, uri.String())
+ }
// Extract account public key and verify ownership to account.
pkey, pkeyURL, pkeyOwnerID, err := ap.ExtractPublicKey(accountable)
diff --git a/internal/typeutils/astointernal_test.go b/internal/typeutils/astointernal_test.go
index 627f9cac7..fc8cd19a0 100644
--- a/internal/typeutils/astointernal_test.go
+++ b/internal/typeutils/astointernal_test.go
@@ -146,6 +146,7 @@ func (suite *ASToInternalTestSuite) TestParseGargron() {
acct, err := suite.typeconverter.ASRepresentationToAccount(context.Background(), rep, "")
suite.NoError(err)
suite.Equal("https://mastodon.social/inbox", *acct.SharedInboxURI)
+ suite.Equal([]string{"https://tooting.ai/users/Gargron"}, acct.AlsoKnownAsURIs)
suite.Equal(int64(1458086400), acct.CreatedAt.Unix())
}
diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go
index dc25babaa..a795541d0 100644
--- a/internal/typeutils/internaltoas.go
+++ b/internal/typeutils/internaltoas.go
@@ -171,7 +171,30 @@ func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab
// alsoKnownAs
// Required for Move activity.
- // TODO: NOT IMPLEMENTED **YET** -- this needs to be added as an activitypub extension to https://github.com/go-fed/activity, see https://github.com/go-fed/activity/tree/master/astool
+ if l := len(a.AlsoKnownAsURIs); l != 0 {
+ alsoKnownAsURIs := make([]*url.URL, l)
+ for i, rawURL := range a.AlsoKnownAsURIs {
+ uri, err := url.Parse(rawURL)
+ if err != nil {
+ return nil, err
+ }
+
+ alsoKnownAsURIs[i] = uri
+ }
+
+ ap.SetAlsoKnownAs(person, alsoKnownAsURIs)
+ }
+
+ // movedTo
+ // Required for Move activity.
+ if a.MovedToURI != "" {
+ movedTo, err := url.Parse(a.MovedToURI)
+ if err != nil {
+ return nil, err
+ }
+
+ ap.SetMovedTo(person, movedTo)
+ }
// publicKey
// Required for signatures.
diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go
index cbeaf3c8c..740938220 100644
--- a/internal/typeutils/internaltoas_test.go
+++ b/internal/typeutils/internaltoas_test.go
@@ -138,6 +138,72 @@ func (suite *InternalToASTestSuite) TestAccountToASWithFields() {
}`, trimmed)
}
+func (suite *InternalToASTestSuite) TestAccountToASAliasedAndMoved() {
+ testAccount := &gtsmodel.Account{}
+ *testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
+
+ ctx := context.Background()
+
+ // Suppose zork has moved account to turtle.
+ testAccount.AlsoKnownAsURIs = []string{"http://localhost:8080/users/1happyturtle"}
+ testAccount.MovedToURI = "http://localhost:8080/users/1happyturtle"
+ if err := suite.state.DB.UpdateAccount(ctx,
+ testAccount,
+ "also_known_as_uris",
+ "moved_to_uri",
+ ); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ asPerson, err := suite.typeconverter.AccountToAS(context.Background(), testAccount)
+ suite.NoError(err)
+
+ ser, err := ap.Serialize(asPerson)
+ suite.NoError(err)
+
+ bytes, err := json.MarshalIndent(ser, "", " ")
+ suite.NoError(err)
+
+ // trim off everything up to 'alsoKnownAs';
+ // this is necessary because the order of multiple 'context' entries is not determinate
+ trimmed := strings.Split(string(bytes), "\"alsoKnownAs\"")[1]
+
+ suite.Equal(`: [
+ "http://localhost:8080/users/1happyturtle"
+ ],
+ "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,
+ "movedTo": "http://localhost:8080/users/1happyturtle",
+ "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",
+ "tag": [],
+ "type": "Person",
+ "url": "http://localhost:8080/@the_mighty_zork"
+}`, trimmed)
+}
+
func (suite *InternalToASTestSuite) TestAccountToASWithOneField() {
testAccount := &gtsmodel.Account{}
*testAccount = *suite.testAccounts["local_account_2"]