Fixes for the first c2s implementation report.
Non Interface Behavior Changes: - Robust header detection for ActivityPub requests - No more panic because of a Bto access after a Bcc condition - PostOutbox Undo activities now required to have matching actors Interface Behavior Changes: - PostOutbox activities themselves are now passed to App.Set - Authentication/Authorization in a SocialAPIVerifier no longer shadowed and ignored. - Add activities can now fetch remote objects (not permanent) The behavior changes are justified as having been broken bugs that would have not met a developer's expectations. So including them as part of the next release maintains major version 0 behavior compatibility.
このコミットが含まれているのは:
コミット
33c5320c8f
54
pub/fed.go
54
pub/fed.go
|
@ -266,8 +266,9 @@ func (f *federator) PostOutbox(c context.Context, w http.ResponseWriter, r *http
|
|||
authenticated := false
|
||||
authorized := true
|
||||
if verifier := f.SocialAPI.GetSocialAPIVerifier(c); verifier != nil {
|
||||
var err error
|
||||
// Use custom Social API method to authenticate and authorize.
|
||||
authenticated, authorized, err := verifier.VerifyForOutbox(r, r.URL)
|
||||
authenticated, authorized, err = verifier.VerifyForOutbox(r, r.URL)
|
||||
if err != nil {
|
||||
return true, err
|
||||
} else if authenticated && !authorized {
|
||||
|
@ -318,20 +319,23 @@ func (f *federator) PostOutbox(c context.Context, w http.ResponseWriter, r *http
|
|||
}
|
||||
typer = f.wrapInCreate(obj, actorIri)
|
||||
}
|
||||
newId := f.App.NewId(c, typer)
|
||||
typer.SetId(newId)
|
||||
if m, err = typer.Serialize(); err != nil {
|
||||
activity, ok := typer.(vocab.ActivityType)
|
||||
if !ok {
|
||||
return true, fmt.Errorf("assigning new ids: cannot convert to vocab.ActivityType: %T", typer)
|
||||
}
|
||||
f.addNewIds(c, activity)
|
||||
if m, err = activity.Serialize(); err != nil {
|
||||
return true, err
|
||||
}
|
||||
deliverable := false
|
||||
if err = f.getPostOutboxResolver(c, m, &deliverable, &m).Deserialize(m); err != nil {
|
||||
if err = f.getPostOutboxResolver(c, m, &deliverable, &m, r.URL).Deserialize(m); err != nil {
|
||||
if err == errObjectRequired || err == errTargetRequired {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return true, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
if err := f.addToOutbox(c, r, m); err != nil {
|
||||
if err = f.addToOutbox(c, r, m); err != nil {
|
||||
return true, err
|
||||
}
|
||||
if f.EnableServer && deliverable {
|
||||
|
@ -343,7 +347,7 @@ func (f *federator) PostOutbox(c context.Context, w http.ResponseWriter, r *http
|
|||
return true, err
|
||||
}
|
||||
}
|
||||
w.Header().Set("Location", newId.String())
|
||||
w.Header().Set("Location", activity.GetId().String())
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
return true, nil
|
||||
}
|
||||
|
@ -376,7 +380,7 @@ func (f *federator) GetOutbox(c context.Context, w http.ResponseWriter, r *http.
|
|||
return true, nil
|
||||
}
|
||||
|
||||
func (f *federator) getPostOutboxResolver(c context.Context, rawJson map[string]interface{}, deliverable *bool, toAddToOutbox *map[string]interface{}) *streams.Resolver {
|
||||
func (f *federator) getPostOutboxResolver(c context.Context, rawJson map[string]interface{}, deliverable *bool, toAddToOutbox *map[string]interface{}, outboxURL *url.URL) *streams.Resolver {
|
||||
return &streams.Resolver{
|
||||
CreateCallback: f.handleClientCreate(c, deliverable, toAddToOutbox),
|
||||
UpdateCallback: f.handleClientUpdate(c, rawJson, deliverable),
|
||||
|
@ -384,7 +388,7 @@ func (f *federator) getPostOutboxResolver(c context.Context, rawJson map[string]
|
|||
FollowCallback: f.handleClientFollow(c, deliverable),
|
||||
AcceptCallback: f.handleClientAccept(c, deliverable),
|
||||
RejectCallback: f.handleClientReject(c, deliverable),
|
||||
AddCallback: f.handleClientAdd(c, deliverable),
|
||||
AddCallback: f.handleClientAdd(c, deliverable, outboxURL),
|
||||
RemoveCallback: f.handleClientRemove(c, deliverable),
|
||||
LikeCallback: f.handleClientLike(c, deliverable),
|
||||
UndoCallback: f.handleClientUndo(c, deliverable),
|
||||
|
@ -600,7 +604,7 @@ func (f *federator) handleClientReject(c context.Context, deliverable *bool) fun
|
|||
}
|
||||
}
|
||||
|
||||
func (f *federator) handleClientAdd(c context.Context, deliverable *bool) func(s *streams.Add) error {
|
||||
func (f *federator) handleClientAdd(c context.Context, deliverable *bool, outboxURL *url.URL) func(s *streams.Add) error {
|
||||
return func(s *streams.Add) error {
|
||||
*deliverable = true
|
||||
if s.LenObject() == 0 {
|
||||
|
@ -641,11 +645,29 @@ func (f *federator) handleClientAdd(c context.Context, deliverable *bool) func(s
|
|||
}
|
||||
}
|
||||
for i := 0; i < raw.ObjectLen(); i++ {
|
||||
if !raw.IsObject(i) {
|
||||
// TODO: Fetch IRIs as well
|
||||
return fmt.Errorf("add object must be object type: %v", raw)
|
||||
var obj vocab.ObjectType
|
||||
if raw.IsObjectIRI(i) {
|
||||
objId := raw.GetObjectIRI(i)
|
||||
if f.App.Owns(c, objId) {
|
||||
pObj, err := f.App.Get(c, objId, Read)
|
||||
var ok bool
|
||||
if obj, ok = pObj.(vocab.ObjectType); !ok {
|
||||
return fmt.Errorf("add object must be an activitypub object: %v", raw)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
obj, err = f.dereferenceAsUser(outboxURL, objId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if raw.IsObject(i) {
|
||||
obj = raw.GetObject(i)
|
||||
} else {
|
||||
return fmt.Errorf("add object must be of object or iri type: %v", raw)
|
||||
}
|
||||
obj := raw.GetObject(i)
|
||||
for _, target := range targets {
|
||||
if !f.App.CanAdd(c, obj, target) {
|
||||
continue
|
||||
|
@ -769,6 +791,10 @@ func (f *federator) handleClientUndo(c context.Context, deliverable *bool) func(
|
|||
if s.LenObject() == 0 {
|
||||
return errObjectRequired
|
||||
}
|
||||
raw := s.Raw()
|
||||
if err := f.ensureActivityActorsMatchObjectActors(raw); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: Determine if we can support common forms of undo natively.
|
||||
return f.ClientCallbacker.Undo(c, s)
|
||||
}
|
||||
|
|
138
pub/fed_test.go
138
pub/fed_test.go
|
@ -28,7 +28,8 @@ const (
|
|||
testAgent = "test agent string"
|
||||
testInboxURI = "https://example.com/sally/inbox"
|
||||
testOutboxURI = "https://example.com/sally/outbox"
|
||||
testNewIRIString = "https://example.com/test/new/iri"
|
||||
testNewIRIString = "https://example.com/test/new/iri/1"
|
||||
testNewIRIString2 = "https://example.com/test/new/iri/2"
|
||||
sallyIRIString = "https://example.com/sally"
|
||||
sallyFollowingIRIString = "https://example.com/sally/following"
|
||||
samIRIString = "https://example.com/sam"
|
||||
|
@ -48,6 +49,7 @@ var (
|
|||
noteActivityIRI *url.URL
|
||||
updateActivityIRI *url.URL
|
||||
testNewIRI *url.URL
|
||||
testNewIRI2 *url.URL
|
||||
sallyIRI *url.URL
|
||||
sallyIRIInbox *url.URL
|
||||
sallyFollowingIRI *url.URL
|
||||
|
@ -118,6 +120,10 @@ func init() {
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
testNewIRI2, err = url.Parse(testNewIRIString2)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sallyIRI, err = url.Parse(sallyIRIString)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -258,7 +264,7 @@ func init() {
|
|||
testBlock.AppendObject(samActor)
|
||||
|
||||
testClientExpectedNote = &vocab.Note{}
|
||||
testClientExpectedNote.SetId(noteIRI)
|
||||
testClientExpectedNote.SetId(testNewIRI2)
|
||||
testClientExpectedNote.AppendNameString(noteName)
|
||||
testClientExpectedNote.AppendContentString("This is a simple note")
|
||||
testClientExpectedNote.AppendAttributedToObject(sallyActor)
|
||||
|
@ -1018,8 +1024,14 @@ func PreparePostOutboxTest(t *testing.T, app *MockApplication, socialApp *MockSo
|
|||
fedApp.privateKey = func(boxIRI *url.URL) (crypto.PrivateKey, string, error) {
|
||||
return testPrivateKey, testPublicKeyId, nil
|
||||
}
|
||||
gotNewId := 0
|
||||
app.newId = func(c context.Context, t Typer) *url.URL {
|
||||
return testNewIRI
|
||||
gotNewId++
|
||||
if gotNewId == 1 {
|
||||
return testNewIRI
|
||||
} else {
|
||||
return testNewIRI2
|
||||
}
|
||||
}
|
||||
app.getOutbox = func(c context.Context, r *http.Request, rw RWType) (vocab.OrderedCollectionType, error) {
|
||||
if rw != ReadWrite {
|
||||
|
@ -1129,7 +1141,10 @@ func TestSocialPubber_PostOutbox(t *testing.T) {
|
|||
gotNewId := 0
|
||||
app.newId = func(c context.Context, t Typer) *url.URL {
|
||||
gotNewId++
|
||||
return testNewIRI
|
||||
if gotNewId == 1 {
|
||||
return testNewIRI
|
||||
}
|
||||
return testNewIRI2
|
||||
}
|
||||
gotOutbox := 0
|
||||
app.getOutbox = func(c context.Context, r *http.Request, rw RWType) (vocab.OrderedCollectionType, error) {
|
||||
|
@ -1143,12 +1158,15 @@ func TestSocialPubber_PostOutbox(t *testing.T) {
|
|||
}
|
||||
gotSet := 0
|
||||
var gotSetOutbox PubObject
|
||||
var gotSetActivity PubObject
|
||||
var gotSetCreateObject PubObject
|
||||
app.set = func(c context.Context, o PubObject) error {
|
||||
gotSet++
|
||||
if gotSet == 1 {
|
||||
gotSetCreateObject = o
|
||||
} else if gotSet == 2 {
|
||||
gotSetActivity = o
|
||||
} else if gotSet == 3 {
|
||||
gotSetOutbox = o
|
||||
}
|
||||
return nil
|
||||
|
@ -1175,16 +1193,18 @@ func TestSocialPubber_PostOutbox(t *testing.T) {
|
|||
t.Fatalf("expected %s, got %s", testOutboxURI, s)
|
||||
} else if gotSocialAPIVerifier != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotSocialAPIVerifier)
|
||||
} else if gotNewId != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotNewId)
|
||||
} else if gotNewId != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotNewId)
|
||||
} else if gotOutbox != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotOutbox)
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if l := gotSetOutbox.GetType(0).(string); l != "OrderedCollection" {
|
||||
t.Fatalf("expected %s, got %s", "OrderedCollection", l)
|
||||
} else if l := gotSetCreateObject.GetType(0).(string); l != "Note" {
|
||||
t.Fatalf("expected %s, got %s", "Note", l)
|
||||
} else if l := gotSetActivity.GetType(0).(string); l != "Create" {
|
||||
t.Fatalf("expected %s, got %s", "Create", l)
|
||||
} else if gotCreate != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotCreate)
|
||||
} else if iri := gotCreateCallback.Raw().GetActorObject(0).GetId(); *iri != *sallyIRI {
|
||||
|
@ -1202,15 +1222,6 @@ func TestSocialPubber_PostOutbox_SocialAPIVerified(t *testing.T) {
|
|||
app, socialApp, cb, p := NewSocialPubberTest(t)
|
||||
resp := httptest.NewRecorder()
|
||||
req := Sign(ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote)))))
|
||||
gotPublicKeyForOutbox := 0
|
||||
var gotPublicKeyId string
|
||||
var gotBoxIRI *url.URL
|
||||
socialApp.getPublicKeyForOutbox = func(c context.Context, publicKeyId string, boxIRI *url.URL) (crypto.PublicKey, httpsig.Algorithm, error) {
|
||||
gotPublicKeyForOutbox++
|
||||
gotPublicKeyId = publicKeyId
|
||||
gotBoxIRI = boxIRI
|
||||
return testPrivateKey.Public(), httpsig.RSA_SHA256, nil
|
||||
}
|
||||
gotVerifyForOutbox := 0
|
||||
var gotVerifiedOutbox *url.URL
|
||||
socialApp.getSocialAPIVerifier = func(c context.Context) SocialAPIVerifier {
|
||||
|
@ -1226,7 +1237,11 @@ func TestSocialPubber_PostOutbox_SocialAPIVerified(t *testing.T) {
|
|||
gotNewId := 0
|
||||
app.newId = func(c context.Context, t Typer) *url.URL {
|
||||
gotNewId++
|
||||
return testNewIRI
|
||||
if gotNewId == 1 {
|
||||
return testNewIRI
|
||||
} else {
|
||||
return testNewIRI2
|
||||
}
|
||||
}
|
||||
gotOutbox := 0
|
||||
app.getOutbox = func(c context.Context, r *http.Request, rw RWType) (vocab.OrderedCollectionType, error) {
|
||||
|
@ -1240,12 +1255,15 @@ func TestSocialPubber_PostOutbox_SocialAPIVerified(t *testing.T) {
|
|||
}
|
||||
gotSet := 0
|
||||
var gotSetOutbox PubObject
|
||||
var gotSetActivity PubObject
|
||||
var gotSetCreateObject PubObject
|
||||
app.set = func(c context.Context, o PubObject) error {
|
||||
gotSet++
|
||||
if gotSet == 1 {
|
||||
gotSetCreateObject = o
|
||||
} else if gotSet == 2 {
|
||||
gotSetActivity = o
|
||||
} else if gotSet == 3 {
|
||||
gotSetOutbox = o
|
||||
}
|
||||
return nil
|
||||
|
@ -1264,26 +1282,22 @@ func TestSocialPubber_PostOutbox_SocialAPIVerified(t *testing.T) {
|
|||
t.Fatalf("expected handled, got !handled")
|
||||
} else if resp.Code != http.StatusCreated {
|
||||
t.Fatalf("expected %d, got %d", http.StatusCreated, resp.Code)
|
||||
} else if gotPublicKeyForOutbox != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotPublicKeyForOutbox)
|
||||
} else if gotPublicKeyId != testPublicKeyId {
|
||||
t.Fatalf("expected %s, got %s", testPublicKeyId, gotPublicKeyId)
|
||||
} else if s := gotBoxIRI.String(); s != testOutboxURI {
|
||||
t.Fatalf("expected %s, got %s", testOutboxURI, s)
|
||||
} else if gotVerifyForOutbox != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotVerifyForOutbox)
|
||||
} else if o := gotVerifiedOutbox.String(); o != testOutboxURI {
|
||||
t.Fatalf("expected %s, got %s", testOutboxURI, o)
|
||||
} else if gotNewId != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotNewId)
|
||||
} else if gotNewId != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotNewId)
|
||||
} else if gotOutbox != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotOutbox)
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if l := gotSetOutbox.GetType(0).(string); l != "OrderedCollection" {
|
||||
t.Fatalf("expected %s, got %s", "OrderedCollection", l)
|
||||
} else if l := gotSetCreateObject.GetType(0).(string); l != "Note" {
|
||||
t.Fatalf("expected %s, got %s", "Note", l)
|
||||
} else if l := gotSetActivity.GetType(0).(string); l != "Create" {
|
||||
t.Fatalf("expected %s, got %s", "Create", l)
|
||||
} else if gotCreate != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotCreate)
|
||||
} else if iri := gotCreateCallback.Raw().GetActorObject(0).GetId(); *iri != *sallyIRI {
|
||||
|
@ -1666,7 +1680,11 @@ func TestPubber_PostOutbox(t *testing.T) {
|
|||
gotNewId := 0
|
||||
app.MockFederateApp.newId = func(c context.Context, t Typer) *url.URL {
|
||||
gotNewId++
|
||||
return testNewIRI
|
||||
if gotNewId == 1 {
|
||||
return testNewIRI
|
||||
} else {
|
||||
return testNewIRI2
|
||||
}
|
||||
}
|
||||
gotOutbox := 0
|
||||
app.MockFederateApp.getOutbox = func(c context.Context, r *http.Request, rw RWType) (vocab.OrderedCollectionType, error) {
|
||||
|
@ -1680,12 +1698,15 @@ func TestPubber_PostOutbox(t *testing.T) {
|
|||
}
|
||||
gotSet := 0
|
||||
var gotSetOutbox PubObject
|
||||
var gotSetActivity PubObject
|
||||
var gotSetCreateObject PubObject
|
||||
app.MockFederateApp.set = func(c context.Context, o PubObject) error {
|
||||
gotSet++
|
||||
if gotSet == 1 {
|
||||
gotSetCreateObject = o
|
||||
} else if gotSet == 2 {
|
||||
gotSetActivity = o
|
||||
} else if gotSet == 3 {
|
||||
gotSetOutbox = o
|
||||
}
|
||||
return nil
|
||||
|
@ -1749,8 +1770,8 @@ func TestPubber_PostOutbox(t *testing.T) {
|
|||
t.Fatalf("expected %s, got %s", testOutboxURI, s)
|
||||
} else if gotSocialAPIVerifier != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotSocialAPIVerifier)
|
||||
} else if gotNewId != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotNewId)
|
||||
} else if gotNewId != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotNewId)
|
||||
} else if gotNewSigner != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotNewSigner)
|
||||
} else if gotPrivateKey != 1 {
|
||||
|
@ -1759,12 +1780,14 @@ func TestPubber_PostOutbox(t *testing.T) {
|
|||
t.Fatalf("expected %s, got %s", testOutboxURI, s)
|
||||
} else if gotOutbox != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotOutbox)
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if l := gotSetOutbox.GetType(0).(string); l != "OrderedCollection" {
|
||||
t.Fatalf("expected %s, got %s", "OrderedCollection", l)
|
||||
} else if l := gotSetCreateObject.GetType(0).(string); l != "Note" {
|
||||
t.Fatalf("expected %s, got %s", "Note", l)
|
||||
} else if l := gotSetActivity.GetType(0).(string); l != "Create" {
|
||||
t.Fatalf("expected %s, got %s", "Create", l)
|
||||
} else if gotCreate != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotCreate)
|
||||
} else if iri := gotCreateCallback.Raw().GetActorObject(0).GetId(); iri.String() != sallyIRIString {
|
||||
|
@ -4224,7 +4247,7 @@ func TestPostOutbox_WrapInCreateActivity(t *testing.T) {
|
|||
rawNote.AppendToObject(samActor)
|
||||
// Expected result
|
||||
expectedNote := &vocab.Note{}
|
||||
expectedNote.SetId(noteIRI)
|
||||
expectedNote.SetId(testNewIRI2)
|
||||
expectedNote.AppendNameString(noteName)
|
||||
expectedNote.AppendContentString("This is a simple note")
|
||||
expectedNote.AppendToObject(samActor)
|
||||
|
@ -4458,12 +4481,15 @@ func TestPostOutbox_Create_SetCreatedObject(t *testing.T) {
|
|||
}
|
||||
gotSet := 0
|
||||
var gotSetOutbox PubObject
|
||||
var gotSetActivity PubObject
|
||||
var gotSetCreate PubObject
|
||||
app.MockFederateApp.set = func(c context.Context, o PubObject) error {
|
||||
gotSet++
|
||||
if gotSet == 1 {
|
||||
gotSetCreate = o
|
||||
} else {
|
||||
} else if gotSet == 2 {
|
||||
gotSetActivity = o
|
||||
} else if gotSet == 3 {
|
||||
gotSetOutbox = o
|
||||
}
|
||||
return nil
|
||||
|
@ -4476,10 +4502,12 @@ func TestPostOutbox_Create_SetCreatedObject(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
} else if !handled {
|
||||
t.Fatalf("expected handled, got !handled")
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if err := PubObjectEquals(gotSetCreate, testClientExpectedNote); err != nil {
|
||||
t.Fatalf("unexpected callback object: %s", err)
|
||||
} else if err := PubObjectEquals(gotSetActivity, testClientExpectedCreateNote); err != nil {
|
||||
t.Fatalf("unexpected callback object: %s", err)
|
||||
} else if err := PubObjectEquals(gotSetOutbox, expectedOutbox); err != nil {
|
||||
t.Fatalf("unexpected callback object: %s", err)
|
||||
}
|
||||
|
@ -4606,8 +4634,8 @@ func TestPostOutbox_Update_DeleteSubFields(t *testing.T) {
|
|||
t.Fatalf("expected handled, got !handled")
|
||||
} else if gotGet != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotGet)
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if err := PubObjectEquals(gotSetObject, expectedUpdatedNote); err != nil {
|
||||
t.Fatalf("unexpected set object: %s", err)
|
||||
}
|
||||
|
@ -4661,8 +4689,8 @@ func TestPostOutbox_Update_DeleteFields(t *testing.T) {
|
|||
t.Fatalf("expected handled, got !handled")
|
||||
} else if gotGet != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotGet)
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if err := PubObjectEquals(gotSetObject, expectedUpdatedNote); err != nil {
|
||||
t.Fatalf("unexpected set object: %s", err)
|
||||
}
|
||||
|
@ -4737,8 +4765,8 @@ func TestPostOutbox_Update_DeleteSubFieldsMultipleObjects(t *testing.T) {
|
|||
t.Fatalf("expected handled, got !handled")
|
||||
} else if gotGet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotGet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if gotSet != 4 {
|
||||
t.Fatalf("expected %d, got %d", 4, gotSet)
|
||||
} else if err := PubObjectEquals(gotSetObject, expectedUpdatedNote); err != nil {
|
||||
t.Fatalf("unexpected set object: %s", err)
|
||||
} else if err := PubObjectEquals(gotSetObject2, expectedUpdatedNote2); err != nil {
|
||||
|
@ -4796,8 +4824,8 @@ func TestPostOutbox_Update_OverwriteUpdatedFields(t *testing.T) {
|
|||
t.Fatalf("expected handled, got !handled")
|
||||
} else if gotGet != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotGet)
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if err := PubObjectEquals(gotSetObject, expectedUpdatedNote); err != nil {
|
||||
t.Fatalf("unexpected set object: %s", err)
|
||||
}
|
||||
|
@ -4951,8 +4979,8 @@ func TestPostOutbox_Delete_SetsTombstone(t *testing.T) {
|
|||
t.Fatalf("expected handled, got !handled")
|
||||
} else if gotGet != 1 {
|
||||
t.Fatalf("expected %d, got %d", 1, gotGet)
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if err := PubObjectEquals(gotSetObject, expectedTombstone); err != nil {
|
||||
t.Fatalf("unexpected set object: %s", err)
|
||||
}
|
||||
|
@ -5344,8 +5372,8 @@ func TestPostOutbox_Add_AddsIfTargetOwnedAndAppCanAdd(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
} else if err := VocabSerializerEquals(canAddObj, testNote); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if err := PubObjectEquals(gotSetObj, expectedTarget); err != nil {
|
||||
t.Fatalf("unexpected set object: %s", err)
|
||||
}
|
||||
|
@ -5561,8 +5589,8 @@ func TestPostOutbox_Remove_RemoveIfTargetOwnedAndCanRemove(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
} else if err := VocabSerializerEquals(canRemoveObj, testNote); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if err := PubObjectEquals(gotSetObj, expectedTarget); err != nil {
|
||||
t.Fatalf("unexpected set object: %s", err)
|
||||
}
|
||||
|
@ -5754,8 +5782,8 @@ func TestPostOutbox_Like_AddsToLikedCollection(t *testing.T) {
|
|||
t.Fatalf("expected %d, got %d", 1, gotGet)
|
||||
} else if gotGetString := gotGetIri.String(); gotGetString != sallyIRIString {
|
||||
t.Fatalf("expected %s, got %s", noteURIString, sallyIRIString)
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if err := PubObjectEquals(gotSetObj, expectedActor); err != nil {
|
||||
t.Fatalf("set obj: %s", err)
|
||||
}
|
||||
|
@ -5801,8 +5829,8 @@ func TestPostOutbox_Like_DoesNotAddIfAlreadyLiked(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
} else if !handled {
|
||||
t.Fatalf("expected handled, got !handled")
|
||||
} else if gotSet != 2 {
|
||||
t.Fatalf("expected %d, got %d", 2, gotSet)
|
||||
} else if gotSet != 3 {
|
||||
t.Fatalf("expected %d, got %d", 3, gotSet)
|
||||
} else if err := PubObjectEquals(gotSetObj, expectedActor); err != nil {
|
||||
t.Fatalf("set obj: %s", err)
|
||||
}
|
||||
|
|
|
@ -35,7 +35,10 @@ const (
|
|||
digestDelimiter = "="
|
||||
)
|
||||
|
||||
var alternatives = []string{"application/activity+json"}
|
||||
var alternatives = []string{
|
||||
"application/activity+json",
|
||||
"application/ld+json; profile=https://www.w3.org/ns/activitystreams",
|
||||
}
|
||||
|
||||
func trimAll(s []string) []string {
|
||||
var r []string
|
||||
|
@ -45,24 +48,36 @@ func trimAll(s []string) []string {
|
|||
return r
|
||||
}
|
||||
|
||||
func headerEqualsOneOf(header string, acceptable []string) bool {
|
||||
sanitizedHeader := strings.Join(trimAll(strings.Split(header, ";")), ";")
|
||||
func headerContainsOneOf(header string, acceptable []string) bool {
|
||||
sanitizedHeaderValues := trimAll(strings.Split(header, ";"))
|
||||
sanitizedHeaderMap := make(map[string]bool, len(sanitizedHeaderValues))
|
||||
for _, s := range sanitizedHeaderValues {
|
||||
sanitizedHeaderMap[s] = true
|
||||
}
|
||||
found := false
|
||||
for _, v := range acceptable {
|
||||
if found {
|
||||
break
|
||||
}
|
||||
// Remove any number of whitespace after ;'s
|
||||
sanitizedV := strings.Join(trimAll(strings.Split(v, ";")), ";")
|
||||
if sanitizedHeader == sanitizedV {
|
||||
return true
|
||||
sanitizedAcceptableValues := trimAll(strings.Split(v, ";"))
|
||||
found = true
|
||||
for _, v := range sanitizedAcceptableValues {
|
||||
if has, ok := sanitizedHeaderMap[v]; !has || !ok {
|
||||
found = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return found
|
||||
}
|
||||
|
||||
func isActivityPubPost(r *http.Request) bool {
|
||||
return r.Method == "POST" && headerEqualsOneOf(r.Header.Get(contentTypeHeader), append([]string{postContentTypeHeader}, alternatives...))
|
||||
return r.Method == "POST" && headerContainsOneOf(r.Header.Get(contentTypeHeader), append([]string{postContentTypeHeader}, alternatives...))
|
||||
}
|
||||
|
||||
func isActivityPubGet(r *http.Request) bool {
|
||||
return r.Method == "GET" && headerEqualsOneOf(r.Header.Get(acceptHeader), append([]string{getAcceptHeader}, alternatives...))
|
||||
return r.Method == "GET" && headerContainsOneOf(r.Header.Get(acceptHeader), append([]string{getAcceptHeader}, alternatives...))
|
||||
}
|
||||
|
||||
// isPublic determines if a target is the Public collection as defined in the
|
||||
|
@ -126,6 +141,34 @@ type creds struct {
|
|||
pubKeyId string
|
||||
}
|
||||
|
||||
// dereferenceAsUser is meant to be used by the activity handlers that need to
|
||||
// handle IRI use cases where objects are expected. Returns an error if not
|
||||
// federating.
|
||||
func (f *federator) dereferenceAsUser(boxIRI, fetchIRI *url.URL) (obj vocab.ObjectType, err error) {
|
||||
if !f.EnableServer {
|
||||
err = fmt.Errorf("cannot dereference iri as user if not federating: %q", fetchIRI)
|
||||
return
|
||||
}
|
||||
creds := &creds{}
|
||||
creds.signer, err = f.FederateAPI.NewSigner()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
creds.privKey, creds.pubKeyId, err = f.FederateAPI.PrivateKey(boxIRI)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
resp, err := dereference(f.Client, fetchIRI, f.Agent, creds, f.Clock)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var m map[string]interface{}
|
||||
if err = json.Unmarshal(resp, &m); err != nil {
|
||||
return
|
||||
}
|
||||
return toAnyObject(m)
|
||||
}
|
||||
|
||||
// postToOutbox will attempt to send a POST request to the given URL with the
|
||||
// body set to the provided bytes.
|
||||
//
|
||||
|
@ -160,11 +203,27 @@ func postToOutbox(c HttpClient, b []byte, to *url.URL, agent string, creds *cred
|
|||
return nil
|
||||
}
|
||||
|
||||
// addNewIds will add new IDs not just for an activity, but all objects
|
||||
// contained within the activity if it is a Create activity.
|
||||
func (f *federator) addNewIds(c context.Context, a vocab.ActivityType) {
|
||||
newId := f.App.NewId(c, a)
|
||||
a.SetId(newId)
|
||||
if hasType(a, "Create") {
|
||||
for i := 0; i < a.ObjectLen(); i++ {
|
||||
if a.IsObject(i) {
|
||||
obj := a.GetObject(i)
|
||||
obj.SetId(f.App.NewId(c, obj))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// wrapInCreate will automatically wrap the provided object in a Create
|
||||
// activity. This will copy over the 'to', 'bto', 'cc', 'bcc', and 'audience'
|
||||
// properties. It will also copy over the published time if present.
|
||||
func (f *federator) wrapInCreate(o vocab.ObjectType, actor *url.URL) *vocab.Create {
|
||||
c := &vocab.Create{}
|
||||
c.AppendType("Create")
|
||||
c.AppendObject(o)
|
||||
c.AppendActorIRI(actor)
|
||||
if o.IsPublished() {
|
||||
|
@ -782,7 +841,7 @@ func stripHiddenRecipients(o deliverableObject) {
|
|||
o.RemoveBccObject(0)
|
||||
} else if o.IsBccLink(0) {
|
||||
o.RemoveBccLink(0)
|
||||
} else if o.IsBtoIRI(0) {
|
||||
} else if o.IsBccIRI(0) {
|
||||
o.RemoveBccIRI(0)
|
||||
}
|
||||
}
|
||||
|
@ -1290,6 +1349,7 @@ func (f *federator) addAllObjectsToActorCollection(ctx context.Context, getter g
|
|||
iri = c.GetActorIRI(i)
|
||||
}
|
||||
if !f.App.Owns(ctx, iri) {
|
||||
// TODO: Fetch or just store
|
||||
continue
|
||||
}
|
||||
var actor vocab.ObjectType
|
||||
|
@ -1395,6 +1455,7 @@ func (f *federator) addAllActorsToObjectCollection(ctx context.Context, getter g
|
|||
iri = c.GetObjectIRI(i)
|
||||
}
|
||||
if !f.App.Owns(ctx, iri) {
|
||||
// TODO: Fetch or just store
|
||||
continue
|
||||
}
|
||||
ownsAny = true
|
||||
|
@ -1532,6 +1593,9 @@ func (f *federator) addToOutbox(c context.Context, r *http.Request, m map[string
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.App.Set(c, activity); err != nil {
|
||||
return err
|
||||
}
|
||||
outbox.PrependOrderedItemsObject(activity)
|
||||
return f.App.Set(c, outbox)
|
||||
}
|
||||
|
@ -1957,3 +2021,15 @@ func isActivityType(t Typer) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasType(t Typer, kind string) bool {
|
||||
for i := 0; i < t.TypeLen(); i++ {
|
||||
v := t.GetType(i)
|
||||
if s, ok := v.(string); ok {
|
||||
if s == kind {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -154,3 +154,14 @@ func toAnyActivity(m map[string]interface{}) (o vocab.ActivityType, err error) {
|
|||
err = r.Deserialize(m)
|
||||
return
|
||||
}
|
||||
|
||||
func toAnyObject(m map[string]interface{}) (o vocab.ObjectType, err error) {
|
||||
r := &streams.Resolver{
|
||||
AnyObjectCallback: func(i vocab.ObjectType) error {
|
||||
o = i
|
||||
return nil
|
||||
},
|
||||
}
|
||||
err = r.Deserialize(m)
|
||||
return
|
||||
}
|
||||
|
|
読み込み中…
新しいイシューから参照