mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[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
This commit is contained in:
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/superseriousbusiness/activity/streams"
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/testrig"
|
||||
)
|
||||
|
||||
@@ -103,6 +104,66 @@ func noteWithMentions1() vocab.ActivityStreamsNote {
|
||||
|
||||
note.SetActivityStreamsContent(content)
|
||||
|
||||
policy := streams.NewGoToSocialInteractionPolicy()
|
||||
|
||||
// Set canLike.
|
||||
canLike := streams.NewGoToSocialCanLike()
|
||||
|
||||
// Anyone can like.
|
||||
canLikeAlwaysProp := streams.NewGoToSocialAlwaysProperty()
|
||||
canLikeAlwaysProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
|
||||
canLike.SetGoToSocialAlways(canLikeAlwaysProp)
|
||||
|
||||
// Empty approvalRequired.
|
||||
canLikeApprovalRequiredProp := streams.NewGoToSocialApprovalRequiredProperty()
|
||||
canLike.SetGoToSocialApprovalRequired(canLikeApprovalRequiredProp)
|
||||
|
||||
// Set canLike on the policy.
|
||||
canLikeProp := streams.NewGoToSocialCanLikeProperty()
|
||||
canLikeProp.AppendGoToSocialCanLike(canLike)
|
||||
policy.SetGoToSocialCanLike(canLikeProp)
|
||||
|
||||
// Build canReply.
|
||||
canReply := streams.NewGoToSocialCanReply()
|
||||
|
||||
// Anyone can reply.
|
||||
canReplyAlwaysProp := streams.NewGoToSocialAlwaysProperty()
|
||||
canReplyAlwaysProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
|
||||
canReply.SetGoToSocialAlways(canReplyAlwaysProp)
|
||||
|
||||
// Set empty approvalRequired.
|
||||
canReplyApprovalRequiredProp := streams.NewGoToSocialApprovalRequiredProperty()
|
||||
canReply.SetGoToSocialApprovalRequired(canReplyApprovalRequiredProp)
|
||||
|
||||
// Set canReply on the policy.
|
||||
canReplyProp := streams.NewGoToSocialCanReplyProperty()
|
||||
canReplyProp.AppendGoToSocialCanReply(canReply)
|
||||
policy.SetGoToSocialCanReply(canReplyProp)
|
||||
|
||||
// Build canAnnounce.
|
||||
canAnnounce := streams.NewGoToSocialCanAnnounce()
|
||||
|
||||
// Only f0x and dumpsterqueer can announce.
|
||||
canAnnounceAlwaysProp := streams.NewGoToSocialAlwaysProperty()
|
||||
canAnnounceAlwaysProp.AppendIRI(testrig.URLMustParse("https://gts.superseriousbusiness.org/users/dumpsterqueer"))
|
||||
canAnnounceAlwaysProp.AppendIRI(testrig.URLMustParse("https://gts.superseriousbusiness.org/users/f0x"))
|
||||
canAnnounce.SetGoToSocialAlways(canAnnounceAlwaysProp)
|
||||
|
||||
// Public requires approval to announce.
|
||||
canAnnounceApprovalRequiredProp := streams.NewGoToSocialApprovalRequiredProperty()
|
||||
canAnnounceApprovalRequiredProp.AppendIRI(testrig.URLMustParse(pub.PublicActivityPubIRI))
|
||||
canAnnounce.SetGoToSocialApprovalRequired(canAnnounceApprovalRequiredProp)
|
||||
|
||||
// Set canAnnounce on the policy.
|
||||
canAnnounceProp := streams.NewGoToSocialCanAnnounceProperty()
|
||||
canAnnounceProp.AppendGoToSocialCanAnnounce(canAnnounce)
|
||||
policy.SetGoToSocialCanAnnounce(canAnnounceProp)
|
||||
|
||||
// Set the policy on the note.
|
||||
policyProp := streams.NewGoToSocialInteractionPolicyProperty()
|
||||
policyProp.AppendGoToSocialInteractionPolicy(policy)
|
||||
note.SetGoToSocialInteractionPolicy(policyProp)
|
||||
|
||||
return note
|
||||
}
|
||||
|
||||
@@ -296,6 +357,7 @@ type APTestSuite struct {
|
||||
addressable3 ap.Addressable
|
||||
addressable4 vocab.ActivityStreamsAnnounce
|
||||
addressable5 ap.Addressable
|
||||
testAccounts map[string]*gtsmodel.Account
|
||||
}
|
||||
|
||||
func (suite *APTestSuite) jsonToType(rawJson string) (vocab.Type, map[string]interface{}) {
|
||||
@@ -336,4 +398,5 @@ func (suite *APTestSuite) SetupTest() {
|
||||
suite.addressable3 = addressable3()
|
||||
suite.addressable4 = addressable4()
|
||||
suite.addressable5 = addressable5()
|
||||
suite.testAccounts = testrig.NewTestAccounts()
|
||||
}
|
||||
|
@@ -1057,6 +1057,137 @@ func ExtractVisibility(addressable Addressable, actorFollowersURI string) (gtsmo
|
||||
return visibility, nil
|
||||
}
|
||||
|
||||
// ExtractInteractionPolicy extracts a *gtsmodel.InteractionPolicy
|
||||
// from the given Statusable created by by the given *gtsmodel.Account.
|
||||
//
|
||||
// Will be nil (default policy) for Statusables that have no policy
|
||||
// set on them, or have a null policy. In such a case, the caller
|
||||
// should assume the default policy for the status's visibility level.
|
||||
func ExtractInteractionPolicy(
|
||||
statusable Statusable,
|
||||
owner *gtsmodel.Account,
|
||||
) *gtsmodel.InteractionPolicy {
|
||||
policyProp := statusable.GetGoToSocialInteractionPolicy()
|
||||
if policyProp == nil || policyProp.Len() != 1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
policyPropIter := policyProp.At(0)
|
||||
if !policyPropIter.IsGoToSocialInteractionPolicy() {
|
||||
return nil
|
||||
}
|
||||
|
||||
policy := policyPropIter.Get()
|
||||
if policy == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return >smodel.InteractionPolicy{
|
||||
CanLike: extractCanLike(policy.GetGoToSocialCanLike(), owner),
|
||||
CanReply: extractCanReply(policy.GetGoToSocialCanReply(), owner),
|
||||
CanAnnounce: extractCanAnnounce(policy.GetGoToSocialCanAnnounce(), owner),
|
||||
}
|
||||
}
|
||||
|
||||
func extractCanLike(
|
||||
prop vocab.GoToSocialCanLikeProperty,
|
||||
owner *gtsmodel.Account,
|
||||
) gtsmodel.PolicyRules {
|
||||
if prop == nil || prop.Len() != 1 {
|
||||
return gtsmodel.PolicyRules{}
|
||||
}
|
||||
|
||||
propIter := prop.At(0)
|
||||
if !propIter.IsGoToSocialCanLike() {
|
||||
return gtsmodel.PolicyRules{}
|
||||
}
|
||||
|
||||
withRules := propIter.Get()
|
||||
if withRules == nil {
|
||||
return gtsmodel.PolicyRules{}
|
||||
}
|
||||
|
||||
return gtsmodel.PolicyRules{
|
||||
Always: extractPolicyValues(withRules.GetGoToSocialAlways(), owner),
|
||||
WithApproval: extractPolicyValues(withRules.GetGoToSocialApprovalRequired(), owner),
|
||||
}
|
||||
}
|
||||
|
||||
func extractCanReply(
|
||||
prop vocab.GoToSocialCanReplyProperty,
|
||||
owner *gtsmodel.Account,
|
||||
) gtsmodel.PolicyRules {
|
||||
if prop == nil || prop.Len() != 1 {
|
||||
return gtsmodel.PolicyRules{}
|
||||
}
|
||||
|
||||
propIter := prop.At(0)
|
||||
if !propIter.IsGoToSocialCanReply() {
|
||||
return gtsmodel.PolicyRules{}
|
||||
}
|
||||
|
||||
withRules := propIter.Get()
|
||||
if withRules == nil {
|
||||
return gtsmodel.PolicyRules{}
|
||||
}
|
||||
|
||||
return gtsmodel.PolicyRules{
|
||||
Always: extractPolicyValues(withRules.GetGoToSocialAlways(), owner),
|
||||
WithApproval: extractPolicyValues(withRules.GetGoToSocialApprovalRequired(), owner),
|
||||
}
|
||||
}
|
||||
|
||||
func extractCanAnnounce(
|
||||
prop vocab.GoToSocialCanAnnounceProperty,
|
||||
owner *gtsmodel.Account,
|
||||
) gtsmodel.PolicyRules {
|
||||
if prop == nil || prop.Len() != 1 {
|
||||
return gtsmodel.PolicyRules{}
|
||||
}
|
||||
|
||||
propIter := prop.At(0)
|
||||
if !propIter.IsGoToSocialCanAnnounce() {
|
||||
return gtsmodel.PolicyRules{}
|
||||
}
|
||||
|
||||
withRules := propIter.Get()
|
||||
if withRules == nil {
|
||||
return gtsmodel.PolicyRules{}
|
||||
}
|
||||
|
||||
return gtsmodel.PolicyRules{
|
||||
Always: extractPolicyValues(withRules.GetGoToSocialAlways(), owner),
|
||||
WithApproval: extractPolicyValues(withRules.GetGoToSocialApprovalRequired(), owner),
|
||||
}
|
||||
}
|
||||
|
||||
func extractPolicyValues[T WithIRI](
|
||||
prop Property[T],
|
||||
owner *gtsmodel.Account,
|
||||
) gtsmodel.PolicyValues {
|
||||
iris := getIRIs(prop)
|
||||
PolicyValues := make(gtsmodel.PolicyValues, 0, len(iris))
|
||||
|
||||
for _, iri := range iris {
|
||||
switch iriStr := iri.String(); iriStr {
|
||||
case pub.PublicActivityPubIRI:
|
||||
PolicyValues = append(PolicyValues, gtsmodel.PolicyValuePublic)
|
||||
case owner.FollowersURI:
|
||||
PolicyValues = append(PolicyValues, gtsmodel.PolicyValueFollowers)
|
||||
case owner.FollowingURI:
|
||||
PolicyValues = append(PolicyValues, gtsmodel.PolicyValueFollowers)
|
||||
case owner.URI:
|
||||
PolicyValues = append(PolicyValues, gtsmodel.PolicyValueAuthor)
|
||||
default:
|
||||
if iri.Scheme == "http" || iri.Scheme == "https" {
|
||||
PolicyValues = append(PolicyValues, gtsmodel.PolicyValue(iriStr))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PolicyValues
|
||||
}
|
||||
|
||||
// ExtractSensitive extracts whether or not an item should
|
||||
// be marked as sensitive according to its ActivityStreams
|
||||
// sensitive property.
|
||||
|
137
internal/ap/extractpolicy_test.go
Normal file
137
internal/ap/extractpolicy_test.go
Normal file
@@ -0,0 +1,137 @@
|
||||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package ap_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
)
|
||||
|
||||
type ExtractPolicyTestSuite struct {
|
||||
APTestSuite
|
||||
}
|
||||
|
||||
func (suite *ExtractPolicyTestSuite) TestExtractPolicy() {
|
||||
rawNote := `{
|
||||
"@context": [
|
||||
"https://gotosocial.org/ns",
|
||||
"https://www.w3.org/ns/activitystreams"
|
||||
],
|
||||
"content": "hey @f0x and @dumpsterqueer",
|
||||
"contentMap": {
|
||||
"en": "hey @f0x and @dumpsterqueer",
|
||||
"fr": "bonjour @f0x et @dumpsterqueer"
|
||||
},
|
||||
"interactionPolicy": {
|
||||
"canLike": {
|
||||
"always": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"approvalRequired": []
|
||||
},
|
||||
"canReply": {
|
||||
"always": [
|
||||
"http://localhost:8080/users/the_mighty_zork",
|
||||
"http://localhost:8080/users/the_mighty_zork/followers",
|
||||
"https://gts.superseriousbusiness.org/users/dumpsterqueer",
|
||||
"https://gts.superseriousbusiness.org/users/f0x"
|
||||
],
|
||||
"approvalRequired": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
]
|
||||
},
|
||||
"canAnnounce": {
|
||||
"always": [
|
||||
"http://localhost:8080/users/the_mighty_zork"
|
||||
],
|
||||
"approvalRequired": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tag": [
|
||||
{
|
||||
"href": "https://gts.superseriousbusiness.org/users/dumpsterqueer",
|
||||
"name": "@dumpsterqueer@superseriousbusiness.org",
|
||||
"type": "Mention"
|
||||
},
|
||||
{
|
||||
"href": "https://gts.superseriousbusiness.org/users/f0x",
|
||||
"name": "@f0x@superseriousbusiness.org",
|
||||
"type": "Mention"
|
||||
}
|
||||
],
|
||||
"type": "Note"
|
||||
}`
|
||||
|
||||
statusable, err := ap.ResolveStatusable(
|
||||
context.Background(),
|
||||
io.NopCloser(
|
||||
bytes.NewBufferString(rawNote),
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
policy := ap.ExtractInteractionPolicy(
|
||||
statusable,
|
||||
// Zork didn't actually create
|
||||
// this status but nevermind.
|
||||
suite.testAccounts["local_account_1"],
|
||||
)
|
||||
|
||||
expectedPolicy := >smodel.InteractionPolicy{
|
||||
CanLike: gtsmodel.PolicyRules{
|
||||
Always: gtsmodel.PolicyValues{
|
||||
gtsmodel.PolicyValuePublic,
|
||||
},
|
||||
WithApproval: gtsmodel.PolicyValues{},
|
||||
},
|
||||
CanReply: gtsmodel.PolicyRules{
|
||||
Always: gtsmodel.PolicyValues{
|
||||
gtsmodel.PolicyValueAuthor,
|
||||
gtsmodel.PolicyValueFollowers,
|
||||
"https://gts.superseriousbusiness.org/users/dumpsterqueer",
|
||||
"https://gts.superseriousbusiness.org/users/f0x",
|
||||
},
|
||||
WithApproval: gtsmodel.PolicyValues{
|
||||
gtsmodel.PolicyValuePublic,
|
||||
},
|
||||
},
|
||||
CanAnnounce: gtsmodel.PolicyRules{
|
||||
Always: gtsmodel.PolicyValues{
|
||||
gtsmodel.PolicyValueAuthor,
|
||||
},
|
||||
WithApproval: gtsmodel.PolicyValues{
|
||||
gtsmodel.PolicyValuePublic,
|
||||
},
|
||||
},
|
||||
}
|
||||
suite.EqualValues(expectedPolicy, policy)
|
||||
}
|
||||
|
||||
func TestExtractPolicyTestSuite(t *testing.T) {
|
||||
suite.Run(t, &ExtractPolicyTestSuite{})
|
||||
}
|
@@ -124,6 +124,24 @@ func ToPollOptionable(t vocab.Type) (PollOptionable, bool) {
|
||||
return note, true
|
||||
}
|
||||
|
||||
// IsAccept returns whether AS vocab type name
|
||||
// is something that can be cast to Accept.
|
||||
func IsAcceptable(typeName string) bool {
|
||||
return typeName == ActivityAccept
|
||||
}
|
||||
|
||||
// ToAcceptable safely tries to cast vocab.Type as vocab.ActivityStreamsAccept.
|
||||
//
|
||||
// TODO: Add additional "Accept" types here, eg., "ApproveReply" from
|
||||
// https://codeberg.org/fediverse/fep/src/branch/main/fep/5624/fep-5624.md
|
||||
func ToAcceptable(t vocab.Type) (vocab.ActivityStreamsAccept, bool) {
|
||||
acceptable, ok := t.(vocab.ActivityStreamsAccept)
|
||||
if !ok || !IsAcceptable(t.GetTypeName()) {
|
||||
return nil, false
|
||||
}
|
||||
return acceptable, true
|
||||
}
|
||||
|
||||
// Activityable represents the minimum activitypub interface for representing an 'activity'.
|
||||
// (see: IsActivityable() for types implementing this, though you MUST make sure to check
|
||||
// the typeName as this bare interface may be implementable by non-Activityable types).
|
||||
@@ -188,6 +206,8 @@ type Statusable interface {
|
||||
WithAttachment
|
||||
WithTag
|
||||
WithReplies
|
||||
WithInteractionPolicy
|
||||
WithApprovedBy
|
||||
}
|
||||
|
||||
// Pollable represents the minimum activitypub interface for representing a 'poll' (it's a subset of a status).
|
||||
@@ -217,6 +237,12 @@ type PollOptionable interface {
|
||||
WithAttributedTo
|
||||
}
|
||||
|
||||
// Acceptable represents the minimum activitypub
|
||||
// interface for representing an Accept.
|
||||
type Acceptable interface {
|
||||
Activityable
|
||||
}
|
||||
|
||||
// Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'. (see: IsAttachmentable).
|
||||
// This interface is fulfilled by: Audio, Document, Image, Video
|
||||
type Attachmentable interface {
|
||||
@@ -657,3 +683,21 @@ type WithVotersCount interface {
|
||||
GetTootVotersCount() vocab.TootVotersCountProperty
|
||||
SetTootVotersCount(vocab.TootVotersCountProperty)
|
||||
}
|
||||
|
||||
// WithReplies represents an object with GoToSocialInteractionPolicy.
|
||||
type WithInteractionPolicy interface {
|
||||
GetGoToSocialInteractionPolicy() vocab.GoToSocialInteractionPolicyProperty
|
||||
SetGoToSocialInteractionPolicy(vocab.GoToSocialInteractionPolicyProperty)
|
||||
}
|
||||
|
||||
// WithPolicyRules represents an activity with always and approvalRequired properties.
|
||||
type WithPolicyRules interface {
|
||||
GetGoToSocialAlways() vocab.GoToSocialAlwaysProperty
|
||||
GetGoToSocialApprovalRequired() vocab.GoToSocialApprovalRequiredProperty
|
||||
}
|
||||
|
||||
// WithApprovedBy represents a Statusable with the approvedBy property.
|
||||
type WithApprovedBy interface {
|
||||
GetGoToSocialApprovedBy() vocab.GoToSocialApprovedByProperty
|
||||
SetGoToSocialApprovedBy(vocab.GoToSocialApprovedByProperty)
|
||||
}
|
||||
|
@@ -575,6 +575,107 @@ func NormalizeOutgoingContentProp(item WithContent, rawJSON map[string]interface
|
||||
}
|
||||
}
|
||||
|
||||
// NormalizeOutgoingInteractionPolicyProp replaces single-entry interactionPolicy values
|
||||
// with single-entry arrays, for better compatibility with other AP implementations.
|
||||
//
|
||||
// Ie:
|
||||
//
|
||||
// "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": []
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// becomes:
|
||||
//
|
||||
// "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": []
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Noop for items with no attachments, or with attachments that are already a slice.
|
||||
func NormalizeOutgoingInteractionPolicyProp(item WithInteractionPolicy, rawJSON map[string]interface{}) {
|
||||
policy, ok := rawJSON["interactionPolicy"]
|
||||
if !ok {
|
||||
// No 'interactionPolicy',
|
||||
// nothing to change.
|
||||
return
|
||||
}
|
||||
|
||||
policyMap, ok := policy.(map[string]interface{})
|
||||
if !ok {
|
||||
// Malformed 'interactionPolicy',
|
||||
// nothing to change.
|
||||
return
|
||||
}
|
||||
|
||||
for _, rulesKey := range []string{
|
||||
"canLike",
|
||||
"canReply",
|
||||
"canAnnounce",
|
||||
} {
|
||||
// Either "canAnnounce",
|
||||
// "canLike", or "canApprove"
|
||||
rulesVal, ok := policyMap[rulesKey]
|
||||
if !ok {
|
||||
// Not set.
|
||||
return
|
||||
}
|
||||
|
||||
rulesValMap, ok := rulesVal.(map[string]interface{})
|
||||
if !ok {
|
||||
// Malformed or not
|
||||
// present skip.
|
||||
return
|
||||
}
|
||||
|
||||
for _, PolicyValuesKey := range []string{
|
||||
"always",
|
||||
"approvalRequired",
|
||||
} {
|
||||
PolicyValuesVal, ok := rulesValMap[PolicyValuesKey]
|
||||
if !ok {
|
||||
// Not set.
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := PolicyValuesVal.([]interface{}); ok {
|
||||
// Already slice,
|
||||
// nothing to change.
|
||||
continue
|
||||
}
|
||||
|
||||
// Coerce single-object to slice.
|
||||
rulesValMap[PolicyValuesKey] = []interface{}{PolicyValuesVal}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NormalizeOutgoingObjectProp normalizes each Object entry in the rawJSON of the given
|
||||
// item by calling custom serialization / normalization functions on them in turn.
|
||||
//
|
||||
|
@@ -520,6 +520,27 @@ func SetManuallyApprovesFollowers(with WithManuallyApprovesFollowers, manuallyAp
|
||||
mafProp.Set(manuallyApprovesFollowers)
|
||||
}
|
||||
|
||||
// GetApprovedBy returns the URL contained in
|
||||
// the ApprovedBy property of 'with', if set.
|
||||
func GetApprovedBy(with WithApprovedBy) *url.URL {
|
||||
mafProp := with.GetGoToSocialApprovedBy()
|
||||
if mafProp == nil || !mafProp.IsIRI() {
|
||||
return nil
|
||||
}
|
||||
return mafProp.Get()
|
||||
}
|
||||
|
||||
// SetApprovedBy sets the given url
|
||||
// on the ApprovedBy property of 'with'.
|
||||
func SetApprovedBy(with WithApprovedBy, approvedBy *url.URL) {
|
||||
abProp := with.GetGoToSocialApprovedBy()
|
||||
if abProp == nil {
|
||||
abProp = streams.NewGoToSocialApprovedByProperty()
|
||||
with.SetGoToSocialApprovedBy(abProp)
|
||||
}
|
||||
abProp.Set(approvedBy)
|
||||
}
|
||||
|
||||
// extractIRIs extracts just the AP IRIs from an iterable
|
||||
// property that may contain types (with IRIs) or just IRIs.
|
||||
//
|
||||
|
@@ -37,6 +37,8 @@ func ResolveIncomingActivity(r *http.Request) (pub.Activity, bool, gtserror.With
|
||||
// Get "raw" map
|
||||
// destination.
|
||||
raw := getMap()
|
||||
// Release.
|
||||
defer putMap(raw)
|
||||
|
||||
// Decode data as JSON into 'raw' map
|
||||
// and get the resolved AS vocab.Type.
|
||||
@@ -79,9 +81,6 @@ func ResolveIncomingActivity(r *http.Request) (pub.Activity, bool, gtserror.With
|
||||
// (see: https://github.com/superseriousbusiness/gotosocial/issues/1661)
|
||||
NormalizeIncomingActivity(activity, raw)
|
||||
|
||||
// Release.
|
||||
putMap(raw)
|
||||
|
||||
return activity, true, nil
|
||||
}
|
||||
|
||||
@@ -93,6 +92,8 @@ func ResolveStatusable(ctx context.Context, body io.ReadCloser) (Statusable, err
|
||||
// Get "raw" map
|
||||
// destination.
|
||||
raw := getMap()
|
||||
// Release.
|
||||
defer putMap(raw)
|
||||
|
||||
// Decode data as JSON into 'raw' map
|
||||
// and get the resolved AS vocab.Type.
|
||||
@@ -121,9 +122,6 @@ func ResolveStatusable(ctx context.Context, body io.ReadCloser) (Statusable, err
|
||||
NormalizeIncomingSummary(statusable, raw)
|
||||
NormalizeIncomingName(statusable, raw)
|
||||
|
||||
// Release.
|
||||
putMap(raw)
|
||||
|
||||
return statusable, nil
|
||||
}
|
||||
|
||||
@@ -135,6 +133,8 @@ func ResolveAccountable(ctx context.Context, body io.ReadCloser) (Accountable, e
|
||||
// Get "raw" map
|
||||
// destination.
|
||||
raw := getMap()
|
||||
// Release.
|
||||
defer putMap(raw)
|
||||
|
||||
// Decode data as JSON into 'raw' map
|
||||
// and get the resolved AS vocab.Type.
|
||||
@@ -153,9 +153,6 @@ func ResolveAccountable(ctx context.Context, body io.ReadCloser) (Accountable, e
|
||||
|
||||
NormalizeIncomingSummary(accountable, raw)
|
||||
|
||||
// Release.
|
||||
putMap(raw)
|
||||
|
||||
return accountable, nil
|
||||
}
|
||||
|
||||
@@ -165,6 +162,8 @@ func ResolveCollection(ctx context.Context, body io.ReadCloser) (CollectionItera
|
||||
// Get "raw" map
|
||||
// destination.
|
||||
raw := getMap()
|
||||
// Release.
|
||||
defer putMap(raw)
|
||||
|
||||
// Decode data as JSON into 'raw' map
|
||||
// and get the resolved AS vocab.Type.
|
||||
@@ -174,9 +173,6 @@ func ResolveCollection(ctx context.Context, body io.ReadCloser) (CollectionItera
|
||||
return nil, gtserror.SetWrongType(err)
|
||||
}
|
||||
|
||||
// Release.
|
||||
putMap(raw)
|
||||
|
||||
// Cast as as Collection-like.
|
||||
return ToCollectionIterator(t)
|
||||
}
|
||||
@@ -187,6 +183,8 @@ func ResolveCollectionPage(ctx context.Context, body io.ReadCloser) (CollectionP
|
||||
// Get "raw" map
|
||||
// destination.
|
||||
raw := getMap()
|
||||
// Release.
|
||||
defer putMap(raw)
|
||||
|
||||
// Decode data as JSON into 'raw' map
|
||||
// and get the resolved AS vocab.Type.
|
||||
@@ -196,13 +194,40 @@ func ResolveCollectionPage(ctx context.Context, body io.ReadCloser) (CollectionP
|
||||
return nil, gtserror.SetWrongType(err)
|
||||
}
|
||||
|
||||
// Release.
|
||||
putMap(raw)
|
||||
|
||||
// Cast as as CollectionPage-like.
|
||||
return ToCollectionPageIterator(t)
|
||||
}
|
||||
|
||||
// ResolveAcceptable tries to resolve the given reader
|
||||
// into an ActivityStreams Acceptable representation.
|
||||
func ResolveAcceptable(
|
||||
ctx context.Context,
|
||||
body io.ReadCloser,
|
||||
) (Acceptable, error) {
|
||||
// Get "raw" map
|
||||
// destination.
|
||||
raw := getMap()
|
||||
// Release.
|
||||
defer putMap(raw)
|
||||
|
||||
// Decode data as JSON into 'raw' map
|
||||
// and get the resolved AS vocab.Type.
|
||||
// (this handles close of given body).
|
||||
t, err := decodeType(ctx, body, raw)
|
||||
if err != nil {
|
||||
return nil, gtserror.SetWrongType(err)
|
||||
}
|
||||
|
||||
// Attempt to cast as acceptable.
|
||||
acceptable, ok := ToAcceptable(t)
|
||||
if !ok {
|
||||
err := gtserror.Newf("cannot resolve vocab type %T as acceptable", t)
|
||||
return nil, gtserror.SetWrongType(err)
|
||||
}
|
||||
|
||||
return acceptable, nil
|
||||
}
|
||||
|
||||
// emptydest is an empty JSON decode
|
||||
// destination useful for "noop" decodes
|
||||
// to check underlying reader is empty.
|
||||
|
@@ -37,7 +37,7 @@ import (
|
||||
// - OrderedCollection: 'orderedItems' property will always be made into an array.
|
||||
// - OrderedCollectionPage: 'orderedItems' property will always be made into an array.
|
||||
// - Any Accountable type: 'attachment' property will always be made into an array.
|
||||
// - Any Statusable type: 'attachment' property will always be made into an array; 'content' and 'contentMap' will be normalized.
|
||||
// - Any Statusable type: 'attachment' property will always be made into an array; 'content', 'contentMap', and 'interactionPolicy' will be normalized.
|
||||
// - Any Activityable type: any 'object's set on an activity will be custom serialized as above.
|
||||
func Serialize(t vocab.Type) (m map[string]interface{}, e error) {
|
||||
switch tn := t.GetTypeName(); {
|
||||
@@ -153,6 +153,7 @@ func serializeStatusable(t vocab.Type, includeContext bool) (map[string]interfac
|
||||
|
||||
NormalizeOutgoingAttachmentProp(statusable, data)
|
||||
NormalizeOutgoingContentProp(statusable, data)
|
||||
NormalizeOutgoingInteractionPolicyProp(statusable, data)
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user