activity/pub/handlers_test.go

699 行
22 KiB
Go

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, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != noteURIString {
t.Fatalf("expected %s, got %s", noteURIString, s)
}
testNote = &vocab.Note{}
testNote.SetId(noteIRI)
testNote.AppendNameString(noteName)
testNote.AppendContentString("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("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.AppendNameString(noteName)
testNote.AppendContentString("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("expected %s, got %s", testPublicKeyId, publicKeyId)
}
return testPrivateKey.Public(), httpsig.RSA_SHA256, samIRI, nil
},
getAsVerifiedUser: func(c context.Context, id, user *url.URL, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != noteURIString {
t.Fatalf("expected %s, got %s", noteURIString, s)
} else if u := user.String(); u != samIRIString {
t.Fatalf("expected %s, got %s", samIRIString, u)
}
testNote = &vocab.Note{}
testNote.SetId(noteIRI)
testNote.AppendNameString(noteName)
testNote.AppendContentString("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("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.AppendNameString(noteName)
testNote.AppendContentString("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("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("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("expected %s, got %s", noteURIString, s)
}
return true
},
},
expectedCode: http.StatusForbidden,
expectHandled: true,
},
{
name: "remove bto & bcc",
app: &MockApplication{
t: t,
get: func(c context.Context, id *url.URL, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != noteURIString {
t.Fatalf("expected %s, got %s", noteURIString, s)
}
testNote = &vocab.Note{}
testNote.SetId(noteIRI)
testNote.AppendNameString(noteName)
testNote.AppendContentString("This is a simple note")
testNote.AppendBtoIRI(samIRI)
testNote.AppendBccIRI(sallyIRI)
return testNote, nil
},
owns: func(c context.Context, id *url.URL) bool {
if s := id.String(); s != noteURIString {
t.Fatalf("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.AppendNameString(noteName)
testNote.AppendContentString("This is a simple note")
return testNote
},
expectHandled: true,
},
{
name: "tombstone is status gone",
app: &MockApplication{
t: t,
get: func(c context.Context, id *url.URL, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != testNewIRIString {
t.Fatalf("expected %s, got %s", testNewIRIString, s)
}
tombstone := &vocab.Tombstone{}
tombstone.SetId(testNewIRI)
return tombstone, nil
},
owns: func(c context.Context, id *url.URL) bool {
if s := id.String(); s != testNewIRIString {
t.Fatalf("expected %s, got %s", testNewIRIString, s)
}
return true
},
},
clock: &MockClock{now},
input: ActivityPubRequest(httptest.NewRequest("GET", testNewIRIString, nil)),
expectedCode: http.StatusGone,
expectedObjFn: func() vocab.Serializer {
tombstone := &vocab.Tombstone{}
tombstone.SetId(testNewIRI)
return tombstone
},
expectHandled: true,
},
}
for _, test := range tests {
t.Logf("Running table test case %q", test.name)
resp := httptest.NewRecorder()
fnUnderTest := ServeActivityPubObject(test.app, test.clock)
handled, err := fnUnderTest(context.Background(), 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, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != noteURIString {
t.Fatalf("expected %s, got %s", noteURIString, s)
}
testNote = &vocab.Note{}
testNote.SetId(noteIRI)
testNote.AppendNameString(noteName)
testNote.AppendContentString("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("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.AppendNameString(noteName)
testNote.AppendContentString("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("expected %s, got %s", testPublicKeyId, publicKeyId)
}
return testPrivateKey.Public(), httpsig.RSA_SHA256, samIRI, nil
},
getAsVerifiedUser: func(c context.Context, id, user *url.URL, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != noteURIString {
t.Fatalf("expected %s, got %s", noteURIString, s)
} else if u := user.String(); u != samIRIString {
t.Fatalf("expected %s, got %s", samIRIString, u)
}
testNote = &vocab.Note{}
testNote.SetId(noteIRI)
testNote.AppendNameString(noteName)
testNote.AppendContentString("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("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.AppendNameString(noteName)
testNote.AppendContentString("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("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("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("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, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != noteURIString {
t.Fatalf("expected %s, got %s", noteURIString, s)
} else if u := user.String(); u != samIRIString {
t.Fatalf("expected %s, got %s", samIRIString, u)
}
testNote = &vocab.Note{}
testNote.SetId(noteIRI)
testNote.AppendNameString(noteName)
testNote.AppendContentString("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("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.AppendNameString(noteName)
testNote.AppendContentString("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, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != noteURIString {
t.Fatalf("expected %s, got %s", noteURIString, s)
} else if u := user.String(); u != samIRIString {
t.Fatalf("expected %s, got %s", samIRIString, u)
}
testNote = &vocab.Note{}
testNote.SetId(noteIRI)
testNote.AppendNameString(noteName)
testNote.AppendContentString("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("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.AppendNameString(noteName)
testNote.AppendContentString("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("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("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("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("expected %s, got %s", testPublicKeyId, publicKeyId)
}
return testPrivateKey.Public(), httpsig.RSA_SHA256, samIRI, nil
},
getAsVerifiedUser: func(c context.Context, id, user *url.URL, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != noteURIString {
t.Fatalf("expected %s, got %s", noteURIString, s)
} else if u := user.String(); u != samIRIString {
t.Fatalf("expected %s, got %s", samIRIString, u)
}
testNote = &vocab.Note{}
testNote.SetId(noteIRI)
testNote.AppendNameString(noteName)
testNote.AppendContentString("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("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.AppendNameString(noteName)
testNote.AppendContentString("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("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,
},
{
name: "remove bto & bcc",
app: &MockApplication{
t: t,
getPublicKey: func(c context.Context, publicKeyId string) (crypto.PublicKey, httpsig.Algorithm, *url.URL, error) {
if publicKeyId != testPublicKeyId {
t.Fatalf("expected %s, got %s", testPublicKeyId, publicKeyId)
}
return testPrivateKey.Public(), httpsig.RSA_SHA256, samIRI, nil
},
getAsVerifiedUser: func(c context.Context, id, user *url.URL, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != noteURIString {
t.Fatalf("expected %s, got %s", noteURIString, s)
} else if u := user.String(); u != samIRIString {
t.Fatalf("expected %s, got %s", samIRIString, u)
}
testNote = &vocab.Note{}
testNote.SetId(noteIRI)
testNote.AppendNameString(noteName)
testNote.AppendContentString("This is a simple note")
testNote.AppendBtoIRI(samIRI)
testNote.AppendBccIRI(sallyIRI)
return testNote, nil
},
owns: func(c context.Context, id *url.URL) bool {
if s := id.String(); s != noteURIString {
t.Fatalf("expected %s, got %s", noteURIString, s)
}
return true
},
},
clock: &MockClock{now},
input: Sign(ActivityPubRequest(httptest.NewRequest("GET", noteURIString, nil))),
expectedCode: http.StatusOK,
expectedObjFn: func() vocab.Serializer {
testNote = &vocab.Note{}
testNote.SetId(noteIRI)
testNote.AppendNameString(noteName)
testNote.AppendContentString("This is a simple note")
return testNote
},
expectHandled: true,
},
{
name: "tombstone is status gone",
app: &MockApplication{
t: t,
getPublicKey: func(c context.Context, publicKeyId string) (crypto.PublicKey, httpsig.Algorithm, *url.URL, error) {
if publicKeyId != testPublicKeyId {
t.Fatalf("expected %s, got %s", testPublicKeyId, publicKeyId)
}
return testPrivateKey.Public(), httpsig.RSA_SHA256, samIRI, nil
},
getAsVerifiedUser: func(c context.Context, id, user *url.URL, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %v, got %v", Read, rw)
} else if s := id.String(); s != testNewIRIString {
t.Fatalf("expected %s, got %s", testNewIRIString, s)
} else if u := user.String(); u != samIRIString {
t.Fatalf("expected %s, got %s", samIRIString, u)
}
tombstone := &vocab.Tombstone{}
tombstone.SetId(testNewIRI)
return tombstone, nil
},
owns: func(c context.Context, id *url.URL) bool {
if s := id.String(); s != testNewIRIString {
t.Fatalf("expected %s, got %s", testNewIRIString, s)
}
return true
},
},
clock: &MockClock{now},
input: Sign(ActivityPubRequest(httptest.NewRequest("GET", testNewIRIString, nil))),
expectedCode: http.StatusGone,
expectedObjFn: func() vocab.Serializer {
tombstone := &vocab.Tombstone{}
tombstone.SetId(testNewIRI)
return tombstone
},
expectHandled: true,
},
}
for _, test := range tests {
t.Logf("Running table test case %q", test.name)
resp := httptest.NewRecorder()
var fnUnderTest HandlerFunc
if test.verifier != nil {
verifierFn := func(c context.Context) SocialAPIVerifier {
return test.verifier
}
fnUnderTest = ServeActivityPubObjectWithVerificationMethod(test.app, test.clock, verifierFn)
} else {
fnUnderTest = ServeActivityPubObjectWithVerificationMethod(test.app, test.clock, nil)
}
handled, err := fnUnderTest(context.Background(), 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)
}
}
}
}