mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 15:52:25 -05:00 
			
		
		
		
	allow dereferencing of groups (#256)
This commit is contained in:
		
					parent
					
						
							
								231075f28d
							
						
					
				
			
			
				commit
				
					
						0cd2bd2960
					
				
			
		
					 4 changed files with 310 additions and 14 deletions
				
			
		|  | @ -165,18 +165,30 @@ func (d *deref) dereferenceAccountable(ctx context.Context, username string, rem | |||
| 	} | ||||
| 
 | ||||
| 	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: | ||||
| 		p, ok := t.(vocab.ActivityStreamsApplication) | ||||
| 		if !ok { | ||||
| 			return nil, errors.New("DereferenceAccountable: error resolving type as activitystreams application") | ||||
| 		} | ||||
| 		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: | ||||
| 		p, ok := t.(vocab.ActivityStreamsService) | ||||
| 		if !ok { | ||||
|  |  | |||
							
								
								
									
										57
									
								
								internal/federation/dereferencing/account_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								internal/federation/dereferencing/account_test.go
									
										
									
									
									
										Normal 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)) | ||||
| } | ||||
|  | @ -45,7 +45,8 @@ type DereferencerStandardTestSuite struct { | |||
| 	storage *kv.KVStore | ||||
| 
 | ||||
| 	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 | ||||
| 	testAccounts          map[string]*gtsmodel.Account | ||||
| 
 | ||||
|  | @ -55,7 +56,8 @@ type DereferencerStandardTestSuite struct { | |||
| func (suite *DereferencerStandardTestSuite) SetupSuite() { | ||||
| 	suite.testAccounts = testrig.NewTestAccounts() | ||||
| 	suite.testRemoteStatuses = testrig.NewTestFediStatuses() | ||||
| 	suite.testRemoteAccounts = testrig.NewTestFediPeople() | ||||
| 	suite.testRemotePeople = testrig.NewTestFediPeople() | ||||
| 	suite.testRemoteGroups = testrig.NewTestFediGroups() | ||||
| 	suite.testRemoteAttachments = testrig.NewTestFediAttachments("../../../testrig/media") | ||||
| } | ||||
| 
 | ||||
|  | @ -89,8 +91,7 @@ func (suite *DereferencerStandardTestSuite) mockTransportController() transport. | |||
| 		responseType := "" | ||||
| 		responseLength := 0 | ||||
| 
 | ||||
| 		note, ok := suite.testRemoteStatuses[req.URL.String()] | ||||
| 		if ok { | ||||
| 		if note, ok := suite.testRemoteStatuses[req.URL.String()]; ok { | ||||
| 			// the request is for a note that we have stored | ||||
| 			noteI, err := streams.Serialize(note) | ||||
| 			if err != nil { | ||||
|  | @ -104,8 +105,7 @@ func (suite *DereferencerStandardTestSuite) mockTransportController() transport. | |||
| 			responseType = "application/activity+json" | ||||
| 		} | ||||
| 
 | ||||
| 		person, ok := suite.testRemoteAccounts[req.URL.String()] | ||||
| 		if ok { | ||||
| 		if person, ok := suite.testRemotePeople[req.URL.String()]; ok { | ||||
| 			// the request is for a person that we have stored | ||||
| 			personI, err := streams.Serialize(person) | ||||
| 			if err != nil { | ||||
|  | @ -119,8 +119,21 @@ func (suite *DereferencerStandardTestSuite) mockTransportController() transport. | |||
| 			responseType = "application/activity+json" | ||||
| 		} | ||||
| 
 | ||||
| 		attachment, ok := suite.testRemoteAttachments[req.URL.String()] | ||||
| 		if ok { | ||||
| 		if group, ok := suite.testRemoteGroups[req.URL.String()]; 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 | ||||
| 			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 | ||||
| type RemoteAttachmentFile struct { | ||||
| 	Data        []byte | ||||
|  | @ -1688,6 +1719,189 @@ func newPerson( | |||
| 	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 { | ||||
| 	mention := streams.NewActivityStreamsMention() | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue