Skip to content

Commit

Permalink
Break out Application into C2S and S2S interfaces
Browse files Browse the repository at this point in the history
This should make it clearer that the Application interface must be
implemented, while the C2S and S2S interfaces only need to be
implemented to support those specific ActivityPub protocol(s).

The example application is updated accordingly.
  • Loading branch information
cjslep committed Dec 13, 2020
1 parent f765c0f commit 289217f
Show file tree
Hide file tree
Showing 8 changed files with 97 additions and 116 deletions.
20 changes: 11 additions & 9 deletions ap/actor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions ap/c2s.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions ap/s2s.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down
166 changes: 71 additions & 95 deletions app/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
//
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
// }
// }
Expand All @@ -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
Expand All @@ -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.
// }
// }
Expand All @@ -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
Expand All @@ -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{})
}
2 changes: 1 addition & 1 deletion dep_inj.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions example/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions framework/framework.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}

Expand Down
10 changes: 5 additions & 5 deletions framework/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -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
Expand Down

0 comments on commit 289217f

Please sign in to comment.