diff options
Diffstat (limited to 'vendor/codeberg.org/superseriousbusiness/activity/pub/base_actor.go')
| -rw-r--r-- | vendor/codeberg.org/superseriousbusiness/activity/pub/base_actor.go | 475 |
1 files changed, 0 insertions, 475 deletions
diff --git a/vendor/codeberg.org/superseriousbusiness/activity/pub/base_actor.go b/vendor/codeberg.org/superseriousbusiness/activity/pub/base_actor.go deleted file mode 100644 index c509383d6..000000000 --- a/vendor/codeberg.org/superseriousbusiness/activity/pub/base_actor.go +++ /dev/null @@ -1,475 +0,0 @@ -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) -} |
