allow dereferencing of groups (#256)

This commit is contained in:
tobi 2021-09-30 12:27:42 +02:00 committed by GitHub
parent 231075f28d
commit 0cd2bd2960
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 310 additions and 14 deletions

View file

@ -165,18 +165,30 @@ func (d *deref) dereferenceAccountable(ctx context.Context, username string, rem
} }
switch t.GetTypeName() { switch t.GetTypeName() {
case ap.ActorPerson:
p, ok := t.(vocab.ActivityStreamsPerson)
if !ok {
return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams person")
}
return p, nil
case ap.ActorApplication: case ap.ActorApplication:
p, ok := t.(vocab.ActivityStreamsApplication) p, ok := t.(vocab.ActivityStreamsApplication)
if !ok { if !ok {
return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams application") return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams application")
} }
return p, nil return p, nil
case ap.ActorGroup:
p, ok := t.(vocab.ActivityStreamsGroup)
if !ok {
return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams group")
}
return p, nil
case ap.ActorOrganization:
p, ok := t.(vocab.ActivityStreamsOrganization)
if !ok {
return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams organization")
}
return p, nil
case ap.ActorPerson:
p, ok := t.(vocab.ActivityStreamsPerson)
if !ok {
return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams person")
}
return p, nil
case ap.ActorService: case ap.ActorService:
p, ok := t.(vocab.ActivityStreamsService) p, ok := t.(vocab.ActivityStreamsService)
if !ok { if !ok {

View file

@ -0,0 +1,57 @@
/*
GoToSocial
Copyright (C) 2021 GoToSocial Authors admin@gotosocial.org
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package dereferencing_test
import (
"context"
"testing"
"github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/testrig"
)
type AccountTestSuite struct {
DereferencerStandardTestSuite
}
func (suite *AccountTestSuite) TestDereferenceGroup() {
fetchingAccount := suite.testAccounts["local_account_1"]
groupURL := testrig.URLMustParse("https://unknown-instance.com/groups/some_group")
group, new, err := suite.dereferencer.GetRemoteAccount(context.Background(), fetchingAccount.Username, groupURL, false)
suite.NoError(err)
suite.NotNil(group)
suite.NotNil(group)
suite.True(new)
// group values should be set
suite.Equal("https://unknown-instance.com/groups/some_group", group.URI)
suite.Equal("https://unknown-instance.com/@some_group", group.URL)
// group should be in the database
dbGroup, err := suite.db.GetAccountByURI(context.Background(), group.URI)
suite.NoError(err)
suite.Equal(group.ID, dbGroup.ID)
suite.Equal(ap.ActorGroup, dbGroup.ActorType)
}
func TestAccountTestSuite(t *testing.T) {
suite.Run(t, new(AccountTestSuite))
}

View file

@ -45,7 +45,8 @@ type DereferencerStandardTestSuite struct {
storage *kv.KVStore storage *kv.KVStore
testRemoteStatuses map[string]vocab.ActivityStreamsNote testRemoteStatuses map[string]vocab.ActivityStreamsNote
testRemoteAccounts map[string]vocab.ActivityStreamsPerson testRemotePeople map[string]vocab.ActivityStreamsPerson
testRemoteGroups map[string]vocab.ActivityStreamsGroup
testRemoteAttachments map[string]testrig.RemoteAttachmentFile testRemoteAttachments map[string]testrig.RemoteAttachmentFile
testAccounts map[string]*gtsmodel.Account testAccounts map[string]*gtsmodel.Account
@ -55,7 +56,8 @@ type DereferencerStandardTestSuite struct {
func (suite *DereferencerStandardTestSuite) SetupSuite() { func (suite *DereferencerStandardTestSuite) SetupSuite() {
suite.testAccounts = testrig.NewTestAccounts() suite.testAccounts = testrig.NewTestAccounts()
suite.testRemoteStatuses = testrig.NewTestFediStatuses() suite.testRemoteStatuses = testrig.NewTestFediStatuses()
suite.testRemoteAccounts = testrig.NewTestFediPeople() suite.testRemotePeople = testrig.NewTestFediPeople()
suite.testRemoteGroups = testrig.NewTestFediGroups()
suite.testRemoteAttachments = testrig.NewTestFediAttachments("../../../testrig/media") suite.testRemoteAttachments = testrig.NewTestFediAttachments("../../../testrig/media")
} }
@ -89,8 +91,7 @@ func (suite *DereferencerStandardTestSuite) mockTransportController() transport.
responseType := "" responseType := ""
responseLength := 0 responseLength := 0
note, ok := suite.testRemoteStatuses[req.URL.String()] if note, ok := suite.testRemoteStatuses[req.URL.String()]; ok {
if ok {
// the request is for a note that we have stored // the request is for a note that we have stored
noteI, err := streams.Serialize(note) noteI, err := streams.Serialize(note)
if err != nil { if err != nil {
@ -104,8 +105,7 @@ func (suite *DereferencerStandardTestSuite) mockTransportController() transport.
responseType = "application/activity+json" responseType = "application/activity+json"
} }
person, ok := suite.testRemoteAccounts[req.URL.String()] if person, ok := suite.testRemotePeople[req.URL.String()]; ok {
if ok {
// the request is for a person that we have stored // the request is for a person that we have stored
personI, err := streams.Serialize(person) personI, err := streams.Serialize(person)
if err != nil { if err != nil {
@ -119,8 +119,21 @@ func (suite *DereferencerStandardTestSuite) mockTransportController() transport.
responseType = "application/activity+json" responseType = "application/activity+json"
} }
attachment, ok := suite.testRemoteAttachments[req.URL.String()] if group, ok := suite.testRemoteGroups[req.URL.String()]; ok {
if ok { // the request is for a person that we have stored
groupI, err := streams.Serialize(group)
if err != nil {
panic(err)
}
groupJson, err := json.Marshal(groupI)
if err != nil {
panic(err)
}
responseBytes = groupJson
responseType = "application/activity+json"
}
if attachment, ok := suite.testRemoteAttachments[req.URL.String()]; ok {
responseBytes = attachment.Data responseBytes = attachment.Data
responseType = attachment.ContentType responseType = attachment.ContentType
} }

View file

@ -1327,6 +1327,37 @@ func NewTestFediPeople() map[string]vocab.ActivityStreamsPerson {
} }
} }
func NewTestFediGroups() map[string]vocab.ActivityStreamsGroup {
newGroup1Priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
newGroup1Pub := &newGroup1Priv.PublicKey
return map[string]vocab.ActivityStreamsGroup{
"https://unknown-instance.com/groups/some_group": newGroup(
URLMustParse("https://unknown-instance.com/groups/some_group"),
URLMustParse("https://unknown-instance.com/groups/some_group/following"),
URLMustParse("https://unknown-instance.com/groups/some_group/followers"),
URLMustParse("https://unknown-instance.com/groups/some_group/inbox"),
URLMustParse("https://unknown-instance.com/groups/some_group/outbox"),
URLMustParse("https://unknown-instance.com/groups/some_group/collections/featured"),
"some_group",
"This is a group about... something?",
"",
URLMustParse("https://unknown-instance.com/@some_group"),
true,
URLMustParse("https://unknown-instance.com/groups/some_group#main-key"),
newGroup1Pub,
nil,
"image/jpeg",
nil,
"image/png",
false,
),
}
}
// RemoteAttachmentFile mimics a remote (federated) attachment // RemoteAttachmentFile mimics a remote (federated) attachment
type RemoteAttachmentFile struct { type RemoteAttachmentFile struct {
Data []byte Data []byte
@ -1688,6 +1719,189 @@ func newPerson(
return person return person
} }
func newGroup(
profileIDURI *url.URL,
followingURI *url.URL,
followersURI *url.URL,
inboxURI *url.URL,
outboxURI *url.URL,
featuredURI *url.URL,
username string,
displayName string,
note string,
profileURL *url.URL,
discoverable bool,
publicKeyURI *url.URL,
pkey *rsa.PublicKey,
avatarURL *url.URL,
avatarContentType string,
headerURL *url.URL,
headerContentType string,
manuallyApprovesFollowers bool) vocab.ActivityStreamsGroup {
group := streams.NewActivityStreamsGroup()
// id should be the activitypub URI of this group
// something like https://example.org/users/example_group
idProp := streams.NewJSONLDIdProperty()
idProp.SetIRI(profileIDURI)
group.SetJSONLDId(idProp)
// following
// The URI for retrieving a list of accounts this group is following
followingProp := streams.NewActivityStreamsFollowingProperty()
followingProp.SetIRI(followingURI)
group.SetActivityStreamsFollowing(followingProp)
// followers
// The URI for retrieving a list of this user's followers
followersProp := streams.NewActivityStreamsFollowersProperty()
followersProp.SetIRI(followersURI)
group.SetActivityStreamsFollowers(followersProp)
// inbox
// the activitypub inbox of this user for accepting messages
inboxProp := streams.NewActivityStreamsInboxProperty()
inboxProp.SetIRI(inboxURI)
group.SetActivityStreamsInbox(inboxProp)
// outbox
// the activitypub outbox of this user for serving messages
outboxProp := streams.NewActivityStreamsOutboxProperty()
outboxProp.SetIRI(outboxURI)
group.SetActivityStreamsOutbox(outboxProp)
// featured posts
// Pinned posts.
featuredProp := streams.NewTootFeaturedProperty()
featuredProp.SetIRI(featuredURI)
group.SetTootFeatured(featuredProp)
// featuredTags
// NOT IMPLEMENTED
// preferredUsername
// Used for Webfinger lookup. Must be unique on the domain, and must correspond to a Webfinger acct: URI.
preferredUsernameProp := streams.NewActivityStreamsPreferredUsernameProperty()
preferredUsernameProp.SetXMLSchemaString(username)
group.SetActivityStreamsPreferredUsername(preferredUsernameProp)
// name
// Used as profile display name.
nameProp := streams.NewActivityStreamsNameProperty()
if displayName != "" {
nameProp.AppendXMLSchemaString(displayName)
} else {
nameProp.AppendXMLSchemaString(username)
}
group.SetActivityStreamsName(nameProp)
// summary
// Used as profile bio.
if note != "" {
summaryProp := streams.NewActivityStreamsSummaryProperty()
summaryProp.AppendXMLSchemaString(note)
group.SetActivityStreamsSummary(summaryProp)
}
// url
// Used as profile link.
urlProp := streams.NewActivityStreamsUrlProperty()
urlProp.AppendIRI(profileURL)
group.SetActivityStreamsUrl(urlProp)
// manuallyApprovesFollowers
manuallyApprovesFollowersProp := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
manuallyApprovesFollowersProp.Set(manuallyApprovesFollowers)
group.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp)
// discoverable
// Will be shown in the profile directory.
discoverableProp := streams.NewTootDiscoverableProperty()
discoverableProp.Set(discoverable)
group.SetTootDiscoverable(discoverableProp)
// devices
// NOT IMPLEMENTED, probably won't implement
// 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
// publicKey
// Required for signatures.
publicKeyProp := streams.NewW3IDSecurityV1PublicKeyProperty()
// create the public key
publicKey := streams.NewW3IDSecurityV1PublicKey()
// set ID for the public key
publicKeyIDProp := streams.NewJSONLDIdProperty()
publicKeyIDProp.SetIRI(publicKeyURI)
publicKey.SetJSONLDId(publicKeyIDProp)
// set owner for the public key
publicKeyOwnerProp := streams.NewW3IDSecurityV1OwnerProperty()
publicKeyOwnerProp.SetIRI(profileIDURI)
publicKey.SetW3IDSecurityV1Owner(publicKeyOwnerProp)
// set the pem key itself
encodedPublicKey, err := x509.MarshalPKIXPublicKey(pkey)
if err != nil {
panic(err)
}
publicKeyBytes := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: encodedPublicKey,
})
publicKeyPEMProp := streams.NewW3IDSecurityV1PublicKeyPemProperty()
publicKeyPEMProp.Set(string(publicKeyBytes))
publicKey.SetW3IDSecurityV1PublicKeyPem(publicKeyPEMProp)
// append the public key to the public key property
publicKeyProp.AppendW3IDSecurityV1PublicKey(publicKey)
// set the public key property on the Person
group.SetW3IDSecurityV1PublicKey(publicKeyProp)
// tag
// TODO: Any tags used in the summary of this profile
// attachment
// Used for profile fields.
// TODO: The PropertyValue type has to be added: https://schema.org/PropertyValue
// endpoints
// NOT IMPLEMENTED -- this is for shared inbox which we don't use
// icon
// Used as profile avatar.
iconProperty := streams.NewActivityStreamsIconProperty()
iconImage := streams.NewActivityStreamsImage()
mediaType := streams.NewActivityStreamsMediaTypeProperty()
mediaType.Set(avatarContentType)
iconImage.SetActivityStreamsMediaType(mediaType)
avatarURLProperty := streams.NewActivityStreamsUrlProperty()
avatarURLProperty.AppendIRI(avatarURL)
iconImage.SetActivityStreamsUrl(avatarURLProperty)
iconProperty.AppendActivityStreamsImage(iconImage)
group.SetActivityStreamsIcon(iconProperty)
// image
// Used as profile header.
headerProperty := streams.NewActivityStreamsImageProperty()
headerImage := streams.NewActivityStreamsImage()
headerMediaType := streams.NewActivityStreamsMediaTypeProperty()
mediaType.Set(headerContentType)
headerImage.SetActivityStreamsMediaType(headerMediaType)
headerURLProperty := streams.NewActivityStreamsUrlProperty()
headerURLProperty.AppendIRI(headerURL)
headerImage.SetActivityStreamsUrl(headerURLProperty)
headerProperty.AppendActivityStreamsImage(headerImage)
group.SetActivityStreamsImage(headerProperty)
return group
}
func newMention(uri *url.URL, namestring string) vocab.ActivityStreamsMention { func newMention(uri *url.URL, namestring string) vocab.ActivityStreamsMention {
mention := streams.NewActivityStreamsMention() mention := streams.NewActivityStreamsMention()