From f8b24eef88aad4ceffc373d4d78f3d15bf19b32f Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sat, 4 Aug 2018 13:51:00 +0200 Subject: [PATCH] Use OrderedCollection by default for collections. The 'liked', 'likes', 'followers', and 'following' collections now default to the 'OrderedCollection' type instead of throwing an error if the client application does not supply an IRI, 'Collection', or 'OrderedCollection' type on the actor or object. --- CHANGELOG | 4 + pub/fed.go | 16 ++- pub/fed_test.go | 331 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 347 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2c00cbd..40a35a7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,10 @@ master (Release Candidate for v0.2.0) * Begin FederateAPI unofficial implementation report. +* Use 'OrderedCollection' as the default type for 'likes', 'liked', + 'following', and 'followers' properties if the actor or object does not + have an IRI, 'Collection', or 'OrderedCollection' set for these + properties. * Examine the IRI of 'objects' when applying the Origin Check policy for Update and Delete activities. * If a federated Activity was already received, do not execute its side effects diff --git a/pub/fed.go b/pub/fed.go index 215c592..9ae7982 100644 --- a/pub/fed.go +++ b/pub/fed.go @@ -803,7 +803,9 @@ func (f *federator) handleClientLike(ctx context.Context, deliverable *bool) fun *loc = actor.GetLikedOrderedCollection() return false, nil } - return false, fmt.Errorf("cannot determine type of actor liked") + *loc = &vocab.OrderedCollection{} + actor.SetLikedOrderedCollection(*loc) + return false, nil } if err := f.addAllObjectsToActorCollection(ctx, getter, s.Raw(), true); err != nil { return err @@ -990,7 +992,9 @@ func (f *federator) handleFollow(c context.Context, inboxURL *url.URL) func(s *s *loc = object.GetFollowersOrderedCollection() return false, nil } - return false, fmt.Errorf("cannot determine type of object followers") + *loc = &vocab.OrderedCollection{} + object.SetFollowersOrderedCollection(*loc) + return false, nil } var err error if ownsAny, err = f.addAllActorsToObjectCollection(c, getter, raw, true); err != nil { @@ -1046,7 +1050,9 @@ func (f *federator) handleAccept(c context.Context) func(s *streams.Accept) erro *loc = actor.GetFollowingOrderedCollection() return false, nil } - return false, fmt.Errorf("cannot determine type of actor following") + *loc = &vocab.OrderedCollection{} + actor.SetFollowingOrderedCollection(*loc) + return false, nil } if err := f.addAllObjectsToActorCollection(c, getter, follow, true); err != nil { return err @@ -1227,7 +1233,9 @@ func (f *federator) handleLike(c context.Context) func(s *streams.Like) error { *loc = object.GetLikesOrderedCollection() return false, nil } - return false, fmt.Errorf("cannot determine type of object likes") + *loc = &vocab.OrderedCollection{} + object.SetLikesOrderedCollection(*loc) + return false, nil } if _, err := f.addActivityToObjectCollection(c, getter, s.Raw(), true); err != nil { return err diff --git a/pub/fed_test.go b/pub/fed_test.go index 937162c..7b6ff62 100644 --- a/pub/fed_test.go +++ b/pub/fed_test.go @@ -2750,6 +2750,146 @@ func TestPostInbox_Follow_AutoAccept(t *testing.T) { } } +func TestPostInbox_Follow_AutoAcceptDefaultFollowersOrderedCollection(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePubberPostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testFollow)))) + gotOnFollow := 0 + fedApp.onFollow = func(c context.Context, s *streams.Follow) FollowResponse { + gotOnFollow++ + return AutomaticAccept + } + gotNewSigner := 0 + fedApp.newSigner = func() (httpsig.Signer, error) { + gotNewSigner++ + s, _, err := httpsig.NewSigner([]httpsig.Algorithm{httpsig.RSA_SHA256}, nil, httpsig.Signature) + if err != nil { + t.Fatal(err) + } + return s, err + } + gotPrivateKey := 0 + var gotPrivateKeyIRI *url.URL + fedApp.privateKey = func(boxIRI *url.URL) (crypto.PrivateKey, string, error) { + gotPrivateKey++ + gotPrivateKeyIRI = boxIRI + return testPrivateKey, testPublicKeyId, nil + } + fedCb.follow = func(c context.Context, s *streams.Follow) error { + return nil + } + gotHttpDo := 0 + var httpActorRequest *http.Request + var httpDeliveryRequest *http.Request + httpClient.do = func(req *http.Request) (*http.Response, error) { + gotHttpDo++ + if gotHttpDo == 1 { + httpActorRequest = req + actorResp := &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewBuffer(sallyActorJSON)), + } + return actorResp, nil + } else if gotHttpDo == 2 { + httpDeliveryRequest = req + okResp := &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewBuffer([]byte{})), + } + return okResp, nil + } + return nil, nil + } + gotDoDelivery := 0 + var doDeliveryURL *url.URL + var bytesToSend []byte + d.do = func(b []byte, u *url.URL, toDo func(b []byte, u *url.URL) error) { + gotDoDelivery++ + doDeliveryURL = u + bytesToSend = b + if err := toDo(b, u); err != nil { + t.Fatalf("Unexpected error in MockDeliverer.Do: %s", err) + } + } + gotOwns := 0 + var ownsIRI *url.URL + app.MockFederateApp.owns = func(c context.Context, id *url.URL) bool { + gotOwns++ + ownsIRI = id + return true + } + gotGet := 0 + var getIRI *url.URL + app.MockFederateApp.get = func(c context.Context, id *url.URL, rw RWType) (PubObject, error) { + if rw != ReadWrite { + t.Fatalf("expected RWType of %d, got %d", ReadWrite, rw) + } + gotGet++ + getIRI = id + samActor := &vocab.Person{} + samActor.SetInboxAnyURI(samIRIInbox) + samActor.SetId(samIRI) + return samActor, nil + } + gotSet := 0 + var setObject PubObject + app.MockFederateApp.set = func(c context.Context, o PubObject) error { + gotSet++ + if gotSet == 1 { + setObject = o + } + return nil + } + expected := &vocab.Accept{} + expected.AppendObject(testFollow) + expected.AppendToIRI(sallyIRI) + expectedFollowers := &vocab.OrderedCollection{} + expectedFollowers.AppendOrderedItemsIRI(sallyIRI) + expectedActor := &vocab.Person{} + expectedActor.SetInboxAnyURI(samIRIInbox) + expectedActor.SetId(samIRI) + expectedActor.SetFollowersOrderedCollection(expectedFollowers) + handled, err := p.PostInbox(context.Background(), resp, req) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if gotOnFollow != 1 { + t.Fatalf("expected %d, got %d", 1, gotOnFollow) + } else if gotNewSigner != 1 { + t.Fatalf("expected %d, got %d", 1, gotNewSigner) + } else if gotPrivateKey != 1 { + t.Fatalf("expected %d, got %d", 1, gotPrivateKey) + } else if s := gotPrivateKeyIRI.String(); s != testInboxURI { + t.Fatalf("expected %s, got %s", testInboxURI, s) + } else if gotHttpDo != 2 { + t.Fatalf("expected %d, got %d", 2, gotHttpDo) + } else if s := httpActorRequest.URL.String(); s != sallyIRIString { + t.Fatalf("expected %s, got %s", sallyIRIString, s) + } else if s := httpDeliveryRequest.URL.String(); s != sallyIRIInboxString { + t.Fatalf("expected %s, got %s", sallyIRIInboxString, s) + } else if gotDoDelivery != 1 { + t.Fatalf("expected %d, got %d", 1, gotDoDelivery) + } else if doDeliveryURL.String() != sallyIRIInboxString { + t.Fatalf("expected %s, got %s", sallyIRIInboxString, doDeliveryURL.String()) + } else if err := VocabEquals(bytes.NewBuffer(bytesToSend), expected); err != nil { + t.Fatal(err) + } else if gotOwns != 1 { + t.Fatalf("expected %d, got %d", 1, gotOwns) + } else if ownsIRI.String() != samIRIString { + t.Fatalf("expected %s, got %s", samIRIString, ownsIRI.String()) + } else if gotGet != 1 { + t.Fatalf("expected %d, got %d", 1, gotGet) + } else if getIRI.String() != samIRIString { + t.Fatalf("expected %s, got %s", samIRIString, getIRI.String()) + } else if gotSet != 2 { + t.Fatalf("expected %d, got %d", 2, gotSet) + } else if err := PubObjectEquals(setObject, expectedActor); err != nil { + t.Fatal(err) + } +} + func TestPostInbox_Follow_DoesNotAddForAutoAcceptIfAlreadyPresent(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePubberPostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) @@ -3153,6 +3293,69 @@ func TestPostInbox_Accept_AcceptFollowAddsToFollowersIfOwned(t *testing.T) { } } +func TestPostInbox_Accept_AcceptFollowAddsToDefaultFollowersOrderedCollection(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePubberPostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testAcceptFollow)))) + gotOwns := 0 + var ownsIRI *url.URL + app.MockFederateApp.owns = func(c context.Context, id *url.URL) bool { + gotOwns++ + ownsIRI = id + return true + } + gotGet := 0 + var getIRI *url.URL + app.MockFederateApp.get = func(c context.Context, id *url.URL, rw RWType) (PubObject, error) { + if rw != ReadWrite { + t.Fatalf("expected RWType of %d, got %d", ReadWrite, rw) + } + gotGet++ + getIRI = id + sallyActor := &vocab.Person{} + sallyActor.SetInboxAnyURI(sallyIRIInbox) + sallyActor.SetId(sallyIRI) + return sallyActor, nil + } + gotSet := 0 + var setObject PubObject + app.MockFederateApp.set = func(c context.Context, o PubObject) error { + gotSet++ + if gotSet == 1 { + setObject = o + } + return nil + } + fedCb.accept = func(c context.Context, s *streams.Accept) error { + return nil + } + expectedFollowing := &vocab.OrderedCollection{} + expectedFollowing.AppendOrderedItemsIRI(samIRI) + expectedActor := &vocab.Person{} + expectedActor.SetInboxAnyURI(sallyIRIInbox) + expectedActor.SetId(sallyIRI) + expectedActor.SetFollowingOrderedCollection(expectedFollowing) + handled, err := p.PostInbox(context.Background(), resp, req) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if gotOwns != 1 { + t.Fatalf("expected %d, got %d", 1, gotOwns) + } else if ownsIRI.String() != sallyIRIString { + t.Fatalf("expected %s, got %s", sallyIRIString, ownsIRI.String()) + } else if gotGet != 1 { + t.Fatalf("expected %d, got %d", 1, gotGet) + } else if getIRI.String() != sallyIRIString { + t.Fatalf("expected %s, got %s", sallyIRIString, getIRI.String()) + } else if gotSet != 2 { + t.Fatalf("expected %d, got %d", 2, gotSet) + } else if err := PubObjectEquals(setObject, expectedActor); err != nil { + t.Fatal(err) + } +} + func TestPostInbox_Accept_AcceptFollowDoesNotAddIfAlreadyInCollection(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePubberPostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) @@ -3823,6 +4026,71 @@ func TestPostInbox_Like_AddsToLikeCollection(t *testing.T) { } } +func TestPostInbox_Like_AddsToDefaultOrderedCollection(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePubberPostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testLikeNote)))) + gotOwns := 0 + var gotOwnsId *url.URL + app.MockFederateApp.owns = func(c context.Context, id *url.URL) bool { + gotOwns++ + gotOwnsId = id + return true + } + gotGet := 0 + var gotGetId *url.URL + app.MockFederateApp.get = func(c context.Context, id *url.URL, rw RWType) (PubObject, error) { + if rw != ReadWrite { + t.Fatalf("expected RWType of %d, got %d", ReadWrite, rw) + } + gotGet++ + gotGetId = id + v := &vocab.Note{} + v.SetId(noteIRI) + v.AppendNameString(noteName) + v.AppendContentString("This is a simple note") + return v, nil + } + gotSet := 0 + var gotSetObject PubObject + app.MockFederateApp.set = func(c context.Context, target PubObject) error { + gotSet++ + if gotSet == 1 { + gotSetObject = target + } + return nil + } + fedCb.like = func(c context.Context, s *streams.Like) error { + return nil + } + handled, err := p.PostInbox(context.Background(), resp, req) + expected := &vocab.OrderedCollection{} + expected.AppendOrderedItemsIRI(noteActivityIRI) + expectedNote := &vocab.Note{} + expectedNote.SetId(noteIRI) + expectedNote.AppendNameString(noteName) + expectedNote.AppendContentString("This is a simple note") + expectedNote.SetLikesOrderedCollection(expected) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if gotOwns != 1 { + t.Fatalf("expected %d, got %d", 1, gotOwns) + } else if gotOwnsId.String() != noteURIString { + t.Fatalf("expected %s, got %s", noteURIString, gotOwnsId.String()) + } else if gotGet != 1 { + t.Fatalf("expected %d, got %d", 1, gotGet) + } else if gotGetId.String() != noteURIString { + t.Fatalf("expected %s, got %s", noteURIString, gotGetId.String()) + } else if gotSet != 2 { + t.Fatalf("expected %d, got %d", 2, gotSet) + } else if err := PubObjectEquals(gotSetObject, expectedNote); err != nil { + t.Fatalf("unexpected callback object: %s", err) + } +} + func TestPostInbox_Like_DoesNotAddLikeToCollectionIfAlreadyPresent(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePubberPostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) @@ -5812,6 +6080,69 @@ func TestPostOutbox_Like_AddsToLikedCollection(t *testing.T) { } } +func TestPostOutbox_Like_AddsToDefaultOrderedCollection(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePubberPostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := Sign(ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testLikeNote))))) + gotOwns := 0 + var gotOwnsIri *url.URL + app.MockFederateApp.owns = func(c context.Context, iri *url.URL) bool { + gotOwns++ + gotOwnsIri = iri + return true + } + gotGet := 0 + var gotGetIri *url.URL + app.MockFederateApp.get = func(c context.Context, iri *url.URL, rw RWType) (PubObject, error) { + if rw != ReadWrite { + t.Fatalf("expected RWType of %d, got %d", ReadWrite, rw) + } + gotGet++ + gotGetIri = iri + v := &vocab.Person{} + v.AppendNameString("Sally") + v.SetId(sallyIRI) + return v, nil + } + gotSet := 0 + var gotSetObj PubObject + app.MockFederateApp.set = func(c context.Context, o PubObject) error { + gotSet++ + if gotSet == 1 { + gotSetObj = o + } + return nil + } + socialCb.like = func(c context.Context, s *streams.Like) error { + return nil + } + handled, err := p.PostOutbox(context.Background(), resp, req) + expectedLikes := &vocab.OrderedCollection{} + expectedLikes.AppendOrderedItemsIRI(noteIRI) + expectedActor := &vocab.Person{} + expectedActor.AppendNameString("Sally") + expectedActor.SetId(sallyIRI) + expectedActor.SetLikedOrderedCollection(expectedLikes) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if gotOwns != 1 { + t.Fatalf("expected %d, got %d", 1, gotOwns) + } else if gotOwnsString := gotOwnsIri.String(); gotOwnsString != sallyIRIString { + t.Fatalf("expected %s, got %s", noteURIString, sallyIRIString) + } else if gotGet != 1 { + t.Fatalf("expected %d, got %d", 1, gotGet) + } else if gotGetString := gotGetIri.String(); gotGetString != sallyIRIString { + t.Fatalf("expected %s, got %s", noteURIString, sallyIRIString) + } else if gotSet != 3 { + t.Fatalf("expected %d, got %d", 3, gotSet) + } else if err := PubObjectEquals(gotSetObj, expectedActor); err != nil { + t.Fatalf("set obj: %s", err) + } +} + func TestPostOutbox_Like_DoesNotAddIfAlreadyLiked(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePubberPostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p)