diff --git a/ap/actor.go b/ap/actor.go index 7195228..55ab4e4 100644 --- a/ap/actor.go +++ b/ap/actor.go @@ -40,26 +40,28 @@ func NewActor(c *config.Config, tc *conn.Controller) (actor pub.Actor, err error) { common := NewCommonBehavior(a, db, tc, o, pk) - if cs, ss := a.C2SEnabled(), a.S2SEnabled(); !cs && !ss { - err = fmt.Errorf("neither C2S nor S2S are enabled by the Application") - } else if cs && ss { - c2s := NewSocialBehavior(a, o) - s2s := NewFederatingBehavior(c, a, db, po, pk, f, u, tc) + ca, isC2S := a.(app.C2SApplication) + sa, isS2S := a.(app.S2SApplication) + if !isC2S && !isS2S { + err = fmt.Errorf("the Application is neither a C2SApplication nor a S2SApplication") + } else if isC2S && isS2S { + c2s := NewSocialBehavior(ca, o) + s2s := NewFederatingBehavior(c, sa, db, po, pk, f, u, tc) actor = pub.NewActor( common, c2s, s2s, apdb, clock) - } else if cs { - c2s := NewSocialBehavior(a, o) + } else if isC2S { + c2s := NewSocialBehavior(ca, o) actor = pub.NewSocialActor( common, c2s, apdb, clock) - } else if ss { - s2s := NewFederatingBehavior(c, a, db, po, pk, f, u, tc) + } else { + s2s := NewFederatingBehavior(c, sa, db, po, pk, f, u, tc) actor = pub.NewFederatingActor( common, s2s, diff --git a/ap/c2s.go b/ap/c2s.go index 80fb351..5288c4c 100644 --- a/ap/c2s.go +++ b/ap/c2s.go @@ -32,11 +32,11 @@ import ( var _ pub.SocialProtocol = &SocialBehavior{} type SocialBehavior struct { - app app.Application + app app.C2SApplication o *oauth2.Server } -func NewSocialBehavior(app app.Application, o *oauth2.Server) *SocialBehavior { +func NewSocialBehavior(app app.C2SApplication, o *oauth2.Server) *SocialBehavior { return &SocialBehavior{ app: app, o: o, diff --git a/ap/s2s.go b/ap/s2s.go index 2aea1c8..7625b1f 100644 --- a/ap/s2s.go +++ b/ap/s2s.go @@ -35,7 +35,7 @@ var _ pub.FederatingProtocol = &FederatingBehavior{} type FederatingBehavior struct { maxInboxForwardingDepth int maxDeliveryDepth int - app app.Application + app app.S2SApplication db *Database po *services.Policies pk *services.PrivateKeys @@ -45,7 +45,7 @@ type FederatingBehavior struct { } func NewFederatingBehavior(c *config.Config, - a app.Application, + a app.S2SApplication, db *Database, po *services.Policies, pk *services.PrivateKeys, diff --git a/app/application.go b/app/application.go index 636873d..6156b2d 100644 --- a/app/application.go +++ b/app/application.go @@ -25,7 +25,9 @@ import ( ) // Application is an ActivityPub application built on top of apcore's -// infrastructure. +// infrastructure. Your application must also implement C2SApplication, +// S2SApplication, or both interfaces in order to gain the benefits of +// federating using ActivityPub's Social or Federating Protocols. type Application interface { // CALLS MADE AT SERVER STARTUP // @@ -82,25 +84,6 @@ type Application interface { // function is only called once during application initialization. SetConfiguration(interface{}) error - // Whether this application supports ActivityPub's C2S protocol, or the - // Social API. - // - // This and S2SEnabled may both be true. If C2SEnabled and S2SEnabled - // both return false, an error will arise at startup. - // - // This is only checked at startup time. Attempting to enable or disable - // C2S at runtime has no effect. - C2SEnabled() bool - // Whether this application supports ActivityPub's S2S protocol, or the - // Federating API. - // - // This and C2SEnabled may both be true. If C2SEnabled and S2SEnabled - // both return false, an error will arise at startup. - // - // This is only checked at startup time. Attempting to enable or disable - // S2S at runtime has no effect. - S2SEnabled() bool - // The handler for the application's "404 Not Found" webpage. NotFoundHandler() http.Handler // The handler when a request makes an unsupported HTTP method against @@ -131,25 +114,12 @@ type Application interface { // Web handlers for ActivityPub related data - // Web handler for a call to GET an actor's inbox. The framework applies - // OAuth2 authorizations to fetch a public-only or private snapshot of - // the inbox, and passes it into this handler function. - // - // The builtin ActivityPub handler will use the OAuth authorization. - // - // Only called if S2SEnabled is true. - // - // Returning a nil handler is allowed, and doing so results in only - // ActivityStreams content being served. - GetInboxWebHandlerFunc() func(w http.ResponseWriter, r *http.Request, outbox vocab.ActivityStreamsOrderedCollectionPage) // Web handler for a call to GET an actor's outbox. The framework // applies OAuth2 authorizations to fetch a public-only or private // snapshot of the outbox, and passes it to this handler function. // // The builtin ActivityPub handler will use the OAuth authorization. // - // Always called regardless whether C2SEnabled or S2SEnabled is true. - // // Returning a nil handler is allowed, and doing so results in only // ActivityStreams content being served. GetOutboxWebHandlerFunc() func(w http.ResponseWriter, r *http.Request, outbox vocab.ActivityStreamsOrderedCollectionPage) @@ -160,8 +130,6 @@ type Application interface { // Also returns for the corresponding AuthorizeFunc handler, which will // be applied to both ActivityPub and web requests. // - // Always called regardless whether C2SEnabled or S2SEnabled is true. - // // Returning a nil handler is allowed, and doing so results in only // ActivityStreams content being served. Returning a nil AuthorizeFunc // results in public access. @@ -173,8 +141,6 @@ type Application interface { // Also returns for the corresponding AuthorizeFunc handler, which will // be applied to both ActivityPub and web requests. // - // Always called regardless whether C2SEnabled or S2SEnabled is true. - // // Returning a nil handler is allowed, and doing so results in only // ActivityStreams content being served. Returning a nil AuthorizeFunc // results in public access. @@ -186,8 +152,6 @@ type Application interface { // Also returns for the corresponding AuthorizeFunc handler, which will // be applied to both ActivityPub and web requests. // - // Always called regardless whether C2SEnabled or S2SEnabled is true. - // // Returning a nil handler is allowed, and doing so results in only // ActivityStreams content being served. Returning a nil AuthorizeFunc // results in public access. @@ -198,8 +162,6 @@ type Application interface { // Also returns for the corresponding AuthorizeFunc handler, which will // be applied to both ActivityPub and web requests. // - // Always called regardless whether C2SEnabled or S2SEnabled is true. - // // Returning a nil handler is allowed, and doing so results in only // ActivityStreams content being served. Returning a nil AuthorizeFunc // results in public access. @@ -236,15 +198,56 @@ type Application interface { // Ensure the route returned by NewIDPath will be servable by a handler // created in the BuildRoutes call. NewIDPath(c context.Context, t vocab.Type) (path string, err error) - // ApplyFederatingCallbacks injects ActivityPub specific behaviors for - // federated data. + + // ScopePermitsPrivateGetInbox determines if an OAuth token scope + // permits the bearer to view private (non-Public) messages in an + // actor's inbox. + ScopePermitsPrivateGetInbox(scope string) (permitted bool, err error) + // ScopePermitsPrivateGetOutbox determines if an OAuth token scope + // permits the bearer to view private (non-Public) messages in an + // actor's outbox. + ScopePermitsPrivateGetOutbox(scope string) (permitted bool, err error) + + // DefaultUserPreferences returns an application-specific preferences + // struct to be serialized into JSON and used as initial user app + // preferences. + DefaultUserPreferences() interface{} + // DefaultUserPrivileges returns an application-specific privileges + // struct to be serialized into JSON and used as initial user app + // privileges. + DefaultUserPrivileges() interface{} + // DefaultAdminPrivileges returns an application-specific privileges + // struct to be serialized into JSON and used as initial user app + // privileges for new admins. + DefaultAdminPrivileges() interface{} + + // CALLS MADE BOTH AT STARTUP AND SERVING TIME + // + // These calls are made at least once during server initialization, and + // are called when the server is handling requests. + + // Information about this application's software. This will be shown at + // the command line and used for NodeInfo statistics, as well as for + // user agent information. + Software() Software +} + +// C2SApplication is an Application with additional methods required to support +// the C2S, or Social, ActivityPub protocol. +type C2SApplication interface { + // ScopePermitsPostOutbox determines if an OAuth token scope permits the + // bearer to post to an actor's outbox. + ScopePermitsPostOutbox(scope string) (permitted bool, err error) + + // ApplySocialCallbacks injects ActivityPub specific behaviors for + // social, or C2S, data. // // Additional behavior for out-of-the-box supported types, such as the // Create type, can be set by directly defining a function on the // callback passed in: // - // func (m *myImpl) ApplyFederatingCallbacks(fwc *pub.FederatingWrappedCallbacks) (others []interface{}) { - // fwc.Create = func(c context.Context, as vocab.ActivityStreamsCreate) error { + // func (m *myImpl) ApplySocialCallbacks(swc *pub.SocialWrappedCallbacks) (others []interface{}) { + // swc.Create = func(c context.Context, as vocab.ActivityStreamsCreate) error { // // Additional application behavior for the Create activity. // } // } @@ -263,7 +266,7 @@ type Application interface { // example, the "Create" behavior's default behavior of creating // ActivityStreams types in the database can be overridden by: // - // func (m *myImpl) ApplyFederatingCallbacks(fwc *pub.FederatingWrappedCallbacks) (others []interface{}) { + // func (m *myImpl) ApplySocialCallbacks(swc *pub.SocialWrappedCallbacks) (others []interface{}) { // others = []interface{}{ // func(c context.Context, as vocab.ActivityStreamsCreate) error { // // New behavior for the Create activity that overrides the @@ -272,21 +275,31 @@ type Application interface { // } // return // } + ApplySocialCallbacks(swc *pub.SocialWrappedCallbacks) (others []interface{}) +} + +// S2SApplication is an Application with the additional methods required to +// support the S2S, or Federating, ActivityPub protocol. +type S2SApplication interface { + // Web handler for a call to GET an actor's inbox. The framework applies + // OAuth2 authorizations to fetch a public-only or private snapshot of + // the inbox, and passes it into this handler function. // - // Note: The `OnFollow` value will already be populated by the user's - // preferred behavior upon receiving a Follow request. + // The builtin ActivityPub handler will use the OAuth authorization. // - // Only called if S2SEnabled returned true at startup time. - ApplyFederatingCallbacks(fwc *pub.FederatingWrappedCallbacks) (others []interface{}) - // ApplySocialCallbacks injects ActivityPub specific behaviors for - // social, or C2S, data. + // Returning a nil handler is allowed, and doing so results in only + // ActivityStreams content being served. + GetInboxWebHandlerFunc() func(w http.ResponseWriter, r *http.Request, outbox vocab.ActivityStreamsOrderedCollectionPage) + + // ApplyFederatingCallbacks injects ActivityPub specific behaviors for + // federated data. // // Additional behavior for out-of-the-box supported types, such as the // Create type, can be set by directly defining a function on the // callback passed in: // - // func (m *myImpl) ApplySocialCallbacks(swc *pub.SocialWrappedCallbacks) (others []interface{}) { - // swc.Create = func(c context.Context, as vocab.ActivityStreamsCreate) error { + // func (m *myImpl) ApplyFederatingCallbacks(fwc *pub.FederatingWrappedCallbacks) (others []interface{}) { + // fwc.Create = func(c context.Context, as vocab.ActivityStreamsCreate) error { // // Additional application behavior for the Create activity. // } // } @@ -305,7 +318,7 @@ type Application interface { // example, the "Create" behavior's default behavior of creating // ActivityStreams types in the database can be overridden by: // - // func (m *myImpl) ApplySocialCallbacks(swc *pub.SocialWrappedCallbacks) (others []interface{}) { + // func (m *myImpl) ApplyFederatingCallbacks(fwc *pub.FederatingWrappedCallbacks) (others []interface{}) { // others = []interface{}{ // func(c context.Context, as vocab.ActivityStreamsCreate) error { // // New behavior for the Create activity that overrides the @@ -315,44 +328,7 @@ type Application interface { // return // } // - // Only called if C2SEnabled returned true at startup time. - ApplySocialCallbacks(swc *pub.SocialWrappedCallbacks) (others []interface{}) - - // ScopePermitsPostOutbox determines if an OAuth token scope permits the - // bearer to post to an actor's outbox. It is only called if C2S is - // enabled. - ScopePermitsPostOutbox(scope string) (permitted bool, err error) - // ScopePermitsPrivateGetInbox determines if an OAuth token scope - // permits the bearer to view private (non-Public) messages in an - // actor's inbox. It is always called, regardless whether C2S or S2S is - // enabled. - ScopePermitsPrivateGetInbox(scope string) (permitted bool, err error) - // ScopePermitsPrivateGetOutbox determines if an OAuth token scope - // permits the bearer to view private (non-Public) messages in an - // actor's outbox. It is always called, regardless whether C2S or S2S is - // enabled. - ScopePermitsPrivateGetOutbox(scope string) (permitted bool, err error) - - // DefaultUserPreferences returns an application-specific preferences - // struct to be serialized into JSON and used as initial user app - // preferences. - DefaultUserPreferences() interface{} - // DefaultUserPrivileges returns an application-specific privileges - // struct to be serialized into JSON and used as initial user app - // privileges. - DefaultUserPrivileges() interface{} - // DefaultAdminPrivileges returns an application-specific privileges - // struct to be serialized into JSON and used as initial user app - // privileges for new admins. - DefaultAdminPrivileges() interface{} - - // CALLS MADE BOTH AT STARTUP AND SERVING TIME - // - // These calls are made at least once during server initialization, and - // are called when the server is handling requests. - - // Information about this application's software. This will be shown at - // the command line and used for NodeInfo statistics, as well as for - // user agent information. - Software() Software + // Note: The `OnFollow` value will already be populated by the user's + // preferred behavior upon receiving a Follow request. + ApplyFederatingCallbacks(fwc *pub.FederatingWrappedCallbacks) (others []interface{}) } diff --git a/dep_inj.go b/dep_inj.go index afc70dc..8ecca68 100644 --- a/dep_inj.go +++ b/dep_inj.go @@ -156,7 +156,7 @@ func newServer(configFileName string, appl app.Application, debug bool) (s *fram badRequestHandler) // Build framework for auxiliary behaviors - fw := framework.NewFramework(oauth, actor, appl.S2SEnabled()) + fw := framework.NewFramework(oauth, actor, appl) // Build application routes for default web support h, err := framework.BuildHandler(r, diff --git a/example/app.go b/example/app.go index 207c69f..d71303d 100644 --- a/example/app.go +++ b/example/app.go @@ -51,6 +51,8 @@ const ( ) var _ app.Application = &App{} +var _ app.S2SApplication = &App{} +var _ app.C2SApplication = &App{} // App is an example application that minimally implements the // app.Application interface. diff --git a/framework/framework.go b/framework/framework.go index 11050af..e8b1bac 100644 --- a/framework/framework.go +++ b/framework/framework.go @@ -37,11 +37,12 @@ type Framework struct { federationEnabled bool } -func NewFramework(o *oauth2.Server, actor pub.Actor, federationEnabled bool) *Framework { +func NewFramework(o *oauth2.Server, actor pub.Actor, a app.Application) *Framework { + _, isS2S := a.(app.S2SApplication) return &Framework{ o: o, actor: actor, - federationEnabled: federationEnabled, + federationEnabled: isS2S, } } diff --git a/framework/handler.go b/framework/handler.go index 133c0a0..7f5c954 100644 --- a/framework/handler.go +++ b/framework/handler.go @@ -96,12 +96,12 @@ func BuildHandler(r *Router, // - Followers // - Following // - Liked - if a.S2SEnabled() { + if sa, isS2S := a.(app.S2SApplication); isS2S { r.userActorPostInbox() - r.userActorGetInbox(a.GetInboxWebHandlerFunc()) + r.userActorGetInbox(sa.GetInboxWebHandlerFunc()) } r.userActorGetOutbox(a.GetOutboxWebHandlerFunc()) - if a.C2SEnabled() { + if _, isC2S := a.(app.C2SApplication); isC2S { r.userActorPostOutbox() } maybeAddWebFn := func(path string, f func() (http.HandlerFunc, app.AuthorizeFunc)) { @@ -120,9 +120,9 @@ func BuildHandler(r *Router, // Built-in routes for non-user actors for _, k := range paths.AllActors { r.knownActorPostInbox(k) - r.knownActorGetInbox(k, a.GetInboxWebHandlerFunc()) + r.knownActorGetInbox(k, nil) r.knownActorPostOutbox(k) - r.knownActorGetOutbox(k, a.GetOutboxWebHandlerFunc()) + r.knownActorGetOutbox(k, nil) } // POST Login and GET logout routes