summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/superseriousbusiness/activity/pub
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/codeberg.org/superseriousbusiness/activity/pub')
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/README.md270
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/activity.go49
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/actor.go128
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/base_actor.go475
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/clock.go11
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/common_behavior.go90
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/database.go152
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/delegate_actor.go249
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/doc.go9
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/federating_protocol.go125
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/federating_wrapped_callbacks.go917
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/handlers.go115
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/property_interfaces.go118
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/side_effect_actor.go1047
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/social_protocol.go83
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/social_wrapped_callbacks.go534
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/transport.go219
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/util.go1077
-rw-r--r--vendor/codeberg.org/superseriousbusiness/activity/pub/version.go15
19 files changed, 5683 insertions, 0 deletions
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/README.md b/vendor/codeberg.org/superseriousbusiness/activity/pub/README.md
new file mode 100644
index 000000000..bb9a49b05
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/README.md
@@ -0,0 +1,270 @@
+# pub
+
+Implements the Social and Federating Protocols in the ActivityPub specification.
+
+## Reference & Tutorial
+
+The [go-fed website](https://go-fed.org/) contains tutorials and reference
+materials, in addition to the rest of this README.
+
+## How To Use
+
+```
+go get github.com/go-fed/activity
+```
+
+The root of all ActivityPub behavior is the `Actor`, which requires you to
+implement a few interfaces:
+
+```golang
+import (
+ "codeberg.org/superseriousbusiness/activity/pub"
+)
+
+type myActivityPubApp struct { /* ... */ }
+type myAppsDatabase struct { /* ... */ }
+type myAppsClock struct { /* ... */ }
+
+var (
+ // Your app will implement pub.CommonBehavior, and either
+ // pub.SocialProtocol, pub.FederatingProtocol, or both.
+ myApp = &myActivityPubApp{}
+ myCommonBehavior pub.CommonBehavior = myApp
+ mySocialProtocol pub.SocialProtocol = myApp
+ myFederatingProtocol pub.FederatingProtocol = myApp
+ // Your app's database implementation.
+ myDatabase pub.Database = &myAppsDatabase{}
+ // Your app's clock.
+ myClock pub.Clock = &myAppsClock{}
+)
+
+// Only support the C2S Social protocol
+actor := pub.NewSocialActor(
+ myCommonBehavior,
+ mySocialProtocol,
+ myDatabase,
+ myClock)
+// OR
+//
+// Only support S2S Federating protocol
+actor = pub.NewFederatingActor(
+ myCommonBehavior,
+ myFederatingProtocol,
+ myDatabase,
+ myClock)
+// OR
+//
+// Support both C2S Social and S2S Federating protocol.
+actor = pub.NewActor(
+ myCommonBehavior,
+ mySocialProtocol,
+ myFederatingProtocol,
+ myDatabase,
+ myClock)
+```
+
+Next, hook the `Actor` into your web server:
+
+```golang
+// The application's actor
+var actor pub.Actor
+var outboxHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
+ c := context.Background()
+ // Populate c with request-specific information
+ if handled, err := actor.PostOutbox(c, w, r); err != nil {
+ // Write to w
+ return
+ } else if handled {
+ return
+ } else if handled, err = actor.GetOutbox(c, w, r); err != nil {
+ // Write to w
+ return
+ } else if handled {
+ return
+ }
+ // else:
+ //
+ // Handle non-ActivityPub request, such as serving a webpage.
+}
+var inboxHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
+ c := context.Background()
+ // Populate c with request-specific information
+ if handled, err := actor.PostInbox(c, w, r); err != nil {
+ // Write to w
+ return
+ } else if handled {
+ return
+ } else if handled, err = actor.GetInbox(c, w, r); err != nil {
+ // Write to w
+ return
+ } else if handled {
+ return
+ }
+ // else:
+ //
+ // Handle non-ActivityPub request, such as serving a webpage.
+}
+// Add the handlers to a HTTP server
+serveMux := http.NewServeMux()
+serveMux.HandleFunc("/actor/outbox", outboxHandler)
+serveMux.HandleFunc("/actor/inbox", inboxHandler)
+var server http.Server
+server.Handler = serveMux
+```
+
+To serve ActivityStreams data:
+
+```golang
+myHander := pub.NewActivityStreamsHandler(myDatabase, myClock)
+var activityStreamsHandler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
+ c := context.Background()
+ // Populate c with request-specific information
+ if handled, err := myHandler(c, w, r); err != nil {
+ // Write to w
+ return
+ } else if handled {
+ return
+ }
+ // else:
+ //
+ // Handle non-ActivityPub request, such as serving a webpage.
+}
+serveMux.HandleFunc("/some/data/like/a/note", activityStreamsHandler)
+```
+
+### Dependency Injection
+
+Package `pub` relies on dependency injection to provide out-of-the-box support
+for ActivityPub. The interfaces to be satisfied are:
+
+* `CommonBehavior` - Behavior needed regardless of which Protocol is used.
+* `SocialProtocol` - Behavior needed for the Social Protocol.
+* `FederatingProtocol` - Behavior needed for the Federating Protocol.
+* `Database` - The data store abstraction, not tied to the `database/sql`
+package.
+* `Clock` - The server's internal clock.
+* `Transport` - Responsible for the network that serves requests and deliveries
+of ActivityStreams data. A `HttpSigTransport` type is provided.
+
+These implementations form the core of an application's behavior without
+worrying about the particulars and pitfalls of the ActivityPub protocol.
+Implementing these interfaces gives you greater assurance about being
+ActivityPub compliant.
+
+### Application Logic
+
+The `SocialProtocol` and `FederatingProtocol` are responsible for returning
+callback functions compatible with `streams.TypeResolver`. They also return
+`SocialWrappedCallbacks` and `FederatingWrappedCallbacks`, which are nothing
+more than a bundle of default behaviors for types like `Create`, `Update`, and
+so on.
+
+Applications will want to focus on implementing their specific behaviors in the
+callbacks, and have fine-grained control over customization:
+
+```golang
+// Implements the FederatingProtocol interface.
+//
+// This illustration can also be applied for the Social Protocol.
+func (m *myAppsFederatingProtocol) Callbacks(c context.Context) (wrapped pub.FederatingWrappedCallbacks, other []interface{}) {
+ // The context 'c' has request-specific logic and can be used to apply complex
+ // logic building the right behaviors, if desired.
+ //
+ // 'c' will later be passed through to the callbacks created below.
+ wrapped = pub.FederatingWrappedCallbacks{
+ Create: func(ctx context.Context, create vocab.ActivityStreamsCreate) error {
+ // This function is wrapped by default behavior.
+ //
+ // More application specific logic can be written here.
+ //
+ // 'ctx' will have request-specific information from the HTTP handler. It
+ // is the same as the 'c' passed to the Callbacks method.
+ // 'create' has, at this point, already triggered the recommended
+ // ActivityPub side effect behavior. The application can process it
+ // further as needed.
+ return nil
+ },
+ }
+ // The 'other' must contain functions that satisfy the signature pattern
+ // required by streams.JSONResolver.
+ //
+ // If they are not, at runtime errors will be returned to indicate this.
+ other = []interface{}{
+ // The FederatingWrappedCallbacks has default behavior for an "Update" type,
+ // but since we are providing this behavior in "other" and not in the
+ // FederatingWrappedCallbacks.Update member, we will entirely replace the
+ // default behavior provided by go-fed. Be careful that this still
+ // implements ActivityPub properly.
+ func(ctx context.Context, update vocab.ActivityStreamsUpdate) error {
+ // This function is NOT wrapped by default behavior.
+ //
+ // Application specific logic can be written here.
+ //
+ // 'ctx' will have request-specific information from the HTTP handler. It
+ // is the same as the 'c' passed to the Callbacks method.
+ // 'update' will NOT trigger the recommended ActivityPub side effect
+ // behavior. The application should do so in addition to any other custom
+ // side effects required.
+ return nil
+ },
+ // The "Listen" type has no default suggested behavior in ActivityPub, so
+ // this just makes this application able to handle "Listen" activities.
+ func(ctx context.Context, listen vocab.ActivityStreamsListen) error {
+ // This function is NOT wrapped by default behavior. There's not a
+ // FederatingWrappedCallbacks.Listen member to wrap.
+ //
+ // Application specific logic can be written here.
+ //
+ // 'ctx' will have request-specific information from the HTTP handler. It
+ // is the same as the 'c' passed to the Callbacks method.
+ // 'listen' can be processed with side effects as the application needs.
+ return nil
+ },
+ }
+ return
+}
+```
+
+The `pub` package supports applications that grow into more custom solutions by
+overriding the default behaviors as needed.
+
+### ActivityStreams Extensions: Future-Proofing An Application
+
+Package `pub` relies on the `streams.TypeResolver` and `streams.JSONResolver`
+code generated types. As new ActivityStreams extensions are developed and their
+code is generated, `pub` will automatically pick up support for these
+extensions.
+
+The steps to rapidly implement a new extension in a `pub` application are:
+
+1. Generate an OWL definition of the ActivityStreams extension. This definition
+could be the same one defining the vocabulary at the `@context` IRI.
+2. Run `astool` to autogenerate the golang types in the `streams` package.
+3. Implement the application's callbacks in the `FederatingProtocol.Callbacks`
+or `SocialProtocol.Callbacks` for the new behaviors needed.
+4. Build the application, which builds `pub`, with the newly generated `streams`
+code. No code changes in `pub` are required.
+
+Whether an author of an ActivityStreams extension or an application developer,
+these quick steps should reduce the barrier to adopion in a statically-typed
+environment.
+
+### DelegateActor
+
+For those that need a near-complete custom ActivityPub solution, or want to have
+that possibility in the future after adopting go-fed, the `DelegateActor`
+interface can be used to obtain an `Actor`:
+
+```golang
+// Use custom ActivityPub implementation
+actor = pub.NewCustomActor(
+ myDelegateActor,
+ isSocialProtocolEnabled,
+ isFederatedProtocolEnabled,
+ myAppsClock)
+```
+
+It does not guarantee that an implementation adheres to the ActivityPub
+specification. It acts as a stepping stone for applications that want to build
+up to a fully custom solution and not be locked into the `pub` package
+implementation.
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/activity.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/activity.go
new file mode 100644
index 000000000..8b3d5b486
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/activity.go
@@ -0,0 +1,49 @@
+package pub
+
+import (
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// Activity represents any ActivityStreams Activity type.
+//
+// The Activity types provided in the streams package implement this.
+type Activity interface {
+ // Activity is also a vocab.Type
+ vocab.Type
+ // GetActivityStreamsActor returns the "actor" property if it exists, and
+ // nil otherwise.
+ GetActivityStreamsActor() vocab.ActivityStreamsActorProperty
+ // GetActivityStreamsAudience returns the "audience" property if it
+ // exists, and nil otherwise.
+ GetActivityStreamsAudience() vocab.ActivityStreamsAudienceProperty
+ // GetActivityStreamsBcc returns the "bcc" property if it exists, and nil
+ // otherwise.
+ GetActivityStreamsBcc() vocab.ActivityStreamsBccProperty
+ // GetActivityStreamsBto returns the "bto" property if it exists, and nil
+ // otherwise.
+ GetActivityStreamsBto() vocab.ActivityStreamsBtoProperty
+ // GetActivityStreamsCc returns the "cc" property if it exists, and nil
+ // otherwise.
+ GetActivityStreamsCc() vocab.ActivityStreamsCcProperty
+ // GetActivityStreamsTo returns the "to" property if it exists, and nil
+ // otherwise.
+ GetActivityStreamsTo() vocab.ActivityStreamsToProperty
+ // GetActivityStreamsAttributedTo returns the "attributedTo" property if
+ // it exists, and nil otherwise.
+ GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty
+ // GetActivityStreamsObject returns the "object" property if it exists,
+ // and nil otherwise.
+ GetActivityStreamsObject() vocab.ActivityStreamsObjectProperty
+ // SetActivityStreamsActor sets the "actor" property.
+ SetActivityStreamsActor(i vocab.ActivityStreamsActorProperty)
+ // SetActivityStreamsObject sets the "object" property.
+ SetActivityStreamsObject(i vocab.ActivityStreamsObjectProperty)
+ // SetActivityStreamsTo sets the "to" property.
+ SetActivityStreamsTo(i vocab.ActivityStreamsToProperty)
+ // SetActivityStreamsBto sets the "bto" property.
+ SetActivityStreamsBto(i vocab.ActivityStreamsBtoProperty)
+ // SetActivityStreamsBcc sets the "bcc" property.
+ SetActivityStreamsBcc(i vocab.ActivityStreamsBccProperty)
+ // SetActivityStreamsAttributedTo sets the "attributedTo" property.
+ SetActivityStreamsAttributedTo(i vocab.ActivityStreamsAttributedToProperty)
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/actor.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/actor.go
new file mode 100644
index 000000000..a9b19d1c1
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/actor.go
@@ -0,0 +1,128 @@
+package pub
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// Actor represents ActivityPub's actor concept. It conceptually has an inbox
+// and outbox that receives either a POST or GET request, which triggers side
+// effects in the federating application.
+//
+// An Actor within an application may federate server-to-server (Federation
+// Protocol), client-to-server (Social API), or both. The Actor represents the
+// server in either use case.
+//
+// An actor can be created by calling NewSocialActor (only the Social Protocol
+// is supported), NewFederatingActor (only the Federating Protocol is
+// supported), NewActor (both are supported), or NewCustomActor (neither are).
+//
+// Not all Actors have the same behaviors depending on the constructor used to
+// create them. Refer to the constructor's documentation to determine the exact
+// behavior of the Actor on an application.
+//
+// The behaviors documented here are common to all Actors returned by any
+// constructor.
+type Actor interface {
+ // PostInbox 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.
+ //
+ // The request and data of your application will be interpreted as
+ // having an HTTPS protocol scheme.
+ PostInbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error)
+ // PostInboxScheme is similar to PostInbox, except clients are able to
+ // specify which protocol scheme to handle the incoming request and the
+ // data stored within the application (HTTP, HTTPS, etc).
+ PostInboxScheme(c context.Context, w http.ResponseWriter, r *http.Request, scheme string) (bool, error)
+ // GetInbox returns true if the request was handled as an ActivityPub
+ // GET 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 request is an ActivityPub request, the Actor will defer to the
+ // application to determine the correct authorization of the request and
+ // the resulting OrderedCollection to respond with. The Actor handles
+ // serializing this OrderedCollection and responding with the correct
+ // headers and http.StatusOK.
+ GetInbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error)
+ // PostOutbox returns true if the request was handled as an ActivityPub
+ // POST to an actor's outbox. 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 Social Protocol enabled, side
+ // effects will occur.
+ //
+ // If the Social Protocol is not enabled, writes the
+ // http.StatusMethodNotAllowed status code in the response. No side
+ // effects occur.
+ //
+ // If the Social and Federated Protocol are both enabled, it will handle
+ // the side effects of receiving an ActivityStream Activity, and then
+ // federate the Activity to peers.
+ //
+ // The request will be interpreted as having an HTTPS scheme.
+ PostOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error)
+ // PostOutboxScheme is similar to PostOutbox, except clients are able to
+ // specify which protocol scheme to handle the incoming request and the
+ // data stored within the application (HTTP, HTTPS, etc).
+ PostOutboxScheme(c context.Context, w http.ResponseWriter, r *http.Request, scheme string) (bool, error)
+ // GetOutbox returns true if the request was handled as an ActivityPub
+ // GET to an actor's outbox. If false, the request was not an
+ // ActivityPub request.
+ //
+ // 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 request is an ActivityPub request, the Actor will defer to the
+ // application to determine the correct authorization of the request and
+ // the resulting OrderedCollection to respond with. The Actor handles
+ // serializing this OrderedCollection and responding with the correct
+ // headers and http.StatusOK.
+ GetOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error)
+}
+
+// FederatingActor is an Actor that allows programmatically delivering an
+// Activity to a federating peer.
+type FederatingActor interface {
+ Actor
+ // Send a federated activity.
+ //
+ // The provided url must be the outbox of the sender. All processing of
+ // the activity occurs similarly to the C2S flow:
+ // - If t is not an Activity, it is wrapped in a Create activity.
+ // - A new ID is generated for the activity.
+ // - The activity is added to the specified outbox.
+ // - The activity is prepared and delivered to recipients.
+ //
+ // Note that this function will only behave as expected if the
+ // implementation has been constructed to support federation. This
+ // method will guaranteed work for non-custom Actors. For custom actors,
+ // care should be used to not call this method if only C2S is supported.
+ Send(c context.Context, outbox *url.URL, t vocab.Type) (Activity, error)
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/base_actor.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/base_actor.go
new file mode 100644
index 000000000..c509383d6
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/base_actor.go
@@ -0,0 +1,475 @@
+package pub
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "codeberg.org/superseriousbusiness/activity/streams"
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// baseActor must satisfy the Actor interface.
+var _ Actor = &baseActor{}
+
+// baseActor is an application-independent ActivityPub implementation. It does
+// not implement the entire protocol, and relies on a delegate to do so. It
+// only implements the part of the protocol that is side-effect-free, allowing
+// an existing application to write a DelegateActor that glues their application
+// into the ActivityPub world.
+//
+// It is preferred to use a DelegateActor provided by this library, so that the
+// application does not need to worry about the ActivityPub implementation.
+type baseActor struct {
+ // delegate contains application-specific delegation logic.
+ delegate DelegateActor
+ // enableSocialProtocol enables or disables the Social API, the client to
+ // server part of ActivityPub. Useful if permitting remote clients to
+ // act on behalf of the users of the client application.
+ enableSocialProtocol bool
+ // enableFederatedProtocol enables or disables the Federated Protocol, or the
+ // server to server part of ActivityPub. Useful to permit integrating
+ // with the rest of the federative web.
+ enableFederatedProtocol bool
+ // clock simply tracks the current time.
+ clock Clock
+}
+
+// baseActorFederating must satisfy the FederatingActor interface.
+var _ FederatingActor = &baseActorFederating{}
+
+// baseActorFederating is a baseActor that also satisfies the FederatingActor
+// interface.
+//
+// The baseActor is preserved as an Actor which will not successfully cast to a
+// FederatingActor.
+type baseActorFederating struct {
+ baseActor
+}
+
+// NewSocialActor builds a new Actor concept that handles only the Social
+// Protocol part of ActivityPub.
+//
+// This Actor can be created once in an application and reused to handle
+// multiple requests concurrently and for different endpoints.
+//
+// It leverages as much of go-fed as possible to ensure the implementation is
+// compliant with the ActivityPub specification, while providing enough freedom
+// to be productive without shooting one's self in the foot.
+//
+// Do not try to use NewSocialActor and NewFederatingActor together to cover
+// both the Social and Federating parts of the protocol. Instead, use NewActor.
+func NewSocialActor(c CommonBehavior,
+ c2s SocialProtocol,
+ db Database,
+ clock Clock) Actor {
+ return &baseActor{
+ // Use SideEffectActor without s2s.
+ delegate: NewSideEffectActor(c, nil, c2s, db, clock),
+ enableSocialProtocol: true,
+ clock: clock,
+ }
+}
+
+// NewFederatingActor builds a new Actor concept that handles only the Federating
+// Protocol part of ActivityPub.
+//
+// This Actor can be created once in an application and reused to handle
+// multiple requests concurrently and for different endpoints.
+//
+// It leverages as much of go-fed as possible to ensure the implementation is
+// compliant with the ActivityPub specification, while providing enough freedom
+// to be productive without shooting one's self in the foot.
+//
+// Do not try to use NewSocialActor and NewFederatingActor together to cover
+// both the Social and Federating parts of the protocol. Instead, use NewActor.
+func NewFederatingActor(c CommonBehavior,
+ s2s FederatingProtocol,
+ db Database,
+ clock Clock) FederatingActor {
+ return &baseActorFederating{
+ baseActor{
+ // Use SideEffectActor without c2s.
+ delegate: NewSideEffectActor(c, s2s, nil, db, clock),
+ enableFederatedProtocol: true,
+ clock: clock,
+ },
+ }
+}
+
+// NewActor builds a new Actor concept that handles both the Social and
+// Federating Protocol parts of ActivityPub.
+//
+// This Actor can be created once in an application and reused to handle
+// multiple requests concurrently and for different endpoints.
+//
+// It leverages as much of go-fed as possible to ensure the implementation is
+// compliant with the ActivityPub specification, while providing enough freedom
+// to be productive without shooting one's self in the foot.
+func NewActor(c CommonBehavior,
+ c2s SocialProtocol,
+ s2s FederatingProtocol,
+ db Database,
+ clock Clock) FederatingActor {
+ return &baseActorFederating{
+ baseActor{
+ delegate: NewSideEffectActor(c, s2s, c2s, db, clock),
+ enableSocialProtocol: true,
+ enableFederatedProtocol: true,
+ clock: clock,
+ },
+ }
+}
+
+// NewCustomActor allows clients to create a custom ActivityPub implementation
+// for the Social Protocol, Federating Protocol, or both.
+//
+// It still uses the library as a high-level scaffold, which has the benefit of
+// allowing applications to grow into a custom ActivityPub solution without
+// having to refactor the code that passes HTTP requests into the Actor.
+//
+// It is possible to create a DelegateActor that is not ActivityPub compliant.
+// Use with due care.
+//
+// If you find yourself passing a SideEffectActor in as the DelegateActor,
+// consider using NewActor, NewFederatingActor, or NewSocialActor instead.
+func NewCustomActor(delegate DelegateActor,
+ enableSocialProtocol, enableFederatedProtocol bool,
+ clock Clock) FederatingActor {
+ return &baseActorFederating{
+ baseActor{
+ delegate: delegate,
+ enableSocialProtocol: enableSocialProtocol,
+ enableFederatedProtocol: enableFederatedProtocol,
+ clock: clock,
+ },
+ }
+}
+
+// PostInbox implements the generic algorithm for handling a POST request to an
+// actor's inbox independent on an application. It relies on a delegate to
+// implement application specific functionality.
+//
+// Only supports serving data with identifiers having the HTTPS scheme.
+func (b *baseActor) PostInbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error) {
+ return b.PostInboxScheme(c, w, r, "https")
+}
+
+// PostInbox implements the generic algorithm for handling a POST request to an
+// actor's inbox independent on an application. It relies on a delegate to
+// implement application specific functionality.
+//
+// Specifying the "scheme" allows for retrieving ActivityStreams content with
+// identifiers such as HTTP, HTTPS, or other protocol schemes.
+func (b *baseActor) PostInboxScheme(c context.Context, w http.ResponseWriter, r *http.Request, scheme string) (bool, error) {
+ // Do nothing if it is not an ActivityPub POST request.
+ if !isActivityPubPost(r) {
+ return false, nil
+ }
+ // If the Federated Protocol is not enabled, then this endpoint is not
+ // enabled.
+ if !b.enableFederatedProtocol {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ return true, nil
+ }
+ // Check the peer request is authentic.
+ c, authenticated, err := b.delegate.AuthenticatePostInbox(c, w, r)
+ if err != nil {
+ return true, err
+ } else if !authenticated {
+ return true, nil
+ }
+ // Begin processing the request, but have not yet applied
+ // authorization (ex: blocks). Obtain the activity reject unknown
+ // activities.
+ m, err := readActivityPubReq(r)
+ if err != nil {
+ return true, err
+ }
+ asValue, err := streams.ToType(c, m)
+ if err != nil && !streams.IsUnmatchedErr(err) {
+ return true, err
+ } else if streams.IsUnmatchedErr(err) {
+ // Respond with bad request -- we do not understand the type.
+ w.WriteHeader(http.StatusBadRequest)
+ return true, nil
+ }
+ activity, ok := asValue.(Activity)
+ if !ok {
+ return true, fmt.Errorf("activity streams value is not an Activity: %T", asValue)
+ }
+ if activity.GetJSONLDId() == nil {
+ w.WriteHeader(http.StatusBadRequest)
+ return true, nil
+ }
+ // Allow server implementations to set context data with a hook.
+ c, err = b.delegate.PostInboxRequestBodyHook(c, r, activity)
+ if err != nil {
+ return true, err
+ }
+ // Check authorization of the activity.
+ authorized, err := b.delegate.AuthorizePostInbox(c, w, activity)
+ if err != nil {
+ return true, err
+ } else if !authorized {
+ return true, nil
+ }
+ // Post the activity to the actor's inbox and trigger side effects for
+ // that particular Activity type. It is up to the delegate to resolve
+ // the given map.
+ inboxId := requestId(r, scheme)
+ err = b.delegate.PostInbox(c, inboxId, activity)
+ if err != nil {
+ // Special case: We know it is a bad request if the object or
+ // target properties needed to be populated, but weren't.
+ //
+ // Send the rejection to the peer.
+ if err == ErrObjectRequired || err == ErrTargetRequired {
+ w.WriteHeader(http.StatusBadRequest)
+ return true, nil
+ }
+ 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 := b.delegate.InboxForwarding(c, inboxId, activity); err != nil {
+ return true, err
+ }
+ // Request has been processed. Begin responding to the request.
+ //
+ // Simply respond with an OK status to the peer.
+ w.WriteHeader(http.StatusOK)
+ return true, nil
+}
+
+// GetInbox implements the generic algorithm for handling a GET request to an
+// actor's inbox independent on an application. It relies on a delegate to
+// implement application specific functionality.
+func (b *baseActor) GetInbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error) {
+ // Do nothing if it is not an ActivityPub GET request.
+ if !isActivityPubGet(r) {
+ return false, nil
+ }
+ // Delegate authenticating and authorizing the request.
+ c, authenticated, err := b.delegate.AuthenticateGetInbox(c, w, r)
+ if err != nil {
+ return true, err
+ } else if !authenticated {
+ return true, nil
+ }
+ // Everything is good to begin processing the request.
+ oc, err := b.delegate.GetInbox(c, r)
+ if err != nil {
+ return true, err
+ }
+ // Deduplicate the 'orderedItems' property by ID.
+ err = dedupeOrderedItems(oc)
+ if err != nil {
+ return true, err
+ }
+ // Request has been processed. Begin responding to the request.
+ //
+ // Serialize the OrderedCollection.
+ m, err := streams.Serialize(oc)
+ if err != nil {
+ return true, err
+ }
+ raw, err := json.Marshal(m)
+ if err != nil {
+ return true, err
+ }
+ // Write the response.
+ addResponseHeaders(w.Header(), b.clock, raw)
+ w.WriteHeader(http.StatusOK)
+ n, err := w.Write(raw)
+ if err != nil {
+ return true, err
+ } else if n != len(raw) {
+ return true, fmt.Errorf("ResponseWriter.Write wrote %d of %d bytes", n, len(raw))
+ }
+ return true, nil
+}
+
+// PostOutbox implements the generic algorithm for handling a POST request to an
+// actor's outbox independent on an application. It relies on a delegate to
+// implement application specific functionality.
+//
+// Only supports serving data with identifiers having the HTTPS scheme.
+func (b *baseActor) PostOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error) {
+ return b.PostOutboxScheme(c, w, r, "https")
+}
+
+// PostOutbox implements the generic algorithm for handling a POST request to an
+// actor's outbox independent on an application. It relies on a delegate to
+// implement application specific functionality.
+//
+// Specifying the "scheme" allows for retrieving ActivityStreams content with
+// identifiers such as HTTP, HTTPS, or other protocol schemes.
+func (b *baseActor) PostOutboxScheme(c context.Context, w http.ResponseWriter, r *http.Request, scheme string) (bool, error) {
+ // Do nothing if it is not an ActivityPub POST request.
+ if !isActivityPubPost(r) {
+ return false, nil
+ }
+ // If the Social API is not enabled, then this endpoint is not enabled.
+ if !b.enableSocialProtocol {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ return true, nil
+ }
+ // Delegate authenticating and authorizing the request.
+ c, authenticated, err := b.delegate.AuthenticatePostOutbox(c, w, r)
+ if err != nil {
+ return true, err
+ } else if !authenticated {
+ return true, nil
+ }
+ // Everything is good to begin processing the request.
+ m, err := readActivityPubReq(r)
+ if err != nil {
+ return true, err
+ }
+ // Note that converting to a Type will NOT successfully convert types
+ // not known to go-fed. This prevents accidentally wrapping an Activity
+ // type unknown to go-fed in a Create below. Instead,
+ // streams.ErrUnhandledType will be returned here.
+ asValue, err := streams.ToType(c, m)
+ if err != nil && !streams.IsUnmatchedErr(err) {
+ return true, err
+ } else if streams.IsUnmatchedErr(err) {
+ // Respond with bad request -- we do not understand the type.
+ w.WriteHeader(http.StatusBadRequest)
+ return true, nil
+ }
+ // Allow server implementations to set context data with a hook.
+ c, err = b.delegate.PostOutboxRequestBodyHook(c, r, asValue)
+ if err != nil {
+ return true, err
+ }
+ // The HTTP request steps are complete, complete the rest of the outbox
+ // and delivery process.
+ outboxId := requestId(r, scheme)
+ activity, err := b.deliver(c, outboxId, asValue, m)
+ // Special case: We know it is a bad request if the object or
+ // target properties needed to be populated, but weren't.
+ //
+ // Send the rejection to the client.
+ if err == ErrObjectRequired || err == ErrTargetRequired {
+ w.WriteHeader(http.StatusBadRequest)
+ return true, nil
+ } else if err != nil {
+ return true, err
+ }
+ // Respond to the request with the new Activity's IRI location.
+ w.Header().Set(locationHeader, activity.GetJSONLDId().Get().String())
+ w.WriteHeader(http.StatusCreated)
+ return true, nil
+}
+
+// GetOutbox implements the generic algorithm for handling a Get request to an
+// actor's outbox independent on an application. It relies on a delegate to
+// implement application specific functionality.
+func (b *baseActor) GetOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (bool, error) {
+ // Do nothing if it is not an ActivityPub GET request.
+ if !isActivityPubGet(r) {
+ return false, nil
+ }
+ // Delegate authenticating and authorizing the request.
+ c, authenticated, err := b.delegate.AuthenticateGetOutbox(c, w, r)
+ if err != nil {
+ return true, err
+ } else if !authenticated {
+ return true, nil
+ }
+ // Everything is good to begin processing the request.
+ oc, err := b.delegate.GetOutbox(c, r)
+ if err != nil {
+ return true, err
+ }
+ // Request has been processed. Begin responding to the request.
+ //
+ // Serialize the OrderedCollection.
+ m, err := streams.Serialize(oc)
+ if err != nil {
+ return true, err
+ }
+ raw, err := json.Marshal(m)
+ if err != nil {
+ return true, err
+ }
+ // Write the response.
+ addResponseHeaders(w.Header(), b.clock, raw)
+ w.WriteHeader(http.StatusOK)
+ n, err := w.Write(raw)
+ if err != nil {
+ return true, err
+ } else if n != len(raw) {
+ return true, fmt.Errorf("ResponseWriter.Write wrote %d of %d bytes", n, len(raw))
+ }
+ return true, nil
+}
+
+// deliver delegates all outbox handling steps and optionally will federate the
+// activity if the federated protocol is enabled.
+//
+// This function is not exported so an Actor that only supports C2S cannot be
+// type casted to a FederatingActor. It doesn't exactly fit the Send method
+// signature anyways.
+//
+// Note: 'm' is nilable.
+func (b *baseActor) deliver(c context.Context, outbox *url.URL, asValue vocab.Type, m map[string]interface{}) (activity Activity, err error) {
+ // If the value is not an Activity or type extending from Activity, then
+ // we need to wrap it in a Create Activity.
+ if !streams.IsOrExtendsActivityStreamsActivity(asValue) {
+ asValue, err = b.delegate.WrapInCreate(c, asValue, outbox)
+ if err != nil {
+ return
+ }
+ }
+ // At this point, this should be a safe conversion. If this error is
+ // triggered, then there is either a bug in the delegation of
+ // WrapInCreate, behavior is not lining up in the generated ExtendedBy
+ // code, or something else is incorrect with the type system.
+ var ok bool
+ activity, ok = asValue.(Activity)
+ if !ok {
+ err = fmt.Errorf("activity streams value is not an Activity: %T", asValue)
+ return
+ }
+ // Delegate generating new IDs for the activity and all new objects.
+ if err = b.delegate.AddNewIDs(c, activity); err != nil {
+ return
+ }
+ // Post the activity to the actor's outbox and trigger side effects for
+ // that particular Activity type.
+ //
+ // Since 'm' is nil-able and side effects may need access to literal nil
+ // values, such as for Update activities, ensure 'm' is non-nil.
+ if m == nil {
+ m, err = asValue.Serialize()
+ if err != nil {
+ return
+ }
+ }
+ deliverable, err := b.delegate.PostOutbox(c, activity, outbox, m)
+ if err != nil {
+ return
+ }
+ // Request has been processed and all side effects internal to this
+ // application server have finished. Begin side effects affecting other
+ // servers and/or the client who sent this request.
+ //
+ // If we are federating and the type is a deliverable one, then deliver
+ // the activity to federating peers.
+ if b.enableFederatedProtocol && deliverable {
+ if err = b.delegate.Deliver(c, outbox, activity); err != nil {
+ return
+ }
+ }
+ return
+}
+
+// Send is programmatically accessible if the federated protocol is enabled.
+func (b *baseActorFederating) Send(c context.Context, outbox *url.URL, t vocab.Type) (Activity, error) {
+ return b.deliver(c, outbox, t, nil)
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/clock.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/clock.go
new file mode 100644
index 000000000..bf19e49f7
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/clock.go
@@ -0,0 +1,11 @@
+package pub
+
+import (
+ "time"
+)
+
+// Clock determines the time.
+type Clock interface {
+ // Now returns the current time.
+ Now() time.Time
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/common_behavior.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/common_behavior.go
new file mode 100644
index 000000000..e33dce842
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/common_behavior.go
@@ -0,0 +1,90 @@
+package pub
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// Common contains functions required for both the Social API and Federating
+// Protocol.
+//
+// It is passed to the library as a dependency injection from the client
+// application.
+type CommonBehavior interface {
+ // AuthenticateGetInbox delegates the authentication of a GET to an
+ // inbox.
+ //
+ // Always called, regardless whether the Federated Protocol or Social
+ // API is enabled.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // GetInbox. In this case, the implementation must not write a
+ // response to the ResponseWriter as is expected that the client will
+ // do so when handling the error. The 'authenticated' is ignored.
+ //
+ // If no error is returned, but authentication or authorization fails,
+ // then authenticated must be false and error nil. It is expected that
+ // the implementation handles writing to the ResponseWriter in this
+ // case.
+ //
+ // Finally, if the authentication and authorization succeeds, then
+ // authenticated must be true and error nil. The request will continue
+ // to be processed.
+ AuthenticateGetInbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error)
+ // AuthenticateGetOutbox delegates the authentication of a GET to an
+ // outbox.
+ //
+ // Always called, regardless whether the Federated Protocol or Social
+ // API is enabled.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // GetOutbox. In this case, the implementation must not write a
+ // response to the ResponseWriter as is expected that the client will
+ // do so when handling the error. The 'authenticated' is ignored.
+ //
+ // If no error is returned, but authentication or authorization fails,
+ // then authenticated must be false and error nil. It is expected that
+ // the implementation handles writing to the ResponseWriter in this
+ // case.
+ //
+ // Finally, if the authentication and authorization succeeds, then
+ // authenticated must be true and error nil. The request will continue
+ // to be processed.
+ AuthenticateGetOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error)
+ // GetOutbox returns the OrderedCollection inbox of the actor for this
+ // context. It is up to the implementation to provide the correct
+ // collection for the kind of authorization given in the request.
+ //
+ // AuthenticateGetOutbox will be called prior to this.
+ //
+ // Always called, regardless whether the Federated Protocol or Social
+ // API is enabled.
+ GetOutbox(c context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error)
+ // NewTransport returns a new Transport on behalf of a specific actor.
+ //
+ // The actorBoxIRI will be either the inbox or outbox of an actor who is
+ // attempting to do the dereferencing or delivery. Any authentication
+ // scheme applied on the request must be based on this actor. The
+ // request must contain some sort of credential of the user, such as a
+ // HTTP Signature.
+ //
+ // The gofedAgent passed in should be used by the Transport
+ // implementation in the User-Agent, as well as the application-specific
+ // user agent string. The gofedAgent will indicate this library's use as
+ // well as the library's version number.
+ //
+ // Any server-wide rate-limiting that needs to occur should happen in a
+ // Transport implementation. This factory function allows this to be
+ // created, so peer servers are not DOS'd.
+ //
+ // Any retry logic should also be handled by the Transport
+ // implementation.
+ //
+ // Note that the library will not maintain a long-lived pointer to the
+ // returned Transport so that any private credentials are able to be
+ // garbage collected.
+ NewTransport(c context.Context, actorBoxIRI *url.URL, gofedAgent string) (t Transport, err error)
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/database.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/database.go
new file mode 100644
index 000000000..5e25455d5
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/database.go
@@ -0,0 +1,152 @@
+package pub
+
+import (
+ "context"
+ "net/url"
+
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+type Database interface {
+ // Lock takes a lock for the object at the specified id. If an error
+ // is returned, the lock must not have been taken.
+ //
+ // The lock must be able to succeed for an id that does not exist in
+ // the database. This means acquiring the lock does not guarantee the
+ // entry exists in the database.
+ //
+ // Locks are encouraged to be lightweight and in the Go layer, as some
+ // processes require tight loops acquiring and releasing locks.
+ //
+ // Used to ensure race conditions in multiple requests do not occur.
+ Lock(c context.Context, id *url.URL) (unlock func(), err error)
+ // InboxContains returns true if the OrderedCollection at 'inbox'
+ // contains the specified 'id'.
+ //
+ // The library makes this call only after acquiring a lock first.
+ InboxContains(c context.Context, inbox, id *url.URL) (contains bool, err error)
+ // GetInbox returns the first ordered collection page of the outbox at
+ // the specified IRI, for prepending new items.
+ //
+ // The library makes this call only after acquiring a lock first.
+ GetInbox(c context.Context, inboxIRI *url.URL) (inbox vocab.ActivityStreamsOrderedCollectionPage, err error)
+ // SetInbox saves the inbox value given from GetInbox, with new items
+ // prepended. Note that the new items must not be added as independent
+ // database entries. Separate calls to Create will do that.
+ //
+ // The library makes this call only after acquiring a lock first.
+ SetInbox(c context.Context, inbox vocab.ActivityStreamsOrderedCollectionPage) error
+ // Owns returns true if the database has an entry for the IRI and it
+ // exists in the database.
+ //
+ // The library makes this call only after acquiring a lock first.
+ Owns(c context.Context, id *url.URL) (owns bool, err error)
+ // ActorForOutbox fetches the actor's IRI for the given outbox IRI.
+ //
+ // The library makes this call only after acquiring a lock first.
+ ActorForOutbox(c context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error)
+ // ActorForInbox fetches the actor's IRI for the given outbox IRI.
+ //
+ // The library makes this call only after acquiring a lock first.
+ ActorForInbox(c context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error)
+ // OutboxForInbox fetches the corresponding actor's outbox IRI for the
+ // actor's inbox IRI.
+ //
+ // The library makes this call only after acquiring a lock first.
+ OutboxForInbox(c context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error)
+ // InboxesForIRI fetches inboxes corresponding to the given iri.
+ // This allows your server to skip remote dereferencing of iris
+ // in order to speed up message delivery, if desired.
+ //
+ // It is acceptable to just return nil or an empty slice for the inboxIRIs,
+ // if you don't know the inbox iri, or you don't wish to use this feature.
+ // In this case, the library will attempt to resolve inboxes of the iri
+ // by remote dereferencing instead.
+ //
+ // If the input iri is the iri of an Actor, then the inbox for the actor
+ // should be returned as a single-entry slice.
+ //
+ // If the input iri is a Collection (such as a Collection of followers),
+ // then each follower inbox IRI should be returned in the inboxIRIs slice.
+ //
+ // The library makes this call only after acquiring a lock first.
+ InboxesForIRI(c context.Context, iri *url.URL) (inboxIRIs []*url.URL, err error)
+ // Exists returns true if the database has an entry for the specified
+ // id. It may not be owned by this application instance.
+ //
+ // The library makes this call only after acquiring a lock first.
+ Exists(c context.Context, id *url.URL) (exists bool, err error)
+ // Get returns the database entry for the specified id.
+ //
+ // The library makes this call only after acquiring a lock first.
+ Get(c context.Context, id *url.URL) (value vocab.Type, err error)
+ // Create adds a new entry to the database which must be able to be
+ // keyed by its id.
+ //
+ // Note that Activity values received from federated peers may also be
+ // created in the database this way if the Federating Protocol is
+ // enabled. The client may freely decide to store only the id instead of
+ // the entire value.
+ //
+ // The library makes this call only after acquiring a lock first.
+ //
+ // Under certain conditions and network activities, Create may be called
+ // multiple times for the same ActivityStreams object.
+ Create(c context.Context, asType vocab.Type) error
+ // Update sets an existing entry to the database based on the value's
+ // id.
+ //
+ // Note that Activity values received from federated peers may also be
+ // updated in the database this way if the Federating Protocol is
+ // enabled. The client may freely decide to store only the id instead of
+ // the entire value.
+ //
+ // The library makes this call only after acquiring a lock first.
+ Update(c context.Context, asType vocab.Type) error
+ // Delete removes the entry with the given id.
+ //
+ // Delete is only called for federated objects. Deletes from the Social
+ // Protocol instead call Update to create a Tombstone.
+ //
+ // The library makes this call only after acquiring a lock first.
+ Delete(c context.Context, id *url.URL) error
+ // GetOutbox returns the first ordered collection page of the outbox
+ // at the specified IRI, for prepending new items.
+ //
+ // The library makes this call only after acquiring a lock first.
+ GetOutbox(c context.Context, outboxIRI *url.URL) (outbox vocab.ActivityStreamsOrderedCollectionPage, err error)
+ // SetOutbox saves the outbox value given from GetOutbox, with new items
+ // prepended. Note that the new items must not be added as independent
+ // database entries. Separate calls to Create will do that.
+ //
+ // The library makes this call only after acquiring a lock first.
+ SetOutbox(c context.Context, outbox vocab.ActivityStreamsOrderedCollectionPage) error
+ // NewID creates a new IRI id for the provided activity or object. The
+ // implementation does not need to set the 'id' property and simply
+ // needs to determine the value.
+ //
+ // The go-fed library will handle setting the 'id' property on the
+ // activity or object provided with the value returned.
+ NewID(c context.Context, t vocab.Type) (id *url.URL, err error)
+ // Followers obtains the Followers Collection for an actor with the
+ // given id.
+ //
+ // If modified, the library will then call Update.
+ //
+ // The library makes this call only after acquiring a lock first.
+ Followers(c context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error)
+ // Following obtains the Following Collection for an actor with the
+ // given id.
+ //
+ // If modified, the library will then call Update.
+ //
+ // The library makes this call only after acquiring a lock first.
+ Following(c context.Context, actorIRI *url.URL) (following vocab.ActivityStreamsCollection, err error)
+ // Liked obtains the Liked Collection for an actor with the
+ // given id.
+ //
+ // If modified, the library will then call Update.
+ //
+ // The library makes this call only after acquiring a lock first.
+ Liked(c context.Context, actorIRI *url.URL) (liked vocab.ActivityStreamsCollection, err error)
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/delegate_actor.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/delegate_actor.go
new file mode 100644
index 000000000..0250f62ee
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/delegate_actor.go
@@ -0,0 +1,249 @@
+package pub
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// DelegateActor contains the detailed interface an application must satisfy in
+// order to implement the ActivityPub specification.
+//
+// Note that an implementation of this interface is implicitly provided in the
+// calls to NewActor, NewSocialActor, and NewFederatingActor.
+//
+// Implementing the DelegateActor requires familiarity with the ActivityPub
+// specification because it does not a strong enough abstraction for the client
+// application to ignore the ActivityPub spec. It is very possible to implement
+// this interface and build a foot-gun that trashes the fediverse without being
+// ActivityPub compliant. Please use with due consideration.
+//
+// Alternatively, build an application that uses the parts of the pub library
+// that do not require implementing a DelegateActor so that the ActivityPub
+// implementation is completely provided out of the box.
+type DelegateActor interface {
+ // Hook callback after parsing the request body for a federated request
+ // to the Actor's inbox.
+ //
+ // Can be used to set contextual information based on the Activity
+ // received.
+ //
+ // Only called if the Federated Protocol is enabled.
+ //
+ // Warning: Neither authentication nor authorization has taken place at
+ // this time. Doing anything beyond setting contextual information is
+ // strongly discouraged.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // PostInbox. In this case, the DelegateActor implementation must not
+ // write a response to the ResponseWriter as is expected that the caller
+ // to PostInbox will do so when handling the error.
+ PostInboxRequestBodyHook(c context.Context, r *http.Request, activity Activity) (context.Context, error)
+ // Hook callback after parsing the request body for a client request
+ // to the Actor's outbox.
+ //
+ // Can be used to set contextual information based on the
+ // ActivityStreams object received.
+ //
+ // Only called if the Social API is enabled.
+ //
+ // Warning: Neither authentication nor authorization has taken place at
+ // this time. Doing anything beyond setting contextual information is
+ // strongly discouraged.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // PostOutbox. In this case, the DelegateActor implementation must not
+ // write a response to the ResponseWriter as is expected that the caller
+ // to PostOutbox will do so when handling the error.
+ PostOutboxRequestBodyHook(c context.Context, r *http.Request, data vocab.Type) (context.Context, error)
+ // AuthenticatePostInbox delegates the authentication of a POST to an
+ // inbox.
+ //
+ // Only called if the Federated Protocol is enabled.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // PostInbox. In this case, the implementation must not write a
+ // response to the ResponseWriter as is expected that the client will
+ // do so when handling the error. The 'authenticated' is ignored.
+ //
+ // If no error is returned, but authentication or authorization fails,
+ // then authenticated must be false and error nil. It is expected that
+ // the implementation handles writing to the ResponseWriter in this
+ // case.
+ //
+ // Finally, if the authentication and authorization succeeds, then
+ // authenticated must be true and error nil. The request will continue
+ // to be processed.
+ AuthenticatePostInbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error)
+ // AuthenticateGetInbox delegates the authentication of a GET to an
+ // inbox.
+ //
+ // Always called, regardless whether the Federated Protocol or Social
+ // API is enabled.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // GetInbox. In this case, the implementation must not write a
+ // response to the ResponseWriter as is expected that the client will
+ // do so when handling the error. The 'authenticated' is ignored.
+ //
+ // If no error is returned, but authentication or authorization fails,
+ // then authenticated must be false and error nil. It is expected that
+ // the implementation handles writing to the ResponseWriter in this
+ // case.
+ //
+ // Finally, if the authentication and authorization succeeds, then
+ // authenticated must be true and error nil. The request will continue
+ // to be processed.
+ AuthenticateGetInbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error)
+ // AuthorizePostInbox delegates the authorization of an activity that
+ // has been sent by POST to an inbox.
+ //
+ // Only called if the Federated Protocol is enabled.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // PostInbox. In this case, the implementation must not write a
+ // response to the ResponseWriter as is expected that the client will
+ // do so when handling the error. The 'authorized' is ignored.
+ //
+ // If no error is returned, but authorization fails, then authorized
+ // must be false and error nil. It is expected that the implementation
+ // handles writing to the ResponseWriter in this case.
+ //
+ // Finally, if the authentication and authorization succeeds, then
+ // authorized must be true and error nil. The request will continue
+ // to be processed.
+ AuthorizePostInbox(c context.Context, w http.ResponseWriter, activity Activity) (authorized bool, err error)
+ // PostInbox delegates the side effects of adding to the inbox and
+ // determining if it is a request that should be blocked.
+ //
+ // Only called if the Federated Protocol is enabled.
+ //
+ // As a side effect, PostInbox sets the federated data in the inbox, but
+ // not on its own in the database, as InboxForwarding (which is called
+ // later) must decide whether it has seen this activity before in order
+ // to determine whether to do the forwarding algorithm.
+ //
+ // If the error is ErrObjectRequired or ErrTargetRequired, then a Bad
+ // Request status is sent in the response.
+ PostInbox(c context.Context, inboxIRI *url.URL, activity Activity) error
+ // InboxForwarding delegates inbox forwarding logic when a POST request
+ // is received in the Actor's inbox.
+ //
+ // Only called if the Federated Protocol is enabled.
+ //
+ // The delegate is responsible for determining whether to do the inbox
+ // forwarding, as well as actually conducting it if it determines it
+ // needs to.
+ //
+ // As a side effect, InboxForwarding must set the federated data in the
+ // database, independently of the inbox, however it sees fit in order to
+ // determine whether it has seen the activity before.
+ //
+ // The provided url is the inbox of the recipient of the Activity. The
+ // Activity is examined for the information about who to inbox forward
+ // to.
+ //
+ // If an error is returned, it is returned to the caller of PostInbox.
+ InboxForwarding(c context.Context, inboxIRI *url.URL, activity Activity) error
+ // PostOutbox delegates the logic for side effects and adding to the
+ // outbox.
+ //
+ // Always called, regardless whether the Federated Protocol or Social
+ // API is enabled. In the case of the Social API being enabled, side
+ // effects of the Activity must occur.
+ //
+ // The delegate is responsible for adding the activity to the database's
+ // general storage for independent retrieval, and not just within the
+ // actor's outbox.
+ //
+ // If the error is ErrObjectRequired or ErrTargetRequired, then a Bad
+ // Request status is sent in the response.
+ //
+ // Note that 'rawJSON' is an unfortunate consequence where an 'Update'
+ // Activity is the only one that explicitly cares about 'null' values in
+ // JSON. Since go-fed does not differentiate between 'null' values and
+ // values that are simply not present, the 'rawJSON' map is ONLY needed
+ // for this narrow and specific use case.
+ PostOutbox(c context.Context, a Activity, outboxIRI *url.URL, rawJSON map[string]interface{}) (deliverable bool, e error)
+ // AddNewIDs sets new URL ids on the activity. It also does so for all
+ // 'object' properties if the Activity is a Create type.
+ //
+ // Only called if the Social API is enabled.
+ //
+ // If an error is returned, it is returned to the caller of PostOutbox.
+ AddNewIDs(c context.Context, a Activity) error
+ // Deliver sends a federated message. Called only if federation is
+ // enabled.
+ //
+ // Called if the Federated Protocol is enabled.
+ //
+ // The provided url is the outbox of the sender. The Activity contains
+ // the information about the intended recipients.
+ //
+ // If an error is returned, it is returned to the caller of PostOutbox.
+ Deliver(c context.Context, outbox *url.URL, activity Activity) error
+ // AuthenticatePostOutbox delegates the authentication and authorization
+ // of a POST to an outbox.
+ //
+ // Only called if the Social API is enabled.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // PostOutbox. In this case, the implementation must not write a
+ // response to the ResponseWriter as is expected that the client will
+ // do so when handling the error. The 'authenticated' is ignored.
+ //
+ // If no error is returned, but authentication or authorization fails,
+ // then authenticated must be false and error nil. It is expected that
+ // the implementation handles writing to the ResponseWriter in this
+ // case.
+ //
+ // Finally, if the authentication and authorization succeeds, then
+ // authenticated must be true and error nil. The request will continue
+ // to be processed.
+ AuthenticatePostOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error)
+ // AuthenticateGetOutbox delegates the authentication of a GET to an
+ // outbox.
+ //
+ // Always called, regardless whether the Federated Protocol or Social
+ // API is enabled.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // GetOutbox. In this case, the implementation must not write a
+ // response to the ResponseWriter as is expected that the client will
+ // do so when handling the error. The 'authenticated' is ignored.
+ //
+ // If no error is returned, but authentication or authorization fails,
+ // then authenticated must be false and error nil. It is expected that
+ // the implementation handles writing to the ResponseWriter in this
+ // case.
+ //
+ // Finally, if the authentication and authorization succeeds, then
+ // authenticated must be true and error nil. The request will continue
+ // to be processed.
+ AuthenticateGetOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error)
+ // WrapInCreate wraps the provided object in a Create ActivityStreams
+ // activity. The provided URL is the actor's outbox endpoint.
+ //
+ // Only called if the Social API is enabled.
+ WrapInCreate(c context.Context, value vocab.Type, outboxIRI *url.URL) (vocab.ActivityStreamsCreate, error)
+ // GetOutbox returns the OrderedCollection inbox of the actor for this
+ // context. It is up to the implementation to provide the correct
+ // collection for the kind of authorization given in the request.
+ //
+ // AuthenticateGetOutbox will be called prior to this.
+ //
+ // Always called, regardless whether the Federated Protocol or Social
+ // API is enabled.
+ GetOutbox(c context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error)
+ // GetInbox returns the OrderedCollection inbox of the actor for this
+ // context. It is up to the implementation to provide the correct
+ // collection for the kind of authorization given in the request.
+ //
+ // AuthenticateGetInbox will be called prior to this.
+ //
+ // Always called, regardless whether the Federated Protocol or Social
+ // API is enabled.
+ GetInbox(c context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error)
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/doc.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/doc.go
new file mode 100644
index 000000000..93c778179
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/doc.go
@@ -0,0 +1,9 @@
+// Package pub implements the ActivityPub protocol.
+//
+// Note that every time the ActivityStreams types are changed (added, removed)
+// due to code generation, the internal function toASType needs to be modified
+// to know about these types.
+//
+// Note that every version change should also include a change in the version.go
+// file.
+package pub
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/federating_protocol.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/federating_protocol.go
new file mode 100644
index 000000000..62bcf44c1
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/federating_protocol.go
@@ -0,0 +1,125 @@
+package pub
+
+import (
+ "context"
+ "net/http"
+ "net/url"
+
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// FederatingProtocol contains behaviors an application needs to satisfy for the
+// full ActivityPub S2S implementation to be supported by this library.
+//
+// It is only required if the client application wants to support the server-to-
+// server, or federating, protocol.
+//
+// It is passed to the library as a dependency injection from the client
+// application.
+type FederatingProtocol interface {
+ // Hook callback after parsing the request body for a federated request
+ // to the Actor's inbox.
+ //
+ // Can be used to set contextual information based on the Activity
+ // received.
+ //
+ // Only called if the Federated Protocol is enabled.
+ //
+ // Warning: Neither authentication nor authorization has taken place at
+ // this time. Doing anything beyond setting contextual information is
+ // strongly discouraged.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // PostInbox. In this case, the DelegateActor implementation must not
+ // write a response to the ResponseWriter as is expected that the caller
+ // to PostInbox will do so when handling the error.
+ PostInboxRequestBodyHook(c context.Context, r *http.Request, activity Activity) (context.Context, error)
+ // AuthenticatePostInbox delegates the authentication of a POST to an
+ // inbox.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // PostInbox. In this case, the implementation must not write a
+ // response to the ResponseWriter as is expected that the client will
+ // do so when handling the error. The 'authenticated' is ignored.
+ //
+ // If no error is returned, but authentication or authorization fails,
+ // then authenticated must be false and error nil. It is expected that
+ // the implementation handles writing to the ResponseWriter in this
+ // case.
+ //
+ // Finally, if the authentication and authorization succeeds, then
+ // authenticated must be true and error nil. The request will continue
+ // to be processed.
+ AuthenticatePostInbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error)
+ // Blocked should determine whether to permit a set of actors given by
+ // their ids are able to interact with this particular end user due to
+ // being blocked or other application-specific logic.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // PostInbox.
+ //
+ // If no error is returned, but authentication or authorization fails,
+ // then blocked must be true and error nil. An http.StatusForbidden
+ // will be written in the wresponse.
+ //
+ // Finally, if the authentication and authorization succeeds, then
+ // blocked must be false and error nil. The request will continue
+ // to be processed.
+ Blocked(c context.Context, actorIRIs []*url.URL) (blocked bool, err error)
+ // FederatingCallbacks returns the application logic that handles
+ // ActivityStreams received from federating peers.
+ //
+ // Note that certain types of callbacks will be 'wrapped' with default
+ // behaviors supported natively by the library. Other callbacks
+ // compatible with streams.TypeResolver can be specified by 'other'.
+ //
+ // For example, setting the 'Create' field in the
+ // FederatingWrappedCallbacks lets an application dependency inject
+ // additional behaviors they want to take place, including the default
+ // behavior supplied by this library. This is guaranteed to be compliant
+ // with the ActivityPub Social protocol.
+ //
+ // To override the default behavior, instead supply the function in
+ // 'other', which does not guarantee the application will be compliant
+ // with the ActivityPub Social Protocol.
+ //
+ // Applications are not expected to handle every single ActivityStreams
+ // type and extension. The unhandled ones are passed to DefaultCallback.
+ FederatingCallbacks(c context.Context) (wrapped FederatingWrappedCallbacks, other []interface{}, err error)
+ // DefaultCallback is called for types that go-fed can deserialize but
+ // are not handled by the application's callbacks returned in the
+ // Callbacks method.
+ //
+ // Applications are not expected to handle every single ActivityStreams
+ // type and extension, so the unhandled ones are passed to
+ // DefaultCallback.
+ DefaultCallback(c context.Context, activity Activity) error
+ // MaxInboxForwardingRecursionDepth determines how deep to search within
+ // an activity to determine if inbox forwarding needs to occur.
+ //
+ // Zero or negative numbers indicate infinite recursion.
+ MaxInboxForwardingRecursionDepth(c context.Context) int
+ // MaxDeliveryRecursionDepth determines how deep to search within
+ // collections owned by peers when they are targeted to receive a
+ // delivery.
+ //
+ // Zero or negative numbers indicate infinite recursion.
+ MaxDeliveryRecursionDepth(c context.Context) int
+ // FilterForwarding allows the implementation to apply business logic
+ // such as blocks, spam filtering, and so on to a list of potential
+ // Collections and OrderedCollections of recipients when inbox
+ // forwarding has been triggered.
+ //
+ // The activity is provided as a reference for more intelligent
+ // logic to be used, but the implementation must not modify it.
+ FilterForwarding(c context.Context, potentialRecipients []*url.URL, a Activity) (filteredRecipients []*url.URL, err error)
+ // GetInbox returns the OrderedCollection inbox of the actor for this
+ // context. It is up to the implementation to provide the correct
+ // collection for the kind of authorization given in the request.
+ //
+ // AuthenticateGetInbox will be called prior to this.
+ //
+ // Always called, regardless whether the Federated Protocol or Social
+ // API is enabled.
+ GetInbox(c context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error)
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/federating_wrapped_callbacks.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/federating_wrapped_callbacks.go
new file mode 100644
index 000000000..9af4f86f1
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/federating_wrapped_callbacks.go
@@ -0,0 +1,917 @@
+package pub
+
+import (
+ "context"
+ "fmt"
+ "net/url"
+
+ "codeberg.org/superseriousbusiness/activity/streams"
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// OnFollowBehavior enumerates the different default actions that the go-fed
+// library can provide when receiving a Follow Activity from a peer.
+type OnFollowBehavior int
+
+const (
+ // OnFollowDoNothing does not take any action when a Follow Activity
+ // is received.
+ OnFollowDoNothing OnFollowBehavior = iota
+ // OnFollowAutomaticallyAccept triggers the side effect of sending an
+ // Accept of this Follow request in response.
+ OnFollowAutomaticallyAccept
+ // OnFollowAutomaticallyAccept triggers the side effect of sending a
+ // Reject of this Follow request in response.
+ OnFollowAutomaticallyReject
+)
+
+// FederatingWrappedCallbacks lists the callback functions that already have
+// some side effect behavior provided by the pub library.
+//
+// These functions are wrapped for the Federating Protocol.
+type FederatingWrappedCallbacks struct {
+ // Create handles additional side effects for the Create ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // The wrapping callback for the Federating Protocol ensures the
+ // 'object' property is created in the database.
+ //
+ // Create calls Create for each object in the federated Activity.
+ Create func(context.Context, vocab.ActivityStreamsCreate) error
+ // Update handles additional side effects for the Update ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // The wrapping callback for the Federating Protocol ensures the
+ // 'object' property is updated in the database.
+ //
+ // Update calls Update on the federated entry from the database, with a
+ // new value.
+ Update func(context.Context, vocab.ActivityStreamsUpdate) error
+ // Delete handles additional side effects for the Delete ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // Delete removes the federated entry from the database.
+ Delete func(context.Context, vocab.ActivityStreamsDelete) error
+ // Follow handles additional side effects for the Follow ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // The wrapping function can have one of several default behaviors,
+ // depending on the value of the OnFollow setting.
+ Follow func(context.Context, vocab.ActivityStreamsFollow) error
+ // OnFollow determines what action to take for this particular callback
+ // if a Follow Activity is handled.
+ OnFollow OnFollowBehavior
+ // Accept handles additional side effects for the Accept ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // The wrapping function determines if this 'Accept' is in response to a
+ // 'Follow'. If so, then the 'actor' is added to the original 'actor's
+ // 'following' collection.
+ //
+ // Otherwise, no side effects are done by go-fed.
+ Accept func(context.Context, vocab.ActivityStreamsAccept) error
+ // Reject handles additional side effects for the Reject ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // The wrapping function has no default side effects. However, if this
+ // 'Reject' is in response to a 'Follow' then the client MUST NOT go
+ // forward with adding the 'actor' to the original 'actor's 'following'
+ // collection by the client application.
+ Reject func(context.Context, vocab.ActivityStreamsReject) error
+ // Add handles additional side effects for the Add ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // The wrapping function will add the 'object' IRIs to a specific
+ // 'target' collection if the 'target' collection(s) live on this
+ // server.
+ Add func(context.Context, vocab.ActivityStreamsAdd) error
+ // Remove handles additional side effects for the Remove ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // The wrapping function will remove all 'object' IRIs from a specific
+ // 'target' collection if the 'target' collection(s) live on this
+ // server.
+ Remove func(context.Context, vocab.ActivityStreamsRemove) error
+ // Like handles additional side effects for the Like ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // The wrapping function will add the activity to the "likes" collection
+ // on all 'object' targets owned by this server.
+ Like func(context.Context, vocab.ActivityStreamsLike) error
+ // Announce handles additional side effects for the Announce
+ // ActivityStreams type, specific to the application using go-fed.
+ //
+ // The wrapping function will add the activity to the "shares"
+ // collection on all 'object' targets owned by this server.
+ Announce func(context.Context, vocab.ActivityStreamsAnnounce) error
+ // Undo handles additional side effects for the Undo ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // The wrapping function ensures the 'actor' on the 'Undo'
+ // is be the same as the 'actor' on all Activities being undone.
+ // It enforces that the actors on the Undo must correspond to all of the
+ // 'object' actors in some manner.
+ //
+ // It is expected that the application will implement the proper
+ // reversal of activities that are being undone.
+ Undo func(context.Context, vocab.ActivityStreamsUndo) error
+ // Block handles additional side effects for the Block ActivityStreams
+ // type, specific to the application using go-fed.
+ //
+ // The wrapping function provides no default side effects. It simply
+ // calls the wrapped function. However, note that Blocks should not be
+ // received from a federated peer, as delivering Blocks explicitly
+ // deviates from the original ActivityPub specification.
+ Block func(context.Context, vocab.ActivityStreamsBlock) error
+
+ // Sidechannel data -- this is set at request handling time. These must
+ // be set before the callbacks are used.
+
+ // db is the Database the FederatingWrappedCallbacks should use.
+ db Database
+ // inboxIRI is the inboxIRI that is handling this callback.
+ inboxIRI *url.URL
+ // addNewIds creates new 'id' entries on an activity and its objects if
+ // it is a Create activity.
+ addNewIds func(c context.Context, activity Activity) error
+ // deliver delivers an outgoing message.
+ deliver func(c context.Context, outboxIRI *url.URL, activity Activity) error
+ // newTransport creates a new Transport.
+ newTransport func(c context.Context, actorBoxIRI *url.URL, gofedAgent string) (t Transport, err error)
+}
+
+// callbacks returns the WrappedCallbacks members into a single interface slice
+// for use in streams.Resolver callbacks.
+//
+// If the given functions have a type that collides with the default behavior,
+// then disable our default behavior
+func (w FederatingWrappedCallbacks) callbacks(fns []interface{}) []interface{} {
+ enableCreate := true
+ enableUpdate := true
+ enableDelete := true
+ enableFollow := true
+ enableAccept := true
+ enableReject := true
+ enableAdd := true
+ enableRemove := true
+ enableLike := true
+ enableAnnounce := true
+ enableUndo := true
+ enableBlock := true
+ for _, fn := range fns {
+ switch fn.(type) {
+ default:
+ continue
+ case func(context.Context, vocab.ActivityStreamsCreate) error:
+ enableCreate = false
+ case func(context.Context, vocab.ActivityStreamsUpdate) error:
+ enableUpdate = false
+ case func(context.Context, vocab.ActivityStreamsDelete) error:
+ enableDelete = false
+ case func(context.Context, vocab.ActivityStreamsFollow) error:
+ enableFollow = false
+ case func(context.Context, vocab.ActivityStreamsAccept) error:
+ enableAccept = false
+ case func(context.Context, vocab.ActivityStreamsReject) error:
+ enableReject = false
+ case func(context.Context, vocab.ActivityStreamsAdd) error:
+ enableAdd = false
+ case func(context.Context, vocab.ActivityStreamsRemove) error:
+ enableRemove = false
+ case func(context.Context, vocab.ActivityStreamsLike) error:
+ enableLike = false
+ case func(context.Context, vocab.ActivityStreamsAnnounce) error:
+ enableAnnounce = false
+ case func(context.Context, vocab.ActivityStreamsUndo) error:
+ enableUndo = false
+ case func(context.Context, vocab.ActivityStreamsBlock) error:
+ enableBlock = false
+ }
+ }
+ if enableCreate {
+ fns = append(fns, w.create)
+ }
+ if enableUpdate {
+ fns = append(fns, w.update)
+ }
+ if enableDelete {
+ fns = append(fns, w.deleteFn)
+ }
+ if enableFollow {
+ fns = append(fns, w.follow)
+ }
+ if enableAccept {
+ fns = append(fns, w.accept)
+ }
+ if enableReject {
+ fns = append(fns, w.reject)
+ }
+ if enableAdd {
+ fns = append(fns, w.add)
+ }
+ if enableRemove {
+ fns = append(fns, w.remove)
+ }
+ if enableLike {
+ fns = append(fns, w.like)
+ }
+ if enableAnnounce {
+ fns = append(fns, w.announce)
+ }
+ if enableUndo {
+ fns = append(fns, w.undo)
+ }
+ if enableBlock {
+ fns = append(fns, w.block)
+ }
+ return fns
+}
+
+// create implements the federating Create activity side effects.
+func (w FederatingWrappedCallbacks) create(c context.Context, a vocab.ActivityStreamsCreate) error {
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(iter vocab.ActivityStreamsObjectPropertyIterator) error {
+ t := iter.GetType()
+ if t == nil && iter.IsIRI() {
+ // Attempt to dereference the IRI instead
+ tport, err := w.newTransport(c, w.inboxIRI, goFedUserAgent())
+ if err != nil {
+ return err
+ }
+ resp, err := tport.Dereference(c, iter.GetIRI())
+ if err != nil {
+ return err
+ }
+ m, err := readActivityPubResp(resp)
+ if err != nil {
+ return err
+ }
+ t, err = streams.ToType(c, m)
+ if err != nil {
+ return err
+ }
+ } else if t == nil {
+ return fmt.Errorf("cannot handle federated create: object is neither a value nor IRI")
+ }
+ id, err := GetId(t)
+ if err != nil {
+ return err
+ }
+ var unlock func()
+ unlock, err = w.db.Lock(c, id)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ if err := w.db.Create(c, t); err != nil {
+ return err
+ }
+ return nil
+ }
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ if err := loopFn(iter); err != nil {
+ return err
+ }
+ }
+ if w.Create != nil {
+ return w.Create(c, a)
+ }
+ return nil
+}
+
+// update implements the federating Update activity side effects.
+func (w FederatingWrappedCallbacks) update(c context.Context, a vocab.ActivityStreamsUpdate) error {
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ if err := mustHaveActivityOriginMatchObjects(a); err != nil {
+ return err
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(iter vocab.ActivityStreamsObjectPropertyIterator) error {
+ t := iter.GetType()
+ if t == nil {
+ return fmt.Errorf("update requires an object to be wholly provided")
+ }
+ id, err := GetId(t)
+ if err != nil {
+ return err
+ }
+ var unlock func()
+ unlock, err = w.db.Lock(c, id)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ if err := w.db.Update(c, t); err != nil {
+ return err
+ }
+ return nil
+ }
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ if err := loopFn(iter); err != nil {
+ return err
+ }
+ }
+ if w.Update != nil {
+ return w.Update(c, a)
+ }
+ return nil
+}
+
+// deleteFn implements the federating Delete activity side effects.
+func (w FederatingWrappedCallbacks) deleteFn(c context.Context, a vocab.ActivityStreamsDelete) error {
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ if err := mustHaveActivityOriginMatchObjects(a); err != nil {
+ return err
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(iter vocab.ActivityStreamsObjectPropertyIterator) error {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ var unlock func()
+ unlock, err = w.db.Lock(c, id)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ if err := w.db.Delete(c, id); err != nil {
+ return err
+ }
+ return nil
+ }
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ if err := loopFn(iter); err != nil {
+ return err
+ }
+ }
+ if w.Delete != nil {
+ return w.Delete(c, a)
+ }
+ return nil
+}
+
+// follow implements the federating Follow activity side effects.
+func (w FederatingWrappedCallbacks) follow(c context.Context, a vocab.ActivityStreamsFollow) error {
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ // Check that we own at least one of the 'object' properties, and ensure
+ // it is to the actor that owns this inbox.
+ //
+ // If not then don't send a response. It was federated to us as an FYI,
+ // by mistake, or some other reason.
+ unlock, err := w.db.Lock(c, w.inboxIRI)
+ if err != nil {
+ return err
+ }
+ // WARNING: Unlock not deferred.
+ actorIRI, err := w.db.ActorForInbox(c, w.inboxIRI)
+ unlock() // unlock even on error
+ if err != nil {
+ return err
+ }
+ // Unlock must be called by now and every branch above.
+ isMe := false
+ if w.OnFollow != OnFollowDoNothing {
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ if id.String() == actorIRI.String() {
+ isMe = true
+ break
+ }
+ }
+ }
+ if isMe {
+ // Prepare the response.
+ var response Activity
+ if w.OnFollow == OnFollowAutomaticallyAccept {
+ response = streams.NewActivityStreamsAccept()
+ } else if w.OnFollow == OnFollowAutomaticallyReject {
+ response = streams.NewActivityStreamsReject()
+ } else {
+ return fmt.Errorf("unknown OnFollowBehavior: %d", w.OnFollow)
+ }
+ // Set us as the 'actor'.
+ me := streams.NewActivityStreamsActorProperty()
+ response.SetActivityStreamsActor(me)
+ me.AppendIRI(actorIRI)
+ // Set the Follow as the 'object' property.
+ op := streams.NewActivityStreamsObjectProperty()
+ response.SetActivityStreamsObject(op)
+ op.AppendActivityStreamsFollow(a)
+ // Add all actors on the original Follow to the 'to' property.
+ recipients := make([]*url.URL, 0)
+ to := streams.NewActivityStreamsToProperty()
+ response.SetActivityStreamsTo(to)
+ followActors := a.GetActivityStreamsActor()
+ for iter := followActors.Begin(); iter != followActors.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ to.AppendIRI(id)
+ recipients = append(recipients, id)
+ }
+ if w.OnFollow == OnFollowAutomaticallyAccept {
+ // If automatically accepting, then also update our
+ // followers collection with the new actors.
+ //
+ // If automatically rejecting, do not update the
+ // followers collection.
+ unlock, err := w.db.Lock(c, actorIRI)
+ if err != nil {
+ return err
+ }
+ // WARNING: Unlock not deferred.
+ followers, err := w.db.Followers(c, actorIRI)
+ if err != nil {
+ unlock()
+ return err
+ }
+ items := followers.GetActivityStreamsItems()
+ if items == nil {
+ items = streams.NewActivityStreamsItemsProperty()
+ followers.SetActivityStreamsItems(items)
+ }
+ for _, elem := range recipients {
+ items.PrependIRI(elem)
+ }
+ err = w.db.Update(c, followers)
+ unlock() // unlock even on error
+ if err != nil {
+ return err
+ }
+ // Unlock must be called by now and every branch above.
+ }
+ // Lock without defer!
+ unlock, err := w.db.Lock(c, w.inboxIRI)
+ if err != nil {
+ return err
+ }
+ outboxIRI, err := w.db.OutboxForInbox(c, w.inboxIRI)
+ unlock() // unlock after, regardless
+ if err != nil {
+ return err
+ }
+ // Everything must be unlocked by now.
+ if err := w.addNewIds(c, response); err != nil {
+ return err
+ } else if err := w.deliver(c, outboxIRI, response); err != nil {
+ return err
+ }
+ }
+ if w.Follow != nil {
+ return w.Follow(c, a)
+ }
+ return nil
+}
+
+// accept implements the federating Accept activity side effects.
+func (w FederatingWrappedCallbacks) accept(c context.Context, a vocab.ActivityStreamsAccept) error {
+ op := a.GetActivityStreamsObject()
+ if op != nil && op.Len() > 0 {
+ // Get this actor's id.
+ unlock, err := w.db.Lock(c, w.inboxIRI)
+ if err != nil {
+ return err
+ }
+ // WARNING: Unlock not deferred.
+ actorIRI, err := w.db.ActorForInbox(c, w.inboxIRI)
+ unlock() // unlock after regardless
+ if err != nil {
+ return err
+ }
+ // Unlock must be called by now and every branch above.
+ //
+ // Determine if we are in a follow on the 'object' property.
+ //
+ // TODO: Handle Accept multiple Follow.
+ var maybeMyFollowIRI *url.URL
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ t := iter.GetType()
+ if t == nil && iter.IsIRI() {
+ // Attempt to dereference the IRI instead
+ tport, err := w.newTransport(c, w.inboxIRI, goFedUserAgent())
+ if err != nil {
+ return err
+ }
+ resp, err := tport.Dereference(c, iter.GetIRI())
+ if err != nil {
+ return err
+ }
+ m, err := readActivityPubResp(resp)
+ if err != nil {
+ return err
+ }
+ t, err = streams.ToType(c, m)
+ if err != nil {
+ return err
+ }
+ } else if t == nil {
+ return fmt.Errorf("cannot handle federated create: object is neither a value nor IRI")
+ }
+ // Ensure it is a Follow.
+ if !streams.IsOrExtendsActivityStreamsFollow(t) {
+ continue
+ }
+ follow, ok := t.(Activity)
+ if !ok {
+ return fmt.Errorf("a Follow in an Accept does not satisfy the Activity interface")
+ }
+ followId, err := GetId(follow)
+ if err != nil {
+ return err
+ }
+ // Ensure that we are one of the actors on the Follow.
+ actors := follow.GetActivityStreamsActor()
+ for iter := actors.Begin(); iter != actors.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ if id.String() == actorIRI.String() {
+ maybeMyFollowIRI = followId
+ break
+ }
+ }
+ // Continue breaking if we found ourselves
+ if maybeMyFollowIRI != nil {
+ break
+ }
+ }
+ // If we received an Accept whose 'object' is a Follow with an
+ // Accept that we sent, add to the following collection.
+ if maybeMyFollowIRI != nil {
+ // Verify our Follow request exists and the peer didn't
+ // fabricate it.
+ activityActors := a.GetActivityStreamsActor()
+ if activityActors == nil || activityActors.Len() == 0 {
+ return fmt.Errorf("an Accept with a Follow has no actors")
+ }
+ // This may be a duplicate check if we dereferenced the
+ // Follow above. TODO: Separate this logic to avoid
+ // redundancy.
+ //
+ // Use an anonymous function to properly scope the
+ // database lock, immediately call it.
+ err = func() error {
+ unlock, err := w.db.Lock(c, maybeMyFollowIRI)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ t, err := w.db.Get(c, maybeMyFollowIRI)
+ if err != nil {
+ return err
+ }
+ if !streams.IsOrExtendsActivityStreamsFollow(t) {
+ return fmt.Errorf("peer gave an Accept wrapping a Follow but provided a non-Follow id")
+ }
+ follow, ok := t.(Activity)
+ if !ok {
+ return fmt.Errorf("a Follow in an Accept does not satisfy the Activity interface")
+ }
+ // Ensure that we are one of the actors on the Follow.
+ ok = false
+ actors := follow.GetActivityStreamsActor()
+ for iter := actors.Begin(); iter != actors.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ if id.String() == actorIRI.String() {
+ ok = true
+ break
+ }
+ }
+ if !ok {
+ return fmt.Errorf("peer gave an Accept wrapping a Follow but we are not the actor on that Follow")
+ }
+ // Build map of original Accept actors
+ acceptActors := make(map[string]bool)
+ for iter := activityActors.Begin(); iter != activityActors.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ acceptActors[id.String()] = false
+ }
+ // Verify all actor(s) were on the original Follow.
+ followObj := follow.GetActivityStreamsObject()
+ for iter := followObj.Begin(); iter != followObj.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ if _, ok := acceptActors[id.String()]; ok {
+ acceptActors[id.String()] = true
+ }
+ }
+ for _, found := range acceptActors {
+ if !found {
+ return fmt.Errorf("peer gave an Accept wrapping a Follow but was not an object in the original Follow")
+ }
+ }
+ return nil
+ }()
+ if err != nil {
+ return err
+ }
+ // Add the peer to our following collection.
+ unlock, err := w.db.Lock(c, actorIRI)
+ if err != nil {
+ return err
+ }
+ // WARNING: Unlock not deferred.
+ following, err := w.db.Following(c, actorIRI)
+ if err != nil {
+ unlock()
+ return err
+ }
+ items := following.GetActivityStreamsItems()
+ if items == nil {
+ items = streams.NewActivityStreamsItemsProperty()
+ following.SetActivityStreamsItems(items)
+ }
+ for iter := activityActors.Begin(); iter != activityActors.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ unlock()
+ return err
+ }
+ items.PrependIRI(id)
+ }
+ err = w.db.Update(c, following)
+ unlock() // unlock after regardless
+ if err != nil {
+ return err
+ }
+ // Unlock must be called by now and every branch above.
+ }
+ }
+ if w.Accept != nil {
+ return w.Accept(c, a)
+ }
+ return nil
+}
+
+// reject implements the federating Reject activity side effects.
+func (w FederatingWrappedCallbacks) reject(c context.Context, a vocab.ActivityStreamsReject) error {
+ if w.Reject != nil {
+ return w.Reject(c, a)
+ }
+ return nil
+}
+
+// add implements the federating Add activity side effects.
+func (w FederatingWrappedCallbacks) add(c context.Context, a vocab.ActivityStreamsAdd) error {
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ target := a.GetActivityStreamsTarget()
+ if target == nil || target.Len() == 0 {
+ return ErrTargetRequired
+ }
+ if err := add(c, op, target, w.db); err != nil {
+ return err
+ }
+ if w.Add != nil {
+ return w.Add(c, a)
+ }
+ return nil
+}
+
+// remove implements the federating Remove activity side effects.
+func (w FederatingWrappedCallbacks) remove(c context.Context, a vocab.ActivityStreamsRemove) error {
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ target := a.GetActivityStreamsTarget()
+ if target == nil || target.Len() == 0 {
+ return ErrTargetRequired
+ }
+ if err := remove(c, op, target, w.db); err != nil {
+ return err
+ }
+ if w.Remove != nil {
+ return w.Remove(c, a)
+ }
+ return nil
+}
+
+// like implements the federating Like activity side effects.
+func (w FederatingWrappedCallbacks) like(c context.Context, a vocab.ActivityStreamsLike) error {
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ id, err := GetId(a)
+ if err != nil {
+ return err
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(iter vocab.ActivityStreamsObjectPropertyIterator) error {
+ objId, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ unlock, err := w.db.Lock(c, objId)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ if owns, err := w.db.Owns(c, objId); err != nil {
+ return err
+ } else if !owns {
+ return nil
+ }
+ t, err := w.db.Get(c, objId)
+ if err != nil {
+ return err
+ }
+ l, ok := t.(likeser)
+ if !ok {
+ return fmt.Errorf("cannot add Like to likes collection for type %T", t)
+ }
+ // Get 'likes' property on the object, creating default if
+ // necessary.
+ likes := l.GetActivityStreamsLikes()
+ if likes == nil {
+ likes = streams.NewActivityStreamsLikesProperty()
+ l.SetActivityStreamsLikes(likes)
+ }
+ // Get 'likes' value, defaulting to a collection.
+ likesT := likes.GetType()
+ if likesT == nil {
+ col := streams.NewActivityStreamsCollection()
+ likesT = col
+ likes.SetActivityStreamsCollection(col)
+ }
+ // Prepend the activity's 'id' on the 'likes' Collection or
+ // OrderedCollection.
+ if col, ok := likesT.(itemser); ok {
+ items := col.GetActivityStreamsItems()
+ if items == nil {
+ items = streams.NewActivityStreamsItemsProperty()
+ col.SetActivityStreamsItems(items)
+ }
+ items.PrependIRI(id)
+ } else if oCol, ok := likesT.(orderedItemser); ok {
+ oItems := oCol.GetActivityStreamsOrderedItems()
+ if oItems == nil {
+ oItems = streams.NewActivityStreamsOrderedItemsProperty()
+ oCol.SetActivityStreamsOrderedItems(oItems)
+ }
+ oItems.PrependIRI(id)
+ } else {
+ return fmt.Errorf("likes type is neither a Collection nor an OrderedCollection: %T", likesT)
+ }
+ err = w.db.Update(c, t)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ if err := loopFn(iter); err != nil {
+ return err
+ }
+ }
+ if w.Like != nil {
+ return w.Like(c, a)
+ }
+ return nil
+}
+
+// announce implements the federating Announce activity side effects.
+func (w FederatingWrappedCallbacks) announce(c context.Context, a vocab.ActivityStreamsAnnounce) error {
+ id, err := GetId(a)
+ if err != nil {
+ return err
+ }
+ op := a.GetActivityStreamsObject()
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(iter vocab.ActivityStreamsObjectPropertyIterator) error {
+ objId, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ unlock, err := w.db.Lock(c, objId)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ if owns, err := w.db.Owns(c, objId); err != nil {
+ return err
+ } else if !owns {
+ return nil
+ }
+ t, err := w.db.Get(c, objId)
+ if err != nil {
+ return err
+ }
+ s, ok := t.(shareser)
+ if !ok {
+ return fmt.Errorf("cannot add Announce to Shares collection for type %T", t)
+ }
+ // Get 'shares' property on the object, creating default if
+ // necessary.
+ shares := s.GetActivityStreamsShares()
+ if shares == nil {
+ shares = streams.NewActivityStreamsSharesProperty()
+ s.SetActivityStreamsShares(shares)
+ }
+ // Get 'shares' value, defaulting to a collection.
+ sharesT := shares.GetType()
+ if sharesT == nil {
+ col := streams.NewActivityStreamsCollection()
+ sharesT = col
+ shares.SetActivityStreamsCollection(col)
+ }
+ // Prepend the activity's 'id' on the 'shares' Collection or
+ // OrderedCollection.
+ if col, ok := sharesT.(itemser); ok {
+ items := col.GetActivityStreamsItems()
+ if items == nil {
+ items = streams.NewActivityStreamsItemsProperty()
+ col.SetActivityStreamsItems(items)
+ }
+ items.PrependIRI(id)
+ } else if oCol, ok := sharesT.(orderedItemser); ok {
+ oItems := oCol.GetActivityStreamsOrderedItems()
+ if oItems == nil {
+ oItems = streams.NewActivityStreamsOrderedItemsProperty()
+ oCol.SetActivityStreamsOrderedItems(oItems)
+ }
+ oItems.PrependIRI(id)
+ } else {
+ return fmt.Errorf("shares type is neither a Collection nor an OrderedCollection: %T", sharesT)
+ }
+ err = w.db.Update(c, t)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+ if op != nil {
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ if err := loopFn(iter); err != nil {
+ return err
+ }
+ }
+ }
+ if w.Announce != nil {
+ return w.Announce(c, a)
+ }
+ return nil
+}
+
+// undo implements the federating Undo activity side effects.
+func (w FederatingWrappedCallbacks) undo(c context.Context, a vocab.ActivityStreamsUndo) error {
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ actors := a.GetActivityStreamsActor()
+ if err := mustHaveActivityActorsMatchObjectActors(c, actors, op, w.newTransport, w.inboxIRI); err != nil {
+ return err
+ }
+ if w.Undo != nil {
+ return w.Undo(c, a)
+ }
+ return nil
+}
+
+// block implements the federating Block activity side effects.
+func (w FederatingWrappedCallbacks) block(c context.Context, a vocab.ActivityStreamsBlock) error {
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ if w.Block != nil {
+ return w.Block(c, a)
+ }
+ return nil
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/handlers.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/handlers.go
new file mode 100644
index 000000000..688b8158f
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/handlers.go
@@ -0,0 +1,115 @@
+package pub
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+
+ "codeberg.org/superseriousbusiness/activity/streams"
+)
+
+var ErrNotFound = errors.New("go-fed/activity: ActivityStreams data not found")
+
+// HandlerFunc determines whether an incoming HTTP request is an ActivityStreams
+// GET request, and if so attempts to serve ActivityStreams data.
+//
+// If an error is returned, then the calling function is responsible for writing
+// to the ResponseWriter as part of error handling.
+//
+// If 'isASRequest' is false and there is no error, then the calling function
+// may continue processing the request, and the HandlerFunc will not have
+// written anything to the ResponseWriter. For example, a webpage may be served
+// instead.
+//
+// If 'isASRequest' is true and there is no error, then the HandlerFunc
+// successfully served the request and wrote to the ResponseWriter.
+//
+// Callers are responsible for authorized access to this resource.
+type HandlerFunc func(c context.Context, w http.ResponseWriter, r *http.Request) (isASRequest bool, err error)
+
+// NewActivityStreamsHandler creates a HandlerFunc to serve ActivityStreams
+// requests which are coming from other clients or servers that wish to obtain
+// an ActivityStreams representation of data.
+//
+// Strips retrieved ActivityStreams values of sensitive fields ('bto' and 'bcc')
+// before responding with them. Sets the appropriate HTTP status code for
+// Tombstone Activities as well.
+//
+// Defaults to supporting content to be retrieved by HTTPS only.
+func NewActivityStreamsHandler(db Database, clock Clock) HandlerFunc {
+ return NewActivityStreamsHandlerScheme(db, clock, "https")
+}
+
+// NewActivityStreamsHandlerScheme creates a HandlerFunc to serve
+// ActivityStreams requests which are coming from other clients or servers that
+// wish to obtain an ActivityStreams representation of data provided by the
+// specified protocol scheme.
+//
+// Strips retrieved ActivityStreams values of sensitive fields ('bto' and 'bcc')
+// before responding with them. Sets the appropriate HTTP status code for
+// Tombstone Activities as well.
+//
+// Specifying the "scheme" allows for retrieving ActivityStreams content with
+// identifiers such as HTTP, HTTPS, or other protocol schemes.
+//
+// Returns ErrNotFound when the database does not retrieve any data and no
+// errors occurred during retrieval.
+func NewActivityStreamsHandlerScheme(db Database, clock Clock, scheme string) HandlerFunc {
+ return func(c context.Context, w http.ResponseWriter, r *http.Request) (isASRequest bool, err error) {
+ // Do nothing if it is not an ActivityPub GET request
+ if !isActivityPubGet(r) {
+ return
+ }
+ isASRequest = true
+ id := requestId(r, scheme)
+
+ var unlock func()
+
+ // Lock and obtain a copy of the requested ActivityStreams value
+ unlock, err = db.Lock(c, id)
+ if err != nil {
+ return
+ }
+ // WARNING: Unlock not deferred
+ t, err := db.Get(c, id)
+ unlock() // unlock even on error
+ if err != nil {
+ return
+ }
+ // Unlock must have been called by this point and in every
+ // branch above
+ if t == nil {
+ err = ErrNotFound
+ return
+ }
+ // Remove sensitive fields.
+ clearSensitiveFields(t)
+ // Serialize the fetched value.
+ m, err := streams.Serialize(t)
+ if err != nil {
+ return
+ }
+ raw, err := json.Marshal(m)
+ if err != nil {
+ return
+ }
+ // Construct the response.
+ addResponseHeaders(w.Header(), clock, raw)
+ // Write the response.
+ if streams.IsOrExtendsActivityStreamsTombstone(t) {
+ w.WriteHeader(http.StatusGone)
+ } else {
+ w.WriteHeader(http.StatusOK)
+ }
+ n, err := w.Write(raw)
+ if err != nil {
+ return
+ } else if n != len(raw) {
+ err = fmt.Errorf("only wrote %d of %d bytes", n, len(raw))
+ return
+ }
+ return
+ }
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/property_interfaces.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/property_interfaces.go
new file mode 100644
index 000000000..abec7ad53
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/property_interfaces.go
@@ -0,0 +1,118 @@
+package pub
+
+import (
+ "net/url"
+
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// inReplyToer is an ActivityStreams type with an 'inReplyTo' property
+type inReplyToer interface {
+ GetActivityStreamsInReplyTo() vocab.ActivityStreamsInReplyToProperty
+}
+
+// objecter is an ActivityStreams type with an 'object' property
+type objecter interface {
+ GetActivityStreamsObject() vocab.ActivityStreamsObjectProperty
+}
+
+// targeter is an ActivityStreams type with a 'target' property
+type targeter interface {
+ GetActivityStreamsTarget() vocab.ActivityStreamsTargetProperty
+}
+
+// tagger is an ActivityStreams type with a 'tag' property
+type tagger interface {
+ GetActivityStreamsTag() vocab.ActivityStreamsTagProperty
+}
+
+// hrefer is an ActivityStreams type with a 'href' property
+type hrefer interface {
+ GetActivityStreamsHref() vocab.ActivityStreamsHrefProperty
+}
+
+// itemser is an ActivityStreams type with an 'items' property
+type itemser interface {
+ GetActivityStreamsItems() vocab.ActivityStreamsItemsProperty
+ SetActivityStreamsItems(vocab.ActivityStreamsItemsProperty)
+}
+
+// orderedItemser is an ActivityStreams type with an 'orderedItems' property
+type orderedItemser interface {
+ GetActivityStreamsOrderedItems() vocab.ActivityStreamsOrderedItemsProperty
+ SetActivityStreamsOrderedItems(vocab.ActivityStreamsOrderedItemsProperty)
+}
+
+// publisheder is an ActivityStreams type with a 'published' property
+type publisheder interface {
+ GetActivityStreamsPublished() vocab.ActivityStreamsPublishedProperty
+}
+
+// updateder is an ActivityStreams type with an 'updateder' property
+type updateder interface {
+ GetActivityStreamsUpdated() vocab.ActivityStreamsUpdatedProperty
+}
+
+// toer is an ActivityStreams type with a 'to' property
+type toer interface {
+ GetActivityStreamsTo() vocab.ActivityStreamsToProperty
+ SetActivityStreamsTo(i vocab.ActivityStreamsToProperty)
+}
+
+// btoer is an ActivityStreams type with a 'bto' property
+type btoer interface {
+ GetActivityStreamsBto() vocab.ActivityStreamsBtoProperty
+ SetActivityStreamsBto(i vocab.ActivityStreamsBtoProperty)
+}
+
+// ccer is an ActivityStreams type with a 'cc' property
+type ccer interface {
+ GetActivityStreamsCc() vocab.ActivityStreamsCcProperty
+ SetActivityStreamsCc(i vocab.ActivityStreamsCcProperty)
+}
+
+// bccer is an ActivityStreams type with a 'bcc' property
+type bccer interface {
+ GetActivityStreamsBcc() vocab.ActivityStreamsBccProperty
+ SetActivityStreamsBcc(i vocab.ActivityStreamsBccProperty)
+}
+
+// audiencer is an ActivityStreams type with an 'audience' property
+type audiencer interface {
+ GetActivityStreamsAudience() vocab.ActivityStreamsAudienceProperty
+ SetActivityStreamsAudience(i vocab.ActivityStreamsAudienceProperty)
+}
+
+// inboxer is an ActivityStreams type with an 'inbox' property
+type inboxer interface {
+ GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty
+}
+
+// attributedToer is an ActivityStreams type with an 'attributedTo' property
+type attributedToer interface {
+ GetActivityStreamsAttributedTo() vocab.ActivityStreamsAttributedToProperty
+ SetActivityStreamsAttributedTo(i vocab.ActivityStreamsAttributedToProperty)
+}
+
+// likeser is an ActivityStreams type with a 'likes' property
+type likeser interface {
+ GetActivityStreamsLikes() vocab.ActivityStreamsLikesProperty
+ SetActivityStreamsLikes(i vocab.ActivityStreamsLikesProperty)
+}
+
+// shareser is an ActivityStreams type with a 'shares' property
+type shareser interface {
+ GetActivityStreamsShares() vocab.ActivityStreamsSharesProperty
+ SetActivityStreamsShares(i vocab.ActivityStreamsSharesProperty)
+}
+
+// actorer is an ActivityStreams type with an 'actor' property
+type actorer interface {
+ GetActivityStreamsActor() vocab.ActivityStreamsActorProperty
+ SetActivityStreamsActor(i vocab.ActivityStreamsActorProperty)
+}
+
+// appendIRIer is an ActivityStreams type that can Append IRIs.
+type appendIRIer interface {
+ AppendIRI(v *url.URL)
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/side_effect_actor.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/side_effect_actor.go
new file mode 100644
index 000000000..d98abfdb3
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/side_effect_actor.go
@@ -0,0 +1,1047 @@
+package pub
+
+import (
+ "context"
+ "fmt"
+ "net/http"
+ "net/url"
+
+ "codeberg.org/superseriousbusiness/activity/streams"
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// sideEffectActor must satisfy the DelegateActor interface.
+var _ DelegateActor = &SideEffectActor{}
+
+// SideEffectActor is a DelegateActor that handles the ActivityPub
+// implementation side effects, but requires a more opinionated application to
+// be written.
+//
+// Note that when using the SideEffectActor with an application that good-faith
+// implements its required interfaces, the ActivityPub specification is
+// guaranteed to be correctly followed.
+type SideEffectActor struct {
+ // When doing deliveries to remote servers via the s2s protocol, the side effect
+ // actor will by default use the Serialize function from the streams package.
+ // However, this can be overridden after the side effect actor is intantiated,
+ // by setting the exposed Serialize function on the struct. For example:
+ //
+ // a := NewSideEffectActor(...)
+ // a.Serialize = func(a vocab.Type) (m map[string]interface{}, e error) {
+ // // Put your custom serializer logic here.
+ // }
+ //
+ // Note that you should only do this *immediately* after instantiating the side
+ // effect actor -- never while your application is already running, as this will
+ // likely cause race conditions or other problems! In most cases, you will never
+ // need to change this; it's provided solely to allow easier customization by
+ // applications.
+ Serialize func(a vocab.Type) (m map[string]interface{}, e error)
+
+ // When doing deliveries to remote servers via the s2s protocol, it may be desirable
+ // for implementations to be able to pre-sort recipients so that higher-priority
+ // recipients are higher up in the delivery queue, and lower-priority recipients
+ // are further down. This can be achieved by setting the DeliveryRecipientPreSort
+ // function on the side effect actor after it's instantiated. For example:
+ //
+ // a := NewSideEffectActor(...)
+ // a.DeliveryRecipientPreSort = func(actorAndCollectionIRIs []*url.URL) []*url.URL {
+ // // Put your sorting logic here.
+ // }
+ //
+ // The actorAndCollectionIRIs parameter will be the initial list of IRIs derived by
+ // looking at the "to", "cc", "bto", "bcc", and "audience" properties of the activity
+ // being delivered, excluding the AP public IRI, and before dereferencing of inboxes.
+ // It may look something like this:
+ //
+ // [
+ // "https://example.org/users/someone/followers", // <-- collection IRI
+ // "https://another.example.org/users/someone_else", // <-- actor IRI
+ // "[...]" // <-- etc
+ // ]
+ //
+ // In this case, implementers may wish to sort the slice so that the directly-addressed
+ // actor "https://another.example.org/users/someone_else" occurs at an earlier index in
+ // the slice than the followers collection "https://example.org/users/someone/followers",
+ // so that "@someone_else" receives the delivery first.
+ //
+ // Note that you should only do this *immediately* after instantiating the side
+ // effect actor -- never while your application is already running, as this will
+ // likely cause race conditions or other problems! It's also completely fine to not
+ // set this function at all -- in this case, no pre-sorting of recipients will be
+ // performed, and delivery will occur in a non-determinate order.
+ DeliveryRecipientPreSort func(actorAndCollectionIRIs []*url.URL) []*url.URL
+
+ common CommonBehavior
+ s2s FederatingProtocol
+ c2s SocialProtocol
+ db Database
+ clock Clock
+}
+
+// NewSideEffectActor returns a new SideEffectActor, which satisfies the
+// DelegateActor interface. Most of the time you will not need to call this
+// function, and should instead rely on the NewSocialActor, NewFederatingActor,
+// and NewActor functions, all of which use a SideEffectActor under the hood.
+// Nevertheless, this function is exposed in case application developers need
+// a SideEffectActor for some other reason (tests, monkey patches, etc).
+//
+// If you are using the returned SideEffectActor for federation, ensure that s2s
+// is not nil. Likewise, if you are using it for the social protocol, ensure
+// that c2s is not nil.
+func NewSideEffectActor(c CommonBehavior,
+ s2s FederatingProtocol,
+ c2s SocialProtocol,
+ db Database,
+ clock Clock) *SideEffectActor {
+ return &SideEffectActor{
+ Serialize: streams.Serialize,
+ common: c,
+ s2s: s2s,
+ c2s: c2s,
+ db: db,
+ clock: clock,
+ }
+}
+
+// PostInboxRequestBodyHook defers to the delegate.
+func (a *SideEffectActor) PostInboxRequestBodyHook(c context.Context, r *http.Request, activity Activity) (context.Context, error) {
+ return a.s2s.PostInboxRequestBodyHook(c, r, activity)
+}
+
+// PostOutboxRequestBodyHook defers to the delegate.
+func (a *SideEffectActor) PostOutboxRequestBodyHook(c context.Context, r *http.Request, data vocab.Type) (context.Context, error) {
+ return a.c2s.PostOutboxRequestBodyHook(c, r, data)
+}
+
+// AuthenticatePostInbox defers to the delegate to authenticate the request.
+func (a *SideEffectActor) AuthenticatePostInbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error) {
+ return a.s2s.AuthenticatePostInbox(c, w, r)
+}
+
+// AuthenticateGetInbox defers to the delegate to authenticate the request.
+func (a *SideEffectActor) AuthenticateGetInbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error) {
+ return a.common.AuthenticateGetInbox(c, w, r)
+}
+
+// AuthenticatePostOutbox defers to the delegate to authenticate the request.
+func (a *SideEffectActor) AuthenticatePostOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error) {
+ return a.c2s.AuthenticatePostOutbox(c, w, r)
+}
+
+// AuthenticateGetOutbox defers to the delegate to authenticate the request.
+func (a *SideEffectActor) AuthenticateGetOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error) {
+ return a.common.AuthenticateGetOutbox(c, w, r)
+}
+
+// GetOutbox delegates to the SocialProtocol.
+func (a *SideEffectActor) GetOutbox(c context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
+ return a.common.GetOutbox(c, r)
+}
+
+// GetInbox delegates to the FederatingProtocol.
+func (a *SideEffectActor) GetInbox(c context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
+ return a.s2s.GetInbox(c, r)
+}
+
+// AuthorizePostInbox defers to the federating protocol whether the peer request
+// is authorized based on the actors' ids.
+func (a *SideEffectActor) AuthorizePostInbox(c context.Context, w http.ResponseWriter, activity Activity) (authorized bool, err error) {
+ authorized = false
+ actor := activity.GetActivityStreamsActor()
+ if actor == nil {
+ err = fmt.Errorf("no actors in post to inbox")
+ return
+ }
+ var iris []*url.URL
+ for i := 0; i < actor.Len(); i++ {
+ iter := actor.At(i)
+ if iter.IsIRI() {
+ iris = append(iris, iter.GetIRI())
+ } else if t := iter.GetType(); t != nil {
+ iris = append(iris, activity.GetJSONLDId().Get())
+ } else {
+ err = fmt.Errorf("actor at index %d is missing an id", i)
+ return
+ }
+ }
+ // Determine if the actor(s) sending this request are blocked.
+ var blocked bool
+ if blocked, err = a.s2s.Blocked(c, iris); err != nil {
+ return
+ } else if blocked {
+ w.WriteHeader(http.StatusForbidden)
+ return
+ }
+ authorized = true
+ return
+}
+
+// PostInbox handles the side effects of determining whether to block the peer's
+// request, adding the activity to the actor's inbox, and triggering side
+// effects based on the activity's type.
+func (a *SideEffectActor) PostInbox(c context.Context, inboxIRI *url.URL, activity Activity) error {
+ isNew, err := a.addToInboxIfNew(c, inboxIRI, activity)
+ if err != nil {
+ return err
+ }
+ if isNew {
+ wrapped, other, err := a.s2s.FederatingCallbacks(c)
+ if err != nil {
+ return err
+ }
+ // Populate side channels.
+ wrapped.db = a.db
+ wrapped.inboxIRI = inboxIRI
+ wrapped.newTransport = a.common.NewTransport
+ wrapped.deliver = a.Deliver
+ wrapped.addNewIds = a.AddNewIDs
+ res, err := streams.NewTypeResolver(wrapped.callbacks(other)...)
+ if err != nil {
+ return err
+ }
+ if err = res.Resolve(c, activity); err != nil && !streams.IsUnmatchedErr(err) {
+ return err
+ } else if streams.IsUnmatchedErr(err) {
+ err = a.s2s.DefaultCallback(c, activity)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+// InboxForwarding implements the 3-part inbox forwarding algorithm specified in
+// the ActivityPub specification. Does not modify the Activity, but may send
+// outbound requests as a side effect.
+//
+// InboxForwarding sets the federated data in the database.
+func (a *SideEffectActor) InboxForwarding(c context.Context, inboxIRI *url.URL, activity Activity) error {
+ // 1. Must be first time we have seen this Activity.
+ //
+ // Obtain the id of the activity
+ id := activity.GetJSONLDId()
+ // Acquire a lock for the id. To be held for the rest of execution.
+ unlock, err := a.db.Lock(c, id.Get())
+ if err != nil {
+ return err
+ }
+ // WARNING: Unlock is not deferred
+ //
+ // If the database already contains the activity, exit early.
+ exists, err := a.db.Exists(c, id.Get())
+ if err != nil {
+ unlock()
+ return err
+ } else if exists {
+ unlock()
+ return nil
+ }
+ // Attempt to create the activity entry.
+ err = a.db.Create(c, activity)
+ unlock() // unlock even on error return
+ if err != nil {
+ return err
+ }
+ // Unlock by this point and in every branch above.
+ //
+ // 2. The values of 'to', 'cc', or 'audience' are Collections owned by
+ // this server.
+ var r []*url.URL
+ to := activity.GetActivityStreamsTo()
+ if to != nil {
+ for iter := to.Begin(); iter != to.End(); iter = iter.Next() {
+ val, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ r = append(r, val)
+ }
+ }
+ cc := activity.GetActivityStreamsCc()
+ if cc != nil {
+ for iter := cc.Begin(); iter != cc.End(); iter = iter.Next() {
+ val, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ r = append(r, val)
+ }
+ }
+ audience := activity.GetActivityStreamsAudience()
+ if audience != nil {
+ for iter := audience.Begin(); iter != audience.End(); iter = iter.Next() {
+ val, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ r = append(r, val)
+ }
+ }
+ // Find all IRIs owned by this server. We need to find all of them so
+ // that forwarding can properly occur.
+ var myIRIs []*url.URL
+ for _, iri := range r {
+ if err != nil {
+ return err
+ }
+ var unlock func()
+ unlock, err = a.db.Lock(c, iri)
+ if err != nil {
+ return err
+ }
+ // WARNING: Unlock is not deferred
+ owns, err := a.db.Owns(c, iri)
+ unlock() // unlock even on error
+ if err != nil {
+ return err
+ } else if !owns {
+ continue
+ }
+ // Unlock by this point and in every branch above.
+ myIRIs = append(myIRIs, iri)
+ }
+ // Finally, load our IRIs to determine if they are a Collection or
+ // OrderedCollection.
+ //
+ // Load the unfiltered IRIs.
+ var colIRIs []*url.URL
+ col := make(map[string]itemser)
+ oCol := make(map[string]orderedItemser)
+ for _, iri := range myIRIs {
+ var unlock func()
+ unlock, err = a.db.Lock(c, iri)
+ if err != nil {
+ return err
+ }
+ // WARNING: Not Unlocked
+ t, err := a.db.Get(c, iri)
+ if err != nil {
+ return err
+ }
+ if streams.IsOrExtendsActivityStreamsOrderedCollection(t) {
+ if im, ok := t.(orderedItemser); ok {
+ oCol[iri.String()] = im
+ colIRIs = append(colIRIs, iri)
+ defer unlock()
+ } else {
+ unlock() // unlock instantly
+ }
+ } else if streams.IsOrExtendsActivityStreamsCollection(t) {
+ if im, ok := t.(itemser); ok {
+ col[iri.String()] = im
+ colIRIs = append(colIRIs, iri)
+ defer unlock()
+ } else {
+ unlock() // unlock instantly
+ }
+ } else {
+ unlock() // unlock instantly
+ }
+ }
+ // If we own none of the Collection IRIs in 'to', 'cc', or 'audience'
+ // then no need to do inbox forwarding. We have nothing to forward to.
+ if len(colIRIs) == 0 {
+ return nil
+ }
+ // 3. The values of 'inReplyTo', 'object', 'target', or 'tag' are owned
+ // by this server. This is only a boolean trigger: As soon as we get
+ // a hit that we own something, then we should do inbox forwarding.
+ maxDepth := a.s2s.MaxInboxForwardingRecursionDepth(c)
+ ownsValue, err := a.hasInboxForwardingValues(c, inboxIRI, activity, maxDepth, 0)
+ if err != nil {
+ return err
+ }
+ // If we don't own any of the 'inReplyTo', 'object', 'target', or 'tag'
+ // values, then no need to do inbox forwarding.
+ if !ownsValue {
+ return nil
+ }
+ // Do the inbox forwarding since the above conditions hold true. Support
+ // the behavior of letting the application filter out the resulting
+ // collections to be targeted.
+ toSend, err := a.s2s.FilterForwarding(c, colIRIs, activity)
+ if err != nil {
+ return err
+ }
+ recipients := make([]*url.URL, 0, len(toSend))
+ for _, iri := range toSend {
+ if c, ok := col[iri.String()]; ok {
+ if it := c.GetActivityStreamsItems(); it != nil {
+ for iter := it.Begin(); iter != it.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ recipients = append(recipients, id)
+ }
+ }
+ } else if oc, ok := oCol[iri.String()]; ok {
+ if oit := oc.GetActivityStreamsOrderedItems(); oit != nil {
+ for iter := oit.Begin(); iter != oit.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ recipients = append(recipients, id)
+ }
+ }
+ }
+ }
+ return a.deliverToRecipients(c, inboxIRI, activity, recipients)
+}
+
+// PostOutbox handles the side effects of adding the activity to the actor's
+// outbox, and triggering side effects based on the activity's type.
+//
+// This implementation assumes all types are meant to be delivered except for
+// the ActivityStreams Block type.
+func (a *SideEffectActor) PostOutbox(c context.Context, activity Activity, outboxIRI *url.URL, rawJSON map[string]interface{}) (deliverable bool, err error) {
+ // TODO: Determine this if c2s is nil
+ deliverable = true
+ if a.c2s != nil {
+ var wrapped SocialWrappedCallbacks
+ var other []interface{}
+ wrapped, other, err = a.c2s.SocialCallbacks(c)
+ if err != nil {
+ return
+ }
+ // Populate side channels.
+ wrapped.db = a.db
+ wrapped.outboxIRI = outboxIRI
+ wrapped.rawActivity = rawJSON
+ wrapped.clock = a.clock
+ wrapped.newTransport = a.common.NewTransport
+ undeliverable := false
+ wrapped.undeliverable = &undeliverable
+ var res *streams.TypeResolver
+ res, err = streams.NewTypeResolver(wrapped.callbacks(other)...)
+ if err != nil {
+ return
+ }
+ if err = res.Resolve(c, activity); err != nil && !streams.IsUnmatchedErr(err) {
+ return
+ } else if streams.IsUnmatchedErr(err) {
+ deliverable = true
+ err = a.c2s.DefaultCallback(c, activity)
+ if err != nil {
+ return
+ }
+ } else {
+ deliverable = !undeliverable
+ }
+ }
+ err = a.addToOutbox(c, outboxIRI, activity)
+ return
+}
+
+// AddNewIDs creates new 'id' entries on an activity and its objects if it is a
+// Create activity.
+func (a *SideEffectActor) AddNewIDs(c context.Context, activity Activity) error {
+ id, err := a.db.NewID(c, activity)
+ if err != nil {
+ return err
+ }
+ activityId := streams.NewJSONLDIdProperty()
+ activityId.Set(id)
+ activity.SetJSONLDId(activityId)
+ if streams.IsOrExtendsActivityStreamsCreate(activity) {
+ o, ok := activity.(objecter)
+ if !ok {
+ return fmt.Errorf("cannot add new id for Create: %T has no object property", activity)
+ }
+ if oProp := o.GetActivityStreamsObject(); oProp != nil {
+ for iter := oProp.Begin(); iter != oProp.End(); iter = iter.Next() {
+ t := iter.GetType()
+ if t == nil {
+ return fmt.Errorf("cannot add new id for object in Create: object is not embedded as a value literal")
+ }
+ id, err = a.db.NewID(c, t)
+ if err != nil {
+ return err
+ }
+ objId := streams.NewJSONLDIdProperty()
+ objId.Set(id)
+ t.SetJSONLDId(objId)
+ }
+ }
+ }
+ return nil
+}
+
+// deliver will complete the peer-to-peer sending of a federated message to
+// another server.
+//
+// Must be called if at least the federated protocol is supported.
+func (a *SideEffectActor) Deliver(c context.Context, outboxIRI *url.URL, activity Activity) error {
+ recipients, err := a.prepare(c, outboxIRI, activity)
+ if err != nil {
+ return err
+ }
+ return a.deliverToRecipients(c, outboxIRI, activity, recipients)
+}
+
+// WrapInCreate wraps an object with a Create activity.
+func (a *SideEffectActor) WrapInCreate(c context.Context, obj vocab.Type, outboxIRI *url.URL) (create vocab.ActivityStreamsCreate, err error) {
+ var unlock func()
+ unlock, err = a.db.Lock(c, outboxIRI)
+ if err != nil {
+ return
+ }
+ // WARNING: No deferring the Unlock
+ actorIRI, err := a.db.ActorForOutbox(c, outboxIRI)
+ unlock() // unlock after regardless
+ if err != nil {
+ return
+ }
+ // Unlock the lock at this point and every branch above
+ return wrapInCreate(c, obj, actorIRI)
+}
+
+// deliverToRecipients will take a prepared Activity and send it to specific
+// recipients on behalf of an actor.
+func (a *SideEffectActor) deliverToRecipients(c context.Context, boxIRI *url.URL, activity Activity, recipients []*url.URL) error {
+ // Call whichever serializer is
+ // set on the side effect actor.
+ m, err := a.Serialize(activity)
+ if err != nil {
+ return err
+ }
+
+ tp, err := a.common.NewTransport(c, boxIRI, goFedUserAgent())
+ if err != nil {
+ return err
+ }
+
+ return tp.BatchDeliver(c, m, recipients)
+}
+
+// addToOutbox adds the activity to the outbox and creates the activity in the
+// internal database as its own entry.
+func (a *SideEffectActor) addToOutbox(c context.Context, outboxIRI *url.URL, activity Activity) error {
+ // Set the activity in the database first.
+ id := activity.GetJSONLDId()
+ unlock, err := a.db.Lock(c, id.Get())
+ if err != nil {
+ return err
+ }
+ // WARNING: Unlock not deferred
+ err = a.db.Create(c, activity)
+ unlock() // unlock after regardless
+ if err != nil {
+ return err
+ }
+ // WARNING: Unlock(c, id) should be called by this point and in every
+ // return before here.
+ //
+ // Acquire a lock to read the outbox. Defer release.
+ unlock, err = a.db.Lock(c, outboxIRI)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ outbox, err := a.db.GetOutbox(c, outboxIRI)
+ if err != nil {
+ return err
+ }
+ // Prepend the activity to the list of 'orderedItems'.
+ oi := outbox.GetActivityStreamsOrderedItems()
+ if oi == nil {
+ oi = streams.NewActivityStreamsOrderedItemsProperty()
+ }
+ oi.PrependIRI(id.Get())
+ outbox.SetActivityStreamsOrderedItems(oi)
+ // Save in the database.
+ err = a.db.SetOutbox(c, outbox)
+ return err
+}
+
+// addToInboxIfNew will add the activity to the inbox at the specified IRI if
+// the activity's ID has not yet been added to the inbox.
+//
+// It does not add the activity to this database's know federated data.
+//
+// Returns true when the activity is novel.
+func (a *SideEffectActor) addToInboxIfNew(c context.Context, inboxIRI *url.URL, activity Activity) (isNew bool, err error) {
+ // Acquire a lock to read the inbox. Defer release.
+ var unlock func()
+ unlock, err = a.db.Lock(c, inboxIRI)
+ if err != nil {
+ return
+ }
+ defer unlock()
+ // Obtain the id of the activity
+ id := activity.GetJSONLDId()
+ // If the inbox already contains the URL, early exit.
+ contains, err := a.db.InboxContains(c, inboxIRI, id.Get())
+ if err != nil {
+ return
+ } else if contains {
+ return
+ }
+ // It is a new id, acquire the inbox.
+ isNew = true
+ inbox, err := a.db.GetInbox(c, inboxIRI)
+ if err != nil {
+ return
+ }
+ // Prepend the activity to the list of 'orderedItems'.
+ oi := inbox.GetActivityStreamsOrderedItems()
+ if oi == nil {
+ oi = streams.NewActivityStreamsOrderedItemsProperty()
+ }
+ oi.PrependIRI(id.Get())
+ inbox.SetActivityStreamsOrderedItems(oi)
+ // Save in the database.
+ err = a.db.SetInbox(c, inbox)
+ return
+}
+
+// Given an ActivityStreams value, recursively examines ownership of the id or
+// href and the ones on properties applicable to inbox forwarding.
+//
+// Recursion may be limited by providing a 'maxDepth' greater than zero. A
+// value of zero or a negative number will result in infinite recursion.
+func (a *SideEffectActor) hasInboxForwardingValues(c context.Context, inboxIRI *url.URL, val vocab.Type, maxDepth, currDepth int) (bool, error) {
+ // Stop recurring if we are exceeding the maximum depth and the maximum
+ // is a positive number.
+ if maxDepth > 0 && currDepth >= maxDepth {
+ return false, nil
+ }
+ // Determine if we own the 'id' of any values on the properties we care
+ // about.
+ types, iris := getInboxForwardingValues(val)
+ // For IRIs, simply check if we own them.
+ for _, iri := range iris {
+ unlock, err := a.db.Lock(c, iri)
+ if err != nil {
+ return false, err
+ }
+ // WARNING: Unlock is not deferred
+ owns, err := a.db.Owns(c, iri)
+ unlock() // unlock after regardless
+ if err != nil {
+ return false, err
+ } else if owns {
+ return true, nil
+ }
+ // Unlock by this point and in every branch above
+ }
+ // For embedded literals, check the id.
+ for _, val := range types {
+ id, err := GetId(val)
+ if err != nil {
+ return false, err
+ }
+ var unlock func()
+ unlock, err = a.db.Lock(c, id)
+ if err != nil {
+ return false, err
+ }
+ // WARNING: Unlock is not deferred
+ owns, err := a.db.Owns(c, id)
+ unlock() // unlock after regardless
+ if err != nil {
+ return false, err
+ } else if owns {
+ return true, nil
+ }
+ // Unlock by this point and in every branch above
+ }
+ // Recur Preparation: Try fetching the IRIs so we can recur into them.
+ for _, iri := range iris {
+ // Dereferencing the IRI.
+ tport, err := a.common.NewTransport(c, inboxIRI, goFedUserAgent())
+ if err != nil {
+ return false, err
+ }
+ resp, err := tport.Dereference(c, iri)
+ if err != nil {
+ // Do not fail the entire process if the data is
+ // missing.
+ continue
+ }
+ m, err := readActivityPubResp(resp)
+ if err != nil {
+ return false, err
+ }
+ t, err := streams.ToType(c, m)
+ if err != nil {
+ // Do not fail the entire process if we cannot handle
+ // the type.
+ continue
+ }
+ types = append(types, t)
+ }
+ // Recur.
+ for _, nextVal := range types {
+ if has, err := a.hasInboxForwardingValues(c, inboxIRI, nextVal, maxDepth, currDepth+1); err != nil {
+ return false, err
+ } else if has {
+ return true, nil
+ }
+ }
+ return false, nil
+}
+
+// prepare takes a deliverableObject and returns a list of the final
+// recipient inbox IRIs. Additionally, the deliverableObject will have
+// any hidden hidden recipients ("bto" and "bcc") stripped from it.
+//
+// Only call if both the social and federated protocol are supported.
+func (a *SideEffectActor) prepare(
+ ctx context.Context,
+ outboxIRI *url.URL,
+ activity Activity,
+) ([]*url.URL, error) {
+ // Iterate through to, bto, cc, bcc, and audience
+ // to extract a slice of addressee IRIs / IDs.
+ //
+ // The resulting slice might look something like:
+ //
+ // [
+ // "https://example.org/users/someone/followers", // <-- collection IRI
+ // "https://another.example.org/users/someone_else", // <-- actor IRI
+ // "[...]" // <-- etc
+ // ]
+ var actorsAndCollections []*url.URL
+ if to := activity.GetActivityStreamsTo(); to != nil {
+ for iter := to.Begin(); iter != to.End(); iter = iter.Next() {
+ var err error
+ actorsAndCollections, err = appendToActorsAndCollectionsIRIs(
+ iter, actorsAndCollections,
+ )
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if bto := activity.GetActivityStreamsBto(); bto != nil {
+ for iter := bto.Begin(); iter != bto.End(); iter = iter.Next() {
+ var err error
+ actorsAndCollections, err = appendToActorsAndCollectionsIRIs(
+ iter, actorsAndCollections,
+ )
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if cc := activity.GetActivityStreamsCc(); cc != nil {
+ for iter := cc.Begin(); iter != cc.End(); iter = iter.Next() {
+ var err error
+ actorsAndCollections, err = appendToActorsAndCollectionsIRIs(
+ iter, actorsAndCollections,
+ )
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if bcc := activity.GetActivityStreamsBcc(); bcc != nil {
+ for iter := bcc.Begin(); iter != bcc.End(); iter = iter.Next() {
+ var err error
+ actorsAndCollections, err = appendToActorsAndCollectionsIRIs(
+ iter, actorsAndCollections,
+ )
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if audience := activity.GetActivityStreamsAudience(); audience != nil {
+ for iter := audience.Begin(); iter != audience.End(); iter = iter.Next() {
+ var err error
+ actorsAndCollections, err = appendToActorsAndCollectionsIRIs(
+ iter, actorsAndCollections,
+ )
+ if err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ // PRE-SORTING
+
+ // If the pre-delivery sort function is defined,
+ // call it now so that implementations can sort
+ // delivery order to their preferences.
+ if a.DeliveryRecipientPreSort != nil {
+ actorsAndCollections = a.DeliveryRecipientPreSort(actorsAndCollections)
+ }
+
+ // We now need to dereference the actor or collection
+ // IRIs to derive inboxes that we can POST requests to.
+ var (
+ inboxes = make([]*url.URL, 0, len(actorsAndCollections))
+ derefdEntries = make(map[string]struct{}, len(actorsAndCollections))
+ )
+
+ // First check if the implemented database logic
+ // can return any of these inboxes without having
+ // to make remote dereference calls (much cheaper).
+ for _, actorOrCollection := range actorsAndCollections {
+ actorOrCollectionStr := actorOrCollection.String()
+ if _, derefd := derefdEntries[actorOrCollectionStr]; derefd {
+ // Ignore potential duplicates
+ // we've already derefd to inbox(es).
+ continue
+ }
+
+ // BEGIN LOCK
+ unlock, err := a.db.Lock(ctx, actorOrCollection)
+ if err != nil {
+ return nil, err
+ }
+
+ // Try to get inbox(es) for this actor or collection.
+ gotInboxes, err := a.db.InboxesForIRI(ctx, actorOrCollection)
+
+ // END LOCK
+ unlock()
+
+ if err != nil {
+ return nil, err
+ }
+
+ if len(gotInboxes) == 0 {
+ // No hit(s).
+ continue
+ }
+
+ // We have one or more hits.
+ inboxes = append(inboxes, gotInboxes...)
+
+ // Mark this actor or collection as deref'd.
+ derefdEntries[actorOrCollectionStr] = struct{}{}
+ }
+
+ // Now look for any remaining actors/collections
+ // that weren't already dereferenced into inboxes
+ // with db calls; find these by making deref calls
+ // to remote instances.
+ //
+ // First get a transport to do the http calls.
+ t, err := a.common.NewTransport(ctx, outboxIRI, goFedUserAgent())
+ if err != nil {
+ return nil, err
+ }
+
+ // Make HTTP calls to unpack collection IRIs into
+ // Actor IRIs and then into Actor types, ignoring
+ // actors or collections we've already deref'd.
+ actorsFromRemote, err := a.resolveActors(
+ ctx,
+ t,
+ actorsAndCollections,
+ derefdEntries,
+ 0, a.s2s.MaxDeliveryRecursionDepth(ctx),
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ // Release no-longer-needed collections.
+ clear(derefdEntries)
+ clear(actorsAndCollections)
+
+ // Extract inbox IRI from each deref'd Actor (if any).
+ inboxesFromRemote, err := actorsToInboxIRIs(actorsFromRemote)
+ if err != nil {
+ return nil, err
+ }
+
+ // Combine db-discovered inboxes and remote-discovered
+ // inboxes into a final list of destination inboxes.
+ inboxes = append(inboxes, inboxesFromRemote...)
+
+ // POST FILTERING
+
+ // Do a final pass of the inboxes to:
+ //
+ // 1. Deduplicate entries.
+ // 2. Ensure that the list of inboxes doesn't
+ // contain the inbox of whoever the outbox
+ // belongs to, no point delivering to oneself.
+ //
+ // To do this we first need to get the
+ // inbox IRI of this outbox's Actor.
+
+ // BEGIN LOCK
+ unlock, err := a.db.Lock(ctx, outboxIRI)
+ if err != nil {
+ return nil, err
+ }
+
+ // Get the IRI of the Actor who owns this outbox.
+ outboxActorIRI, err := a.db.ActorForOutbox(ctx, outboxIRI)
+
+ // END LOCK
+ unlock()
+
+ if err != nil {
+ return nil, err
+ }
+
+ // BEGIN LOCK
+ unlock, err = a.db.Lock(ctx, outboxActorIRI)
+ if err != nil {
+ return nil, err
+ }
+
+ // Now get the Actor who owns this outbox.
+ outboxActor, err := a.db.Get(ctx, outboxActorIRI)
+
+ // END LOCK
+ unlock()
+
+ if err != nil {
+ return nil, err
+ }
+
+ // Extract the inbox IRI for the outbox Actor.
+ inboxOfOutboxActor, err := getInbox(outboxActor)
+ if err != nil {
+ return nil, err
+ }
+
+ // Deduplicate the final inboxes slice, and filter
+ // out of the inbox of this outbox actor (if present).
+ inboxes = filterInboxIRIs(inboxes, inboxOfOutboxActor)
+
+ // Now that we've derived inboxes to deliver
+ // the activity to, strip off any bto or bcc
+ // recipients, as per the AP spec requirements.
+ stripHiddenRecipients(activity)
+
+ // All done!
+ return inboxes, nil
+}
+
+// resolveActors takes a list of Actor id URIs and returns them as concrete
+// instances of actorObject. It attempts to apply recursively when it encounters
+// a target that is a Collection or OrderedCollection.
+//
+// Any IRI strings in the ignores map will be skipped (use this when
+// you've already dereferenced some of the actorAndCollectionIRIs).
+//
+// If maxDepth is zero or negative, then recursion is infinitely applied.
+//
+// If a recipient is a Collection or OrderedCollection, then the server MUST
+// dereference the collection, WITH the user's credentials.
+//
+// Note that this also applies to CollectionPage and OrderedCollectionPage.
+func (a *SideEffectActor) resolveActors(
+ ctx context.Context,
+ t Transport,
+ actorAndCollectionIRIs []*url.URL,
+ ignores map[string]struct{},
+ depth, maxDepth int,
+) ([]vocab.Type, error) {
+ if maxDepth > 0 && depth >= maxDepth {
+ // Hit our max depth.
+ return nil, nil
+ }
+
+ if len(actorAndCollectionIRIs) == 0 {
+ // Nothing to do.
+ return nil, nil
+ }
+
+ // Optimistically assume 1:1 mapping of IRIs to actors.
+ actors := make([]vocab.Type, 0, len(actorAndCollectionIRIs))
+
+ // Deref each actorOrCollectionIRI if not ignored.
+ for _, actorOrCollectionIRI := range actorAndCollectionIRIs {
+ _, ignore := ignores[actorOrCollectionIRI.String()]
+ if ignore {
+ // Don't try to
+ // deref this one.
+ continue
+ }
+
+ // TODO: Determine if more logic is needed here for
+ // inaccessible collections owned by peer servers.
+ actor, more, err := a.dereferenceForResolvingInboxes(ctx, t, actorOrCollectionIRI)
+ if err != nil {
+ // Missing recipient -- skip.
+ continue
+ }
+
+ if actor != nil {
+ // Got a hit.
+ actors = append(actors, actor)
+ }
+
+ // If this was a collection, get more.
+ recurActors, err := a.resolveActors(
+ ctx,
+ t,
+ more,
+ ignores,
+ depth+1, maxDepth,
+ )
+ if err != nil {
+ return nil, err
+ }
+
+ actors = append(actors, recurActors...)
+ }
+
+ return actors, nil
+}
+
+// dereferenceForResolvingInboxes dereferences an IRI solely for finding an
+// actor's inbox IRI to deliver to.
+//
+// The returned actor could be nil, if it wasn't an actor (ex: a Collection or
+// OrderedCollection).
+func (a *SideEffectActor) dereferenceForResolvingInboxes(c context.Context, t Transport, actorIRI *url.URL) (actor vocab.Type, moreActorIRIs []*url.URL, err error) {
+ var resp *http.Response
+ resp, err = t.Dereference(c, actorIRI)
+ if err != nil {
+ return
+ }
+ var m map[string]interface{}
+ m, err = readActivityPubResp(resp)
+ if err != nil {
+ return
+ }
+ actor, err = streams.ToType(c, m)
+ if err != nil {
+ return
+ }
+ // Attempt to see if the 'actor' is really some sort of type that has
+ // an 'items' or 'orderedItems' property.
+ if v, ok := actor.(itemser); ok {
+ if i := v.GetActivityStreamsItems(); i != nil {
+ for iter := i.Begin(); iter != i.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ moreActorIRIs = append(moreActorIRIs, id)
+ }
+ }
+ actor = nil
+ } else if v, ok := actor.(orderedItemser); ok {
+ if i := v.GetActivityStreamsOrderedItems(); i != nil {
+ for iter := i.Begin(); iter != i.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ moreActorIRIs = append(moreActorIRIs, id)
+ }
+ }
+ actor = nil
+ }
+ return
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/social_protocol.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/social_protocol.go
new file mode 100644
index 000000000..1f882b284
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/social_protocol.go
@@ -0,0 +1,83 @@
+package pub
+
+import (
+ "context"
+ "net/http"
+
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// SocialProtocol contains behaviors an application needs to satisfy for the
+// full ActivityPub C2S implementation to be supported by this library.
+//
+// It is only required if the client application wants to support the client-to-
+// server, or social, protocol.
+//
+// It is passed to the library as a dependency injection from the client
+// application.
+type SocialProtocol interface {
+ // Hook callback after parsing the request body for a client request
+ // to the Actor's outbox.
+ //
+ // Can be used to set contextual information based on the
+ // ActivityStreams object received.
+ //
+ // Only called if the Social API is enabled.
+ //
+ // Warning: Neither authentication nor authorization has taken place at
+ // this time. Doing anything beyond setting contextual information is
+ // strongly discouraged.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // PostOutbox. In this case, the DelegateActor implementation must not
+ // write a response to the ResponseWriter as is expected that the caller
+ // to PostOutbox will do so when handling the error.
+ PostOutboxRequestBodyHook(c context.Context, r *http.Request, data vocab.Type) (context.Context, error)
+ // AuthenticatePostOutbox delegates the authentication of a POST to an
+ // outbox.
+ //
+ // Only called if the Social API is enabled.
+ //
+ // If an error is returned, it is passed back to the caller of
+ // PostOutbox. In this case, the implementation must not write a
+ // response to the ResponseWriter as is expected that the client will
+ // do so when handling the error. The 'authenticated' is ignored.
+ //
+ // If no error is returned, but authentication or authorization fails,
+ // then authenticated must be false and error nil. It is expected that
+ // the implementation handles writing to the ResponseWriter in this
+ // case.
+ //
+ // Finally, if the authentication and authorization succeeds, then
+ // authenticated must be true and error nil. The request will continue
+ // to be processed.
+ AuthenticatePostOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error)
+ // SocialCallbacks returns the application logic that handles
+ // ActivityStreams received from C2S clients.
+ //
+ // Note that certain types of callbacks will be 'wrapped' with default
+ // behaviors supported natively by the library. Other callbacks
+ // compatible with streams.TypeResolver can be specified by 'other'.
+ //
+ // For example, setting the 'Create' field in the SocialWrappedCallbacks
+ // lets an application dependency inject additional behaviors they want
+ // to take place, including the default behavior supplied by this
+ // library. This is guaranteed to be compliant with the ActivityPub
+ // Social protocol.
+ //
+ // To override the default behavior, instead supply the function in
+ // 'other', which does not guarantee the application will be compliant
+ // with the ActivityPub Social Protocol.
+ //
+ // Applications are not expected to handle every single ActivityStreams
+ // type and extension. The unhandled ones are passed to DefaultCallback.
+ SocialCallbacks(c context.Context) (wrapped SocialWrappedCallbacks, other []interface{}, err error)
+ // DefaultCallback is called for types that go-fed can deserialize but
+ // are not handled by the application's callbacks returned in the
+ // Callbacks method.
+ //
+ // Applications are not expected to handle every single ActivityStreams
+ // type and extension, so the unhandled ones are passed to
+ // DefaultCallback.
+ DefaultCallback(c context.Context, activity Activity) error
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/social_wrapped_callbacks.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/social_wrapped_callbacks.go
new file mode 100644
index 000000000..9f93a9c65
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/social_wrapped_callbacks.go
@@ -0,0 +1,534 @@
+package pub
+
+import (
+ "context"
+ "fmt"
+ "net/url"
+
+ "codeberg.org/superseriousbusiness/activity/streams"
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+// SocialWrappedCallbacks lists the callback functions that already have some
+// side effect behavior provided by the pub library.
+//
+// These functions are wrapped for the Social Protocol.
+type SocialWrappedCallbacks struct {
+ // Create handles additional side effects for the Create ActivityStreams
+ // type.
+ //
+ // The wrapping callback copies the actor(s) to the 'attributedTo'
+ // property and copies recipients between the Create activity and all
+ // objects. It then saves the entry in the database.
+ Create func(context.Context, vocab.ActivityStreamsCreate) error
+ // Update handles additional side effects for the Update ActivityStreams
+ // type.
+ //
+ // The wrapping callback applies new top-level values on an object to
+ // the stored objects. Any top-level null literals will be deleted on
+ // the stored objects as well.
+ Update func(context.Context, vocab.ActivityStreamsUpdate) error
+ // Delete handles additional side effects for the Delete ActivityStreams
+ // type.
+ //
+ // The wrapping callback replaces the object(s) with tombstones in the
+ // database.
+ Delete func(context.Context, vocab.ActivityStreamsDelete) error
+ // Follow handles additional side effects for the Follow ActivityStreams
+ // type.
+ //
+ // The wrapping callback only ensures the 'Follow' has at least one
+ // 'object' entry, but otherwise has no default side effect.
+ Follow func(context.Context, vocab.ActivityStreamsFollow) error
+ // Add handles additional side effects for the Add ActivityStreams
+ // type.
+ //
+ //
+ // The wrapping function will add the 'object' IRIs to a specific
+ // 'target' collection if the 'target' collection(s) live on this
+ // server.
+ Add func(context.Context, vocab.ActivityStreamsAdd) error
+ // Remove handles additional side effects for the Remove ActivityStreams
+ // type.
+ //
+ // The wrapping function will remove all 'object' IRIs from a specific
+ // 'target' collection if the 'target' collection(s) live on this
+ // server.
+ Remove func(context.Context, vocab.ActivityStreamsRemove) error
+ // Like handles additional side effects for the Like ActivityStreams
+ // type.
+ //
+ // The wrapping function will add the objects on the activity to the
+ // "liked" collection of this actor.
+ Like func(context.Context, vocab.ActivityStreamsLike) error
+ // Undo handles additional side effects for the Undo ActivityStreams
+ // type.
+ //
+ //
+ // The wrapping function ensures the 'actor' on the 'Undo'
+ // is be the same as the 'actor' on all Activities being undone.
+ // It enforces that the actors on the Undo must correspond to all of the
+ // 'object' actors in some manner.
+ //
+ // It is expected that the application will implement the proper
+ // reversal of activities that are being undone.
+ Undo func(context.Context, vocab.ActivityStreamsUndo) error
+ // Block handles additional side effects for the Block ActivityStreams
+ // type.
+ //
+ // The wrapping callback only ensures the 'Block' has at least one
+ // 'object' entry, but otherwise has no default side effect. It is up
+ // to the wrapped application function to properly enforce the new
+ // blocking behavior.
+ //
+ // Note that go-fed does not federate 'Block' activities received in the
+ // Social Protocol.
+ Block func(context.Context, vocab.ActivityStreamsBlock) error
+
+ // Sidechannel data -- this is set at request handling time. These must
+ // be set before the callbacks are used.
+
+ // db is the Database the SocialWrappedCallbacks should use. It must be
+ // set before calling the callbacks.
+ db Database
+ // outboxIRI is the outboxIRI that is handling this callback.
+ outboxIRI *url.URL
+ // rawActivity is the JSON map literal received when deserializing the
+ // request body.
+ rawActivity map[string]interface{}
+ // clock is the server's clock.
+ clock Clock
+ // newTransport creates a new Transport.
+ newTransport func(c context.Context, actorBoxIRI *url.URL, gofedAgent string) (t Transport, err error)
+ // undeliverable is a sidechannel out, indicating if the handled activity
+ // should not be delivered to a peer.
+ //
+ // Its provided default value will always be used when a custom function
+ // is called.
+ undeliverable *bool
+}
+
+// callbacks returns the WrappedCallbacks members into a single interface slice
+// for use in streams.Resolver callbacks.
+//
+// If the given functions have a type that collides with the default behavior,
+// then disable our default behavior
+func (w SocialWrappedCallbacks) callbacks(fns []interface{}) []interface{} {
+ enableCreate := true
+ enableUpdate := true
+ enableDelete := true
+ enableFollow := true
+ enableAdd := true
+ enableRemove := true
+ enableLike := true
+ enableUndo := true
+ enableBlock := true
+ for _, fn := range fns {
+ switch fn.(type) {
+ default:
+ continue
+ case func(context.Context, vocab.ActivityStreamsCreate) error:
+ enableCreate = false
+ case func(context.Context, vocab.ActivityStreamsUpdate) error:
+ enableUpdate = false
+ case func(context.Context, vocab.ActivityStreamsDelete) error:
+ enableDelete = false
+ case func(context.Context, vocab.ActivityStreamsFollow) error:
+ enableFollow = false
+ case func(context.Context, vocab.ActivityStreamsAdd) error:
+ enableAdd = false
+ case func(context.Context, vocab.ActivityStreamsRemove) error:
+ enableRemove = false
+ case func(context.Context, vocab.ActivityStreamsLike) error:
+ enableLike = false
+ case func(context.Context, vocab.ActivityStreamsUndo) error:
+ enableUndo = false
+ case func(context.Context, vocab.ActivityStreamsBlock) error:
+ enableBlock = false
+ }
+ }
+ if enableCreate {
+ fns = append(fns, w.create)
+ }
+ if enableUpdate {
+ fns = append(fns, w.update)
+ }
+ if enableDelete {
+ fns = append(fns, w.deleteFn)
+ }
+ if enableFollow {
+ fns = append(fns, w.follow)
+ }
+ if enableAdd {
+ fns = append(fns, w.add)
+ }
+ if enableRemove {
+ fns = append(fns, w.remove)
+ }
+ if enableLike {
+ fns = append(fns, w.like)
+ }
+ if enableUndo {
+ fns = append(fns, w.undo)
+ }
+ if enableBlock {
+ fns = append(fns, w.block)
+ }
+ return fns
+}
+
+// create implements the social Create activity side effects.
+func (w SocialWrappedCallbacks) create(c context.Context, a vocab.ActivityStreamsCreate) error {
+ *w.undeliverable = false
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ // Obtain all actor IRIs.
+ actors := a.GetActivityStreamsActor()
+ createActorIds := make(map[string]*url.URL)
+ if actors != nil {
+ createActorIds = make(map[string]*url.URL, actors.Len())
+ for iter := actors.Begin(); iter != actors.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ createActorIds[id.String()] = id
+ }
+ }
+ // Obtain each object's 'attributedTo' IRIs.
+ objectAttributedToIds := make([]map[string]*url.URL, op.Len())
+ for i := range objectAttributedToIds {
+ objectAttributedToIds[i] = make(map[string]*url.URL)
+ }
+ for i := 0; i < op.Len(); i++ {
+ t := op.At(i).GetType()
+ attrToer, ok := t.(attributedToer)
+ if !ok {
+ continue
+ }
+ attr := attrToer.GetActivityStreamsAttributedTo()
+ if attr == nil {
+ attr = streams.NewActivityStreamsAttributedToProperty()
+ attrToer.SetActivityStreamsAttributedTo(attr)
+ }
+ for iter := attr.Begin(); iter != attr.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objectAttributedToIds[i][id.String()] = id
+ }
+ }
+ // Put all missing actor IRIs onto all object attributedTo properties.
+ for k, v := range createActorIds {
+ for i, attributedToMap := range objectAttributedToIds {
+ if _, ok := attributedToMap[k]; !ok {
+ t := op.At(i).GetType()
+ attrToer, ok := t.(attributedToer)
+ if !ok {
+ continue
+ }
+ attr := attrToer.GetActivityStreamsAttributedTo()
+ attr.AppendIRI(v)
+ }
+ }
+ }
+ // Put all missing object attributedTo IRIs onto the actor property
+ // if there is one.
+ if actors != nil {
+ for _, attributedToMap := range objectAttributedToIds {
+ for k, v := range attributedToMap {
+ if _, ok := createActorIds[k]; !ok {
+ actors.AppendIRI(v)
+ }
+ }
+ }
+ }
+ // Copy over the 'to', 'bto', 'cc', 'bcc', and 'audience' recipients
+ // between the activity and all child objects and vice versa.
+ if err := normalizeRecipients(a); err != nil {
+ return err
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(i int) error {
+ obj := op.At(i).GetType()
+ id, err := GetId(obj)
+ if err != nil {
+ return err
+ }
+ var unlock func()
+ unlock, err = w.db.Lock(c, id)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ if err := w.db.Create(c, obj); err != nil {
+ return err
+ }
+ return nil
+ }
+ // Persist all objects we've created, which will include sensitive
+ // recipients such as 'bcc' and 'bto'.
+ for i := 0; i < op.Len(); i++ {
+ if err := loopFn(i); err != nil {
+ return err
+ }
+ }
+ if w.Create != nil {
+ return w.Create(c, a)
+ }
+ return nil
+}
+
+// update implements the social Update activity side effects.
+func (w SocialWrappedCallbacks) update(c context.Context, a vocab.ActivityStreamsUpdate) error {
+ *w.undeliverable = false
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ // Obtain all object ids, which should be owned by this server.
+ objIds := make([]*url.URL, 0, op.Len())
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objIds = append(objIds, id)
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(idx int, loopId *url.URL) error {
+ unlock, err := w.db.Lock(c, loopId)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ t, err := w.db.Get(c, loopId)
+ if err != nil {
+ return err
+ }
+ m, err := t.Serialize()
+ if err != nil {
+ return err
+ }
+ // Copy over new top-level values.
+ objType := op.At(idx).GetType()
+ if objType == nil {
+ return fmt.Errorf("object at index %d is not a literal type value", idx)
+ }
+ newM, err := objType.Serialize()
+ if err != nil {
+ return err
+ }
+ for k, v := range newM {
+ m[k] = v
+ }
+ // Delete top-level values where the raw Activity had nils.
+ for k, v := range w.rawActivity {
+ if _, ok := m[k]; v == nil && ok {
+ delete(m, k)
+ }
+ }
+ newT, err := streams.ToType(c, m)
+ if err != nil {
+ return err
+ }
+ if err = w.db.Update(c, newT); err != nil {
+ return err
+ }
+ return nil
+ }
+ for i, id := range objIds {
+ if err := loopFn(i, id); err != nil {
+ return err
+ }
+ }
+ if w.Update != nil {
+ return w.Update(c, a)
+ }
+ return nil
+}
+
+// deleteFn implements the social Delete activity side effects.
+func (w SocialWrappedCallbacks) deleteFn(c context.Context, a vocab.ActivityStreamsDelete) error {
+ *w.undeliverable = false
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ // Obtain all object ids, which should be owned by this server.
+ objIds := make([]*url.URL, 0, op.Len())
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objIds = append(objIds, id)
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(idx int, loopId *url.URL) error {
+ unlock, err := w.db.Lock(c, loopId)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ t, err := w.db.Get(c, loopId)
+ if err != nil {
+ return err
+ }
+ tomb := toTombstone(t, loopId, w.clock.Now())
+ if err := w.db.Update(c, tomb); err != nil {
+ return err
+ }
+ return nil
+ }
+ for i, id := range objIds {
+ if err := loopFn(i, id); err != nil {
+ return err
+ }
+ }
+ if w.Delete != nil {
+ return w.Delete(c, a)
+ }
+ return nil
+}
+
+// follow implements the social Follow activity side effects.
+func (w SocialWrappedCallbacks) follow(c context.Context, a vocab.ActivityStreamsFollow) error {
+ *w.undeliverable = false
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ if w.Follow != nil {
+ return w.Follow(c, a)
+ }
+ return nil
+}
+
+// add implements the social Add activity side effects.
+func (w SocialWrappedCallbacks) add(c context.Context, a vocab.ActivityStreamsAdd) error {
+ *w.undeliverable = false
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ target := a.GetActivityStreamsTarget()
+ if target == nil || target.Len() == 0 {
+ return ErrTargetRequired
+ }
+ if err := add(c, op, target, w.db); err != nil {
+ return err
+ }
+ if w.Add != nil {
+ return w.Add(c, a)
+ }
+ return nil
+}
+
+// remove implements the social Remove activity side effects.
+func (w SocialWrappedCallbacks) remove(c context.Context, a vocab.ActivityStreamsRemove) error {
+ *w.undeliverable = false
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ target := a.GetActivityStreamsTarget()
+ if target == nil || target.Len() == 0 {
+ return ErrTargetRequired
+ }
+ if err := remove(c, op, target, w.db); err != nil {
+ return err
+ }
+ if w.Remove != nil {
+ return w.Remove(c, a)
+ }
+ return nil
+}
+
+// like implements the social Like activity side effects.
+func (w SocialWrappedCallbacks) like(c context.Context, a vocab.ActivityStreamsLike) error {
+ *w.undeliverable = false
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ // Get this actor's IRI.
+ unlock, err := w.db.Lock(c, w.outboxIRI)
+ if err != nil {
+ return err
+ }
+ // WARNING: Unlock not deferred.
+ actorIRI, err := w.db.ActorForOutbox(c, w.outboxIRI)
+ unlock() // unlock even on error
+ if err != nil {
+ return err
+ }
+ // Unlock must be called by now and every branch above.
+ //
+ // Now obtain this actor's 'liked' collection.
+ unlock, err = w.db.Lock(c, actorIRI)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ liked, err := w.db.Liked(c, actorIRI)
+ if err != nil {
+ return err
+ }
+ likedItems := liked.GetActivityStreamsItems()
+ if likedItems == nil {
+ likedItems = streams.NewActivityStreamsItemsProperty()
+ liked.SetActivityStreamsItems(likedItems)
+ }
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ objId, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ likedItems.PrependIRI(objId)
+ }
+ err = w.db.Update(c, liked)
+ if err != nil {
+ return err
+ }
+ if w.Like != nil {
+ return w.Like(c, a)
+ }
+ return nil
+}
+
+// undo implements the social Undo activity side effects.
+func (w SocialWrappedCallbacks) undo(c context.Context, a vocab.ActivityStreamsUndo) error {
+ *w.undeliverable = false
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ actors := a.GetActivityStreamsActor()
+ if err := mustHaveActivityActorsMatchObjectActors(c, actors, op, w.newTransport, w.outboxIRI); err != nil {
+ return err
+ }
+ if w.Undo != nil {
+ return w.Undo(c, a)
+ }
+ return nil
+}
+
+// block implements the social Block activity side effects.
+func (w SocialWrappedCallbacks) block(c context.Context, a vocab.ActivityStreamsBlock) error {
+ *w.undeliverable = true
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return ErrObjectRequired
+ }
+ if w.Block != nil {
+ return w.Block(c, a)
+ }
+ return nil
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/transport.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/transport.go
new file mode 100644
index 000000000..101ff5c07
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/transport.go
@@ -0,0 +1,219 @@
+package pub
+
+import (
+ "bytes"
+ "context"
+ "crypto"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "sync"
+
+ "github.com/go-fed/httpsig"
+)
+
+const (
+ // acceptHeaderValue is the Accept header value indicating that the
+ // response should contain an ActivityStreams object.
+ acceptHeaderValue = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
+)
+
+// isSuccess returns true if the HTTP status code is either OK, Created, or
+// Accepted.
+func isSuccess(code int) bool {
+ return code == http.StatusOK ||
+ code == http.StatusCreated ||
+ code == http.StatusAccepted
+}
+
+// Transport makes ActivityStreams calls to other servers in order to send or
+// receive ActivityStreams data.
+//
+// It is responsible for setting the appropriate request headers, signing the
+// requests if needed, and facilitating the traffic between this server and
+// another.
+//
+// The transport is exclusively used to issue requests on behalf of an actor,
+// and is never sending requests on behalf of the server in general.
+//
+// It may be reused multiple times, but never concurrently.
+type Transport interface {
+ // Dereference fetches the ActivityStreams object located at this IRI with
+ // a GET request. Note that Response will only be returned on status = OK.
+ Dereference(c context.Context, iri *url.URL) (*http.Response, error)
+
+ // Deliver sends an ActivityStreams object.
+ Deliver(c context.Context, obj map[string]interface{}, to *url.URL) error
+
+ // BatchDeliver sends an ActivityStreams object to multiple recipients.
+ BatchDeliver(c context.Context, obj map[string]interface{}, recipients []*url.URL) error
+}
+
+// Transport must be implemented by HttpSigTransport.
+var _ Transport = &HttpSigTransport{}
+
+// HttpSigTransport makes a dereference call using HTTP signatures to
+// authenticate the request on behalf of a particular actor.
+//
+// No rate limiting is applied.
+//
+// Only one request is tried per call.
+type HttpSigTransport struct {
+ client HttpClient
+ appAgent string
+ gofedAgent string
+ clock Clock
+ getSigner httpsig.Signer
+ getSignerMu *sync.Mutex
+ postSigner httpsig.Signer
+ postSignerMu *sync.Mutex
+ pubKeyId string
+ privKey crypto.PrivateKey
+}
+
+// NewHttpSigTransport returns a new Transport.
+//
+// It sends requests specifically on behalf of a specific actor on this server.
+// The actor's credentials are used to add an HTTP Signature to requests, which
+// requires an actor's private key, a unique identifier for their public key,
+// and an HTTP Signature signing algorithm.
+//
+// The client lets users issue requests through any HTTP client, including the
+// standard library's HTTP client.
+//
+// The appAgent uniquely identifies the calling application's requests, so peers
+// may aid debugging the requests incoming from this server. Note that the
+// agent string will also include one for go-fed, so at minimum peer servers can
+// reach out to the go-fed library to aid in notifying implementors of malformed
+// or unsupported requests.
+func NewHttpSigTransport(
+ client HttpClient,
+ appAgent string,
+ clock Clock,
+ getSigner, postSigner httpsig.Signer,
+ pubKeyId string,
+ privKey crypto.PrivateKey) *HttpSigTransport {
+ return &HttpSigTransport{
+ client: client,
+ appAgent: appAgent,
+ gofedAgent: goFedUserAgent(),
+ clock: clock,
+ getSigner: getSigner,
+ getSignerMu: &sync.Mutex{},
+ postSigner: postSigner,
+ postSignerMu: &sync.Mutex{},
+ pubKeyId: pubKeyId,
+ privKey: privKey,
+ }
+}
+
+// Dereference sends a GET request signed with an HTTP Signature to obtain an ActivityStreams value.
+func (h HttpSigTransport) Dereference(c context.Context, iri *url.URL) (*http.Response, error) {
+ req, err := http.NewRequest("GET", iri.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+ req = req.WithContext(c)
+ req.Header.Add(acceptHeader, acceptHeaderValue)
+ req.Header.Add("Accept-Charset", "utf-8")
+ req.Header.Add("Date", h.clock.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
+ req.Header.Add("User-Agent", fmt.Sprintf("%s %s", h.appAgent, h.gofedAgent))
+ req.Header.Set("Host", iri.Host)
+ h.getSignerMu.Lock()
+ err = h.getSigner.SignRequest(h.privKey, h.pubKeyId, req, nil)
+ h.getSignerMu.Unlock()
+ if err != nil {
+ return nil, err
+ }
+ resp, err := h.client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ if resp.StatusCode != http.StatusOK {
+ _ = resp.Body.Close()
+ return nil, fmt.Errorf("GET request to %s failed (%d): %s", iri.String(), resp.StatusCode, resp.Status)
+ }
+ return resp, nil
+}
+
+// Deliver sends a POST request with an HTTP Signature.
+func (h HttpSigTransport) Deliver(c context.Context, data map[string]interface{}, to *url.URL) error {
+ b, err := json.Marshal(data)
+ if err != nil {
+ return err
+ }
+ return h.deliver(c, b, to)
+}
+
+// BatchDeliver sends concurrent POST requests. Returns an error if any of the requests had an error.
+func (h HttpSigTransport) BatchDeliver(c context.Context, data map[string]interface{}, recipients []*url.URL) error {
+ b, err := json.Marshal(data)
+ if err != nil {
+ return err
+ }
+ var wg sync.WaitGroup
+ errCh := make(chan error, len(recipients))
+ for _, recipient := range recipients {
+ wg.Add(1)
+ go func(r *url.URL) {
+ defer wg.Done()
+ if err := h.deliver(c, b, r); err != nil {
+ errCh <- err
+ }
+ }(recipient)
+ }
+ wg.Wait()
+ errs := make([]string, 0, len(recipients))
+outer:
+ for {
+ select {
+ case e := <-errCh:
+ errs = append(errs, e.Error())
+ default:
+ break outer
+ }
+ }
+ if len(errs) > 0 {
+ return fmt.Errorf("batch deliver had at least one failure: %s", strings.Join(errs, "; "))
+ }
+ return nil
+}
+
+func (h HttpSigTransport) deliver(c context.Context, b []byte, to *url.URL) error {
+ req, err := http.NewRequest("POST", to.String(), bytes.NewReader(b))
+ if err != nil {
+ return err
+ }
+ req = req.WithContext(c)
+ req.Header.Add(contentTypeHeader, contentTypeHeaderValue)
+ req.Header.Add("Accept-Charset", "utf-8")
+ req.Header.Add("Date", h.clock.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
+ req.Header.Add("User-Agent", fmt.Sprintf("%s %s", h.appAgent, h.gofedAgent))
+ req.Header.Set("Host", to.Host)
+ h.postSignerMu.Lock()
+ err = h.postSigner.SignRequest(h.privKey, h.pubKeyId, req, b)
+ h.postSignerMu.Unlock()
+ if err != nil {
+ return err
+ }
+ resp, err := h.client.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+ if !isSuccess(resp.StatusCode) {
+ return fmt.Errorf("POST request to %s failed (%d): %s", to.String(), resp.StatusCode, resp.Status)
+ }
+ return nil
+}
+
+// HttpClient sends http requests, and is an abstraction only needed by the
+// HttpSigTransport. The standard library's Client satisfies this interface.
+type HttpClient interface {
+ Do(req *http.Request) (*http.Response, error)
+}
+
+// HttpClient must be implemented by http.Client.
+var _ HttpClient = &http.Client{}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/util.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/util.go
new file mode 100644
index 000000000..1a5ea7640
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/util.go
@@ -0,0 +1,1077 @@
+package pub
+
+import (
+ "bytes"
+ "context"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "codeberg.org/superseriousbusiness/activity/streams"
+ "codeberg.org/superseriousbusiness/activity/streams/vocab"
+)
+
+var (
+ // ErrObjectRequired indicates the activity needs its object property
+ // set. Can be returned by DelegateActor's PostInbox or PostOutbox so a
+ // Bad Request response is set.
+ ErrObjectRequired = errors.New("object property required on the provided activity")
+ // ErrTargetRequired indicates the activity needs its target property
+ // set. Can be returned by DelegateActor's PostInbox or PostOutbox so a
+ // Bad Request response is set.
+ ErrTargetRequired = errors.New("target property required on the provided activity")
+)
+
+// activityStreamsMediaTypes contains all of the accepted ActivityStreams media
+// types. Generated at init time.
+var activityStreamsMediaTypes []string
+
+func init() {
+ activityStreamsMediaTypes = []string{
+ "application/activity+json",
+ }
+ jsonLdType := "application/ld+json"
+ for _, semi := range []string{";", " ;", " ; ", "; "} {
+ for _, profile := range []string{
+ "profile=https://www.w3.org/ns/activitystreams",
+ "profile=\"https://www.w3.org/ns/activitystreams\"",
+ } {
+ activityStreamsMediaTypes = append(
+ activityStreamsMediaTypes,
+ fmt.Sprintf("%s%s%s", jsonLdType, semi, profile))
+ }
+ }
+}
+
+// headerIsActivityPubMediaType returns true if the header string contains one
+// of the accepted ActivityStreams media types.
+//
+// Note we don't try to build a comprehensive parser and instead accept a
+// tolerable amount of whitespace since the HTTP specification is ambiguous
+// about the format and significance of whitespace.
+func headerIsActivityPubMediaType(header string) bool {
+ for _, mediaType := range activityStreamsMediaTypes {
+ if strings.Contains(header, mediaType) {
+ return true
+ }
+ }
+ return false
+}
+
+const (
+ // The Content-Type header.
+ contentTypeHeader = "Content-Type"
+ // The Accept header.
+ acceptHeader = "Accept"
+)
+
+// readActivityPubReq reads ActivityPub data from an incoming request, handling body close.
+func readActivityPubReq(req *http.Request) (map[string]interface{}, error) {
+ // Ensure closed when done.
+ defer req.Body.Close()
+
+ var m map[string]interface{}
+
+ // Wrap body in a JSON decoder.
+ dec := json.NewDecoder(req.Body)
+
+ // Decode JSON body as "raw" AP data map.
+ if err := dec.Decode(&m); err != nil {
+ return nil, err
+ }
+
+ // Perform a final second decode to ensure no trailing
+ // garbage data or second JSON value (indicates malformed).
+ if err := dec.Decode(&struct{}{}); err != io.EOF {
+ return nil, errors.New("trailing data after json")
+ }
+
+ return m, nil
+}
+
+// readActivityPubResp reads ActivityPub data from a dereference response, handling media type check and body close.
+func readActivityPubResp(resp *http.Response) (map[string]interface{}, error) {
+ // Ensure closed when done.
+ defer resp.Body.Close()
+
+ // Check the incoming response media type is the expected ActivityPub content-type.
+ if mediaType := resp.Header.Get("Content-Type"); !headerIsActivityPubMediaType(mediaType) {
+ return nil, fmt.Errorf("%s %s resp was not ActivityPub media type: %s", resp.Request.Method, resp.Request.URL, mediaType)
+ }
+
+ var m map[string]interface{}
+
+ // Wrap body in a JSON decoder.
+ dec := json.NewDecoder(resp.Body)
+
+ // Decode JSON body as "raw" AP data map.
+ if err := dec.Decode(&m); err != nil {
+ return nil, err
+ }
+
+ // Perform a final second decode to ensure no trailing
+ // garbage data or second JSON value (indicates malformed).
+ if err := dec.Decode(&struct{}{}); err != io.EOF {
+ return nil, errors.New("trailing data after json")
+ }
+
+ return m, nil
+}
+
+// isActivityPubPost returns true if the request is a POST request that has the
+// ActivityStreams content type header
+func isActivityPubPost(r *http.Request) bool {
+ return r.Method == "POST" && headerIsActivityPubMediaType(r.Header.Get(contentTypeHeader))
+}
+
+// isActivityPubGet returns true if the request is a GET request that has the
+// ActivityStreams content type header
+func isActivityPubGet(r *http.Request) bool {
+ return r.Method == "GET" && headerIsActivityPubMediaType(r.Header.Get(acceptHeader))
+}
+
+// dedupeOrderedItems deduplicates the 'orderedItems' within an ordered
+// collection type. Deduplication happens by the 'id' property.
+func dedupeOrderedItems(oc orderedItemser) error {
+ oi := oc.GetActivityStreamsOrderedItems()
+ if oi == nil {
+ return nil
+ }
+ seen := make(map[string]bool, oi.Len())
+ for i := 0; i < oi.Len(); {
+ var id *url.URL
+
+ iter := oi.At(i)
+ asType := iter.GetType()
+ if asType != nil {
+ var err error
+ id, err = GetId(asType)
+ if err != nil {
+ return err
+ }
+ } else if iter.IsIRI() {
+ id = iter.GetIRI()
+ } else {
+ return fmt.Errorf("element %d in OrderedCollection does not have an ID nor is an IRI", i)
+ }
+ if seen[id.String()] {
+ oi.Remove(i)
+ } else {
+ seen[id.String()] = true
+ i++
+ }
+ }
+ return nil
+}
+
+const (
+ // The Location header
+ locationHeader = "Location"
+ // Contains the ActivityStreams Content-Type value.
+ contentTypeHeaderValue = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
+ // The Date header.
+ dateHeader = "Date"
+ // The Digest header.
+ digestHeader = "Digest"
+ // The delimiter used in the Digest header.
+ digestDelimiter = "="
+ // SHA-256 string for the Digest header.
+ sha256Digest = "SHA-256"
+)
+
+// addResponseHeaders sets headers needed in the HTTP response, such but not
+// limited to the Content-Type, Date, and Digest headers.
+func addResponseHeaders(h http.Header, c Clock, responseContent []byte) {
+ h.Set(contentTypeHeader, contentTypeHeaderValue)
+ // RFC 7231 ยง7.1.1.2
+ h.Set(dateHeader, c.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
+ // RFC 3230 and RFC 5843
+ var b bytes.Buffer
+ b.WriteString(sha256Digest)
+ b.WriteString(digestDelimiter)
+ hashed := sha256.Sum256(responseContent)
+ b.WriteString(base64.StdEncoding.EncodeToString(hashed[:]))
+ h.Set(digestHeader, b.String())
+}
+
+// IdProperty is a property that can readily have its id obtained
+type IdProperty interface {
+ // GetIRI returns the IRI of this property. When IsIRI returns false,
+ // GetIRI will return an arbitrary value.
+ GetIRI() *url.URL
+ // GetType returns the value in this property as a Type. Returns nil if
+ // the value is not an ActivityStreams type, such as an IRI or another
+ // value.
+ GetType() vocab.Type
+ // IsIRI returns true if this property is an IRI.
+ IsIRI() bool
+}
+
+// ToId returns an IdProperty's id.
+func ToId(i IdProperty) (*url.URL, error) {
+ if i.GetType() != nil {
+ return GetId(i.GetType())
+ } else if i.IsIRI() {
+ return i.GetIRI(), nil
+ }
+ return nil, fmt.Errorf("cannot determine id of activitystreams property")
+}
+
+// GetId will attempt to find the 'id' property or, if it happens to be a
+// Link or derived from Link type, the 'href' property instead.
+//
+// Returns an error if the id is not set and either the 'href' property is not
+// valid on this type, or it is also not set.
+func GetId(t vocab.Type) (*url.URL, error) {
+ if id := t.GetJSONLDId(); id != nil {
+ return id.Get(), nil
+ } else if h, ok := t.(hrefer); ok {
+ if href := h.GetActivityStreamsHref(); href != nil {
+ return href.Get(), nil
+ }
+ }
+ return nil, fmt.Errorf("cannot determine id of activitystreams value")
+}
+
+// getInboxForwardingValues obtains the 'inReplyTo', 'object', 'target', and
+// 'tag' values on an ActivityStreams value.
+func getInboxForwardingValues(o vocab.Type) (t []vocab.Type, iri []*url.URL) {
+ // 'inReplyTo'
+ if i, ok := o.(inReplyToer); ok {
+ if irt := i.GetActivityStreamsInReplyTo(); irt != nil {
+ for iter := irt.Begin(); iter != irt.End(); iter = iter.Next() {
+ if tv := iter.GetType(); tv != nil {
+ t = append(t, tv)
+ } else {
+ iri = append(iri, iter.GetIRI())
+ }
+ }
+ }
+ }
+ // 'tag'
+ if i, ok := o.(tagger); ok {
+ if tag := i.GetActivityStreamsTag(); tag != nil {
+ for iter := tag.Begin(); iter != tag.End(); iter = iter.Next() {
+ if tv := iter.GetType(); tv != nil {
+ t = append(t, tv)
+ } else {
+ iri = append(iri, iter.GetIRI())
+ }
+ }
+ }
+ }
+ // 'object'
+ if i, ok := o.(objecter); ok {
+ if obj := i.GetActivityStreamsObject(); obj != nil {
+ for iter := obj.Begin(); iter != obj.End(); iter = iter.Next() {
+ if tv := iter.GetType(); tv != nil {
+ t = append(t, tv)
+ } else {
+ iri = append(iri, iter.GetIRI())
+ }
+ }
+ }
+ }
+ // 'target'
+ if i, ok := o.(targeter); ok {
+ if tar := i.GetActivityStreamsTarget(); tar != nil {
+ for iter := tar.Begin(); iter != tar.End(); iter = iter.Next() {
+ if tv := iter.GetType(); tv != nil {
+ t = append(t, tv)
+ } else {
+ iri = append(iri, iter.GetIRI())
+ }
+ }
+ }
+ }
+ return
+}
+
+// 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 wrapInCreate(ctx context.Context, o vocab.Type, actor *url.URL) (c vocab.ActivityStreamsCreate, err error) {
+ c = streams.NewActivityStreamsCreate()
+ // Object property
+ oProp := streams.NewActivityStreamsObjectProperty()
+ oProp.AppendType(o)
+ c.SetActivityStreamsObject(oProp)
+ // Actor Property
+ actorProp := streams.NewActivityStreamsActorProperty()
+ actorProp.AppendIRI(actor)
+ c.SetActivityStreamsActor(actorProp)
+ // Published Property
+ if v, ok := o.(publisheder); ok {
+ c.SetActivityStreamsPublished(v.GetActivityStreamsPublished())
+ }
+ // Copying over properties.
+ if v, ok := o.(toer); ok {
+ if to := v.GetActivityStreamsTo(); to != nil {
+ activityTo := streams.NewActivityStreamsToProperty()
+ for iter := to.Begin(); iter != to.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ activityTo.AppendIRI(id)
+ }
+ c.SetActivityStreamsTo(activityTo)
+ }
+ }
+ if v, ok := o.(btoer); ok {
+ if bto := v.GetActivityStreamsBto(); bto != nil {
+ activityBto := streams.NewActivityStreamsBtoProperty()
+ for iter := bto.Begin(); iter != bto.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ activityBto.AppendIRI(id)
+ }
+ c.SetActivityStreamsBto(activityBto)
+ }
+ }
+ if v, ok := o.(ccer); ok {
+ if cc := v.GetActivityStreamsCc(); cc != nil {
+ activityCc := streams.NewActivityStreamsCcProperty()
+ for iter := cc.Begin(); iter != cc.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ activityCc.AppendIRI(id)
+ }
+ c.SetActivityStreamsCc(activityCc)
+ }
+ }
+ if v, ok := o.(bccer); ok {
+ if bcc := v.GetActivityStreamsBcc(); bcc != nil {
+ activityBcc := streams.NewActivityStreamsBccProperty()
+ for iter := bcc.Begin(); iter != bcc.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ activityBcc.AppendIRI(id)
+ }
+ c.SetActivityStreamsBcc(activityBcc)
+ }
+ }
+ if v, ok := o.(audiencer); ok {
+ if aud := v.GetActivityStreamsAudience(); aud != nil {
+ activityAudience := streams.NewActivityStreamsAudienceProperty()
+ for iter := aud.Begin(); iter != aud.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ activityAudience.AppendIRI(id)
+ }
+ c.SetActivityStreamsAudience(activityAudience)
+ }
+ }
+ return
+}
+
+const (
+ // PublicActivityPubIRI is the IRI that indicates an Activity is meant
+ // to be visible for general public consumption.
+ PublicActivityPubIRI = "https://www.w3.org/ns/activitystreams#Public"
+ publicJsonLD = "Public"
+ publicJsonLDAS = "as:Public"
+)
+
+// IsPublic determines if an IRI string is the Public collection as defined in
+// the spec, including JSON-LD compliant collections.
+func IsPublic(s string) bool {
+ return s == PublicActivityPubIRI || s == publicJsonLD || s == publicJsonLDAS
+}
+
+// Derives an ID URI from the given IdProperty and, if it's not the
+// magic AP Public IRI, appends it to the actorsAndCollections slice.
+func appendToActorsAndCollectionsIRIs(
+ iter IdProperty,
+ actorsAndCollections []*url.URL,
+) ([]*url.URL, error) {
+ id, err := ToId(iter)
+ if err != nil {
+ return nil, err
+ }
+
+ // Ignore Public IRI as we
+ // can't deliver to it directly.
+ if !IsPublic(id.String()) {
+ actorsAndCollections = append(actorsAndCollections, id)
+ }
+
+ return actorsAndCollections, nil
+}
+
+// actorsToInboxIRIs extracts the 'inbox' IRIs from actor types.
+func actorsToInboxIRIs(t []vocab.Type) (u []*url.URL, err error) {
+ for _, elem := range t {
+ var iri *url.URL
+ iri, err = getInbox(elem)
+ if err != nil {
+ return
+ }
+ u = append(u, iri)
+ }
+ return
+}
+
+// getInbox extracts the 'inbox' IRI from an actor type.
+func getInbox(t vocab.Type) (u *url.URL, err error) {
+ ib, ok := t.(inboxer)
+ if !ok {
+ err = fmt.Errorf("actor type %T has no inbox", t)
+ return
+ }
+ inbox := ib.GetActivityStreamsInbox()
+ return ToId(inbox)
+}
+
+// filterInboxIRIs will deduplicate the given inboxes
+// slice, while also leaving out any filtered IRIs.
+func filterInboxIRIs(
+ inboxes []*url.URL,
+ filtered ...*url.URL,
+) []*url.URL {
+ // Prepopulate the ignored map with each filtered IRI.
+ ignored := make(map[string]struct{}, len(filtered)+len(inboxes))
+ for _, filteredIRI := range filtered {
+ ignored[filteredIRI.String()] = struct{}{}
+ }
+
+ deduped := make([]*url.URL, 0, len(inboxes))
+ for _, inbox := range inboxes {
+ inboxStr := inbox.String()
+ _, ignore := ignored[inboxStr]
+ if ignore {
+ // We already included
+ // this URI in out, or
+ // we should ignore it.
+ continue
+ }
+
+ // Include this IRI in output, and
+ // add entry to the ignored map to
+ // ensure we don't include it again.
+ deduped = append(deduped, inbox)
+ ignored[inboxStr] = struct{}{}
+ }
+
+ return deduped
+}
+
+// stripHiddenRecipients removes "bto" and "bcc" from the activity.
+//
+// Note that this requirement of the specification is under "Section 6: Client
+// to Server Interactions", the Social API, and not the Federative API.
+func stripHiddenRecipients(activity Activity) {
+ activity.SetActivityStreamsBto(nil)
+ activity.SetActivityStreamsBcc(nil)
+ op := activity.GetActivityStreamsObject()
+ if op != nil {
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ if v, ok := iter.GetType().(btoer); ok {
+ v.SetActivityStreamsBto(nil)
+ }
+ if v, ok := iter.GetType().(bccer); ok {
+ v.SetActivityStreamsBcc(nil)
+ }
+ }
+ }
+}
+
+// mustHaveActivityOriginMatchObjects ensures that the Host in the activity id
+// IRI matches all of the Hosts in the object id IRIs.
+func mustHaveActivityOriginMatchObjects(a Activity) error {
+ originIRI, err := GetId(a)
+ if err != nil {
+ return err
+ }
+ originHost := originIRI.Host
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return nil
+ }
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ iri, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ if originHost != iri.Host {
+ return fmt.Errorf("object %q: not in activity origin", iri)
+ }
+ }
+ return nil
+}
+
+// normalizeRecipients ensures the activity and object have the same 'to',
+// 'bto', 'cc', 'bcc', and 'audience' properties. Copy the Activity's recipients
+// to objects, and the objects to the activity, but does NOT copy objects'
+// recipients to each other.
+func normalizeRecipients(a vocab.ActivityStreamsCreate) error {
+ // Phase 0: Acquire all recipients on the activity.
+ //
+ // Obtain the actorTo map
+ actorToMap := make(map[string]*url.URL)
+ actorTo := a.GetActivityStreamsTo()
+ if actorTo == nil {
+ actorTo = streams.NewActivityStreamsToProperty()
+ a.SetActivityStreamsTo(actorTo)
+ }
+ for iter := actorTo.Begin(); iter != actorTo.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ actorToMap[id.String()] = id
+ }
+ // Obtain the actorBto map
+ actorBtoMap := make(map[string]*url.URL)
+ actorBto := a.GetActivityStreamsBto()
+ if actorBto == nil {
+ actorBto = streams.NewActivityStreamsBtoProperty()
+ a.SetActivityStreamsBto(actorBto)
+ }
+ for iter := actorBto.Begin(); iter != actorBto.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ actorBtoMap[id.String()] = id
+ }
+ // Obtain the actorCc map
+ actorCcMap := make(map[string]*url.URL)
+ actorCc := a.GetActivityStreamsCc()
+ if actorCc == nil {
+ actorCc = streams.NewActivityStreamsCcProperty()
+ a.SetActivityStreamsCc(actorCc)
+ }
+ for iter := actorCc.Begin(); iter != actorCc.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ actorCcMap[id.String()] = id
+ }
+ // Obtain the actorBcc map
+ actorBccMap := make(map[string]*url.URL)
+ actorBcc := a.GetActivityStreamsBcc()
+ if actorBcc == nil {
+ actorBcc = streams.NewActivityStreamsBccProperty()
+ a.SetActivityStreamsBcc(actorBcc)
+ }
+ for iter := actorBcc.Begin(); iter != actorBcc.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ actorBccMap[id.String()] = id
+ }
+ // Obtain the actorAudience map
+ actorAudienceMap := make(map[string]*url.URL)
+ actorAudience := a.GetActivityStreamsAudience()
+ if actorAudience == nil {
+ actorAudience = streams.NewActivityStreamsAudienceProperty()
+ a.SetActivityStreamsAudience(actorAudience)
+ }
+ for iter := actorAudience.Begin(); iter != actorAudience.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ actorAudienceMap[id.String()] = id
+ }
+ // Obtain the objects maps for each recipient type.
+ o := a.GetActivityStreamsObject()
+ objsTo := make([]map[string]*url.URL, o.Len())
+ objsBto := make([]map[string]*url.URL, o.Len())
+ objsCc := make([]map[string]*url.URL, o.Len())
+ objsBcc := make([]map[string]*url.URL, o.Len())
+ objsAudience := make([]map[string]*url.URL, o.Len())
+ for i := 0; i < o.Len(); i++ {
+ iter := o.At(i)
+ // Phase 1: Acquire all existing recipients on the object.
+ //
+ // Object to
+ objsTo[i] = make(map[string]*url.URL)
+ var oTo vocab.ActivityStreamsToProperty
+ if tr, ok := iter.GetType().(toer); !ok {
+ return fmt.Errorf("the Create object at %d has no 'to' property", i)
+ } else {
+ oTo = tr.GetActivityStreamsTo()
+ if oTo == nil {
+ oTo = streams.NewActivityStreamsToProperty()
+ tr.SetActivityStreamsTo(oTo)
+ }
+ }
+ for iter := oTo.Begin(); iter != oTo.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objsTo[i][id.String()] = id
+ }
+ // Object bto
+ objsBto[i] = make(map[string]*url.URL)
+ var oBto vocab.ActivityStreamsBtoProperty
+ if tr, ok := iter.GetType().(btoer); !ok {
+ return fmt.Errorf("the Create object at %d has no 'bto' property", i)
+ } else {
+ oBto = tr.GetActivityStreamsBto()
+ if oBto == nil {
+ oBto = streams.NewActivityStreamsBtoProperty()
+ tr.SetActivityStreamsBto(oBto)
+ }
+ }
+ for iter := oBto.Begin(); iter != oBto.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objsBto[i][id.String()] = id
+ }
+ // Object cc
+ objsCc[i] = make(map[string]*url.URL)
+ var oCc vocab.ActivityStreamsCcProperty
+ if tr, ok := iter.GetType().(ccer); !ok {
+ return fmt.Errorf("the Create object at %d has no 'cc' property", i)
+ } else {
+ oCc = tr.GetActivityStreamsCc()
+ if oCc == nil {
+ oCc = streams.NewActivityStreamsCcProperty()
+ tr.SetActivityStreamsCc(oCc)
+ }
+ }
+ for iter := oCc.Begin(); iter != oCc.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objsCc[i][id.String()] = id
+ }
+ // Object bcc
+ objsBcc[i] = make(map[string]*url.URL)
+ var oBcc vocab.ActivityStreamsBccProperty
+ if tr, ok := iter.GetType().(bccer); !ok {
+ return fmt.Errorf("the Create object at %d has no 'bcc' property", i)
+ } else {
+ oBcc = tr.GetActivityStreamsBcc()
+ if oBcc == nil {
+ oBcc = streams.NewActivityStreamsBccProperty()
+ tr.SetActivityStreamsBcc(oBcc)
+ }
+ }
+ for iter := oBcc.Begin(); iter != oBcc.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objsBcc[i][id.String()] = id
+ }
+ // Object audience
+ objsAudience[i] = make(map[string]*url.URL)
+ var oAudience vocab.ActivityStreamsAudienceProperty
+ if tr, ok := iter.GetType().(audiencer); !ok {
+ return fmt.Errorf("the Create object at %d has no 'audience' property", i)
+ } else {
+ oAudience = tr.GetActivityStreamsAudience()
+ if oAudience == nil {
+ oAudience = streams.NewActivityStreamsAudienceProperty()
+ tr.SetActivityStreamsAudience(oAudience)
+ }
+ }
+ for iter := oAudience.Begin(); iter != oAudience.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objsAudience[i][id.String()] = id
+ }
+ // Phase 2: Apply missing recipients to the object from the
+ // activity.
+ //
+ // Activity to -> Object to
+ for k, v := range actorToMap {
+ if _, ok := objsTo[i][k]; !ok {
+ oTo.AppendIRI(v)
+ }
+ }
+ // Activity bto -> Object bto
+ for k, v := range actorBtoMap {
+ if _, ok := objsBto[i][k]; !ok {
+ oBto.AppendIRI(v)
+ }
+ }
+ // Activity cc -> Object cc
+ for k, v := range actorCcMap {
+ if _, ok := objsCc[i][k]; !ok {
+ oCc.AppendIRI(v)
+ }
+ }
+ // Activity bcc -> Object bcc
+ for k, v := range actorBccMap {
+ if _, ok := objsBcc[i][k]; !ok {
+ oBcc.AppendIRI(v)
+ }
+ }
+ // Activity audience -> Object audience
+ for k, v := range actorAudienceMap {
+ if _, ok := objsAudience[i][k]; !ok {
+ oAudience.AppendIRI(v)
+ }
+ }
+ }
+ // Phase 3: Apply missing recipients to the activity from the objects.
+ //
+ // Object to -> Activity to
+ for i := 0; i < len(objsTo); i++ {
+ for k, v := range objsTo[i] {
+ if _, ok := actorToMap[k]; !ok {
+ actorTo.AppendIRI(v)
+ }
+ }
+ }
+ // Object bto -> Activity bto
+ for i := 0; i < len(objsBto); i++ {
+ for k, v := range objsBto[i] {
+ if _, ok := actorBtoMap[k]; !ok {
+ actorBto.AppendIRI(v)
+ }
+ }
+ }
+ // Object cc -> Activity cc
+ for i := 0; i < len(objsCc); i++ {
+ for k, v := range objsCc[i] {
+ if _, ok := actorCcMap[k]; !ok {
+ actorCc.AppendIRI(v)
+ }
+ }
+ }
+ // Object bcc -> Activity bcc
+ for i := 0; i < len(objsBcc); i++ {
+ for k, v := range objsBcc[i] {
+ if _, ok := actorBccMap[k]; !ok {
+ actorBcc.AppendIRI(v)
+ }
+ }
+ }
+ // Object audience -> Activity audience
+ for i := 0; i < len(objsAudience); i++ {
+ for k, v := range objsAudience[i] {
+ if _, ok := actorAudienceMap[k]; !ok {
+ actorAudience.AppendIRI(v)
+ }
+ }
+ }
+ return nil
+}
+
+// toTombstone creates a Tombstone object for the given ActivityStreams value.
+func toTombstone(obj vocab.Type, id *url.URL, now time.Time) vocab.ActivityStreamsTombstone {
+ tomb := streams.NewActivityStreamsTombstone()
+ // id property
+ idProp := streams.NewJSONLDIdProperty()
+ idProp.Set(id)
+ tomb.SetJSONLDId(idProp)
+ // formerType property
+ former := streams.NewActivityStreamsFormerTypeProperty()
+ tomb.SetActivityStreamsFormerType(former)
+ // Populate Former Type
+ former.AppendXMLSchemaString(obj.GetTypeName())
+ // Copy over the published property if it existed
+ if pubber, ok := obj.(publisheder); ok {
+ if pub := pubber.GetActivityStreamsPublished(); pub != nil {
+ tomb.SetActivityStreamsPublished(pub)
+ }
+ }
+ // Copy over the updated property if it existed
+ if upder, ok := obj.(updateder); ok {
+ if upd := upder.GetActivityStreamsUpdated(); upd != nil {
+ tomb.SetActivityStreamsUpdated(upd)
+ }
+ }
+ // Set deleted time to now.
+ deleted := streams.NewActivityStreamsDeletedProperty()
+ deleted.Set(now)
+ tomb.SetActivityStreamsDeleted(deleted)
+ return tomb
+}
+
+// mustHaveActivityActorsMatchObjectActors ensures that the actors on types in
+// the 'object' property are all listed in the 'actor' property.
+func mustHaveActivityActorsMatchObjectActors(c context.Context,
+ actors vocab.ActivityStreamsActorProperty,
+ op vocab.ActivityStreamsObjectProperty,
+ newTransport func(c context.Context, actorBoxIRI *url.URL, gofedAgent string) (t Transport, err error),
+ boxIRI *url.URL,
+) error {
+ activityActorMap := make(map[string]bool, actors.Len())
+ for iter := actors.Begin(); iter != actors.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ activityActorMap[id.String()] = true
+ }
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ iri, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ // Attempt to dereference the IRI, regardless whether it is a
+ // type or IRI
+ tport, err := newTransport(c, boxIRI, goFedUserAgent())
+ if err != nil {
+ return err
+ }
+ resp, err := tport.Dereference(c, iri)
+ if err != nil {
+ return err
+ }
+ m, err := readActivityPubResp(resp)
+ if err != nil {
+ return err
+ }
+ t, err := streams.ToType(c, m)
+ if err != nil {
+ return err
+ }
+ ac, ok := t.(actorer)
+ if !ok {
+ return fmt.Errorf("cannot verify actors: object value has no 'actor' property")
+ }
+ objActors := ac.GetActivityStreamsActor()
+ for iter := objActors.Begin(); iter != objActors.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ if !activityActorMap[id.String()] {
+ return fmt.Errorf("activity does not have all actors from its object's actors")
+ }
+ }
+ }
+ return nil
+}
+
+// add implements the logic of adding object ids to a target Collection or
+// OrderedCollection. This logic is shared by both the C2S and S2S protocols.
+func add(c context.Context,
+ op vocab.ActivityStreamsObjectProperty,
+ target vocab.ActivityStreamsTargetProperty,
+ db Database,
+) error {
+ opIds := make([]*url.URL, 0, op.Len())
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ opIds = append(opIds, id)
+ }
+ targetIds := make([]*url.URL, 0, op.Len())
+ for iter := target.Begin(); iter != target.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ targetIds = append(targetIds, id)
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(t *url.URL) error {
+ unlock, err := db.Lock(c, t)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ if owns, err := db.Owns(c, t); err != nil {
+ return err
+ } else if !owns {
+ return nil
+ }
+ tp, err := db.Get(c, t)
+ if err != nil {
+ return err
+ }
+ if streams.IsOrExtendsActivityStreamsOrderedCollection(tp) {
+ oi, ok := tp.(orderedItemser)
+ if !ok {
+ return fmt.Errorf("type extending from OrderedCollection cannot convert to orderedItemser interface")
+ }
+ oiProp := oi.GetActivityStreamsOrderedItems()
+ if oiProp == nil {
+ oiProp = streams.NewActivityStreamsOrderedItemsProperty()
+ oi.SetActivityStreamsOrderedItems(oiProp)
+ }
+ for _, objId := range opIds {
+ oiProp.AppendIRI(objId)
+ }
+ } else if streams.IsOrExtendsActivityStreamsCollection(tp) {
+ i, ok := tp.(itemser)
+ if !ok {
+ return fmt.Errorf("type extending from Collection cannot convert to itemser interface")
+ }
+ iProp := i.GetActivityStreamsItems()
+ if iProp == nil {
+ iProp = streams.NewActivityStreamsItemsProperty()
+ i.SetActivityStreamsItems(iProp)
+ }
+ for _, objId := range opIds {
+ iProp.AppendIRI(objId)
+ }
+ } else {
+ return fmt.Errorf("target in Add is neither a Collection nor an OrderedCollection")
+ }
+ err = db.Update(c, tp)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+ for _, t := range targetIds {
+ if err := loopFn(t); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// remove implements the logic of removing object ids to a target Collection or
+// OrderedCollection. This logic is shared by both the C2S and S2S protocols.
+func remove(c context.Context,
+ op vocab.ActivityStreamsObjectProperty,
+ target vocab.ActivityStreamsTargetProperty,
+ db Database,
+) error {
+ opIds := make(map[string]bool, op.Len())
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ opIds[id.String()] = true
+ }
+ targetIds := make([]*url.URL, 0, op.Len())
+ for iter := target.Begin(); iter != target.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ targetIds = append(targetIds, id)
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(t *url.URL) error {
+ unlock, err := db.Lock(c, t)
+ if err != nil {
+ return err
+ }
+ defer unlock()
+ if owns, err := db.Owns(c, t); err != nil {
+ return err
+ } else if !owns {
+ return nil
+ }
+ tp, err := db.Get(c, t)
+ if err != nil {
+ return err
+ }
+ if streams.IsOrExtendsActivityStreamsOrderedCollection(tp) {
+ oi, ok := tp.(orderedItemser)
+ if !ok {
+ return fmt.Errorf("type extending from OrderedCollection cannot convert to orderedItemser interface")
+ }
+ oiProp := oi.GetActivityStreamsOrderedItems()
+ if oiProp != nil {
+ for i := 0; i < oiProp.Len(); /*Conditional*/ {
+ id, err := ToId(oiProp.At(i))
+ if err != nil {
+ return err
+ }
+ if opIds[id.String()] {
+ oiProp.Remove(i)
+ } else {
+ i++
+ }
+ }
+ }
+ } else if streams.IsOrExtendsActivityStreamsCollection(tp) {
+ i, ok := tp.(itemser)
+ if !ok {
+ return fmt.Errorf("type extending from Collection cannot convert to itemser interface")
+ }
+ iProp := i.GetActivityStreamsItems()
+ if iProp != nil {
+ for i := 0; i < iProp.Len(); /*Conditional*/ {
+ id, err := ToId(iProp.At(i))
+ if err != nil {
+ return err
+ }
+ if opIds[id.String()] {
+ iProp.Remove(i)
+ } else {
+ i++
+ }
+ }
+ }
+ } else {
+ return fmt.Errorf("target in Remove is neither a Collection nor an OrderedCollection")
+ }
+ err = db.Update(c, tp)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+ for _, t := range targetIds {
+ if err := loopFn(t); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// clearSensitiveFields removes the 'bto' and 'bcc' entries on the given value
+// and recursively on every 'object' property value.
+func clearSensitiveFields(obj vocab.Type) {
+ if t, ok := obj.(btoer); ok {
+ t.SetActivityStreamsBto(nil)
+ }
+ if t, ok := obj.(bccer); ok {
+ t.SetActivityStreamsBcc(nil)
+ }
+ if t, ok := obj.(objecter); ok {
+ op := t.GetActivityStreamsObject()
+ if op != nil {
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ clearSensitiveFields(iter.GetType())
+ }
+ }
+ }
+}
+
+// requestId forms an ActivityPub id based on the HTTP request. Always assumes
+// that the id is HTTPS.
+func requestId(r *http.Request, scheme string) *url.URL {
+ id := r.URL
+ id.Host = r.Host
+ id.Scheme = scheme
+ return id
+}
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/version.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/version.go
new file mode 100644
index 000000000..23b958ce5
--- /dev/null
+++ b/vendor/codeberg.org/superseriousbusiness/activity/pub/version.go
@@ -0,0 +1,15 @@
+package pub
+
+import (
+ "fmt"
+)
+
+const (
+ // Version string, used in the User-Agent
+ version = "v1.0.0"
+)
+
+// goFedUserAgent returns the user agent string for the go-fed library.
+func goFedUserAgent() string {
+ return fmt.Sprintf("(go-fed/activity %s)", version)
+}