From 4bad90902e54a3082467658f4bb2ecb4c3fbb312 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sat, 12 May 2018 23:03:20 +0200 Subject: [PATCH] Add extra integration tests. These generally include improving the handling of OrderedCollections and IRIs. Note that improvement to setting IRI'd fetches from the implementing application were made. Improve the handing of AutoAccept and AutoReject follows. If there are no owned objects in the Activity, we prevent sending the automatic reply in case the implemented application is not checking for ownership of the object of the original Follow activity. --- pub/fed.go | 76 ++++---- pub/fed_test.go | 437 +++++++++++++++++++++++++++++++++++++++++++--- pub/interfaces.go | 5 +- pub/internal.go | 66 +++++-- 4 files changed, 513 insertions(+), 71 deletions(-) diff --git a/pub/fed.go b/pub/fed.go index ae08d51..5ff4913 100644 --- a/pub/fed.go +++ b/pub/fed.go @@ -695,27 +695,27 @@ func (f *federator) handleClientLike(ctx context.Context, deliverable *bool) fun if s.LenObject() == 0 { return ErrObjectRequired } - getter := func(actor vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) error { + getter := func(actor vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) (bool, error) { if actor.IsLikedAnyURI() { pObj, err := f.App.Get(ctx, actor.GetLikedAnyURI()) if err != nil { - return err + return true, err } ok := false if *lc, ok = pObj.(vocab.CollectionType); !ok { if *loc, ok = pObj.(vocab.OrderedCollectionType); !ok { - return fmt.Errorf("actors liked collection not CollectionType nor OrderedCollectionType") + return true, fmt.Errorf("actors liked collection not CollectionType nor OrderedCollectionType") } } - return nil + return true, nil } else if actor.IsLikedCollection() { *lc = actor.GetLikedCollection() - return nil + return false, nil } else if actor.IsLikedOrderedCollection() { *loc = actor.GetLikedOrderedCollection() - return nil + return false, nil } - return fmt.Errorf("cannot determine type of actor liked") + return false, fmt.Errorf("cannot determine type of actor liked") } if err := f.addAllObjectsToActorCollection(ctx, getter, s.Raw()); err != nil { return err @@ -863,34 +863,44 @@ func (f *federator) handleFollow(c context.Context) func(s *streams.Follow) erro activity.AddToIRI(raw.GetActorIRI(i)) } } - if err := f.deliver(activity); err != nil { - return err - } + ownsAny := false if todo == AutomaticAccept { - getter := func(object vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) error { + getter := func(object vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) (bool, error) { if object.IsFollowersAnyURI() { pObj, err := f.App.Get(c, object.GetFollowersAnyURI()) if err != nil { - return err + return true, err } ok := false if *lc, ok = pObj.(vocab.CollectionType); !ok { if *loc, ok = pObj.(vocab.OrderedCollectionType); !ok { - return fmt.Errorf("object followers collection not CollectionType nor OrderedCollectionType") + return true, fmt.Errorf("object followers collection not CollectionType nor OrderedCollectionType") } } - return nil + return true, nil } else if object.IsFollowersCollection() { *lc = object.GetFollowersCollection() - return nil + return false, nil } else if object.IsFollowersOrderedCollection() { *loc = object.GetFollowersOrderedCollection() - return nil + return false, nil } - return fmt.Errorf("cannot determine type of object followers") + return false, fmt.Errorf("cannot determine type of object followers") } // TODO: Deduplication detection. - if err := f.addAllActorsToObjectCollection(c, getter, raw); err != nil { + var err error + if ownsAny, err = f.addAllActorsToObjectCollection(c, getter, raw); err != nil { + return err + } + } else if todo == AutomaticReject { + var err error + ownsAny, err = f.ownsAnyObjects(c, raw) + if err != nil { + return err + } + } + if ownsAny { + if err := f.deliver(activity); err != nil { return err } } @@ -912,27 +922,27 @@ func (f *federator) handleAccept(c context.Context) func(s *streams.Accept) erro if !ok { continue } - getter := func(actor vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) error { + getter := func(actor vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) (bool, error) { if actor.IsFollowingAnyURI() { pObj, err := f.App.Get(c, actor.GetFollowingAnyURI()) if err != nil { - return err + return true, err } ok := false if *lc, ok = pObj.(vocab.CollectionType); !ok { if *loc, ok = pObj.(vocab.OrderedCollectionType); !ok { - return fmt.Errorf("actors following collection not CollectionType nor OrderedCollectionType") + return true, fmt.Errorf("actors following collection not CollectionType nor OrderedCollectionType") } } - return nil + return true, nil } else if actor.IsFollowingCollection() { *lc = actor.GetFollowingCollection() - return nil + return false, nil } else if actor.IsFollowingOrderedCollection() { *loc = actor.GetFollowingOrderedCollection() - return nil + return false, nil } - return fmt.Errorf("cannot determine type of actor following") + return false, fmt.Errorf("cannot determine type of actor following") } // TODO: Deduplication detection. if err := f.addAllObjectsToActorCollection(c, getter, follow); err != nil { @@ -1087,29 +1097,29 @@ func (f *federator) handleLike(c context.Context) func(s *streams.Like) error { if s.LenObject() == 0 { return ErrObjectRequired } - getter := func(object vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) error { + getter := func(object vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) (bool, error) { if object.IsLikesAnyURI() { pObj, err := f.App.Get(c, object.GetLikesAnyURI()) if err != nil { - return err + return true, err } ok := false if *lc, ok = pObj.(vocab.CollectionType); !ok { if *loc, ok = pObj.(vocab.OrderedCollectionType); !ok { - return fmt.Errorf("object likes collection not CollectionType nor OrderedCollectionType") + return true, fmt.Errorf("object likes collection not CollectionType nor OrderedCollectionType") } } - return nil + return true, nil } else if object.IsLikesCollection() { *lc = object.GetLikesCollection() - return nil + return false, nil } else if object.IsLikesOrderedCollection() { *loc = object.GetLikesOrderedCollection() - return nil + return false, nil } - return fmt.Errorf("cannot determine type of object likes") + return false, fmt.Errorf("cannot determine type of object likes") } - if err := f.addAllActorsToObjectCollection(c, getter, s.Raw()); err != nil { + if _, err := f.addAllActorsToObjectCollection(c, getter, s.Raw()); err != nil { return err } return f.ServerCallbacker.Like(c, s) diff --git a/pub/fed_test.go b/pub/fed_test.go index ad0df28..dbbefa2 100644 --- a/pub/fed_test.go +++ b/pub/fed_test.go @@ -17,20 +17,21 @@ import ( ) const ( - iriString = "https://example.com/something" - noteURIString = "https://example.com/note/123" - updateURIString = "https://example.com/note/update/123" - noteActivityURIString = "https://example.com/activity/987" - testAgent = "test agent string" - testInboxURI = "https://example.com/sally/inbox" - testOutboxURI = "https://example.com/sally/outbox" - testNewIRIString = "https://example.com/test/new/iri" - sallyIRIString = "https://example.com/sally" - samIRIString = "https://example.com/sam" - samIRIInboxString = "https://example.com/sam/inbox" - samIRIFollowersString = "https://example.com/sam/followers" - sallyIRIInboxString = "https://example.com/sally/inbox" - noteName = "A Note" + iriString = "https://example.com/something" + noteURIString = "https://example.com/note/123" + updateURIString = "https://example.com/note/update/123" + noteActivityURIString = "https://example.com/activity/987" + testAgent = "test agent string" + testInboxURI = "https://example.com/sally/inbox" + testOutboxURI = "https://example.com/sally/outbox" + testNewIRIString = "https://example.com/test/new/iri" + sallyIRIString = "https://example.com/sally" + sallyFollowingIRIString = "https://example.com/sally/following" + samIRIString = "https://example.com/sam" + samIRIInboxString = "https://example.com/sam/inbox" + samIRIFollowersString = "https://example.com/sam/followers" + sallyIRIInboxString = "https://example.com/sally/inbox" + noteName = "A Note" ) var ( @@ -41,6 +42,7 @@ var ( testNewIRI *url.URL sallyIRI *url.URL sallyIRIInbox *url.URL + sallyFollowingIRI *url.URL sallyActor *vocab.Person sallyActorJSON []byte samIRI *url.URL @@ -111,6 +113,10 @@ func init() { if err != nil { panic(err) } + sallyFollowingIRI, err = url.Parse(sallyFollowingIRIString) + if err != nil { + panic(err) + } samIRI, err = url.Parse(samIRIString) if err != nil { panic(err) @@ -1979,6 +1985,13 @@ func TestPostInbox_Follow_AutoReject(t *testing.T) { fedCb.follow = func(c context.Context, s *streams.Follow) error { return nil } + gotOwns := 0 + var ownsIRI url.URL + app.owns = func(c context.Context, id url.URL) bool { + gotOwns++ + ownsIRI = id + return true + } gotHttpDo := 0 var httpActorRequest *http.Request var httpDeliveryRequest *http.Request @@ -2028,6 +2041,10 @@ func TestPostInbox_Follow_AutoReject(t *testing.T) { 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 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 gotDoDelivery != 1 { t.Fatalf("expected %d, got %d", 1, gotDoDelivery) } else if doDeliveryURL.String() != sallyIRIInboxString { @@ -2037,8 +2054,6 @@ func TestPostInbox_Follow_AutoReject(t *testing.T) { } } -// TODO: Test follower OrderedCollection & IRI. -// TODO: Test does not own one of the objects. func TestPostInbox_Follow_AutoAccept(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) @@ -2155,6 +2170,190 @@ func TestPostInbox_Follow_AutoAccept(t *testing.T) { } } +func TestPostInbox_Follow_AutoAcceptFollowersIsOrderedCollection(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testFollow)))) + fedApp.onFollow = func(c context.Context, s *streams.Follow) FollowResponse { + return AutomaticAccept + } + fedCb.follow = func(c context.Context, s *streams.Follow) error { + return nil + } + gotHttpDo := 0 + httpClient.do = func(req *http.Request) (*http.Response, error) { + gotHttpDo++ + if gotHttpDo == 1 { + actorResp := &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewBuffer(sallyActorJSON)), + } + return actorResp, nil + } else if gotHttpDo == 2 { + okResp := &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewBuffer([]byte{})), + } + return okResp, nil + } + return nil, nil + } + d.do = func(b []byte, u url.URL, toDo func(b []byte, u url.URL) error) { + if err := toDo(b, u); err != nil { + t.Fatalf("Unexpected error in MockDeliverer.Do: %s", err) + } + } + app.owns = func(c context.Context, id url.URL) bool { + return true + } + app.get = func(c context.Context, id url.URL) (PubObject, error) { + samActor := &vocab.Person{} + samActor.SetInboxAnyURI(*samIRIInbox) + samActor.SetId(*samIRI) + samActor.SetFollowersOrderedCollection(&vocab.OrderedCollection{}) + return samActor, nil + } + gotSet := 0 + var setObject PubObject + app.set = func(c context.Context, o PubObject) error { + gotSet++ + if gotSet == 1 { + setObject = o + } + return nil + } + handled, err := p.PostInbox(context.Background(), resp, req) + expectedFollowers := &vocab.OrderedCollection{} + expectedFollowers.AddOrderedItemsObject(sallyActor) + expectedActor := &vocab.Person{} + expectedActor.SetInboxAnyURI(*samIRIInbox) + expectedActor.SetId(*samIRI) + expectedActor.SetFollowersOrderedCollection(expectedFollowers) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if err := PubObjectEquals(setObject, expectedActor); err != nil { + t.Fatal(err) + } +} + +func TestPostInbox_Follow_AutoAcceptFollowersIsIRI(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testFollow)))) + fedApp.onFollow = func(c context.Context, s *streams.Follow) FollowResponse { + return AutomaticAccept + } + fedCb.follow = func(c context.Context, s *streams.Follow) error { + return nil + } + gotHttpDo := 0 + httpClient.do = func(req *http.Request) (*http.Response, error) { + gotHttpDo++ + if gotHttpDo == 1 { + actorResp := &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewBuffer(sallyActorJSON)), + } + return actorResp, nil + } else if gotHttpDo == 2 { + okResp := &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewBuffer([]byte{})), + } + return okResp, nil + } + return nil, nil + } + d.do = func(b []byte, u url.URL, toDo func(b []byte, u url.URL) error) { + if err := toDo(b, u); err != nil { + t.Fatalf("Unexpected error in MockDeliverer.Do: %s", err) + } + } + app.owns = func(c context.Context, id url.URL) bool { + return true + } + app.get = func(c context.Context, id url.URL) (PubObject, error) { + if id == *samIRI { + samActor := &vocab.Person{} + samActor.SetInboxAnyURI(*samIRIInbox) + samActor.SetId(*samIRI) + samActor.SetFollowersAnyURI(*testNewIRI) + return samActor, nil + } else if id == *testNewIRI { + return &vocab.Collection{}, nil + } + t.Fatalf("unexpected get(%s)", &id) + return nil, nil + } + gotSet := 0 + var setObject PubObject + app.set = func(c context.Context, o PubObject) error { + gotSet++ + if gotSet == 1 { + setObject = o + } + return nil + } + handled, err := p.PostInbox(context.Background(), resp, req) + expectedFollowers := &vocab.Collection{} + expectedFollowers.AddItemsObject(sallyActor) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if err := PubObjectEquals(setObject, expectedFollowers); err != nil { + t.Fatal(err) + } +} + +func TestPostInbox_Follow_DoesNotAutoAcceptIfNotOwned(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testFollow)))) + fedApp.onFollow = func(c context.Context, s *streams.Follow) FollowResponse { + return AutomaticAccept + } + fedCb.follow = func(c context.Context, s *streams.Follow) error { + return nil + } + app.owns = func(c context.Context, id url.URL) bool { + return false + } + handled, err := p.PostInbox(context.Background(), resp, req) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } +} + +func TestPostInbox_Follow_DoesNotAutoRejectIfNotOwned(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testFollow)))) + fedApp.onFollow = func(c context.Context, s *streams.Follow) FollowResponse { + return AutomaticReject + } + fedCb.follow = func(c context.Context, s *streams.Follow) error { + return nil + } + app.owns = func(c context.Context, id url.URL) bool { + return false + } + handled, err := p.PostInbox(context.Background(), resp, req) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } +} + func TestPostInbox_Follow_CallsCallback(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) @@ -2198,7 +2397,6 @@ func TestPostInbox_Accept_DoesNothingIfNotAcceptingFollow(t *testing.T) { } } -// TODO: Test follower OrderedCollection & IRI. func TestPostInbox_Accept_AcceptFollowAddsToFollowersIfOwned(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) @@ -2260,6 +2458,94 @@ func TestPostInbox_Accept_AcceptFollowAddsToFollowersIfOwned(t *testing.T) { } } +func TestPostInbox_Accept_AcceptFollowAddsToFollowersOrderedCollection(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testAcceptFollow)))) + app.owns = func(c context.Context, id url.URL) bool { + return true + } + app.get = func(c context.Context, id url.URL) (PubObject, error) { + sallyActor := &vocab.Person{} + sallyActor.SetInboxAnyURI(*sallyIRIInbox) + sallyActor.SetId(*sallyIRI) + sallyActor.SetFollowingOrderedCollection(&vocab.OrderedCollection{}) + return sallyActor, nil + } + gotSet := 0 + var setObject PubObject + app.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.AddOrderedItemsObject(samActor) + 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 err := PubObjectEquals(setObject, expectedActor); err != nil { + t.Fatal(err) + } +} + +func TestPostInbox_Accept_AcceptFollowAddsToFollowersIRI(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testAcceptFollow)))) + app.owns = func(c context.Context, id url.URL) bool { + return true + } + app.get = func(c context.Context, id url.URL) (PubObject, error) { + if id == *sallyIRI { + sallyActor := &vocab.Person{} + sallyActor.SetInboxAnyURI(*sallyIRIInbox) + sallyActor.SetId(*sallyIRI) + sallyActor.SetFollowingAnyURI(*sallyFollowingIRI) + return sallyActor, nil + } else if id == *sallyFollowingIRI { + return &vocab.OrderedCollection{}, nil + } + t.Fatalf("Unexpected get(%s)", (&id).String()) + return nil, nil + } + gotSet := 0 + var setObject PubObject + app.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.AddOrderedItemsObject(samActor) + 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 err := PubObjectEquals(setObject, expectedFollowing); err != nil { + t.Fatal(err) + } +} + func TestPostInbox_Accept_DoesNothingIfNotOwned(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) @@ -2717,7 +3003,6 @@ func TestPostInbox_Remove_CallsCallback(t *testing.T) { } } -// TODO: Test likes OrderedCollection & IRI. func TestPostInbox_Like_AddsToLikeCollection(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) @@ -2781,6 +3066,97 @@ func TestPostInbox_Like_AddsToLikeCollection(t *testing.T) { } } +func TestPostInbox_Like_AddsToLikeOrderedCollection(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testLikeNote)))) + app.owns = func(c context.Context, id url.URL) bool { + return true + } + app.get = func(c context.Context, id url.URL) (PubObject, error) { + v := &vocab.Note{} + v.SetId(*noteIRI) + v.AddNameString(noteName) + v.AddContentString("This is a simple note") + v.SetLikesOrderedCollection(&vocab.OrderedCollection{}) + return v, nil + } + gotSet := 0 + var gotSetObject PubObject + app.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.AddOrderedItemsObject(sallyActor) + expectedNote := &vocab.Note{} + expectedNote.SetId(*noteIRI) + expectedNote.AddNameString(noteName) + expectedNote.AddContentString("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 err := PubObjectEquals(gotSetObject, expectedNote); err != nil { + t.Fatalf("unexpected callback object: %s", err) + } +} + +func TestPostInbox_Like_AddsToLikeIRI(t *testing.T) { + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(testLikeNote)))) + app.owns = func(c context.Context, id url.URL) bool { + return true + } + app.get = func(c context.Context, id url.URL) (PubObject, error) { + if id == *noteIRI { + v := &vocab.Note{} + v.SetId(*noteIRI) + v.AddNameString(noteName) + v.AddContentString("This is a simple note") + v.SetLikesAnyURI(*testNewIRI) + return v, nil + } else if id == *testNewIRI { + return &vocab.OrderedCollection{}, nil + } + t.Fatalf("unexpected get(%s)", &id) + return nil, nil + } + gotSet := 0 + var gotSetObject PubObject + app.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.AddOrderedItemsObject(sallyActor) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if err := PubObjectEquals(gotSetObject, expected); err != nil { + t.Fatalf("unexpected callback object: %s", err) + } +} + func TestPostInbox_Like_CallsCallback(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) @@ -4718,12 +5094,26 @@ func TestPostOutbox_Block_IsNotDelivered(t *testing.T) { } } -func TestPostOutbox_DoesNotDeliverNondeliverable(t *testing.T) { - // TODO: Implement -} - func TestPostOutbox_SetsLocationHeader(t *testing.T) { - // TODO: Implement + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote)))) + socialCb.create = func(c context.Context, s *streams.Create) error { + return nil + } + handled, err := p.PostOutbox(context.Background(), resp, req) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if loc, ok := resp.HeaderMap["Location"]; !ok { + t.Fatalf("expected Location header, got none") + } else if len(loc) != 1 { + t.Fatalf("expected Location header to have length 1, got %d", len(loc)) + } else if loc[0] != testNewIRIString { + t.Fatalf("expected %s, got %s", testNewIRIString, loc[0]) + } } func TestGetOutbox_RejectNonActivityPub(t *testing.T) { @@ -4739,7 +5129,6 @@ func TestGetOutbox_RejectNonActivityPub(t *testing.T) { } else if handled { t.Fatalf("expected !handled, got handled") } - } func TestGetOutbox_SetsContentTypeHeader(t *testing.T) { diff --git a/pub/interfaces.go b/pub/interfaces.go index 3b25afa..466e383 100644 --- a/pub/interfaces.go +++ b/pub/interfaces.go @@ -88,8 +88,9 @@ type FederateApp interface { // the given target collection. CanRemove(c context.Context, obj vocab.ObjectType, target vocab.ObjectType) bool // OnFollow determines whether to take any automatic reactions in - // response to this follow. Note that this method must check ownership - // to automatically Accept the Follow. + // response to this follow. Note that if this application does not own + // an object on the activity, then the 'AutomaticAccept' and + // 'AutomaticReject' results will behave as if they were 'DoNothing'. OnFollow(c context.Context, s *streams.Follow) FollowResponse // Unblocked should return an error if the provided actor ids are not // able to interact with this particular end user due to being blocked diff --git a/pub/internal.go b/pub/internal.go index 3fde7fc..0faeb43 100644 --- a/pub/internal.go +++ b/pub/internal.go @@ -1187,7 +1187,9 @@ func toTombstone(obj vocab.ObjectType, id url.URL, now time.Time) vocab.Tombston return tomb } -func (f *federator) addAllObjectsToActorCollection(ctx context.Context, getter func(actor vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) error, c vocab.ActivityType) error { +type getActorCollectionFn func(actor vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) (isIRI bool, e error) + +func (f *federator) addAllObjectsToActorCollection(ctx context.Context, getter getActorCollectionFn, c vocab.ActivityType) error { for i := 0; i < c.ActorLen(); i++ { var iri url.URL if c.IsActorObject(i) { @@ -1221,7 +1223,8 @@ func (f *federator) addAllObjectsToActorCollection(ctx context.Context, getter f } var lc vocab.CollectionType var loc vocab.OrderedCollectionType - if err := getter(actor, &lc, &loc); err != nil { + isIRI := false + if isIRI, err = getter(actor, &lc, &loc); err != nil { return err } for i := 0; i < c.ObjectLen(); i++ { @@ -1239,20 +1242,32 @@ func (f *federator) addAllObjectsToActorCollection(ctx context.Context, getter f } } } - if err := f.App.Set(ctx, actor); err != nil { + if isIRI { + if lc != nil { + err = f.App.Set(ctx, lc) + } else if loc != nil { + err = f.App.Set(ctx, loc) + } + if err != nil { + return err + } + } else if err := f.App.Set(ctx, actor); err != nil { return err } } return nil } -func (f *federator) addAllActorsToObjectCollection(ctx context.Context, getter func(object vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) error, c vocab.ActivityType) error { +type getObjectCollectionFn func(object vocab.ObjectType, lc *vocab.CollectionType, loc *vocab.OrderedCollectionType) (isIRI bool, e error) + +func (f *federator) addAllActorsToObjectCollection(ctx context.Context, getter getObjectCollectionFn, c vocab.ActivityType) (bool, error) { + ownsAny := false for i := 0; i < c.ObjectLen(); i++ { var iri url.URL if c.IsObject(i) { obj := c.GetObject(i) if !obj.HasId() { - return fmt.Errorf("object does not have id") + return ownsAny, fmt.Errorf("object does not have id") } iri = obj.GetId() } else if c.IsObjectIRI(i) { @@ -1261,21 +1276,23 @@ func (f *federator) addAllActorsToObjectCollection(ctx context.Context, getter f if !f.App.Owns(ctx, iri) { continue } + ownsAny = true var object vocab.ObjectType pObj, err := f.App.Get(ctx, iri) if err != nil { - return err + return ownsAny, err } ok := false object, ok = pObj.(vocab.ObjectType) if !ok { // TODO: Handle links, too - return fmt.Errorf("object is not vocab.ObjectType") + return ownsAny, fmt.Errorf("object is not vocab.ObjectType") } var lc vocab.CollectionType var loc vocab.OrderedCollectionType - if err := getter(object, &lc, &loc); err != nil { - return err + isIRI := false + if isIRI, err = getter(object, &lc, &loc); err != nil { + return ownsAny, err } for i := 0; i < c.ActorLen(); i++ { if c.IsActorIRI(i) { @@ -1298,11 +1315,36 @@ func (f *federator) addAllActorsToObjectCollection(ctx context.Context, getter f } } } - if err := f.App.Set(ctx, object); err != nil { - return err + if isIRI { + if lc != nil { + err = f.App.Set(ctx, lc) + } else if loc != nil { + err = f.App.Set(ctx, loc) + } + if err != nil { + return ownsAny, err + } + } else if err := f.App.Set(ctx, object); err != nil { + return ownsAny, err } } - return nil + return ownsAny, nil +} + +func (f *federator) ownsAnyObjects(c context.Context, a vocab.ActivityType) (bool, error) { + var iris []url.URL + for i := 0; i < a.ObjectLen(); i++ { + if a.IsObject(i) { + obj := a.GetObject(i) + if !obj.HasId() { + return false, fmt.Errorf("object missing id") + } + iris = append(iris, obj.GetId()) + } else if a.IsObjectIRI(i) { + iris = append(iris, a.GetObjectIRI(i)) + } + } + return f.ownsAnyIRIs(c, iris), nil } func (f *federator) addToOutbox(c context.Context, r *http.Request, m map[string]interface{}) error {