activity/pub/handlers_test.go
Cory Slep 96c9afee84 Add Read and ReadWrite when getting data.
This allows implementations to prevent race conditions between
contentious objects.
2018-05-27 22:57:43 +02:00

543 行
18 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 %d, got %d", Read, rw)
} else if s := (&id).String(); s != noteURIString {
t.Fatalf("(%q) 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("(%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.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("(%q) 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 %d, got %d", Read, rw)
} else 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.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("(%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.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("(%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, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %d, got %d", Read, rw)
} else if s := (&id).String(); s != noteURIString {
t.Fatalf("(%q) 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("(%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.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("(%q) 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 %d, got %d", Read, rw)
} else 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.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("(%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.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("(%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, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %d, got %d", Read, rw)
} else 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.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("(%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.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 %d, got %d", Read, rw)
} else 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.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("(%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.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("(%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, rw RWType) (PubObject, error) {
if rw != Read {
t.Fatalf("expected RWType of %d, got %d", Read, rw)
} else 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.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("(%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.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("(%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)
}
}
}
}