diff --git a/pub/fed.go b/pub/fed.go index 6e616e8..eeacc8b 100644 --- a/pub/fed.go +++ b/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) } diff --git a/pub/fed_test.go b/pub/fed_test.go index 754a03c..6a49f7a 100644 --- a/pub/fed_test.go +++ b/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) } diff --git a/pub/internal.go b/pub/internal.go index 8647799..5fe6384 100644 --- a/pub/internal.go +++ b/pub/internal.go @@ -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 +} diff --git a/pub/resolvers.go b/pub/resolvers.go index 98ad634..f731880 100644 --- a/pub/resolvers.go +++ b/pub/resolvers.go @@ -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 +}