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 {