diff --git a/internal/api/activitypub/users/inboxpost.go b/internal/api/activitypub/users/inboxpost.go index 9f74d9c31..20bc434f1 100644 --- a/internal/api/activitypub/users/inboxpost.go +++ b/internal/api/activitypub/users/inboxpost.go @@ -19,32 +19,37 @@ import ( "errors" - "strings" "github.com/gin-gonic/gin" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" "github.com/superseriousbusiness/gotosocial/internal/gtserror" //nolint:typecheck + "github.com/superseriousbusiness/gotosocial/internal/log" ) +var errMalformed = errors.New("inbox POST could not be processed as an ActivityPub request; request may be malformed or insufficient") // mood + // InboxPOSTHandler deals with incoming POST requests to an actor's inbox. // Eg., POST to https://example.org/users/whatever/inbox. func (m *Module) InboxPOSTHandler(c *gin.Context) { - // usernames on our instance are always lowercase - requestedUsername := strings.ToLower(c.Param(UsernameKey)) - if requestedUsername == "" { - err := errors.New("no username specified in request") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + handled, err := m.processor.Fedi().InboxPost(apiutil.TransferSignatureContext(c), c.Writer, c.Request) + if err != nil { + // If a WithCode was returned to us, we can be nice and + // try to return something informative to the caller. + if withCode := new(gtserror.WithCode); errors.As(err, withCode) { + apiutil.ErrorHandler(c, *withCode, m.processor.InstanceGetV1) + return + } + + // Something else went wrong, log the error but don't + // return it to the caller, to avoid leaking internals. + log.WithContext(c.Request.Context()).Errorf("returning Bad Request to caller, err was: %q", err) + apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err), m.processor.InstanceGetV1) return } - if posted, err := m.processor.Fedi().InboxPost(apiutil.TransferSignatureContext(c), c.Writer, c.Request); err != nil { - if withCode, ok := err.(gtserror.WithCode); ok { - apiutil.ErrorHandler(c, withCode, m.processor.InstanceGetV1) - } else { - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) - } - } else if !posted { - err := errors.New("unable to process request") - apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1) + if !handled { + // Wasn't a type we could understand; we should let the caller know this. + withCode := gtserror.NewErrorBadRequest(errMalformed, errMalformed.Error()) + apiutil.ErrorHandler(c, withCode, m.processor.InstanceGetV1) } } diff --git a/internal/api/activitypub/users/inboxpost_test.go b/internal/api/activitypub/users/inboxpost_test.go index 26c4029a2..aa6c25fc7 100644 --- a/internal/api/activitypub/users/inboxpost_test.go +++ b/internal/api/activitypub/users/inboxpost_test.go @@ -21,6 +21,8 @@ "bytes" "context" "encoding/json" + "fmt" + "io" "io/ioutil" "net/http" "net/http/httptest" @@ -35,6 +37,7 @@ "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/api/activitypub/users" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/testrig" @@ -44,11 +47,79 @@ type InboxPostTestSuite struct { UserStandardTestSuite } -func (suite *InboxPostTestSuite) TestPostBlock() { - blockingAccount := suite.testAccounts["remote_account_1"] - blockedAccount := suite.testAccounts["local_account_1"] - blockURI := testrig.URLMustParse("http://fossbros-anonymous.io/users/foss_satan/blocks/01FG9C441MCTW3R2W117V2PQK3") +func (suite *InboxPostTestSuite) inboxPost( + activity pub.Activity, + requestingAccount *gtsmodel.Account, + targetAccount *gtsmodel.Account, + expectedHTTPStatus int, + expectedBody string, +) { + var ( + recorder = httptest.NewRecorder() + ctx, _ = testrig.CreateGinTestContext(recorder, nil) + ) + // Prepare the requst body bytes. + bodyI, err := ap.Serialize(activity) + if err != nil { + suite.FailNow(err.Error()) + } + + b, err := json.Marshal(bodyI) + if err != nil { + suite.FailNow(err.Error()) + } + + // Prepare signature headers for this Activity. + signature, digestHeader, dateHeader := testrig.GetSignatureForActivity( + activity, + requestingAccount.PublicKeyURI, + requestingAccount.PrivateKey, + testrig.URLMustParse(targetAccount.InboxURI), + ) + + // Put the request together. + ctx.AddParam(users.UsernameKey, targetAccount.Username) + ctx.Request = httptest.NewRequest(http.MethodPost, targetAccount.InboxURI, bytes.NewReader(b)) + ctx.Request.Header.Set("Signature", signature) + ctx.Request.Header.Set("Date", dateHeader) + ctx.Request.Header.Set("Digest", digestHeader) + ctx.Request.Header.Set("Content-Type", "application/activity+json") + + // Pass the context through signature check + // middleware first to populate it appropriately. + suite.signatureCheck(ctx) + + // Trigger the function being tested. + suite.userModule.InboxPOSTHandler(ctx) + + // Read the result. + result := recorder.Result() + defer result.Body.Close() + + b, err = io.ReadAll(result.Body) + if err != nil { + suite.FailNow(err.Error()) + } + + errs := gtserror.MultiError{} + + // Check expected code + body. + if resultCode := recorder.Code; expectedHTTPStatus != resultCode { + errs = append(errs, fmt.Sprintf("expected %d got %d", expectedHTTPStatus, resultCode)) + } + + // If we got an expected body, return early. + if expectedBody != "" && string(b) != expectedBody { + errs = append(errs, fmt.Sprintf("expected %s got %s", expectedBody, string(b))) + } + + if err := errs.Combine(); err != nil { + suite.FailNow("", "%v (body %s)", err, string(b)) + } +} + +func (suite *InboxPostTestSuite) newBlock(blockID string, blockingAccount *gtsmodel.Account, blockedAccount *gtsmodel.Account) vocab.ActivityStreamsBlock { block := streams.NewActivityStreamsBlock() // set the actor property to the block-ing account's URI @@ -59,7 +130,7 @@ func (suite *InboxPostTestSuite) TestPostBlock() { // set the ID property to the blocks's URI idProp := streams.NewJSONLDIdProperty() - idProp.Set(blockURI) + idProp.Set(testrig.URLMustParse(blockID)) block.SetJSONLDId(idProp) // set the object property to the target account's URI @@ -74,57 +145,32 @@ func (suite *InboxPostTestSuite) TestPostBlock() { toProp.AppendIRI(toIRI) block.SetActivityStreamsTo(toProp) - targetURI := testrig.URLMustParse(blockedAccount.InboxURI) + return block +} - signature, digestHeader, dateHeader := testrig.GetSignatureForActivity(block, blockingAccount.PublicKeyURI, blockingAccount.PrivateKey, targetURI) - bodyI, err := ap.Serialize(block) - suite.NoError(err) +func (suite *InboxPostTestSuite) TestPostBlock() { + var ( + requestingAccount = suite.testAccounts["remote_account_1"] + targetAccount = suite.testAccounts["local_account_1"] + activityID = requestingAccount.URI + "/some-new-activity/01FG9C441MCTW3R2W117V2PQK3" + ) - bodyJson, err := json.Marshal(bodyI) - suite.NoError(err) - body := bytes.NewReader(bodyJson) - - tc := testrig.NewTestTransportController(&suite.state, testrig.NewMockHTTPClient(nil, "../../../../testrig/media")) - federator := testrig.NewTestFederator(&suite.state, tc, suite.mediaManager) - emailSender := testrig.NewEmailSender("../../../../web/template/", nil) - processor := testrig.NewTestProcessor(&suite.state, federator, emailSender, suite.mediaManager) - userModule := users.New(processor) - suite.NoError(processor.Start()) - - // setup request - recorder := httptest.NewRecorder() - ctx, _ := testrig.CreateGinTestContext(recorder, nil) - ctx.Request = httptest.NewRequest(http.MethodPost, targetURI.String(), body) // the endpoint we're hitting - ctx.Request.Header.Set("Signature", signature) - ctx.Request.Header.Set("Date", dateHeader) - ctx.Request.Header.Set("Digest", digestHeader) - ctx.Request.Header.Set("Content-Type", "application/activity+json") - - // we need to pass the context through signature check first to set appropriate values on it - suite.signatureCheck(ctx) - - // normally the router would populate these params from the path values, - // but because we're calling the function directly, we need to set them manually. - ctx.Params = gin.Params{ - gin.Param{ - Key: users.UsernameKey, - Value: blockedAccount.Username, - }, - } - - // trigger the function being tested - userModule.InboxPOSTHandler(ctx) - - result := recorder.Result() - defer result.Body.Close() - b, err := ioutil.ReadAll(result.Body) - suite.NoError(err) - suite.Empty(b) + block := suite.newBlock(activityID, requestingAccount, targetAccount) + suite.inboxPost(block, requestingAccount, targetAccount, http.StatusAccepted, "") // there should be a block in the database now between the accounts - dbBlock, err := suite.db.GetBlock(context.Background(), blockingAccount.ID, blockedAccount.ID) - suite.NoError(err) - suite.NotNil(dbBlock) + var ( + dbBlock *gtsmodel.Block + err error + ) + + if !testrig.WaitFor(func() bool { + dbBlock, err = suite.db.GetBlock(context.Background(), requestingAccount.ID, targetAccount.ID) + return err == nil && dbBlock != nil + }) { + suite.FailNow("timed out waiting for block to be created") + } + suite.WithinDuration(time.Now(), dbBlock.CreatedAt, 30*time.Second) suite.WithinDuration(time.Now(), dbBlock.UpdatedAt, 30*time.Second) suite.Equal("http://fossbros-anonymous.io/users/foss_satan/blocks/01FG9C441MCTW3R2W117V2PQK3", dbBlock.URI) diff --git a/internal/federation/federatingactor.go b/internal/federation/federatingactor.go index 33ae38220..cbd0b6bd5 100644 --- a/internal/federation/federatingactor.go +++ b/internal/federation/federatingactor.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -152,7 +153,7 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr activity, ok := t.(pub.Activity) if !ok { - err = fmt.Errorf("ActivityStreams value with type %T is not a pub.Activity", t) + err = fmt.Errorf("PostInboxScheme: ActivityStreams value with type %T is not a pub.Activity", t) return true, err } @@ -199,20 +200,22 @@ func (f *federatingActor) PostInboxScheme(ctx context.Context, w http.ResponseWr // target properties needed to be populated, but weren't. // // Send the rejection to the peer. - if err == pub.ErrObjectRequired || err == pub.ErrTargetRequired { + if errors.Is(err, pub.ErrObjectRequired) || errors.Is(err, pub.ErrTargetRequired) { l.Debugf("malformed incoming Activity: %s", err) w.WriteHeader(http.StatusBadRequest) return true, nil } + + // There's been some real error. err = fmt.Errorf("PostInboxScheme: error calling sideEffectActor.PostInbox: %w", err) return true, err } - // Our side effects are complete, now delegate determining whether to do inbox forwarding, as well as the action to do it. - if err := f.sideEffectActor.InboxForwarding(ctx, inboxID, activity); err != nil { - err = fmt.Errorf("PostInboxScheme: error calling sideEffectActor.InboxForwarding: %w", err) - return true, err - } + // // Our side effects are complete, now delegate determining whether to do inbox forwarding, as well as the action to do it. + // if err := f.sideEffectActor.InboxForwarding(ctx, inboxID, activity); err != nil { + // err = fmt.Errorf("PostInboxScheme: error calling sideEffectActor.InboxForwarding: %w", err) + // return true, err + // } // Request is now undergoing processing. // Respond with an Accepted status. diff --git a/internal/federation/federatingdb/block.go b/internal/federation/federatingdb/block.go new file mode 100644 index 000000000..0e1ba1e4a --- /dev/null +++ b/internal/federation/federatingdb/block.go @@ -0,0 +1,41 @@ +package federatingdb + +import ( + "context" + "fmt" + + "github.com/superseriousbusiness/activity/streams/vocab" + "github.com/superseriousbusiness/gotosocial/internal/ap" + "github.com/superseriousbusiness/gotosocial/internal/id" + "github.com/superseriousbusiness/gotosocial/internal/messages" +) + +func (f *federatingDB) Block(ctx context.Context, block vocab.ActivityStreamsBlock) error { + receivingAccount, _ := extractFromCtx(ctx) + if receivingAccount == nil { + // If the receiving account wasn't set on the context, that means this request didn't pass + // through the API, but came from inside GtS as the result of another activity on this instance. That being so, + // we can safely just ignore this activity, since we know we've already processed it elsewhere. + return nil + } + + gtsBlock, err := f.typeConverter.ASBlockToBlock(ctx, block) + if err != nil { + return fmt.Errorf("Block: could not convert Block to gts model block") + } + + gtsBlock.ID = id.NewULID() + + if err := f.state.DB.PutBlock(ctx, gtsBlock); err != nil { + return fmt.Errorf("Block: database error inserting block: %s", err) + } + + f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{ + APObjectType: ap.ActivityBlock, + APActivityType: ap.ActivityCreate, + GTSModel: block, + ReceivingAccount: receivingAccount, + }) + + return nil +} diff --git a/internal/federation/federatingdb/create.go b/internal/federation/federatingdb/create.go index f68279d37..0ac41610a 100644 --- a/internal/federation/federatingdb/create.go +++ b/internal/federation/federatingdb/create.go @@ -21,13 +21,13 @@ "context" "errors" "fmt" - "strings" "codeberg.org/gruf/go-kv" "codeberg.org/gruf/go-logger/v2/level" "github.com/superseriousbusiness/activity/streams/vocab" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/log" @@ -66,9 +66,6 @@ func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error { } switch asType.GetTypeName() { - case ap.ActivityBlock: - // BLOCK SOMETHING - return f.activityBlock(ctx, asType, receivingAccount, requestingAccount) case ap.ActivityCreate: // CREATE SOMETHING return f.activityCreate(ctx, asType, receivingAccount, requestingAccount) @@ -122,41 +119,52 @@ func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, rec func (f *federatingDB) activityCreate(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { create, ok := asType.(vocab.ActivityStreamsCreate) if !ok { - return errors.New("activityCreate: could not convert type to create") + return errors.New("activityCreate: could resolve %T to Create") } - // create should have an object + // Create should have an object. object := create.GetActivityStreamsObject() if object == nil { return errors.New("Create had no Object") } - errs := []string{} - // iterate through the object(s) to see what we're meant to be creating - for objectIter := object.Begin(); objectIter != object.End(); objectIter = objectIter.Next() { - asObjectType := objectIter.GetType() - if asObjectType == nil { - // currently we can't do anything with just a Create of something that's not an Object with a type - // TODO: process a Create with an Object that's just a URI or something - errs = append(errs, "object of Create was not a Type") + // Iterate through the Object(s) to see what we're meant to be creating. + errs := make(gtserror.MultiError, 0, object.Len()) + for iter := object.Begin(); iter != object.End(); iter = iter.Next() { + objectType := iter.GetType() + if objectType == nil { + // Currently we can't do anything with just a Create + // of something that's not an Object with a type. + errs.Append(errors.New("object of Create was not a Type")) continue } - // we have a type -- what is it? - asObjectTypeName := asObjectType.GetTypeName() - switch asObjectTypeName { + // Process object according to its type. + // TODO: possibly add more types here. + switch typeName := objectType.GetTypeName(); typeName { case ap.ObjectNote: // CREATE A NOTE - if err := f.createNote(ctx, objectIter.GetActivityStreamsNote(), receivingAccount, requestingAccount); err != nil { - errs = append(errs, err.Error()) + if err := f.createNote( + ctx, + iter.GetActivityStreamsNote(), + receivingAccount, + requestingAccount, + ); err != nil { + errs.Append(err) } default: - errs = append(errs, fmt.Sprintf("received an object on a Create that we couldn't handle: %s", asObjectType.GetTypeName())) + log.WithContext(ctx). + WithFields(kv.Fields{ + {"receivingAccount", receivingAccount.URI}, + {"requestingAccount", requestingAccount.URI}, + {"typeName", typeName}, + }...). + Debug("Object of Create was a type we couldn't handle") } } if len(errs) != 0 { - return fmt.Errorf("activityCreate: one or more errors while processing activity: %s", strings.Join(errs, "; ")) + return fmt.Errorf("activityCreate: one or more errors while processing activity: %w", errs.Combine()) } return nil @@ -164,43 +172,44 @@ func (f *federatingDB) activityCreate(ctx context.Context, asType vocab.Type, re // createNote handles a Create activity with a Note type. func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStreamsNote, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { - l := log.WithContext(ctx). - WithFields(kv.Fields{ - {"receivingAccount", receivingAccount.URI}, - {"requestingAccount", requestingAccount.URI}, - }...) - - // Check if we have a forward. - // In other words, was the note posted to our inbox by at least one actor who actually created the note, or are they just forwarding it? - forward := true - - // note should have an attributedTo + // Note must have an attributedTo for us to process it. noteAttributedTo := note.GetActivityStreamsAttributedTo() if noteAttributedTo == nil { return errors.New("createNote: note had no attributedTo") } - // compare the attributedTo(s) with the actor who posted this to our inbox - for attributedToIter := noteAttributedTo.Begin(); attributedToIter != noteAttributedTo.End(); attributedToIter = attributedToIter.Next() { - if !attributedToIter.IsIRI() { + // Check if we have a forward. In other words, was the + // note posted to our inbox by at least one actor who + // created the note, or are they just forwarding it? + forward := true + + // Compare the attributedTo(s) with the URI of the Actor + // who posted this to our inbox. If the actor who posted + // the Note to our inbox is the same as at least one of + // the creators of the Note, then it's not a forward. + for iter := noteAttributedTo.Begin(); iter != noteAttributedTo.End(); iter = iter.Next() { + if !iter.IsIRI() { continue } - iri := attributedToIter.GetIRI() - if requestingAccount.URI == iri.String() { - // at least one creator of the note, and the actor who posted the note to our inbox, are the same, so it's not a forward + + if iri := iter.GetIRI(); iri != nil && iri.String() == requestingAccount.URI { forward = false + break } } - // If we do have a forward, we should ignore the content for now and just dereference based on the URL/ID of the note instead, to get the note straight from the horse's mouth + // If we do have a forward, we should ignore the content for + // now and just dereference based on the URL/ID of the note + // instead, to get the content straight from the poster's mouth. if forward { - l.Trace("note is a forward") id := note.GetJSONLDId() if !id.IsIRI() { - // if the note id isn't an IRI, there's nothing we can do here + // If the ID isn't an IRI, then firstly this + // is weird, and secondly we can't process it. return nil } - // pass the note iri into the processor and have it do the dereferencing instead of doing it here + + // Process the Note asynchronously, we're done here. f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{ APObjectType: ap.ObjectNote, APActivityType: ap.ActivityCreate, @@ -209,33 +218,39 @@ func (f *federatingDB) createNote(ctx context.Context, note vocab.ActivityStream GTSModel: nil, ReceivingAccount: receivingAccount, }) + return nil } - // if we reach this point, we know it's not a forwarded status, so proceed with processing it as normal - + // If we reach this point, we know the status wasn't forwarded + // to us, but was delivered by at least one of the Actors who + // created it, so proceed with processing it as normal. status, err := f.typeConverter.ASStatusToStatus(ctx, note) if err != nil { - return fmt.Errorf("createNote: error converting note to status: %s", err) + return fmt.Errorf("createNote: error converting note to status: %w", err) } - // id the status based on the time it was created + // id the status based on the time it was created; + // this allows for backdating of statuses. statusID, err := id.NewULIDFromTime(status.CreatedAt) if err != nil { - return err + return fmt.Errorf("createNote: error creating id for note: %w", err) } status.ID = statusID if err := f.state.DB.PutStatus(ctx, status); err != nil { if errors.Is(err, db.ErrAlreadyExists) { - // the status already exists in the database, which means we've already handled everything else, - // so we can just return nil here and be done with it. + // The status already exists in the database, which + // means we've already handled everything else, so + // we can just return nil here and be done with it. return nil } - // an actual error has happened - return fmt.Errorf("createNote: database error inserting status: %s", err) + + // An actual error has happened. + return fmt.Errorf("createNote: database error inserting status: %w", err) } + // Do further processing asynchronously. f.state.Workers.EnqueueFederator(ctx, messages.FromFederator{ APObjectType: ap.ObjectNote, APActivityType: ap.ActivityCreate, diff --git a/internal/federation/federatingdb/db.go b/internal/federation/federatingdb/db.go index bc1415dd5..f9da5d543 100644 --- a/internal/federation/federatingdb/db.go +++ b/internal/federation/federatingdb/db.go @@ -34,6 +34,7 @@ type DB interface { Accept(ctx context.Context, accept vocab.ActivityStreamsAccept) error Reject(ctx context.Context, reject vocab.ActivityStreamsReject) error Announce(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error + Block(ctx context.Context, block vocab.ActivityStreamsBlock) error } // FederatingDB uses the underlying DB interface to implement the go-fed pub.Database interface. diff --git a/internal/federation/federatingdb/follow.go b/internal/federation/federatingdb/follow.go new file mode 100644 index 000000000..b9e06dabf --- /dev/null +++ b/internal/federation/federatingdb/follow.go @@ -0,0 +1,3 @@ +package federatingdb + + diff --git a/internal/federation/federatingprotocol.go b/internal/federation/federatingprotocol.go index d7e598edc..2ca3eb3dc 100644 --- a/internal/federation/federatingprotocol.go +++ b/internal/federation/federatingprotocol.go @@ -378,6 +378,9 @@ func(ctx context.Context, reject vocab.ActivityStreamsReject) error { func(ctx context.Context, announce vocab.ActivityStreamsAnnounce) error { return f.FederatingDB().Announce(ctx, announce) }, + func(ctx context.Context, block vocab.ActivityStreamsBlock) error { + return f.FederatingDB().Block(ctx, block) + }, } return diff --git a/internal/processing/fedi/collections.go b/internal/processing/fedi/collections.go index a56b001b7..621a27cfc 100644 --- a/internal/processing/fedi/collections.go +++ b/internal/processing/fedi/collections.go @@ -21,7 +21,6 @@ "context" "errors" "fmt" - "net/http" "net/url" "github.com/superseriousbusiness/gotosocial/internal/ap" @@ -29,20 +28,6 @@ "github.com/superseriousbusiness/gotosocial/internal/gtserror" ) -// InboxPost handles POST requests to a user's inbox for new activitypub messages. -// -// InboxPost returns true if the request was handled as an ActivityPub POST to an actor's inbox. -// If false, the request was not an ActivityPub request and may still be handled by the caller in another way, such as serving a web page. -// -// If the error is nil, then the ResponseWriter's headers and response has already been written. If a non-nil error is returned, then no response has been written. -// -// If the Actor was constructed with the Federated Protocol enabled, side effects will occur. -// -// If the Federated Protocol is not enabled, writes the http.StatusMethodNotAllowed status code in the response. No side effects occur. -func (p *Processor) InboxPost(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { - return p.federator.FederatingActor().PostInbox(ctx, w, r) -} - // OutboxGet returns the activitypub representation of a local user's outbox. // This contains links to PUBLIC posts made by this user. func (p *Processor) OutboxGet(ctx context.Context, requestedUsername string, page bool, maxID string, minID string) (interface{}, gtserror.WithCode) { diff --git a/internal/processing/fedi/postinbox.go b/internal/processing/fedi/postinbox.go new file mode 100644 index 000000000..d03b4e480 --- /dev/null +++ b/internal/processing/fedi/postinbox.go @@ -0,0 +1,37 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package fedi + +import ( + "context" + "net/http" +) + +// InboxPost handles POST requests to a user's inbox for new activitypub messages. +// +// InboxPost returns true if the request was handled as an ActivityPub POST to an actor's inbox. +// If false, the request was not an ActivityPub request and may still be handled by the caller in another way, such as serving a web page. +// +// If the error is nil, then the ResponseWriter's headers and response has already been written. If a non-nil error is returned, then no response has been written. +// +// If the Actor was constructed with the Federated Protocol enabled, side effects will occur. +// +// If the Federated Protocol is not enabled, writes the http.StatusMethodNotAllowed status code in the response. No side effects occur. +func (p *Processor) InboxPost(ctx context.Context, w http.ResponseWriter, r *http.Request) (bool, error) { + return p.federator.FederatingActor().PostInbox(ctx, w, r) +}