From 7f2faf98e1bef9f58334d15da2f0fca284234b39 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sun, 27 May 2018 01:49:03 +0200 Subject: [PATCH] Add handlers test. Also, for all tests that use tables, log the test case being executed. That way verbose mode will indicate which test case is causing any nil pointer dereference or other segfault interrupts. Also, it will inform anyone who runs in verbose mode just how many tests there really are. --- pub/fed_test.go | 7 + pub/handlers.go | 4 +- pub/handlers_test.go | 528 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 537 insertions(+), 2 deletions(-) create mode 100644 pub/handlers_test.go diff --git a/pub/fed_test.go b/pub/fed_test.go index 0a2d259..38045a8 100644 --- a/pub/fed_test.go +++ b/pub/fed_test.go @@ -1896,6 +1896,7 @@ func TestPostInbox_RequiresObject(t *testing.T) { return nil } for _, test := range tests { + t.Logf("Running table test case %q", test.name) resp := httptest.NewRecorder() req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(test.input())))) handled, err := p.PostInbox(context.Background(), resp, req) @@ -1940,6 +1941,7 @@ func TestPostInbox_RequiresTarget(t *testing.T) { return nil } for _, test := range tests { + t.Logf("Running table test case %q", test.name) resp := httptest.NewRecorder() req := ActivityPubRequest(httptest.NewRequest("POST", testInboxURI, bytes.NewBuffer(MustSerialize(test.input())))) handled, err := p.PostInbox(context.Background(), resp, req) @@ -2008,6 +2010,7 @@ func TestPostInbox_OriginMustMatch(t *testing.T) { }, } for _, test := range tests { + t.Logf("Running table test case %q", test.name) app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) resp := httptest.NewRecorder() @@ -2038,6 +2041,7 @@ func TestPostInbox_ActivityActorsMustCoverObjectActors(t *testing.T) { }, } for _, test := range tests { + t.Logf("Running table test case %q", test.name) app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePostInboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) resp := httptest.NewRecorder() @@ -2291,6 +2295,7 @@ func TestPostInbox_Delete_SetsTombstone(t *testing.T) { return nil } for _, test := range tests { + t.Logf("Running table test case %q", test.name) app.get = func(c context.Context, id url.URL) (PubObject, error) { return test.input(), nil } @@ -4241,6 +4246,7 @@ func TestPostOutbox_RequiresObject(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) for _, test := range tests { + t.Logf("Running table test case %q", test.name) resp := httptest.NewRecorder() req := Sign(ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(test.input()))))) handled, err := p.PostOutbox(context.Background(), resp, req) @@ -4283,6 +4289,7 @@ func TestPostOutbox_RequiresTarget(t *testing.T) { app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) for _, test := range tests { + t.Logf("Running table test case %q", test.name) resp := httptest.NewRecorder() req := Sign(ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(test.input()))))) handled, err := p.PostOutbox(context.Background(), resp, req) diff --git a/pub/handlers.go b/pub/handlers.go index bd03d82..4621c88 100644 --- a/pub/handlers.go +++ b/pub/handlers.go @@ -11,8 +11,8 @@ import ( ) // ServeActivityPubObject will serve the ActivityPub object with the given IRI -// in the request. Note that requests must be signed with HTTP signatures or -// else they will be denied access. To explicitly opt out of this protection, +// in the request. Note that requests may be signed with HTTP signatures or be +// permitted without any authentication scheme. To change this default behavior, // use ServeActivityPubObjectWithVerificationMethod instead. func ServeActivityPubObject(c context.Context, a Application, clock Clock, w http.ResponseWriter, r *http.Request) (handled bool, err error) { return serveActivityPubObject(c, a, clock, w, r, nil) diff --git a/pub/handlers_test.go b/pub/handlers_test.go new file mode 100644 index 0000000..53b55d2 --- /dev/null +++ b/pub/handlers_test.go @@ -0,0 +1,528 @@ +package pub + +import ( + "context" + "crypto" + "github.com/go-fed/activity/vocab" + "github.com/go-fed/httpsig" + "net/http" + "net/http/httptest" + "net/url" + "testing" +) + +func TestServeActivityPubObject(t *testing.T) { + tests := []struct { + name string + app *MockApplication + clock *MockClock + input *http.Request + expectedCode int + expectedObjFn func() vocab.Serializer + expectHandled bool + }{ + { + name: "unsigned request", + app: &MockApplication{ + t: t, + get: func(c context.Context, id url.URL) (PubObject, error) { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote, nil + }, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + input: ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil)), + expectedCode: http.StatusOK, + expectedObjFn: func() vocab.Serializer { + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote + }, + expectHandled: true, + }, + { + name: "http signature request", + input: Sign(ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil))), + app: &MockApplication{ + t: t, + getPublicKey: func(c context.Context, publicKeyId string) (crypto.PublicKey, httpsig.Algorithm, url.URL, error) { + if publicKeyId != testPublicKeyId { + t.Fatalf("(%q) expected %s, got %s", testPublicKeyId, publicKeyId) + } + return testPrivateKey.Public(), httpsig.RSA_SHA256, *samIRI, nil + }, + getAsVerifiedUser: func(c context.Context, id, user url.URL) (PubObject, error) { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } else if u := (&user).String(); u != samIRIString { + t.Fatalf("(%q) expected %s, got %s", samIRIString, u) + } + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote, nil + }, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + expectedCode: http.StatusOK, + expectedObjFn: func() vocab.Serializer { + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote + }, + expectHandled: true, + }, + { + name: "not owned", + input: ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil)), + app: &MockApplication{ + t: t, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return false + }, + }, + expectedCode: http.StatusNotFound, + expectHandled: true, + }, + { + name: "not activitypub get", + input: httptest.NewRequest("GET", noteURIString, nil), + expectHandled: false, + }, + { + name: "bad http signature", + input: BadSignature(ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil))), + app: &MockApplication{ + t: t, + getPublicKey: func(c context.Context, publicKeyId string) (crypto.PublicKey, httpsig.Algorithm, url.URL, error) { + if publicKeyId != testPublicKeyId { + t.Fatalf("(%q) expected %s, got %s", testPublicKeyId, publicKeyId) + } + return testPrivateKey.Public(), httpsig.RSA_SHA256, *samIRI, nil + }, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + expectedCode: http.StatusForbidden, + expectHandled: true, + }, + } + for _, test := range tests { + t.Logf("Running table test case %q", test.name) + resp := httptest.NewRecorder() + handled, err := ServeActivityPubObject(context.Background(), test.app, test.clock, resp, test.input) + if err != nil { + t.Fatalf("(%q) %s", test.name, err) + } else if handled != test.expectHandled { + t.Fatalf("(%q) expected %v, got %v", test.name, test.expectHandled, handled) + } else if test.expectedCode != 0 { + if resp.Code != test.expectedCode { + t.Fatalf("(%q) expected %d, got %d", test.name, test.expectedCode, resp.Code) + } + } else if test.expectedObjFn != nil { + if err := VocabEquals(resp.Body, test.expectedObjFn()); err != nil { + t.Fatalf("(%q) unexpected object: %s", test.name, err) + } + } + } +} + +func TestServeActivityPubObjectWithVerificationMethod(t *testing.T) { + tests := []struct { + name string + app *MockApplication + clock *MockClock + verifier *MockSocialAPIVerifier + input *http.Request + expectedCode int + expectedObjFn func() vocab.Serializer + expectHandled bool + }{ + { + name: "unsigned request", + app: &MockApplication{ + t: t, + get: func(c context.Context, id url.URL) (PubObject, error) { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote, nil + }, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + input: ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil)), + expectedCode: http.StatusOK, + expectedObjFn: func() vocab.Serializer { + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote + }, + expectHandled: true, + }, + { + name: "http signature request", + input: Sign(ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil))), + app: &MockApplication{ + t: t, + getPublicKey: func(c context.Context, publicKeyId string) (crypto.PublicKey, httpsig.Algorithm, url.URL, error) { + if publicKeyId != testPublicKeyId { + t.Fatalf("(%q) expected %s, got %s", testPublicKeyId, publicKeyId) + } + return testPrivateKey.Public(), httpsig.RSA_SHA256, *samIRI, nil + }, + getAsVerifiedUser: func(c context.Context, id, user url.URL) (PubObject, error) { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } else if u := (&user).String(); u != samIRIString { + t.Fatalf("(%q) expected %s, got %s", samIRIString, u) + } + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote, nil + }, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + expectedCode: http.StatusOK, + expectedObjFn: func() vocab.Serializer { + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote + }, + expectHandled: true, + }, + { + name: "not owned", + input: ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil)), + app: &MockApplication{ + t: t, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return false + }, + }, + expectedCode: http.StatusNotFound, + expectHandled: true, + }, + { + name: "not activitypub get", + input: httptest.NewRequest("GET", noteURIString, nil), + expectHandled: false, + }, + { + name: "bad http signature", + input: BadSignature(ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil))), + app: &MockApplication{ + t: t, + getPublicKey: func(c context.Context, publicKeyId string) (crypto.PublicKey, httpsig.Algorithm, url.URL, error) { + if publicKeyId != testPublicKeyId { + t.Fatalf("(%q) expected %s, got %s", testPublicKeyId, publicKeyId) + } + return testPrivateKey.Public(), httpsig.RSA_SHA256, *samIRI, nil + }, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + expectedCode: http.StatusForbidden, + expectHandled: true, + }, + { + name: "unsigned request passes verifier", + app: &MockApplication{ + t: t, + getAsVerifiedUser: func(c context.Context, id, user url.URL) (PubObject, error) { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } else if u := (&user).String(); u != samIRIString { + t.Fatalf("(%q) expected %s, got %s", samIRIString, u) + } + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote, nil + }, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + verifier: &MockSocialAPIVerifier{ + t: t, + verify: func(r *http.Request) (*url.URL, bool, bool, error) { + return samIRI, true, true, nil + }, + }, + input: ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil)), + expectedCode: http.StatusOK, + expectedObjFn: func() vocab.Serializer { + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote + }, + expectHandled: true, + }, + { + name: "http signature request passes verifier", + input: Sign(ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil))), + app: &MockApplication{ + t: t, + getAsVerifiedUser: func(c context.Context, id, user url.URL) (PubObject, error) { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } else if u := (&user).String(); u != samIRIString { + t.Fatalf("(%q) expected %s, got %s", samIRIString, u) + } + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote, nil + }, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + verifier: &MockSocialAPIVerifier{ + t: t, + verify: func(r *http.Request) (*url.URL, bool, bool, error) { + return samIRI, true, true, nil + }, + }, + expectedCode: http.StatusOK, + expectedObjFn: func() vocab.Serializer { + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote + }, + expectHandled: true, + }, + { + name: "verifier authed unauthz", + app: &MockApplication{ + t: t, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + verifier: &MockSocialAPIVerifier{ + t: t, + verify: func(r *http.Request) (*url.URL, bool, bool, error) { + return samIRI, true, false, nil + }, + }, + input: ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil)), + expectedCode: http.StatusForbidden, + expectHandled: true, + }, + { + name: "verifier unauthed unauthz", + app: &MockApplication{ + t: t, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + verifier: &MockSocialAPIVerifier{ + t: t, + verify: func(r *http.Request) (*url.URL, bool, bool, error) { + return nil, false, false, nil + }, + }, + input: ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil)), + expectedCode: http.StatusBadRequest, + expectHandled: true, + }, + { + name: "verifier unauthed authz unsigned fails", + app: &MockApplication{ + t: t, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + verifier: &MockSocialAPIVerifier{ + t: t, + verify: func(r *http.Request) (*url.URL, bool, bool, error) { + return nil, false, true, nil + }, + }, + input: ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil)), + expectedCode: http.StatusBadRequest, + expectHandled: true, + }, + { + name: "verifier unauthed authz signed success", + input: Sign(ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil))), + app: &MockApplication{ + t: t, + getPublicKey: func(c context.Context, publicKeyId string) (crypto.PublicKey, httpsig.Algorithm, url.URL, error) { + if publicKeyId != testPublicKeyId { + t.Fatalf("(%q) expected %s, got %s", testPublicKeyId, publicKeyId) + } + return testPrivateKey.Public(), httpsig.RSA_SHA256, *samIRI, nil + }, + getAsVerifiedUser: func(c context.Context, id, user url.URL) (PubObject, error) { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } else if u := (&user).String(); u != samIRIString { + t.Fatalf("(%q) expected %s, got %s", samIRIString, u) + } + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote, nil + }, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + verifier: &MockSocialAPIVerifier{ + t: t, + verify: func(r *http.Request) (*url.URL, bool, bool, error) { + return nil, false, true, nil + }, + }, + expectedCode: http.StatusOK, + expectedObjFn: func() vocab.Serializer { + testNote = &vocab.Note{} + testNote.SetId(*noteIRI) + testNote.AddNameString(noteName) + testNote.AddContentString("This is a simple note") + return testNote + }, + expectHandled: true, + }, + { + name: "verifier unauthed authz unsigned fails with bad impl returning user", + app: &MockApplication{ + t: t, + owns: func(c context.Context, id url.URL) bool { + if s := (&id).String(); s != noteURIString { + t.Fatalf("(%q) expected %s, got %s", noteURIString, s) + } + return true + }, + }, + clock: &MockClock{now}, + verifier: &MockSocialAPIVerifier{ + t: t, + verify: func(r *http.Request) (*url.URL, bool, bool, error) { + return samIRI, false, true, nil + }, + }, + input: ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil)), + expectedCode: http.StatusBadRequest, + expectHandled: true, + }, + } + for _, test := range tests { + t.Logf("Running table test case %q", test.name) + resp := httptest.NewRecorder() + var handled bool + var err error + if test.verifier != nil { + handled, err = ServeActivityPubObjectWithVerificationMethod(context.Background(), test.app, test.clock, resp, test.input, test.verifier) + } else { + handled, err = ServeActivityPubObjectWithVerificationMethod(context.Background(), test.app, test.clock, resp, test.input, nil) + } + if err != nil { + t.Fatalf("(%q) %s", test.name, err) + } else if handled != test.expectHandled { + t.Fatalf("(%q) expected %v, got %v", test.name, test.expectHandled, handled) + } else if test.expectedCode != 0 { + if resp.Code != test.expectedCode { + t.Fatalf("(%q) expected %d, got %d", test.name, test.expectedCode, resp.Code) + } + } else if test.expectedObjFn != nil { + if err := VocabEquals(resp.Body, test.expectedObjFn()); err != nil { + t.Fatalf("(%q) unexpected object: %s", test.name, err) + } + } + } +}