From b084d68fb18f4f6c220f4858c98c37658d112b4d Mon Sep 17 00:00:00 2001 From: Gabe Kangas Date: Thu, 10 Feb 2022 11:35:48 -0800 Subject: [PATCH] Clean up and consolidate AP actor resolution logic - Consolidate Person, Service and Application to adhere to a single interface type. - Add additional error checking around actor properties. - Remove redundant IRI->Actor resolution callbacks and use only the ones in the ResolveIRI method. --- activitypub/apmodels/actor.go | 120 ++++++++++++----------------- activitypub/apmodels/actor_test.go | 12 ++- activitypub/resolvers/resolve.go | 102 ++++++++++++------------ 3 files changed, 111 insertions(+), 123 deletions(-) diff --git a/activitypub/apmodels/actor.go b/activitypub/apmodels/actor.go index 5a00aa188..28e894971 100644 --- a/activitypub/apmodels/actor.go +++ b/activitypub/apmodels/actor.go @@ -1,6 +1,7 @@ package apmodels import ( + "errors" "fmt" "net/url" "time" @@ -40,61 +41,53 @@ type DeleteRequest struct { ActorIri string } -// MakeActorFromPerson takes a full ActivityPub Person and returns our internal -// representation of an actor. -func MakeActorFromPerson(person vocab.ActivityStreamsPerson) ActivityPubActor { - apActor := ActivityPubActor{ - ActorIri: person.GetJSONLDId().Get(), - Inbox: person.GetActivityStreamsInbox().GetIRI(), - Name: person.GetActivityStreamsName().Begin().GetXMLSchemaString(), - Username: person.GetActivityStreamsPreferredUsername().GetXMLSchemaString(), - FullUsername: GetFullUsernameFromPerson(person), - W3IDSecurityV1PublicKey: person.GetW3IDSecurityV1PublicKey(), - } - - if person.GetActivityStreamsIcon() != nil && person.GetActivityStreamsIcon().Len() > 0 && person.GetActivityStreamsIcon().At(0).GetActivityStreamsImage() != nil { - apActor.Image = person.GetActivityStreamsIcon().At(0).GetActivityStreamsImage().GetActivityStreamsUrl().Begin().GetIRI() - } - - return apActor +// ExternalEntity represents an ActivityPub Person, Service or Application. +type ExternalEntity interface { + GetJSONLDId() vocab.JSONLDIdProperty + GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty + GetActivityStreamsName() vocab.ActivityStreamsNameProperty + GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty + GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty + GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty } -// MakeActorFromService takes a full ActivityPub Service and returns our internal -// representation of an actor. -func MakeActorFromService(service vocab.ActivityStreamsService) ActivityPubActor { +// MakeActorFromExernalAPEntity takes a full ActivityPub entity and returns our +// internal representation of an actor. +func MakeActorFromExernalAPEntity(entity ExternalEntity) (*ActivityPubActor, error) { + // Username is required (but not a part of the official ActivityPub spec) + if entity.GetActivityStreamsPreferredUsername() == nil || entity.GetActivityStreamsPreferredUsername().GetXMLSchemaString() == "" { + return nil, errors.New("remote activitypub entity does not have a preferred username set, rejecting") + } + username := GetFullUsernameFromExternalEntity(entity) + + // Key is required + if entity.GetW3IDSecurityV1PublicKey() == nil { + return nil, errors.New("remote activitypub entity does not have a public key set, rejecting") + } + + // Name is optional + var name string + if entity.GetActivityStreamsName() != nil && !entity.GetActivityStreamsName().Empty() { + name = entity.GetActivityStreamsName().At(0).GetXMLSchemaString() + } + + // Image is optional + var image *url.URL + if entity.GetActivityStreamsIcon() != nil && !entity.GetActivityStreamsIcon().Empty() && entity.GetActivityStreamsIcon().At(0).GetActivityStreamsImage() != nil { + image = entity.GetActivityStreamsIcon().At(0).GetActivityStreamsImage().GetActivityStreamsUrl().Begin().GetIRI() + } + apActor := ActivityPubActor{ - ActorIri: service.GetJSONLDId().Get(), - Inbox: service.GetActivityStreamsInbox().GetIRI(), - Name: service.GetActivityStreamsName().Begin().GetXMLSchemaString(), - Username: service.GetActivityStreamsPreferredUsername().GetXMLSchemaString(), - FullUsername: GetFullUsernameFromService(service), - W3IDSecurityV1PublicKey: service.GetW3IDSecurityV1PublicKey(), + ActorIri: entity.GetJSONLDId().Get(), + Inbox: entity.GetActivityStreamsInbox().GetIRI(), + Name: name, + Username: entity.GetActivityStreamsPreferredUsername().GetXMLSchemaString(), + FullUsername: username, + W3IDSecurityV1PublicKey: entity.GetW3IDSecurityV1PublicKey(), + Image: image, } - if service.GetActivityStreamsIcon() != nil && service.GetActivityStreamsIcon().Len() > 0 && service.GetActivityStreamsIcon().At(0).GetActivityStreamsImage() != nil { - apActor.Image = service.GetActivityStreamsIcon().At(0).GetActivityStreamsImage().GetActivityStreamsUrl().Begin().GetIRI() - } - - return apActor -} - -// MakeActorFromApplication takes a full ActivityPub application and returns -// our internal representation of an actor. -func MakeActorFromApplication(application vocab.ActivityStreamsApplication) ActivityPubActor { - apActor := ActivityPubActor{ - ActorIri: application.GetJSONLDId().Get(), - Inbox: application.GetActivityStreamsInbox().GetIRI(), - Name: application.GetActivityStreamsName().Begin().GetXMLSchemaString(), - Username: application.GetActivityStreamsPreferredUsername().GetXMLSchemaString(), - FullUsername: GetFullUsernameFromApplication(application), - W3IDSecurityV1PublicKey: application.GetW3IDSecurityV1PublicKey(), - } - - if application.GetActivityStreamsIcon() != nil && application.GetActivityStreamsIcon().Len() > 0 && application.GetActivityStreamsIcon().At(0).GetActivityStreamsImage() != nil { - apActor.Image = application.GetActivityStreamsIcon().At(0).GetActivityStreamsImage().GetActivityStreamsUrl().Begin().GetIRI() - } - - return apActor + return &apActor, nil } // MakeActorPropertyWithID will return an actor property filled with the provided IRI. @@ -239,28 +232,11 @@ func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService { return person } -// GetFullUsernameFromPerson will return the user@host.tld formatted user given a person object. -func GetFullUsernameFromPerson(person vocab.ActivityStreamsPerson) string { - hostname := person.GetJSONLDId().GetIRI().Hostname() - username := person.GetActivityStreamsPreferredUsername().GetXMLSchemaString() - fullUsername := fmt.Sprintf("%s@%s", username, hostname) - - return fullUsername -} - -// GetFullUsernameFromService will return the user@host.tld formatted user given a service object. -func GetFullUsernameFromService(person vocab.ActivityStreamsService) string { - hostname := person.GetJSONLDId().GetIRI().Hostname() - username := person.GetActivityStreamsPreferredUsername().GetXMLSchemaString() - fullUsername := fmt.Sprintf("%s@%s", username, hostname) - - return fullUsername -} - -// GetFullUsernameFromApplication will return the user@host.tld formatted user given a service object. -func GetFullUsernameFromApplication(person vocab.ActivityStreamsApplication) string { - hostname := person.GetJSONLDId().GetIRI().Hostname() - username := person.GetActivityStreamsPreferredUsername().GetXMLSchemaString() +// GetFullUsernameFromExternalEntity will return the full username from an +// internal representation of an ExternalEntity. Returns user@host.tld. +func GetFullUsernameFromExternalEntity(entity ExternalEntity) string { + hostname := entity.GetJSONLDId().GetIRI().Hostname() + username := entity.GetActivityStreamsPreferredUsername().GetXMLSchemaString() fullUsername := fmt.Sprintf("%s@%s", username, hostname) return fullUsername diff --git a/activitypub/apmodels/actor_test.go b/activitypub/apmodels/actor_test.go index fb52f3e62..b0158ac00 100644 --- a/activitypub/apmodels/actor_test.go +++ b/activitypub/apmodels/actor_test.go @@ -44,6 +44,9 @@ func makeFakeService() vocab.ActivityStreamsService { icon.AppendActivityStreamsImage(image) service.SetActivityStreamsIcon(icon) + publicKeyProperty := streams.NewW3IDSecurityV1PublicKeyProperty() + service.SetW3IDSecurityV1PublicKey(publicKeyProperty) + return service } @@ -59,9 +62,12 @@ func TestMain(m *testing.M) { m.Run() } -func TestMakeActorFromService(t *testing.T) { +func TestMakeActorFromExternalAPEntity(t *testing.T) { service := makeFakeService() - actor := MakeActorFromService(service) + actor, err := MakeActorFromExernalAPEntity(service) + if err != nil { + t.Error(err) + } if actor.ActorIri != service.GetJSONLDId().GetIRI() { t.Errorf("actor.ID = %v, want %v", actor.ActorIri, service.GetJSONLDId().GetIRI()) @@ -96,7 +102,7 @@ func TestMakeActorPropertyWithID(t *testing.T) { func TestGetFullUsernameFromPerson(t *testing.T) { expected := "foodawg@fake.fediverse.server" person := makeFakeService() - username := GetFullUsernameFromService(person) + username := GetFullUsernameFromExternalEntity(person) if username != expected { t.Errorf("actor.Username = %v, want %v", username, expected) diff --git a/activitypub/resolvers/resolve.go b/activitypub/resolvers/resolve.go index df7fa4df8..060cb217d 100644 --- a/activitypub/resolvers/resolve.go +++ b/activitypub/resolvers/resolve.go @@ -49,7 +49,7 @@ func Resolve(c context.Context, data []byte, callbacks ...interface{}) error { func ResolveIRI(c context.Context, iri string, callbacks ...interface{}) error { log.Debugln("Resolving", iri) - req, _ := http.NewRequest("GET", iri, nil) + req, _ := http.NewRequest(http.MethodGet, iri, nil) actor := apmodels.MakeLocalIRIForAccount(data.GetDefaultFederationUsername()) if err := crypto.SignRequest(req, nil, actor); err != nil { @@ -72,56 +72,53 @@ func ResolveIRI(c context.Context, iri string, callbacks ...interface{}) error { return Resolve(c, data, callbacks...) } -// GetResolvedActorFromActorProperty resolve an actor property to a fully populated person. +// GetResolvedActorFromActorProperty resolve an external actor property to a +// fully populated internal actor representation. func GetResolvedActorFromActorProperty(actor vocab.ActivityStreamsActorProperty) (apmodels.ActivityPubActor, error) { var err error var apActor apmodels.ActivityPubActor resolved := false - personCallback := func(c context.Context, person vocab.ActivityStreamsPerson) error { - apActor = apmodels.MakeActorFromPerson(person) - return nil - } + if !actor.Empty() && actor.Len() > 0 && actor.At(0) != nil { + // Explicitly use only the first actor that might be listed. + actorObjectOrIRI := actor.At(0) + var actorEntity apmodels.ExternalEntity - serviceCallback := func(c context.Context, s vocab.ActivityStreamsService) error { - apActor = apmodels.MakeActorFromService(s) - return nil - } + // If the actor is an unresolved IRI then we need to resolve it. + if actorObjectOrIRI.IsIRI() { + iri := actorObjectOrIRI.GetIRI().String() + return GetResolvedActorFromIRI(iri) + } - applicationCallback := func(c context.Context, s vocab.ActivityStreamsApplication) error { - apActor = apmodels.MakeActorFromApplication(s) - resolved = true - return nil - } + if actorObjectOrIRI.IsActivityStreamsPerson() { + actorEntity = actorObjectOrIRI.GetActivityStreamsPerson() + } else if actorObjectOrIRI.IsActivityStreamsService() { + actorEntity = actorObjectOrIRI.GetActivityStreamsService() + } else if actorObjectOrIRI.IsActivityStreamsApplication() { + actorEntity = actorObjectOrIRI.GetActivityStreamsApplication() + } else { + err = errors.New("unrecognized external ActivityPub type: " + actorObjectOrIRI.Name()) + return apActor, err + } - for iter := actor.Begin(); iter != actor.End(); iter = iter.Next() { - if iter.IsIRI() { - iri := iter.GetIRI() - if e := ResolveIRI(context.Background(), iri.String(), personCallback, serviceCallback, applicationCallback); e != nil { - err = e - } - } else if iter.IsActivityStreamsPerson() { - person := iter.GetActivityStreamsPerson() - apActor = apmodels.MakeActorFromPerson(person) - resolved = true - } else if iter.IsActivityStreamsService() { - person := iter.GetActivityStreamsService() - apActor = apmodels.MakeActorFromService(person) - resolved = true - } else if iter.IsActivityStreamsApplication() { - person := iter.GetActivityStreamsApplication() - apActor = apmodels.MakeActorFromApplication(person) + // If any of the resolution or population failed then return the error. + if err != nil { + return apActor, err + } + + // Convert the external AP entity into an internal actor representation. + apa, e := apmodels.MakeActorFromExernalAPEntity(actorEntity) + if apa != nil { + apActor = *apa resolved = true } + err = e } - if err != nil { - err = errors.Wrap(err, "error resolving actor from property value") + if !resolved && err == nil { + err = errors.New("unknown error resolving actor from property value") } - if !resolved { - err = errors.New("error resolving actor from property value") - } return apActor, err } @@ -131,21 +128,30 @@ func GetResolvedActorFromIRI(personOrServiceIRI string) (apmodels.ActivityPubAct var apActor apmodels.ActivityPubActor resolved := false personCallback := func(c context.Context, person vocab.ActivityStreamsPerson) error { - apActor = apmodels.MakeActorFromPerson(person) - resolved = true - return nil + apa, e := apmodels.MakeActorFromExernalAPEntity(person) + if apa != nil { + apActor = *apa + resolved = true + } + return e } - serviceCallback := func(c context.Context, s vocab.ActivityStreamsService) error { - apActor = apmodels.MakeActorFromService(s) - resolved = true - return nil + serviceCallback := func(c context.Context, service vocab.ActivityStreamsService) error { + apa, e := apmodels.MakeActorFromExernalAPEntity(service) + if apa != nil { + apActor = *apa + resolved = true + } + return e } - applicationCallback := func(c context.Context, s vocab.ActivityStreamsApplication) error { - apActor = apmodels.MakeActorFromApplication(s) - resolved = true - return nil + applicationCallback := func(c context.Context, app vocab.ActivityStreamsApplication) error { + apa, e := apmodels.MakeActorFromExernalAPEntity(app) + if apa != nil { + apActor = *apa + resolved = true + } + return e } if e := ResolveIRI(context.Background(), personOrServiceIRI, personCallback, serviceCallback, applicationCallback); e != nil {