allow dereferencing of groups (#256)
This commit is contained in:
parent
231075f28d
commit
0cd2bd2960
|
@ -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 {
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue