From 4368380651766feb481c994363d1c631762fbe38 Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Tue, 20 Feb 2018 22:50:13 +0100 Subject: [PATCH] Implement Accept in response to a Follow --- pub/fed.go | 59 +++++++++++++++++++++++++++++++++++++++++---- pub/interfaces.go | 61 ++++++++++++++++++++++++++++------------------- pub/internal.go | 4 ++++ 3 files changed, 95 insertions(+), 29 deletions(-) diff --git a/pub/fed.go b/pub/fed.go index 05c7fef..864cf2f 100644 --- a/pub/fed.go +++ b/pub/fed.go @@ -23,6 +23,10 @@ var ( // TODO: Helper http Handler for serving Tombstone objects // TODO: Helper http Handler for serving deleted objects +// TODO: Helper http Handler for serving actor's likes +// TODO: Helper http Handler for serving actor's followers +// TODO: Helper http Handler for serving actor's following + // TODO: Authorization client-to-server. // TODO: Authenticate server-to-server deliveries. @@ -169,7 +173,7 @@ func (f *federator) GetInbox(c context.Context, w http.ResponseWriter, r *http.R return true, nil } -// PostOutpox provides a HTTP handler for ActivityPub requests for the given id +// PostOutbox provides a HTTP handler for ActivityPub requests for the given id // token. The client ID token is passed forwards to other interfaces for // application specific behavior. The handler will return true if it handled // the request as an ActivityPub request. If it returns an error, it is up to @@ -220,7 +224,7 @@ func (f *federator) PostOutbox(c context.Context, w http.ResponseWriter, r *http if m, err = typer.Serialize(); err != nil { return true, err } - if err := f.addToOutbox(c, m); err != nil { + if err := f.addToOutbox(c, r, m); err != nil { return true, err } deliverable := false @@ -268,8 +272,8 @@ func (f *federator) GetOutbox(c context.Context, w http.ResponseWriter, r *http. return true, nil } -func (f *federator) addToOutbox(c context.Context, m map[string]interface{}) error { - outbox, err := f.SocialApp.GetOutbox(c) +func (f *federator) addToOutbox(c context.Context, r *http.Request, m map[string]interface{}) error { + outbox, err := f.App.GetOutbox(c, r) if err != nil { return err } @@ -778,6 +782,9 @@ func (f *federator) handleFollow(c context.Context) func(s *streams.Follow) erro if err := f.deliver(activity); err != nil { return err } + if todo == AutomaticAccept { + // TODO: Add to followers collection. + } } return f.ServerCallbacker.Follow(c, s) } @@ -785,10 +792,52 @@ func (f *federator) handleFollow(c context.Context) func(s *streams.Follow) erro func (f *federator) handleAccept(c context.Context) func(s *streams.Accept) error { return func(s *streams.Accept) error { - // TODO: Implement. // Accept can be client application specific. However, if this 'Accept' // is in response to a 'Follow' then the 'actor' should be added to the // original 'actor's 'following' collection by the client application. + raw := s.Raw() + for i := 0; i < raw.ObjectLen(); i++ { + if raw.IsObject(i) { + obj := raw.GetObject(i) + follow, ok := obj.(vocab.FollowType) + if !ok { + continue + } + for j := 0; j < follow.ActorLen(); j++ { + var iri url.URL + if follow.IsActorObject(j) { + actor := follow.GetActorObject(j) + if !actor.HasId() { + return fmt.Errorf("actor object on follow must have id") + } + iri = actor.GetId() + } else if follow.IsActorLink(j) { + l := follow.GetActorLink(j) + if !l.HasHref() { + return fmt.Errorf("actor link on follow must have href") + } + iri = l.GetHref() + } else if follow.IsActorIRI(j) { + iri = follow.GetActorIRI(j) + } + following, err := f.FederateApp.GetFollowing(c, iri) + if err != nil { + return err + } + // TODO: Deduplication detection. + for k := 0; k < raw.ActorLen(); k++ { + if raw.IsActorObject(k) { + following.AddItemsObject(raw.GetActorObject(k)) + } else if raw.IsActorLink(k) { + following.AddItemsLink(raw.GetActorLink(k)) + } else if raw.IsActorIRI(k) { + following.AddItemsIRI(raw.GetActorIRI(k)) + } + } + f.App.Set(c, following) + } + } + } return f.ServerCallbacker.Accept(c, s) } } diff --git a/pub/interfaces.go b/pub/interfaces.go index 035461b..d56b43b 100644 --- a/pub/interfaces.go +++ b/pub/interfaces.go @@ -37,12 +37,12 @@ type Application interface { // Set should write or overwrite the value of the provided object for // its 'id'. Set(c context.Context, o PubObject) error - // GetInbox returns the OrderedCollection inbox of the actor with the - // provided ID. It is up to the implementation to provide the correct + // 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. GetInbox(c context.Context, r *http.Request) (vocab.OrderedCollectionType, error) - // GetOutbox returns the OrderedCollection inbox of the actor with the - // provided ID. It is up to the implementation to provide the correct + // 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. GetOutbox(c context.Context, r *http.Request) (vocab.OrderedCollectionType, error) // PostOutboxAuthorized determines whether the request is able to post @@ -68,8 +68,27 @@ type SocialApp interface { // CanRemove returns true if the provided object is allowed to be // removed from the given target collection. CanRemove(c context.Context, o vocab.ObjectType, t vocab.ObjectType) bool - // GetOutbox gets the outbox of an actor. - GetOutbox(c context.Context) (vocab.OrderedCollectionType, error) +} + +// FederateApp is provided by users of this library and designed to handle +// receiving messages from ActivityPub servers through the Federative API. +type FederateApp interface { + // OnFollow determines whether to take any automatic reactions in + // response to this follow. + OnFollow(c context.Context, s *streams.Follow) FollowResponse + // Unblocked should return an error if the provided actor ids are not + // able to interact with this particular end user due to being blocked + // or other application-specific logic. This error is passed + // transparently back to the request thread via PostInbox. + // + // If nil error is returned, then the received activity is processed as + // a normal unblocked interaction. + Unblocked(c context.Context, actorIRIs []url.URL) error + // GetFollowing returns the 'following' collection for the given actor + // IRI. It must be a CollectionType; this library does not support an + // OrderedCollectionType, as then it would have to be in reverse + // chronological order. + GetFollowing(c context.Context, actor url.URL) (vocab.CollectionType, error) } // FollowResponse instructs how to proceed upon immediately receiving a request @@ -113,33 +132,27 @@ type Callbacker interface { // option of automatically replying with an 'Accept', 'Reject', or // waiting for human interaction as provided in the FederateApp // interface. + // + // In the special case that the FederateApp returned AutomaticAccept, + // this library automatically handles adding the 'actor' to the + // 'followers' collection of the 'object'. Follow(c context.Context, s *streams.Follow) error // Undo Activity callback. It is up to the client to provide support // for all 'Undo' operations; this implementation does not attempt to // provide a generic implementation. Undo(c context.Context, s *streams.Undo) error - // Accept Activity callback. + // Accept Activity callback. In the special case that this 'Accept' + // activity has an 'object' of 'Follow' type, then the library will + // handle adding the 'actor' to the 'following' collection of the + // original 'actor' who requested the 'Follow'. Accept(c context.Context, s *streams.Accept) error - // Reject Activity callback. + // Reject Activity callback. Note that in the special case that this + // 'Reject' activity has an 'object' of 'Follow' type, then the client + // MUST NOT add the 'actor' to the 'following' collection of the + // original 'actor' who requested the 'Follow'. Reject(c context.Context, s *streams.Reject) error } -// FederateApp is provided by users of this library and designed to handle -// receiving messages from ActivityPub servers through the Federative API. -type FederateApp interface { - // OnFollow determines whether to take any automatic reactions in - // response to this follow. - OnFollow(c context.Context, s *streams.Follow) FollowResponse - // Unblocked should return an error if the provided actor ids are not - // able to interact with this particular end user due to being blocked - // or other application-specific logic. This error is passed - // transparently back to the request thread via PostInbox. - // - // If nil error is returned, then the received activity is processed as - // a normal unblocked interaction. - Unblocked(c context.Context, actorIRIs []url.URL) error -} - // PubObject is an ActivityPub Object. type PubObject interface { GetId() url.URL diff --git a/pub/internal.go b/pub/internal.go index b98440d..4acf2c3 100644 --- a/pub/internal.go +++ b/pub/internal.go @@ -906,8 +906,10 @@ func (f *federator) addToAllActorLikedCollection(ctx context.Context, c vocab.Li } } } else if actor.IsLikedCollection() { + // TODO: Fetch collection via f.App.Get lc = actor.GetLikedCollection() } else if actor.IsLikedOrderedCollection() { + // TODO: Fetch collection via f.App.Get loc = actor.GetLikedOrderedCollection() } for i := 0; i < c.ObjectLen(); i++ { @@ -969,8 +971,10 @@ func (f *federator) addToAllLikesCollections(ctx context.Context, c vocab.LikeTy } } } else if object.IsLikesCollection() { + // TODO: Fetch collection via f.App.Get lc = object.GetLikesCollection() } else if object.IsLikesOrderedCollection() { + // TODO: Fetch collection via f.App.Get loc = object.GetLikesOrderedCollection() } for i := 0; i < c.ActorLen(); i++ {