commit dc8cc7e364de3342c84c2329da6a0addfcbba273
parent 1cdc163276f3437645c0b2c01602e53fa2292c46
Author: tobi <31960611+tsmethurst@users.noreply.github.com>
Date: Fri, 27 May 2022 16:35:35 +0200
[chore] Add test for dereferencing Owncast service account (#613)
Diffstat:
5 files changed, 344 insertions(+), 0 deletions(-)
diff --git a/internal/federation/dereferencing/account_test.go b/internal/federation/dereferencing/account_test.go
@@ -51,6 +51,26 @@ func (suite *AccountTestSuite) TestDereferenceGroup() {
suite.Equal(ap.ActorGroup, dbGroup.ActorType)
}
+func (suite *AccountTestSuite) TestDereferenceService() {
+ fetchingAccount := suite.testAccounts["local_account_1"]
+
+ serviceURL := testrig.URLMustParse("https://owncast.example.org/federation/user/rgh")
+ service, err := suite.dereferencer.GetRemoteAccount(context.Background(), fetchingAccount.Username, serviceURL, false, false)
+ suite.NoError(err)
+ suite.NotNil(service)
+ suite.NotNil(service)
+
+ // service values should be set
+ suite.Equal("https://owncast.example.org/federation/user/rgh", service.URI)
+ suite.Equal("https://owncast.example.org/federation/user/rgh", service.URL)
+
+ // service should be in the database
+ dbService, err := suite.db.GetAccountByURI(context.Background(), service.URI)
+ suite.NoError(err)
+ suite.Equal(service.ID, dbService.ID)
+ suite.Equal(ap.ActorService, dbService.ActorType)
+}
+
func TestAccountTestSuite(t *testing.T) {
suite.Run(t, new(AccountTestSuite))
}
diff --git a/internal/federation/dereferencing/dereferencer_test.go b/internal/federation/dereferencing/dereferencer_test.go
@@ -46,6 +46,7 @@ type DereferencerStandardTestSuite struct {
testRemoteStatuses map[string]vocab.ActivityStreamsNote
testRemotePeople map[string]vocab.ActivityStreamsPerson
testRemoteGroups map[string]vocab.ActivityStreamsGroup
+ testRemoteServices map[string]vocab.ActivityStreamsService
testRemoteAttachments map[string]testrig.RemoteAttachmentFile
testAccounts map[string]*gtsmodel.Account
@@ -60,6 +61,7 @@ func (suite *DereferencerStandardTestSuite) SetupTest() {
suite.testRemoteStatuses = testrig.NewTestFediStatuses()
suite.testRemotePeople = testrig.NewTestFediPeople()
suite.testRemoteGroups = testrig.NewTestFediGroups()
+ suite.testRemoteServices = testrig.NewTestFediServices()
suite.testRemoteAttachments = testrig.NewTestFediAttachments("../../../testrig/media")
suite.db = testrig.NewTestDB()
@@ -126,6 +128,19 @@ func (suite *DereferencerStandardTestSuite) mockTransportController() transport.
responseType = "application/activity+json"
}
+ if service, ok := suite.testRemoteServices[req.URL.String()]; ok {
+ serviceI, err := streams.Serialize(service)
+ if err != nil {
+ panic(err)
+ }
+ serviceJson, err := json.Marshal(serviceI)
+ if err != nil {
+ panic(err)
+ }
+ responseBytes = serviceJson
+ responseType = "application/activity+json"
+ }
+
if attachment, ok := suite.testRemoteAttachments[req.URL.String()]; ok {
responseBytes = attachment.Data
responseType = attachment.ContentType
diff --git a/internal/typeutils/astointernal_test.go b/internal/typeutils/astointernal_test.go
@@ -157,6 +157,49 @@ func (suite *ASToInternalTestSuite) TestParseReplyWithMention() {
suite.Equal(gtsmodel.VisibilityUnlocked, status.Visibility)
}
+func (suite *ASToInternalTestSuite) TestParseOwncastService() {
+ m := make(map[string]interface{})
+ err := json.Unmarshal([]byte(owncastService), &m)
+ suite.NoError(err)
+
+ t, err := streams.ToType(context.Background(), m)
+ suite.NoError(err)
+
+ rep, ok := t.(ap.Accountable)
+ suite.True(ok)
+
+ acct, err := suite.typeconverter.ASRepresentationToAccount(context.Background(), rep, false)
+ suite.NoError(err)
+
+ suite.Equal("rgh", acct.Username)
+ suite.Equal("owncast.example.org", acct.Domain)
+ suite.Equal("https://owncast.example.org/logo/external", acct.AvatarRemoteURL)
+ suite.Equal("https://owncast.example.org/logo/external", acct.HeaderRemoteURL)
+ suite.Equal("Rob's Owncast Server", acct.DisplayName)
+ suite.Equal("linux audio stuff ", acct.Note)
+ suite.True(acct.Bot)
+ suite.False(acct.Locked)
+ suite.True(acct.Discoverable)
+ suite.Equal("https://owncast.example.org/federation/user/rgh", acct.URI)
+ suite.Equal("https://owncast.example.org/federation/user/rgh", acct.URL)
+ suite.Equal("https://owncast.example.org/federation/user/rgh/inbox", acct.InboxURI)
+ suite.Equal("https://owncast.example.org/federation/user/rgh/outbox", acct.OutboxURI)
+ suite.Equal("https://owncast.example.org/federation/user/rgh/followers", acct.FollowersURI)
+ suite.Equal("Service", acct.ActorType)
+ suite.Equal("https://owncast.example.org/federation/user/rgh#main-key", acct.PublicKeyURI)
+
+ acct.ID = "01G42D57DTCJQE8XT9KD4K88RK"
+
+ apiAcct, err := suite.typeconverter.AccountToAPIAccountPublic(context.Background(), acct)
+ suite.NoError(err)
+ suite.NotNil(apiAcct)
+
+ b, err := json.Marshal(apiAcct)
+ suite.NoError(err)
+
+ fmt.Printf("\n\n\n%s\n\n\n", string(b))
+}
+
func TestASToInternalTestSuite(t *testing.T) {
suite.Run(t, new(ASToInternalTestSuite))
}
diff --git a/internal/typeutils/converter_test.go b/internal/typeutils/converter_test.go
@@ -414,6 +414,57 @@ const (
}
}
`
+ owncastService = `
+ {
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "http://joinmastodon.org/ns",
+ "https://w3id.org/security/v1"
+ ],
+ "attachment": {
+ "name": "Stream",
+ "type": "PropertyValue",
+ "value": "<a href=\"https://owncast.example.org\" rel=\"me nofollow noopener noreferrer\" target=\"_blank\">https://owncast.example.org</a>"
+ },
+ "discoverable": true,
+ "followers": "https://owncast.example.org/federation/user/rgh/followers",
+ "icon": {
+ "type": "Image",
+ "url": "https://owncast.example.org/logo/external"
+ },
+ "id": "https://owncast.example.org/federation/user/rgh",
+ "image": {
+ "type": "Image",
+ "url": "https://owncast.example.org/logo/external"
+ },
+ "inbox": "https://owncast.example.org/federation/user/rgh/inbox",
+ "manuallyApprovesFollowers": false,
+ "name": "Rob's Owncast Server",
+ "outbox": "https://owncast.example.org/federation/user/rgh/outbox",
+ "preferredUsername": "rgh",
+ "publicKey": {
+ "id": "https://owncast.example.org/federation/user/rgh#main-key",
+ "owner": "https://owncast.example.org/federation/user/rgh",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAurN+lUNwcGV2poLNtaoT\naRtJzN6s4SDcBmIFk82lxhdMKC6/Nssm+hvDuxWGqL0+dHwSvrG11rA6irGuSzRk\niHjYyVwYe/p1CxqJxzUfZVJAWdsCFWy+HtDrTWs5sggj1MiL59uGxvkCep+OYBuG\nBI8CvSOMLrDp8soCg3EY+zSgpXtGMuRaaUukavsfuglApShB61ny7W8LG252iKC5\nmyO8L7l8TNa5BrIi/pRHLzvv9aWiCa8VKtvmqj+mClEhpkRhImSk5GPJXgouTTgl\ntT28NYYciSf9YYgZ0SNWHdLUCdkMF592j4+BbkPvdgzc70G4yyu2GcWnTzBuvF5X\nYwIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "published": "2022-05-22T18:44:57Z",
+ "summary": "linux audio stuff ",
+ "tag": [
+ {
+ "href": "https://directory.owncast.online/tags/owncast",
+ "name": "#owncast",
+ "type": "Hashtag"
+ },
+ {
+ "href": "https://directory.owncast.online/tags/streaming",
+ "name": "#streaming",
+ "type": "Hashtag"
+ }
+ ],
+ "type": "Service",
+ "url": "https://owncast.example.org/federation/user/rgh"
+ }
+`
)
type TypeUtilsTestSuite struct {
diff --git a/testrig/testmodels.go b/testrig/testmodels.go
@@ -1821,6 +1821,37 @@ func NewTestFediGroups() map[string]vocab.ActivityStreamsGroup {
}
}
+func NewTestFediServices() map[string]vocab.ActivityStreamsService {
+ newService1Priv, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ panic(err)
+ }
+ newService1Pub := &newService1Priv.PublicKey
+
+ return map[string]vocab.ActivityStreamsService{
+ "https://owncast.example.org/federation/user/rgh": newAPService(
+ URLMustParse("https://owncast.example.org/federation/user/rgh"),
+ nil,
+ URLMustParse("https://owncast.example.org/federation/user/rgh/followers"),
+ URLMustParse("https://owncast.example.org/federation/user/rgh/inbox"),
+ URLMustParse("https://owncast.example.org/federation/user/rgh/outbox"),
+ nil,
+ "rgh",
+ "linux audio stuff ",
+ "",
+ URLMustParse("https://owncast.example.org/federation/user/rgh"),
+ true,
+ URLMustParse("https://owncast.example.org/federation/user/rgh#main-key"),
+ newService1Pub,
+ nil,
+ "image/jpeg",
+ nil,
+ "image/png",
+ false,
+ ),
+ }
+}
+
// RemoteAttachmentFile mimics a remote (federated) attachment
type RemoteAttachmentFile struct {
Data []byte
@@ -2459,6 +2490,190 @@ func newAPGroup(
return group
}
+func newAPService(
+ 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.ActivityStreamsService {
+ service := streams.NewActivityStreamsService()
+
+ // id should be the activitypub URI of this group
+ // something like https://example.org/users/example_group
+ idProp := streams.NewJSONLDIdProperty()
+ idProp.SetIRI(profileIDURI)
+ service.SetJSONLDId(idProp)
+
+ // following
+ // The URI for retrieving a list of accounts this group is following
+ followingProp := streams.NewActivityStreamsFollowingProperty()
+ followingProp.SetIRI(followingURI)
+ service.SetActivityStreamsFollowing(followingProp)
+
+ // followers
+ // The URI for retrieving a list of this user's followers
+ followersProp := streams.NewActivityStreamsFollowersProperty()
+ followersProp.SetIRI(followersURI)
+ service.SetActivityStreamsFollowers(followersProp)
+
+ // inbox
+ // the activitypub inbox of this user for accepting messages
+ inboxProp := streams.NewActivityStreamsInboxProperty()
+ inboxProp.SetIRI(inboxURI)
+ service.SetActivityStreamsInbox(inboxProp)
+
+ // outbox
+ // the activitypub outbox of this user for serving messages
+ outboxProp := streams.NewActivityStreamsOutboxProperty()
+ outboxProp.SetIRI(outboxURI)
+ service.SetActivityStreamsOutbox(outboxProp)
+
+ // featured posts
+ // Pinned posts.
+ featuredProp := streams.NewTootFeaturedProperty()
+ featuredProp.SetIRI(featuredURI)
+ service.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)
+ service.SetActivityStreamsPreferredUsername(preferredUsernameProp)
+
+ // name
+ // Used as profile display name.
+ nameProp := streams.NewActivityStreamsNameProperty()
+ if displayName != "" {
+ nameProp.AppendXMLSchemaString(displayName)
+ } else {
+ nameProp.AppendXMLSchemaString(username)
+ }
+ service.SetActivityStreamsName(nameProp)
+
+ // summary
+ // Used as profile bio.
+ if note != "" {
+ summaryProp := streams.NewActivityStreamsSummaryProperty()
+ summaryProp.AppendXMLSchemaString(note)
+ service.SetActivityStreamsSummary(summaryProp)
+ }
+
+ // url
+ // Used as profile link.
+ urlProp := streams.NewActivityStreamsUrlProperty()
+ urlProp.AppendIRI(profileURL)
+ service.SetActivityStreamsUrl(urlProp)
+
+ // manuallyApprovesFollowers
+ manuallyApprovesFollowersProp := streams.NewActivityStreamsManuallyApprovesFollowersProperty()
+ manuallyApprovesFollowersProp.Set(manuallyApprovesFollowers)
+ service.SetActivityStreamsManuallyApprovesFollowers(manuallyApprovesFollowersProp)
+
+ // discoverable
+ // Will be shown in the profile directory.
+ discoverableProp := streams.NewTootDiscoverableProperty()
+ discoverableProp.Set(discoverable)
+ service.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
+ service.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)
+ service.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)
+ service.SetActivityStreamsImage(headerProperty)
+
+ return service
+}
+
func newAPMention(uri *url.URL, namestring string) vocab.ActivityStreamsMention {
mention := streams.NewActivityStreamsMention()