1
0
Fork 0
mirror of https://github.com/owncast/owncast.git synced 2024-10-28 10:09:39 +01:00

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.
This commit is contained in:
Gabe Kangas 2022-02-10 11:35:48 -08:00
parent 0ca0620aef
commit b084d68fb1
No known key found for this signature in database
GPG key ID: 9A56337728BC81EA
3 changed files with 111 additions and 123 deletions

View file

@ -1,6 +1,7 @@
package apmodels package apmodels
import ( import (
"errors"
"fmt" "fmt"
"net/url" "net/url"
"time" "time"
@ -40,61 +41,53 @@ type DeleteRequest struct {
ActorIri string ActorIri string
} }
// MakeActorFromPerson takes a full ActivityPub Person and returns our internal // ExternalEntity represents an ActivityPub Person, Service or Application.
// representation of an actor. type ExternalEntity interface {
func MakeActorFromPerson(person vocab.ActivityStreamsPerson) ActivityPubActor { GetJSONLDId() vocab.JSONLDIdProperty
apActor := ActivityPubActor{ GetActivityStreamsInbox() vocab.ActivityStreamsInboxProperty
ActorIri: person.GetJSONLDId().Get(), GetActivityStreamsName() vocab.ActivityStreamsNameProperty
Inbox: person.GetActivityStreamsInbox().GetIRI(), GetActivityStreamsPreferredUsername() vocab.ActivityStreamsPreferredUsernameProperty
Name: person.GetActivityStreamsName().Begin().GetXMLSchemaString(), GetActivityStreamsIcon() vocab.ActivityStreamsIconProperty
Username: person.GetActivityStreamsPreferredUsername().GetXMLSchemaString(), GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty
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
} }
// MakeActorFromService takes a full ActivityPub Service and returns our internal // MakeActorFromExernalAPEntity takes a full ActivityPub entity and returns our
// representation of an actor. // internal representation of an actor.
func MakeActorFromService(service vocab.ActivityStreamsService) ActivityPubActor { 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{ apActor := ActivityPubActor{
ActorIri: service.GetJSONLDId().Get(), ActorIri: entity.GetJSONLDId().Get(),
Inbox: service.GetActivityStreamsInbox().GetIRI(), Inbox: entity.GetActivityStreamsInbox().GetIRI(),
Name: service.GetActivityStreamsName().Begin().GetXMLSchemaString(), Name: name,
Username: service.GetActivityStreamsPreferredUsername().GetXMLSchemaString(), Username: entity.GetActivityStreamsPreferredUsername().GetXMLSchemaString(),
FullUsername: GetFullUsernameFromService(service), FullUsername: username,
W3IDSecurityV1PublicKey: service.GetW3IDSecurityV1PublicKey(), W3IDSecurityV1PublicKey: entity.GetW3IDSecurityV1PublicKey(),
Image: image,
} }
if service.GetActivityStreamsIcon() != nil && service.GetActivityStreamsIcon().Len() > 0 && service.GetActivityStreamsIcon().At(0).GetActivityStreamsImage() != nil { return &apActor, 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
} }
// MakeActorPropertyWithID will return an actor property filled with the provided IRI. // MakeActorPropertyWithID will return an actor property filled with the provided IRI.
@ -239,28 +232,11 @@ func MakeServiceForAccount(accountName string) vocab.ActivityStreamsService {
return person return person
} }
// GetFullUsernameFromPerson will return the user@host.tld formatted user given a person object. // GetFullUsernameFromExternalEntity will return the full username from an
func GetFullUsernameFromPerson(person vocab.ActivityStreamsPerson) string { // internal representation of an ExternalEntity. Returns user@host.tld.
hostname := person.GetJSONLDId().GetIRI().Hostname() func GetFullUsernameFromExternalEntity(entity ExternalEntity) string {
username := person.GetActivityStreamsPreferredUsername().GetXMLSchemaString() hostname := entity.GetJSONLDId().GetIRI().Hostname()
fullUsername := fmt.Sprintf("%s@%s", username, hostname) username := entity.GetActivityStreamsPreferredUsername().GetXMLSchemaString()
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()
fullUsername := fmt.Sprintf("%s@%s", username, hostname) fullUsername := fmt.Sprintf("%s@%s", username, hostname)
return fullUsername return fullUsername

View file

@ -44,6 +44,9 @@ func makeFakeService() vocab.ActivityStreamsService {
icon.AppendActivityStreamsImage(image) icon.AppendActivityStreamsImage(image)
service.SetActivityStreamsIcon(icon) service.SetActivityStreamsIcon(icon)
publicKeyProperty := streams.NewW3IDSecurityV1PublicKeyProperty()
service.SetW3IDSecurityV1PublicKey(publicKeyProperty)
return service return service
} }
@ -59,9 +62,12 @@ func TestMain(m *testing.M) {
m.Run() m.Run()
} }
func TestMakeActorFromService(t *testing.T) { func TestMakeActorFromExternalAPEntity(t *testing.T) {
service := makeFakeService() service := makeFakeService()
actor := MakeActorFromService(service) actor, err := MakeActorFromExernalAPEntity(service)
if err != nil {
t.Error(err)
}
if actor.ActorIri != service.GetJSONLDId().GetIRI() { if actor.ActorIri != service.GetJSONLDId().GetIRI() {
t.Errorf("actor.ID = %v, want %v", 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) { func TestGetFullUsernameFromPerson(t *testing.T) {
expected := "foodawg@fake.fediverse.server" expected := "foodawg@fake.fediverse.server"
person := makeFakeService() person := makeFakeService()
username := GetFullUsernameFromService(person) username := GetFullUsernameFromExternalEntity(person)
if username != expected { if username != expected {
t.Errorf("actor.Username = %v, want %v", username, expected) t.Errorf("actor.Username = %v, want %v", username, expected)

View file

@ -49,7 +49,7 @@ func Resolve(c context.Context, data []byte, callbacks ...interface{}) error {
func ResolveIRI(c context.Context, iri string, callbacks ...interface{}) error { func ResolveIRI(c context.Context, iri string, callbacks ...interface{}) error {
log.Debugln("Resolving", iri) log.Debugln("Resolving", iri)
req, _ := http.NewRequest("GET", iri, nil) req, _ := http.NewRequest(http.MethodGet, iri, nil)
actor := apmodels.MakeLocalIRIForAccount(data.GetDefaultFederationUsername()) actor := apmodels.MakeLocalIRIForAccount(data.GetDefaultFederationUsername())
if err := crypto.SignRequest(req, nil, actor); err != nil { 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...) 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) { func GetResolvedActorFromActorProperty(actor vocab.ActivityStreamsActorProperty) (apmodels.ActivityPubActor, error) {
var err error var err error
var apActor apmodels.ActivityPubActor var apActor apmodels.ActivityPubActor
resolved := false resolved := false
personCallback := func(c context.Context, person vocab.ActivityStreamsPerson) error { if !actor.Empty() && actor.Len() > 0 && actor.At(0) != nil {
apActor = apmodels.MakeActorFromPerson(person) // Explicitly use only the first actor that might be listed.
return nil actorObjectOrIRI := actor.At(0)
} var actorEntity apmodels.ExternalEntity
serviceCallback := func(c context.Context, s vocab.ActivityStreamsService) error { // If the actor is an unresolved IRI then we need to resolve it.
apActor = apmodels.MakeActorFromService(s) if actorObjectOrIRI.IsIRI() {
return nil iri := actorObjectOrIRI.GetIRI().String()
} return GetResolvedActorFromIRI(iri)
}
applicationCallback := func(c context.Context, s vocab.ActivityStreamsApplication) error { if actorObjectOrIRI.IsActivityStreamsPerson() {
apActor = apmodels.MakeActorFromApplication(s) actorEntity = actorObjectOrIRI.GetActivityStreamsPerson()
resolved = true } else if actorObjectOrIRI.IsActivityStreamsService() {
return nil 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 any of the resolution or population failed then return the error.
if iter.IsIRI() { if err != nil {
iri := iter.GetIRI() return apActor, err
if e := ResolveIRI(context.Background(), iri.String(), personCallback, serviceCallback, applicationCallback); e != nil { }
err = e
} // Convert the external AP entity into an internal actor representation.
} else if iter.IsActivityStreamsPerson() { apa, e := apmodels.MakeActorFromExernalAPEntity(actorEntity)
person := iter.GetActivityStreamsPerson() if apa != nil {
apActor = apmodels.MakeActorFromPerson(person) apActor = *apa
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)
resolved = true resolved = true
} }
err = e
} }
if err != nil { if !resolved && err == nil {
err = errors.Wrap(err, "error resolving actor from property value") err = errors.New("unknown error resolving actor from property value")
} }
if !resolved {
err = errors.New("error resolving actor from property value")
}
return apActor, err return apActor, err
} }
@ -131,21 +128,30 @@ func GetResolvedActorFromIRI(personOrServiceIRI string) (apmodels.ActivityPubAct
var apActor apmodels.ActivityPubActor var apActor apmodels.ActivityPubActor
resolved := false resolved := false
personCallback := func(c context.Context, person vocab.ActivityStreamsPerson) error { personCallback := func(c context.Context, person vocab.ActivityStreamsPerson) error {
apActor = apmodels.MakeActorFromPerson(person) apa, e := apmodels.MakeActorFromExernalAPEntity(person)
resolved = true if apa != nil {
return nil apActor = *apa
resolved = true
}
return e
} }
serviceCallback := func(c context.Context, s vocab.ActivityStreamsService) error { serviceCallback := func(c context.Context, service vocab.ActivityStreamsService) error {
apActor = apmodels.MakeActorFromService(s) apa, e := apmodels.MakeActorFromExernalAPEntity(service)
resolved = true if apa != nil {
return nil apActor = *apa
resolved = true
}
return e
} }
applicationCallback := func(c context.Context, s vocab.ActivityStreamsApplication) error { applicationCallback := func(c context.Context, app vocab.ActivityStreamsApplication) error {
apActor = apmodels.MakeActorFromApplication(s) apa, e := apmodels.MakeActorFromExernalAPEntity(app)
resolved = true if apa != nil {
return nil apActor = *apa
resolved = true
}
return e
} }
if e := ResolveIRI(context.Background(), personOrServiceIRI, personCallback, serviceCallback, applicationCallback); e != nil { if e := ResolveIRI(context.Background(), personOrServiceIRI, personCallback, serviceCallback, applicationCallback); e != nil {