mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-11-08 03:39:33 -06:00
Grand test fixup (#138)
* start fixing up tests * fix up tests + automate with drone * fiddle with linting * messing about with drone.yml * some more fiddling * hmmm * add cache * add vendor directory * verbose * ci updates * update some little things * update sig
This commit is contained in:
parent
329a5e8144
commit
98263a7de6
2677 changed files with 1090869 additions and 219 deletions
270
vendor/github.com/go-fed/activity/pub/README.md
generated
vendored
Normal file
270
vendor/github.com/go-fed/activity/pub/README.md
generated
vendored
Normal file
|
|
@ -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 (
|
||||
"github.com/go-fed/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.
|
||||
49
vendor/github.com/go-fed/activity/pub/activity.go
generated
vendored
Normal file
49
vendor/github.com/go-fed/activity/pub/activity.go
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"github.com/go-fed/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)
|
||||
}
|
||||
127
vendor/github.com/go-fed/activity/pub/actor.go
generated
vendored
Normal file
127
vendor/github.com/go-fed/activity/pub/actor.go
generated
vendored
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
494
vendor/github.com/go-fed/activity/pub/base_actor.go
generated
vendored
Normal file
494
vendor/github.com/go-fed/activity/pub/base_actor.go
generated
vendored
Normal file
|
|
@ -0,0 +1,494 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// 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{
|
||||
delegate: &sideEffectActor{
|
||||
common: c,
|
||||
c2s: c2s,
|
||||
db: db,
|
||||
clock: 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{
|
||||
delegate: &sideEffectActor{
|
||||
common: c,
|
||||
s2s: s2s,
|
||||
db: db,
|
||||
clock: 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: &sideEffectActor{
|
||||
common: c,
|
||||
c2s: c2s,
|
||||
s2s: s2s,
|
||||
db: db,
|
||||
clock: 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.
|
||||
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.
|
||||
raw, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
var m map[string]interface{}
|
||||
if err = json.Unmarshal(raw, &m); 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.
|
||||
raw, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return true, err
|
||||
}
|
||||
var m map[string]interface{}
|
||||
if err = json.Unmarshal(raw, &m); 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)
|
||||
}
|
||||
11
vendor/github.com/go-fed/activity/pub/clock.go
generated
vendored
Normal file
11
vendor/github.com/go-fed/activity/pub/clock.go
generated
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Clock determines the time.
|
||||
type Clock interface {
|
||||
// Now returns the current time.
|
||||
Now() time.Time
|
||||
}
|
||||
89
vendor/github.com/go-fed/activity/pub/common_behavior.go
generated
vendored
Normal file
89
vendor/github.com/go-fed/activity/pub/common_behavior.go
generated
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
139
vendor/github.com/go-fed/activity/pub/database.go
generated
vendored
Normal file
139
vendor/github.com/go-fed/activity/pub/database.go
generated
vendored
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
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) error
|
||||
// Unlock makes the lock for the object at the specified id available.
|
||||
// If an error is returned, the lock must have still been freed.
|
||||
//
|
||||
// Used to ensure race conditions in multiple requests do not occur.
|
||||
Unlock(c context.Context, id *url.URL) 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)
|
||||
// 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)
|
||||
}
|
||||
248
vendor/github.com/go-fed/activity/pub/delegate_actor.go
generated
vendored
Normal file
248
vendor/github.com/go-fed/activity/pub/delegate_actor.go
generated
vendored
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
9
vendor/github.com/go-fed/activity/pub/doc.go
generated
vendored
Normal file
9
vendor/github.com/go-fed/activity/pub/doc.go
generated
vendored
Normal file
|
|
@ -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
|
||||
124
vendor/github.com/go-fed/activity/pub/federating_protocol.go
generated
vendored
Normal file
124
vendor/github.com/go-fed/activity/pub/federating_protocol.go
generated
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
907
vendor/github.com/go-fed/activity/pub/federating_wrapped_callbacks.go
generated
vendored
Normal file
907
vendor/github.com/go-fed/activity/pub/federating_wrapped_callbacks.go
generated
vendored
Normal file
|
|
@ -0,0 +1,907 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
b, err := tport.Dereference(c, iter.GetIRI())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var m map[string]interface{}
|
||||
if err = json.Unmarshal(b, &m); 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
|
||||
}
|
||||
err = w.db.Lock(c, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.db.Unlock(c, id)
|
||||
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
|
||||
}
|
||||
err = w.db.Lock(c, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.db.Unlock(c, id)
|
||||
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
|
||||
}
|
||||
err = w.db.Lock(c, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.db.Unlock(c, id)
|
||||
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.
|
||||
if err := w.db.Lock(c, w.inboxIRI); err != nil {
|
||||
return err
|
||||
}
|
||||
// WARNING: Unlock not deferred.
|
||||
actorIRI, err := w.db.ActorForInbox(c, w.inboxIRI)
|
||||
if err != nil {
|
||||
w.db.Unlock(c, w.inboxIRI)
|
||||
return err
|
||||
}
|
||||
w.db.Unlock(c, w.inboxIRI)
|
||||
// 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.
|
||||
if err := w.db.Lock(c, actorIRI); err != nil {
|
||||
return err
|
||||
}
|
||||
// WARNING: Unlock not deferred.
|
||||
followers, err := w.db.Followers(c, actorIRI)
|
||||
if err != nil {
|
||||
w.db.Unlock(c, actorIRI)
|
||||
return err
|
||||
}
|
||||
items := followers.GetActivityStreamsItems()
|
||||
if items == nil {
|
||||
items = streams.NewActivityStreamsItemsProperty()
|
||||
followers.SetActivityStreamsItems(items)
|
||||
}
|
||||
for _, elem := range recipients {
|
||||
items.PrependIRI(elem)
|
||||
}
|
||||
if err = w.db.Update(c, followers); err != nil {
|
||||
w.db.Unlock(c, actorIRI)
|
||||
return err
|
||||
}
|
||||
w.db.Unlock(c, actorIRI)
|
||||
// Unlock must be called by now and every branch above.
|
||||
}
|
||||
// Lock without defer!
|
||||
w.db.Lock(c, w.inboxIRI)
|
||||
outboxIRI, err := w.db.OutboxForInbox(c, w.inboxIRI)
|
||||
if err != nil {
|
||||
w.db.Unlock(c, w.inboxIRI)
|
||||
return err
|
||||
}
|
||||
w.db.Unlock(c, w.inboxIRI)
|
||||
// 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.
|
||||
if err := w.db.Lock(c, w.inboxIRI); err != nil {
|
||||
return err
|
||||
}
|
||||
// WARNING: Unlock not deferred.
|
||||
actorIRI, err := w.db.ActorForInbox(c, w.inboxIRI)
|
||||
if err != nil {
|
||||
w.db.Unlock(c, w.inboxIRI)
|
||||
return err
|
||||
}
|
||||
w.db.Unlock(c, w.inboxIRI)
|
||||
// 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
|
||||
}
|
||||
b, err := tport.Dereference(c, iter.GetIRI())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var m map[string]interface{}
|
||||
if err = json.Unmarshal(b, &m); 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 {
|
||||
if err := w.db.Lock(c, maybeMyFollowIRI); err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.db.Unlock(c, maybeMyFollowIRI)
|
||||
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.
|
||||
if err := w.db.Lock(c, actorIRI); err != nil {
|
||||
return err
|
||||
}
|
||||
// WARNING: Unlock not deferred.
|
||||
following, err := w.db.Following(c, actorIRI)
|
||||
if err != nil {
|
||||
w.db.Unlock(c, actorIRI)
|
||||
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 {
|
||||
w.db.Unlock(c, actorIRI)
|
||||
return err
|
||||
}
|
||||
items.PrependIRI(id)
|
||||
}
|
||||
if err = w.db.Update(c, following); err != nil {
|
||||
w.db.Unlock(c, actorIRI)
|
||||
return err
|
||||
}
|
||||
w.db.Unlock(c, actorIRI)
|
||||
// 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
|
||||
}
|
||||
if err := w.db.Lock(c, objId); err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.db.Unlock(c, objId)
|
||||
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
|
||||
}
|
||||
if err := w.db.Lock(c, objId); err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.db.Unlock(c, objId)
|
||||
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
|
||||
}
|
||||
113
vendor/github.com/go-fed/activity/pub/handlers.go
generated
vendored
Normal file
113
vendor/github.com/go-fed/activity/pub/handlers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-fed/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)
|
||||
// Lock and obtain a copy of the requested ActivityStreams value
|
||||
err = db.Lock(c, id)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// WARNING: Unlock not deferred
|
||||
t, err := db.Get(c, id)
|
||||
if err != nil {
|
||||
db.Unlock(c, id)
|
||||
return
|
||||
}
|
||||
db.Unlock(c, id)
|
||||
// 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
|
||||
}
|
||||
}
|
||||
117
vendor/github.com/go-fed/activity/pub/property_interfaces.go
generated
vendored
Normal file
117
vendor/github.com/go-fed/activity/pub/property_interfaces.go
generated
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
810
vendor/github.com/go-fed/activity/pub/side_effect_actor.go
generated
vendored
Normal file
810
vendor/github.com/go-fed/activity/pub/side_effect_actor.go
generated
vendored
Normal file
|
|
@ -0,0 +1,810 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
common CommonBehavior
|
||||
s2s FederatingProtocol
|
||||
c2s SocialProtocol
|
||||
db Database
|
||||
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.
|
||||
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 {
|
||||
a.db.Unlock(c, id.Get())
|
||||
return err
|
||||
} else if exists {
|
||||
a.db.Unlock(c, id.Get())
|
||||
return nil
|
||||
}
|
||||
// Attempt to create the activity entry.
|
||||
err = a.db.Create(c, activity)
|
||||
if err != nil {
|
||||
a.db.Unlock(c, id.Get())
|
||||
return err
|
||||
}
|
||||
a.db.Unlock(c, id.Get())
|
||||
// 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
|
||||
}
|
||||
err = a.db.Lock(c, iri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// WARNING: Unlock is not deferred
|
||||
if owns, err := a.db.Owns(c, iri); err != nil {
|
||||
a.db.Unlock(c, iri)
|
||||
return err
|
||||
} else if !owns {
|
||||
a.db.Unlock(c, iri)
|
||||
continue
|
||||
}
|
||||
a.db.Unlock(c, iri)
|
||||
// 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 {
|
||||
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 a.db.Unlock(c, iri)
|
||||
} else {
|
||||
a.db.Unlock(c, iri)
|
||||
}
|
||||
} else if streams.IsOrExtendsActivityStreamsCollection(t) {
|
||||
if im, ok := t.(itemser); ok {
|
||||
col[iri.String()] = im
|
||||
colIRIs = append(colIRIs, iri)
|
||||
defer a.db.Unlock(c, iri)
|
||||
} else {
|
||||
a.db.Unlock(c, iri)
|
||||
}
|
||||
} else {
|
||||
a.db.Unlock(c, iri)
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
err = a.db.Lock(c, outboxIRI)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// WARNING: No deferring the Unlock
|
||||
actorIRI, err := a.db.ActorForOutbox(c, outboxIRI)
|
||||
if err != nil {
|
||||
a.db.Unlock(c, outboxIRI)
|
||||
return
|
||||
}
|
||||
a.db.Unlock(c, outboxIRI)
|
||||
// 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 {
|
||||
m, err := streams.Serialize(activity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tp, err := a.common.NewTransport(c, boxIRI, goFedUserAgent())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tp.BatchDeliver(c, b, 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()
|
||||
err := a.db.Lock(c, id.Get())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// WARNING: Unlock not deferred
|
||||
err = a.db.Create(c, activity)
|
||||
if err != nil {
|
||||
a.db.Unlock(c, id.Get())
|
||||
return err
|
||||
}
|
||||
a.db.Unlock(c, id.Get())
|
||||
// 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.
|
||||
err = a.db.Lock(c, outboxIRI)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer a.db.Unlock(c, outboxIRI)
|
||||
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.
|
||||
err = a.db.Lock(c, inboxIRI)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer a.db.Unlock(c, inboxIRI)
|
||||
// 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 {
|
||||
err := a.db.Lock(c, iri)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// WARNING: Unlock is not deferred
|
||||
if owns, err := a.db.Owns(c, iri); err != nil {
|
||||
a.db.Unlock(c, iri)
|
||||
return false, err
|
||||
} else if owns {
|
||||
a.db.Unlock(c, iri)
|
||||
return true, nil
|
||||
}
|
||||
a.db.Unlock(c, iri)
|
||||
// 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
|
||||
}
|
||||
err = a.db.Lock(c, id)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
// WARNING: Unlock is not deferred
|
||||
if owns, err := a.db.Owns(c, id); err != nil {
|
||||
a.db.Unlock(c, id)
|
||||
return false, err
|
||||
} else if owns {
|
||||
a.db.Unlock(c, id)
|
||||
return true, nil
|
||||
}
|
||||
a.db.Unlock(c, id)
|
||||
// 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
|
||||
}
|
||||
b, err := tport.Dereference(c, iri)
|
||||
if err != nil {
|
||||
// Do not fail the entire process if the data is
|
||||
// missing.
|
||||
continue
|
||||
}
|
||||
var m map[string]interface{}
|
||||
if err = json.Unmarshal(b, &m); 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 proper recipient
|
||||
// target URIs. 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(c context.Context, outboxIRI *url.URL, activity Activity) (r []*url.URL, err error) {
|
||||
// Get inboxes of recipients
|
||||
if to := activity.GetActivityStreamsTo(); to != nil {
|
||||
for iter := to.Begin(); iter != to.End(); iter = iter.Next() {
|
||||
var val *url.URL
|
||||
val, err = ToId(iter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r = append(r, val)
|
||||
}
|
||||
}
|
||||
if bto := activity.GetActivityStreamsBto(); bto != nil {
|
||||
for iter := bto.Begin(); iter != bto.End(); iter = iter.Next() {
|
||||
var val *url.URL
|
||||
val, err = ToId(iter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r = append(r, val)
|
||||
}
|
||||
}
|
||||
if cc := activity.GetActivityStreamsCc(); cc != nil {
|
||||
for iter := cc.Begin(); iter != cc.End(); iter = iter.Next() {
|
||||
var val *url.URL
|
||||
val, err = ToId(iter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r = append(r, val)
|
||||
}
|
||||
}
|
||||
if bcc := activity.GetActivityStreamsBcc(); bcc != nil {
|
||||
for iter := bcc.Begin(); iter != bcc.End(); iter = iter.Next() {
|
||||
var val *url.URL
|
||||
val, err = ToId(iter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r = append(r, val)
|
||||
}
|
||||
}
|
||||
if audience := activity.GetActivityStreamsAudience(); audience != nil {
|
||||
for iter := audience.Begin(); iter != audience.End(); iter = iter.Next() {
|
||||
var val *url.URL
|
||||
val, err = ToId(iter)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
r = append(r, val)
|
||||
}
|
||||
}
|
||||
// 1. When an object is being delivered to the originating actor's
|
||||
// followers, a server MAY reduce the number of receiving actors
|
||||
// delivered to by identifying all followers which share the same
|
||||
// sharedInbox who would otherwise be individual recipients and
|
||||
// instead deliver objects to said sharedInbox.
|
||||
// 2. If an object is addressed to the Public special collection, a
|
||||
// server MAY deliver that object to all known sharedInbox endpoints
|
||||
// on the network.
|
||||
r = filterURLs(r, IsPublic)
|
||||
t, err := a.common.NewTransport(c, outboxIRI, goFedUserAgent())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
receiverActors, err := a.resolveInboxes(c, t, r, 0, a.s2s.MaxDeliveryRecursionDepth(c))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
targets, err := getInboxes(receiverActors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Get inboxes of sender.
|
||||
err = a.db.Lock(c, outboxIRI)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
// WARNING: No deferring the Unlock
|
||||
actorIRI, err := a.db.ActorForOutbox(c, outboxIRI)
|
||||
if err != nil {
|
||||
a.db.Unlock(c, outboxIRI)
|
||||
return
|
||||
}
|
||||
a.db.Unlock(c, outboxIRI)
|
||||
// Get the inbox on the sender.
|
||||
err = a.db.Lock(c, actorIRI)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// BEGIN LOCK
|
||||
thisActor, err := a.db.Get(c, actorIRI)
|
||||
a.db.Unlock(c, actorIRI)
|
||||
// END LOCK -- Still need to handle err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Post-processing
|
||||
var ignore *url.URL
|
||||
ignore, err = getInbox(thisActor)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r = dedupeIRIs(targets, []*url.URL{ignore})
|
||||
stripHiddenRecipients(activity)
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// resolveInboxes 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.
|
||||
//
|
||||
// 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) resolveInboxes(c context.Context, t Transport, r []*url.URL, depth, maxDepth int) (actors []vocab.Type, err error) {
|
||||
if maxDepth > 0 && depth >= maxDepth {
|
||||
return
|
||||
}
|
||||
for _, u := range r {
|
||||
var act vocab.Type
|
||||
var more []*url.URL
|
||||
// TODO: Determine if more logic is needed here for inaccessible
|
||||
// collections owned by peer servers.
|
||||
act, more, err = a.dereferenceForResolvingInboxes(c, t, u)
|
||||
if err != nil {
|
||||
// Missing recipient -- skip.
|
||||
continue
|
||||
}
|
||||
var recurActors []vocab.Type
|
||||
recurActors, err = a.resolveInboxes(c, t, more, depth+1, maxDepth)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if act != nil {
|
||||
actors = append(actors, act)
|
||||
}
|
||||
actors = append(actors, recurActors...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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 []byte
|
||||
resp, err = t.Dereference(c, actorIRI)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var m map[string]interface{}
|
||||
if err = json.Unmarshal(resp, &m); 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
|
||||
}
|
||||
82
vendor/github.com/go-fed/activity/pub/social_protocol.go
generated
vendored
Normal file
82
vendor/github.com/go-fed/activity/pub/social_protocol.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
531
vendor/github.com/go-fed/activity/pub/social_wrapped_callbacks.go
generated
vendored
Normal file
531
vendor/github.com/go-fed/activity/pub/social_wrapped_callbacks.go
generated
vendored
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
err = w.db.Lock(c, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.db.Unlock(c, id)
|
||||
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 {
|
||||
err := w.db.Lock(c, loopId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.db.Unlock(c, loopId)
|
||||
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 {
|
||||
err := w.db.Lock(c, loopId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.db.Unlock(c, loopId)
|
||||
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.
|
||||
if err := w.db.Lock(c, w.outboxIRI); err != nil {
|
||||
return err
|
||||
}
|
||||
// WARNING: Unlock not deferred.
|
||||
actorIRI, err := w.db.ActorForOutbox(c, w.outboxIRI)
|
||||
if err != nil {
|
||||
w.db.Unlock(c, w.outboxIRI)
|
||||
return err
|
||||
}
|
||||
w.db.Unlock(c, w.outboxIRI)
|
||||
// Unlock must be called by now and every branch above.
|
||||
//
|
||||
// Now obtain this actor's 'liked' collection.
|
||||
if err := w.db.Lock(c, actorIRI); err != nil {
|
||||
return err
|
||||
}
|
||||
defer w.db.Unlock(c, actorIRI)
|
||||
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
|
||||
}
|
||||
207
vendor/github.com/go-fed/activity/pub/transport.go
generated
vendored
Normal file
207
vendor/github.com/go-fed/activity/pub/transport.go
generated
vendored
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"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.
|
||||
Dereference(c context.Context, iri *url.URL) ([]byte, error)
|
||||
// Deliver sends an ActivityStreams object.
|
||||
Deliver(c context.Context, b []byte, to *url.URL) error
|
||||
// BatchDeliver sends an ActivityStreams object to multiple recipients.
|
||||
BatchDeliver(c context.Context, b []byte, 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) ([]byte, 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
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("GET request to %s failed (%d): %s", iri.String(), resp.StatusCode, resp.Status)
|
||||
}
|
||||
return ioutil.ReadAll(resp.Body)
|
||||
}
|
||||
|
||||
// Deliver sends a POST request with an HTTP Signature.
|
||||
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
|
||||
}
|
||||
|
||||
// BatchDeliver sends concurrent POST requests. Returns an error if any of the
|
||||
// requests had an error.
|
||||
func (h HttpSigTransport) BatchDeliver(c context.Context, b []byte, recipients []*url.URL) error {
|
||||
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
|
||||
}
|
||||
|
||||
// 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{}
|
||||
995
vendor/github.com/go-fed/activity/pub/util.go
generated
vendored
Normal file
995
vendor/github.com/go-fed/activity/pub/util.go
generated
vendored
Normal file
|
|
@ -0,0 +1,995 @@
|
|||
package pub
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/go-fed/activity/streams"
|
||||
"github.com/go-fed/activity/streams/vocab"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// filterURLs removes urls whose strings match the provided filter
|
||||
func filterURLs(u []*url.URL, fn func(s string) bool) []*url.URL {
|
||||
i := 0
|
||||
for i < len(u) {
|
||||
if fn(u[i].String()) {
|
||||
u = append(u[:i], u[i+1:]...)
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// getInboxes extracts the 'inbox' IRIs from actor types.
|
||||
func getInboxes(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)
|
||||
}
|
||||
|
||||
// dedupeIRIs will deduplicate final inbox IRIs. The ignore list is applied to
|
||||
// the final list.
|
||||
func dedupeIRIs(recipients, ignored []*url.URL) (out []*url.URL) {
|
||||
ignoredMap := make(map[string]bool, len(ignored))
|
||||
for _, elem := range ignored {
|
||||
ignoredMap[elem.String()] = true
|
||||
}
|
||||
outMap := make(map[string]bool, len(recipients))
|
||||
for _, k := range recipients {
|
||||
kStr := k.String()
|
||||
if !ignoredMap[kStr] && !outMap[kStr] {
|
||||
out = append(out, k)
|
||||
outMap[kStr] = true
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
b, err := tport.Dereference(c, iri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var m map[string]interface{}
|
||||
if err = json.Unmarshal(b, &m); 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 {
|
||||
if err := db.Lock(c, t); err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Unlock(c, t)
|
||||
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 {
|
||||
if err := db.Lock(c, t); err != nil {
|
||||
return err
|
||||
}
|
||||
defer db.Unlock(c, t)
|
||||
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
|
||||
}
|
||||
15
vendor/github.com/go-fed/activity/pub/version.go
generated
vendored
Normal file
15
vendor/github.com/go-fed/activity/pub/version.go
generated
vendored
Normal file
|
|
@ -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)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue