diff options
author | 2024-07-26 12:04:28 +0200 | |
---|---|---|
committer | 2024-07-26 12:04:28 +0200 | |
commit | 8ab2b19a946251f258446d22f420d401f61d22f6 (patch) | |
tree | 39fb674f135fd1cfcf4de5b319913f0d0c17d11a /internal/typeutils | |
parent | [docs] Add separate migration section + instructions for moving to GtS and no... (diff) | |
download | gotosocial-8ab2b19a946251f258446d22f420d401f61d22f6.tar.xz |
[feature] Federate interaction policies + Accepts; enforce policies (#3138)
* [feature] Federate interaction policies + Accepts; enforce policies
* use Acceptable type
* fix index
* remove appendIRIStrs
* add GetAccept federatingdb function
* lock on object IRI
Diffstat (limited to 'internal/typeutils')
-rw-r--r-- | internal/typeutils/astointernal.go | 20 | ||||
-rw-r--r-- | internal/typeutils/internaltoas.go | 281 | ||||
-rw-r--r-- | internal/typeutils/internaltoas_test.go | 243 | ||||
-rw-r--r-- | internal/typeutils/wrap_test.go | 25 |
4 files changed, 509 insertions, 60 deletions
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index cb3e320d9..2946c8d09 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -393,13 +393,23 @@ func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab return nil, gtserror.SetMalformed(err) } - // Advanced visibility toggles for this status. - // - // TODO: a lot of work to be done here -- a new type - // needs to be created for this in go-fed/activity. - // Until this is implemented, assume all true. + // Status was sent to us or dereffed + // by us so it must be federated. status.Federated = util.Ptr(true) + // Derive interaction policy for this status. + status.InteractionPolicy = ap.ExtractInteractionPolicy( + statusable, + status.Account, + ) + + // Set approvedByURI if present, + // for later dereferencing. + approvedByURI := ap.GetApprovedBy(statusable) + if approvedByURI != nil { + status.ApprovedByURI = approvedByURI.String() + } + // status.Sensitive sensitive := ap.ExtractSensitive(statusable) status.Sensitive = &sensitive diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index 567493673..31b256b6c 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -36,6 +36,7 @@ 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" ) // AccountToAS converts a gts model account into an activity streams person, suitable for federation @@ -672,6 +673,38 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat sensitiveProp.AppendXMLSchemaBoolean(*s.Sensitive) status.SetActivityStreamsSensitive(sensitiveProp) + // interactionPolicy + var p *gtsmodel.InteractionPolicy + if s.InteractionPolicy != nil { + // Use InteractionPolicy + // set on the status. + p = s.InteractionPolicy + } else { + // Fall back to default policy + // for the status's visibility. + p = gtsmodel.DefaultInteractionPolicyFor(s.Visibility) + } + policy, err := c.InteractionPolicyToASInteractionPolicy(ctx, p, s) + if err != nil { + return nil, fmt.Errorf("error creating interactionPolicy: %w", err) + } + + policyProp := streams.NewGoToSocialInteractionPolicyProperty() + policyProp.AppendGoToSocialInteractionPolicy(policy) + status.SetGoToSocialInteractionPolicy(policyProp) + + // Parse + set approvedBy. + if s.ApprovedByURI != "" { + approvedBy, err := url.Parse(s.ApprovedByURI) + if err != nil { + return nil, fmt.Errorf("error parsing approvedBy: %w", err) + } + + approvedByProp := streams.NewGoToSocialApprovedByProperty() + approvedByProp.Set(approvedBy) + status.SetGoToSocialApprovedBy(approvedByProp) + } + return status, nil } @@ -1169,6 +1202,18 @@ func (c *Converter) FaveToAS(ctx context.Context, f *gtsmodel.StatusFave) (vocab toProp.AppendIRI(toIRI) like.SetActivityStreamsTo(toProp) + // Parse + set approvedBy. + if f.ApprovedByURI != "" { + approvedBy, err := url.Parse(f.ApprovedByURI) + if err != nil { + return nil, fmt.Errorf("error parsing approvedBy: %w", err) + } + + approvedByProp := streams.NewGoToSocialApprovedByProperty() + approvedByProp.Set(approvedBy) + like.SetGoToSocialApprovedBy(approvedByProp) + } + return like, nil } @@ -1247,6 +1292,18 @@ func (c *Converter) BoostToAS(ctx context.Context, boostWrapperStatus *gtsmodel. announce.SetActivityStreamsCc(ccProp) + // Parse + set approvedBy. + if boostWrapperStatus.ApprovedByURI != "" { + approvedBy, err := url.Parse(boostWrapperStatus.ApprovedByURI) + if err != nil { + return nil, fmt.Errorf("error parsing approvedBy: %w", err) + } + + approvedByProp := streams.NewGoToSocialApprovedByProperty() + approvedByProp.Set(approvedBy) + announce.SetGoToSocialApprovedBy(approvedByProp) + } + return announce, nil } @@ -1724,3 +1781,227 @@ func (c *Converter) PollVoteToASCreate( return create, nil } + +// populateValuesForProp appends the given PolicyValues +// to the given property, for the given status. +func populateValuesForProp[T ap.WithIRI]( + prop ap.Property[T], + status *gtsmodel.Status, + urns gtsmodel.PolicyValues, +) error { + iriStrs := make([]string, 0) + + for _, urn := range urns { + switch urn { + + case gtsmodel.PolicyValueAuthor: + iriStrs = append(iriStrs, status.Account.URI) + + case gtsmodel.PolicyValueMentioned: + for _, m := range status.Mentions { + iriStrs = append(iriStrs, m.TargetAccount.URI) + } + + case gtsmodel.PolicyValueFollowing: + iriStrs = append(iriStrs, status.Account.FollowingURI) + + case gtsmodel.PolicyValueFollowers: + iriStrs = append(iriStrs, status.Account.FollowersURI) + + case gtsmodel.PolicyValuePublic: + iriStrs = append(iriStrs, pub.PublicActivityPubIRI) + + default: + iriStrs = append(iriStrs, string(urn)) + } + } + + // Deduplicate the iri strings to + // make sure we're not parsing + adding + // the same string multiple times. + iriStrs = util.Deduplicate(iriStrs) + + // Append them to the property. + for _, iriStr := range iriStrs { + iri, err := url.Parse(iriStr) + if err != nil { + return err + } + + prop.AppendIRI(iri) + } + + return nil +} + +// InteractionPolicyToASInteractionPolicy returns a +// GoToSocial interaction policy suitable for federation. +func (c *Converter) InteractionPolicyToASInteractionPolicy( + ctx context.Context, + interactionPolicy *gtsmodel.InteractionPolicy, + status *gtsmodel.Status, +) (vocab.GoToSocialInteractionPolicy, error) { + policy := streams.NewGoToSocialInteractionPolicy() + + /* + CAN LIKE + */ + + // Build canLike + canLike := streams.NewGoToSocialCanLike() + + // Build canLike.always + canLikeAlwaysProp := streams.NewGoToSocialAlwaysProperty() + if err := populateValuesForProp( + canLikeAlwaysProp, + status, + interactionPolicy.CanLike.Always, + ); err != nil { + return nil, gtserror.Newf("error setting canLike.always: %w", err) + } + + // Set canLike.always + canLike.SetGoToSocialAlways(canLikeAlwaysProp) + + // Build canLike.approvalRequired + canLikeApprovalRequiredProp := streams.NewGoToSocialApprovalRequiredProperty() + if err := populateValuesForProp( + canLikeApprovalRequiredProp, + status, + interactionPolicy.CanLike.WithApproval, + ); err != nil { + return nil, gtserror.Newf("error setting canLike.approvalRequired: %w", err) + } + + // Set canLike.approvalRequired. + canLike.SetGoToSocialApprovalRequired(canLikeApprovalRequiredProp) + + // Set canLike on the policy. + canLikeProp := streams.NewGoToSocialCanLikeProperty() + canLikeProp.AppendGoToSocialCanLike(canLike) + policy.SetGoToSocialCanLike(canLikeProp) + + /* + CAN REPLY + */ + + // Build canReply + canReply := streams.NewGoToSocialCanReply() + + // Build canReply.always + canReplyAlwaysProp := streams.NewGoToSocialAlwaysProperty() + if err := populateValuesForProp( + canReplyAlwaysProp, + status, + interactionPolicy.CanReply.Always, + ); err != nil { + return nil, gtserror.Newf("error setting canReply.always: %w", err) + } + + // Set canReply.always + canReply.SetGoToSocialAlways(canReplyAlwaysProp) + + // Build canReply.approvalRequired + canReplyApprovalRequiredProp := streams.NewGoToSocialApprovalRequiredProperty() + if err := populateValuesForProp( + canReplyApprovalRequiredProp, + status, + interactionPolicy.CanReply.WithApproval, + ); err != nil { + return nil, gtserror.Newf("error setting canReply.approvalRequired: %w", err) + } + + // Set canReply.approvalRequired. + canReply.SetGoToSocialApprovalRequired(canReplyApprovalRequiredProp) + + // Set canReply on the policy. + canReplyProp := streams.NewGoToSocialCanReplyProperty() + canReplyProp.AppendGoToSocialCanReply(canReply) + policy.SetGoToSocialCanReply(canReplyProp) + + /* + CAN ANNOUNCE + */ + + // Build canAnnounce + canAnnounce := streams.NewGoToSocialCanAnnounce() + + // Build canAnnounce.always + canAnnounceAlwaysProp := streams.NewGoToSocialAlwaysProperty() + if err := populateValuesForProp( + canAnnounceAlwaysProp, + status, + interactionPolicy.CanAnnounce.Always, + ); err != nil { + return nil, gtserror.Newf("error setting canAnnounce.always: %w", err) + } + + // Set canAnnounce.always + canAnnounce.SetGoToSocialAlways(canAnnounceAlwaysProp) + + // Build canAnnounce.approvalRequired + canAnnounceApprovalRequiredProp := streams.NewGoToSocialApprovalRequiredProperty() + if err := populateValuesForProp( + canAnnounceApprovalRequiredProp, + status, + interactionPolicy.CanAnnounce.WithApproval, + ); err != nil { + return nil, gtserror.Newf("error setting canAnnounce.approvalRequired: %w", err) + } + + // Set canAnnounce.approvalRequired. + canAnnounce.SetGoToSocialApprovalRequired(canAnnounceApprovalRequiredProp) + + // Set canAnnounce on the policy. + canAnnounceProp := streams.NewGoToSocialCanAnnounceProperty() + canAnnounceProp.AppendGoToSocialCanAnnounce(canAnnounce) + policy.SetGoToSocialCanAnnounce(canAnnounceProp) + + return policy, nil +} + +// InteractionApprovalToASAccept converts a *gtsmodel.InteractionApproval +// to an ActivityStreams Accept, addressed to the interacting account. +func (c *Converter) InteractionApprovalToASAccept( + ctx context.Context, + approval *gtsmodel.InteractionApproval, +) (vocab.ActivityStreamsAccept, error) { + accept := streams.NewActivityStreamsAccept() + + acceptID, err := url.Parse(approval.URI) + if err != nil { + return nil, gtserror.Newf("invalid accept uri: %w", err) + } + + actorIRI, err := url.Parse(approval.Account.URI) + if err != nil { + return nil, gtserror.Newf("invalid account uri: %w", err) + } + + objectIRI, err := url.Parse(approval.InteractionURI) + if err != nil { + return nil, gtserror.Newf("invalid target uri: %w", err) + } + + toIRI, err := url.Parse(approval.InteractingAccount.URI) + if err != nil { + return nil, gtserror.Newf("invalid interacting account uri: %w", err) + } + + // Set id to the URI of + // interactionApproval. + ap.SetJSONLDId(accept, acceptID) + + // Actor is the account that + // owns the approval / accept. + ap.AppendActorIRIs(accept, actorIRI) + + // Object is the interaction URI. + ap.AppendObjectIRIs(accept, objectIRI) + + // Address to the owner + // of interaction URI. + ap.AppendTo(accept, toIRI) + + return accept, nil +} diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go index ca8143436..905dccfad 100644 --- a/internal/typeutils/internaltoas_test.go +++ b/internal/typeutils/internaltoas_test.go @@ -21,8 +21,6 @@ import ( "context" "encoding/json" "errors" - "fmt" - "strings" "testing" "github.com/stretchr/testify/suite" @@ -46,14 +44,15 @@ func (suite *InternalToASTestSuite) TestAccountToAS() { ser, err := ap.Serialize(asPerson) suite.NoError(err) + // Drop "@context" property as + // the ordering is non-determinate. + delete(ser, "@context") + bytes, err := json.MarshalIndent(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, + suite.Equal(`{ + "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", @@ -82,7 +81,7 @@ func (suite *InternalToASTestSuite) TestAccountToAS() { "tag": [], "type": "Person", "url": "http://localhost:8080/@the_mighty_zork" -}`, trimmed) +}`, string(bytes)) } func (suite *InternalToASTestSuite) TestAccountToASWithFields() { @@ -95,16 +94,15 @@ func (suite *InternalToASTestSuite) TestAccountToASWithFields() { ser, err := ap.Serialize(asPerson) suite.NoError(err) + // Drop "@context" property as + // the ordering is non-determinate. + delete(ser, "@context") + bytes, err := json.MarshalIndent(ser, "", " ") suite.NoError(err) - // trim off everything up to 'attachment'; - // this is necessary because the order of multiple 'context' entries is not determinate - trimmed := strings.Split(string(bytes), "\"attachment\"")[1] - - fmt.Printf("\n\n\n%s\n\n\n", string(bytes)) - - suite.Equal(`: [ + suite.Equal(`{ + "attachment": [ { "name": "should you follow me?", "type": "PropertyValue", @@ -135,7 +133,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithFields() { "tag": [], "type": "Person", "url": "http://localhost:8080/@1happyturtle" -}`, trimmed) +}`, string(bytes)) } func (suite *InternalToASTestSuite) TestAccountToASAliasedAndMoved() { @@ -161,14 +159,15 @@ func (suite *InternalToASTestSuite) TestAccountToASAliasedAndMoved() { ser, err := ap.Serialize(asPerson) suite.NoError(err) + // Drop "@context" property as + // the ordering is non-determinate. + delete(ser, "@context") + 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(`: [ + suite.Equal(`{ + "alsoKnownAs": [ "http://localhost:8080/users/1happyturtle" ], "discoverable": true, @@ -201,7 +200,7 @@ func (suite *InternalToASTestSuite) TestAccountToASAliasedAndMoved() { "tag": [], "type": "Person", "url": "http://localhost:8080/@the_mighty_zork" -}`, trimmed) +}`, string(bytes)) } func (suite *InternalToASTestSuite) TestAccountToASWithOneField() { @@ -215,15 +214,16 @@ func (suite *InternalToASTestSuite) TestAccountToASWithOneField() { ser, err := ap.Serialize(asPerson) suite.NoError(err) + // Drop "@context" property as + // the ordering is non-determinate. + delete(ser, "@context") + bytes, err := json.MarshalIndent(ser, "", " ") suite.NoError(err) - // trim off everything up to 'attachment'; - // this is necessary because the order of multiple 'context' entries is not determinate - trimmed := strings.Split(string(bytes), "\"attachment\"")[1] - // Despite only one field being set, attachments should still be a slice/array. - suite.Equal(`: [ + suite.Equal(`{ + "attachment": [ { "name": "should you follow me?", "type": "PropertyValue", @@ -249,7 +249,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithOneField() { "tag": [], "type": "Person", "url": "http://localhost:8080/@1happyturtle" -}`, trimmed) +}`, string(bytes)) } func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() { @@ -263,14 +263,15 @@ func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() { ser, err := ap.Serialize(asPerson) suite.NoError(err) + // Drop "@context" property as + // the ordering is non-determinate. + delete(ser, "@context") + bytes, err := json.MarshalIndent(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, + suite.Equal(`{ + "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", @@ -309,7 +310,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithEmoji() { }, "type": "Person", "url": "http://localhost:8080/@the_mighty_zork" -}`, trimmed) +}`, string(bytes)) } func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() { @@ -324,14 +325,15 @@ func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() { ser, err := ap.Serialize(asPerson) suite.NoError(err) + // Drop "@context" property as + // the ordering is non-determinate. + delete(ser, "@context") + bytes, err := json.MarshalIndent(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, + suite.Equal(`{ + "discoverable": true, "endpoints": { "sharedInbox": "http://localhost:8080/sharedInbox" }, @@ -363,7 +365,7 @@ func (suite *InternalToASTestSuite) TestAccountToASWithSharedInbox() { "tag": [], "type": "Person", "url": "http://localhost:8080/@the_mighty_zork" -}`, trimmed) +}`, string(bytes)) } func (suite *InternalToASTestSuite) TestStatusToAS() { @@ -376,11 +378,14 @@ func (suite *InternalToASTestSuite) TestStatusToAS() { ser, err := ap.Serialize(asStatus) suite.NoError(err) + // Drop "@context" property as + // the ordering is non-determinate. + delete(ser, "@context") + bytes, err := json.MarshalIndent(ser, "", " ") suite.NoError(err) suite.Equal(`{ - "@context": "https://www.w3.org/ns/activitystreams", "attachment": [], "attributedTo": "http://localhost:8080/users/the_mighty_zork", "cc": "http://localhost:8080/users/the_mighty_zork/followers", @@ -389,6 +394,26 @@ func (suite *InternalToASTestSuite) TestStatusToAS() { "en": "hello everyone!" }, "id": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY", + "interactionPolicy": { + "canAnnounce": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + }, + "canLike": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + }, + "canReply": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + } + }, "published": "2021-10-20T12:40:37+02:00", "replies": { "first": { @@ -420,14 +445,15 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASWithIDs() { ser, err := ap.Serialize(asStatus) suite.NoError(err) + // Drop "@context" property as + // the ordering is non-determinate. + delete(ser, "@context") + bytes, err := json.MarshalIndent(ser, "", " ") suite.NoError(err) - // we can't be sure in what order the two context entries -- - // http://joinmastodon.org/ns, https://www.w3.org/ns/activitystreams -- - // will appear, so trim them out of the string for consistency - trimmed := strings.SplitAfter(string(bytes), `"attachment":`)[1] - suite.Equal(` [ + suite.Equal(`{ + "attachment": [ { "blurhash": "LIIE|gRj00WB-;j[t7j[4nWBj[Rj", "mediaType": "image/jpeg", @@ -443,6 +469,26 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASWithIDs() { "en": "hello world! #welcome ! first post on the instance :rainbow: !" }, "id": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R", + "interactionPolicy": { + "canAnnounce": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + }, + "canLike": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + }, + "canReply": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + } + }, "published": "2021-10-20T11:36:45Z", "replies": { "first": { @@ -477,7 +523,7 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASWithIDs() { "to": "https://www.w3.org/ns/activitystreams#Public", "type": "Note", "url": "http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R" -}`, trimmed) +}`, string(bytes)) } func (suite *InternalToASTestSuite) TestStatusWithTagsToASFromDB() { @@ -492,14 +538,15 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASFromDB() { ser, err := ap.Serialize(asStatus) suite.NoError(err) + // Drop "@context" property as + // the ordering is non-determinate. + delete(ser, "@context") + bytes, err := json.MarshalIndent(ser, "", " ") suite.NoError(err) - // we can't be sure in what order the two context entries -- - // http://joinmastodon.org/ns, https://www.w3.org/ns/activitystreams -- - // will appear, so trim them out of the string for consistency - trimmed := strings.SplitAfter(string(bytes), `"attachment":`)[1] - suite.Equal(` [ + suite.Equal(`{ + "attachment": [ { "blurhash": "LIIE|gRj00WB-;j[t7j[4nWBj[Rj", "mediaType": "image/jpeg", @@ -515,6 +562,26 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASFromDB() { "en": "hello world! #welcome ! first post on the instance :rainbow: !" }, "id": "http://localhost:8080/users/admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R", + "interactionPolicy": { + "canAnnounce": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + }, + "canLike": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + }, + "canReply": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + } + }, "published": "2021-10-20T11:36:45Z", "replies": { "first": { @@ -549,7 +616,7 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASFromDB() { "to": "https://www.w3.org/ns/activitystreams#Public", "type": "Note", "url": "http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R" -}`, trimmed) +}`, string(bytes)) } func (suite *InternalToASTestSuite) TestStatusToASWithMentions() { @@ -565,11 +632,14 @@ func (suite *InternalToASTestSuite) TestStatusToASWithMentions() { ser, err := ap.Serialize(asStatus) suite.NoError(err) + // Drop "@context" property as + // the ordering is non-determinate. + delete(ser, "@context") + bytes, err := json.MarshalIndent(ser, "", " ") suite.NoError(err) suite.Equal(`{ - "@context": "https://www.w3.org/ns/activitystreams", "attachment": [], "attributedTo": "http://localhost:8080/users/admin", "cc": [ @@ -582,6 +652,26 @@ func (suite *InternalToASTestSuite) TestStatusToASWithMentions() { }, "id": "http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0", "inReplyTo": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY", + "interactionPolicy": { + "canAnnounce": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + }, + "canLike": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + }, + "canReply": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + } + }, "published": "2021-11-20T13:32:16Z", "replies": { "first": { @@ -967,6 +1057,51 @@ func (suite *InternalToASTestSuite) TestPollVoteToASCreate() { }`, string(bytes)) } +func (suite *InternalToASTestSuite) TestInteractionApprovalToASAccept() { + acceptingAccount := suite.testAccounts["local_account_1"] + interactingAccount := suite.testAccounts["remote_account_1"] + + interactionApproval := >smodel.InteractionApproval{ + ID: "01J1AKMZ8JE5NW0ZSFTRC1JJNE", + CreatedAt: testrig.TimeMustParse("2022-06-09T13:12:00Z"), + UpdatedAt: testrig.TimeMustParse("2022-06-09T13:12:00Z"), + AccountID: acceptingAccount.ID, + Account: acceptingAccount, + InteractingAccountID: interactingAccount.ID, + InteractingAccount: interactingAccount, + InteractionURI: "https://fossbros-anonymous.io/users/foss_satan/statuses/01J1AKRRHQ6MDDQHV0TP716T2K", + InteractionType: gtsmodel.InteractionAnnounce, + URI: "http://localhost:8080/users/the_mighty_zork/accepts/01J1AKMZ8JE5NW0ZSFTRC1JJNE", + } + + accept, err := suite.typeconverter.InteractionApprovalToASAccept( + context.Background(), + interactionApproval, + ) + if err != nil { + suite.FailNow(err.Error()) + } + + i, err := ap.Serialize(accept) + if err != nil { + suite.FailNow(err.Error()) + } + + b, err := json.MarshalIndent(i, "", " ") + if err != nil { + suite.FailNow(err.Error()) + } + + suite.Equal(`{ + "@context": "https://www.w3.org/ns/activitystreams", + "actor": "http://localhost:8080/users/the_mighty_zork", + "id": "http://localhost:8080/users/the_mighty_zork/accepts/01J1AKMZ8JE5NW0ZSFTRC1JJNE", + "object": "https://fossbros-anonymous.io/users/foss_satan/statuses/01J1AKRRHQ6MDDQHV0TP716T2K", + "to": "http://fossbros-anonymous.io/users/foss_satan", + "type": "Accept" +}`, string(b)) +} + func TestInternalToASTestSuite(t *testing.T) { suite.Run(t, new(InternalToASTestSuite)) } diff --git a/internal/typeutils/wrap_test.go b/internal/typeutils/wrap_test.go index 453073ed6..833b18bac 100644 --- a/internal/typeutils/wrap_test.go +++ b/internal/typeutils/wrap_test.go @@ -72,11 +72,14 @@ func (suite *WrapTestSuite) TestWrapNoteInCreate() { createI, err := ap.Serialize(create) suite.NoError(err) + // Chop off @context since + // ordering is non-determinate. + delete(createI, "@context") + bytes, err := json.MarshalIndent(createI, "", " ") suite.NoError(err) suite.Equal(`{ - "@context": "https://www.w3.org/ns/activitystreams", "actor": "http://localhost:8080/users/the_mighty_zork", "cc": "http://localhost:8080/users/the_mighty_zork/followers", "id": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/activity#Create", @@ -89,6 +92,26 @@ func (suite *WrapTestSuite) TestWrapNoteInCreate() { "en": "hello everyone!" }, "id": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY", + "interactionPolicy": { + "canAnnounce": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + }, + "canLike": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + }, + "canReply": { + "always": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "approvalRequired": [] + } + }, "published": "2021-10-20T12:40:37+02:00", "replies": { "first": { |