tavern/fed/actor.go

205 lines
4.4 KiB
Go

package fed
import (
"context"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
"go.uber.org/zap"
"github.com/ngerakines/tavern/common"
"github.com/ngerakines/tavern/storage"
)
type ActorClient struct {
HTTPClient common.HTTPClient
Logger *zap.Logger
}
func (client ActorClient) Get(location string) (string, storage.Payload, error) {
client.Logger.Debug("Sending actor request", zap.String("url", location))
request, err := http.NewRequest("GET", location, nil)
if err != nil {
return "", nil, err
}
request.Header.Add("Accept", "application/ld+json")
resp, err := client.HTTPClient.Do(request)
if err != nil {
return "", nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1*1024*1024))
if err != nil {
return "", nil, err
}
p, err := storage.PayloadFromBytes(body)
if err != nil {
return "", nil, err
}
// return storage.PayloadFromReader(io.LimitReader(resp.Body, 1*1024*1024))
return string(body), p, nil
}
func GetOrFetchActor(ctx context.Context, store storage.Storage, logger *zap.Logger, httpClient common.HTTPClient, hint string) (storage.Actor, error) {
var actorURL string
if strings.HasPrefix(hint, "https://") {
actorURL = hint
}
count, err := store.RowCount(ctx, `SELECT COUNT(*) FROM actors WHERE aliases @> ARRAY[$1]::varchar[]`, hint)
if err != nil {
return nil, err
}
if count > 0 {
return store.GetActorByAlias(ctx, hint)
}
if len(actorURL) == 0 {
wfc := WebFingerClient{
HTTPClient: httpClient,
Logger: logger,
}
wfp, err := wfc.Fetch(hint)
if err != nil {
return nil, err
}
actorURL, err = ActorIDFromWebFingerPayload(wfp)
if err != nil {
return nil, err
}
logger.Debug("parsed actor id from webfinger payload", zap.String("actor", actorURL))
}
ac := ActorClient{
HTTPClient: httpClient,
Logger: logger,
}
actorBody, actorPayload, err := ac.Get(actorURL)
if err != nil {
return nil, err
}
keyID, keyPEM, err := storage.KeyFromActor(actorPayload)
actorRowID := storage.NewV4()
keyRowID := storage.NewV4()
actorID, ok := storage.JSONString(actorPayload, "id")
if !ok {
return nil, fmt.Errorf("no id found for actor")
}
name, ok := storage.JSONString(actorPayload, "preferredUsername")
if !ok {
return nil, fmt.Errorf("no preferredUsername found for actor")
}
u, err := url.Parse(actorID)
if err != nil {
return nil, err
}
domain := u.Hostname()
err = store.CreateActor(ctx, actorRowID, keyRowID, actorID, actorBody, keyID, keyPEM, name, domain)
if err != nil {
return nil, err
}
for _, a := range []string{actorID, actorURL, hint} {
if err = store.RecordActorAlias(ctx, actorID, a); err != nil {
return nil, err
}
}
if endpoints, ok := storage.JSONMap(actorPayload, "endpoints"); ok {
sharedInbox, ok := storage.JSONString(endpoints, "sharedInbox")
if ok {
peerID := storage.NewV4()
err = store.CreatePeer(ctx, peerID, sharedInbox)
if err != nil {
return nil, err
}
}
}
return store.GetActor(ctx, actorID)
}
func ActorsFromActivity(activity storage.Payload) []string {
var actors []string
actor, ok := storage.JSONString(activity, "actor")
if ok {
actors = append(actors, actor)
}
obj, ok := storage.JSONMap(activity, "object")
if !ok {
obj = activity
}
if ok {
attributedTo, ok := storage.JSONString(obj, "attributedTo")
if ok {
actors = append(actors, attributedTo)
}
tag, ok := storage.JSONMapList(obj, "tag")
if ok {
for _, i := range tag {
tagType, hasTagType := storage.JSONString(i, "type")
href, hasHref := storage.JSONString(i, "href")
if hasTagType && hasHref && tagType == "Mention" {
actors = append(actors, href)
}
}
}
}
return actors
}
func ActorsFromObject(obj storage.Payload) []string {
var actors []string
actor, ok := storage.JSONString(obj, "actor")
if ok {
actors = append(actors, actor)
}
attributedTo, ok := storage.JSONString(obj, "attributedTo")
if ok {
actors = append(actors, attributedTo)
}
if ok {
tag, ok := storage.JSONMapList(obj, "tag")
if ok {
for _, i := range tag {
tagType, hasTagType := storage.JSONString(i, "type")
href, hasHref := storage.JSONString(i, "href")
if hasTagType && hasHref && tagType == "Mention" {
actors = append(actors, href)
}
}
}
}
return actors
}