diff --git a/pub/fed.go b/pub/fed.go index ac74f9f..9a9bf4b 100644 --- a/pub/fed.go +++ b/pub/fed.go @@ -944,18 +944,136 @@ func (f *federator) handleReject(c context.Context) func(s *streams.Reject) erro func (f *federator) handleAdd(c context.Context) func(s *streams.Add) error { return func(s *streams.Add) error { - // TODO: Implement. // Add is client application specific, generally involving adding an // 'object' to a specific 'target' collection. + if s.LenObject() == 0 { + return ErrObjectRequired + } else if s.LenType() == 0 { + return ErrTypeRequired + } + raw := s.Raw() + ids, err := getTargetIds(raw) + if err != nil { + return err + } else if len(ids) == 0 { + return fmt.Errorf("add target has no ids: %v", s) + } + objIds, err := getObjectIds(s.Raw()) + if err != nil { + return err + } else if len(objIds) == 0 { + return fmt.Errorf("add object has no ids: %v", s) + } + var targets []vocab.ObjectType + for _, id := range ids { + if !f.FederateApp.Owns(c, id) { + continue + } + target, err := f.App.Get(c, id) + if err != nil { + return err + } + ct, okCollection := target.(vocab.CollectionType) + oct, okOrdered := target.(vocab.OrderedCollectionType) + if !okCollection && !okOrdered { + return fmt.Errorf("cannot add to type that is not Collection and not OrderedCollection: %v", target) + } else if okCollection { + targets = append(targets, ct) + } else { + targets = append(targets, oct) + } + } + for i := 0; i < raw.ObjectLen(); i++ { + if !raw.IsObject(i) { + // TODO: Fetch IRIs as well + return fmt.Errorf("add object must be object type: %v", raw) + } + obj := raw.GetObject(i) + if !f.FederateApp.Owns(c, obj.GetId()) { + continue + } + for _, target := range targets { + if !f.FederateApp.CanAdd(c, obj, target) { + continue + } + if ct, ok := target.(vocab.CollectionType); ok { + ct.AddItemsObject(obj) + } else if oct, ok := target.(vocab.OrderedCollectionType); ok { + oct.AddOrderedItemsObject(obj) + } + if err := f.App.Set(c, target); err != nil { + return err + } + } + } return f.ServerCallbacker.Add(c, s) } } func (f *federator) handleRemove(c context.Context) func(s *streams.Remove) error { return func(s *streams.Remove) error { - // TODO: Implement. // Remove is client application specific, generally involving removing // an 'object' from a specific 'target' collection. + if s.LenObject() == 0 { + return ErrObjectRequired + } else if s.LenType() == 0 { + return ErrTypeRequired + } + raw := s.Raw() + ids, err := getTargetIds(raw) + if err != nil { + return err + } else if len(ids) == 0 { + return fmt.Errorf("remove target has no ids: %v", s) + } + objIds, err := getObjectIds(s.Raw()) + if err != nil { + return err + } else if len(objIds) == 0 { + return fmt.Errorf("remove object has no ids: %v", s) + } + var targets []vocab.ObjectType + for _, id := range ids { + if !f.FederateApp.Owns(c, id) { + continue + } + target, err := f.App.Get(c, id) + if err != nil { + return err + } + ct, okCollection := target.(vocab.CollectionType) + oct, okOrdered := target.(vocab.OrderedCollectionType) + if !okCollection && !okOrdered { + return fmt.Errorf("cannot remove from type that is not Collection and not OrderedCollection: %v", target) + } else if okCollection { + targets = append(targets, ct) + } else { + targets = append(targets, oct) + } + } + for i := 0; i < raw.ObjectLen(); i++ { + if !raw.IsObject(i) { + // TODO: Fetch IRIs as well + return fmt.Errorf("remove object must be object type: %v", raw) + } + obj := raw.GetObject(i) + if !f.FederateApp.Owns(c, obj.GetId()) { + continue + } + for _, target := range targets { + if !f.FederateApp.CanRemove(c, obj, target) { + continue + } + if ct, ok := target.(vocab.CollectionType); ok { + removeCollectionItemWithId(ct, obj.GetId()) + } else if oct, ok := target.(vocab.OrderedCollectionType); ok { + removeOrderedCollectionItemWithId(oct, obj.GetId()) + } + if err := f.App.Set(c, target); err != nil { + return err + } + } + } return f.ServerCallbacker.Remove(c, s) } } diff --git a/pub/interfaces.go b/pub/interfaces.go index 2deddb3..3e573b1 100644 --- a/pub/interfaces.go +++ b/pub/interfaces.go @@ -73,6 +73,14 @@ type SocialApp interface { // FederateApp is provided by users of this library and designed to handle // receiving messages from ActivityPub servers through the Federative API. type FederateApp interface { + // Owns returns true if the provided id is owned by this server. + Owns(c context.Context, id url.URL) bool + // CanAdd returns true if the provided object is allowed to be added to + // the given target collection. + CanAdd(c context.Context, obj vocab.ObjectType, target vocab.ObjectType) bool + // CanRemove returns true if the provided object is allowed to be added to + // the given target collection. + CanRemove(c context.Context, obj vocab.ObjectType, target vocab.ObjectType) bool // OnFollow determines whether to take any automatic reactions in // response to this follow. OnFollow(c context.Context, s *streams.Follow) FollowResponse @@ -155,11 +163,10 @@ type Callbacker interface { // PubObject is an ActivityPub Object. type PubObject interface { + Typer GetId() url.URL SetId(url.URL) HasId() bool - TypeLen() int - GetType(int) interface{} AddType(interface{}) RemoveType(int) }