ActivityPub/entities/collection/collectionController.go
2020-11-27 05:46:17 +01:00

332 lines
9.1 KiB
Go

package collection
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log"
"math/rand"
"net/http"
"strings"
"time"
"git.nutfactory.org/hoernschen/ActivityPub/config"
"git.nutfactory.org/hoernschen/ActivityPub/entities/activity"
"git.nutfactory.org/hoernschen/ActivityPub/entities/actor"
"git.nutfactory.org/hoernschen/ActivityPub/entities/object"
"git.nutfactory.org/hoernschen/ActivityPub/entities/user"
"git.nutfactory.org/hoernschen/ActivityPub/utils"
"github.com/cenkalti/backoff/v4"
"github.com/gorilla/mux"
)
func PostInboxHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", utils.GetContentTypeString())
newActivity := &activity.Activity{}
err := utils.CheckRequest(r)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(err); err != nil {
panic(err)
}
return
}
token, err := utils.GetAccessToken(r)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(err); err != nil {
panic(err)
}
return
}
if token == "" {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode("Missing Token"); err != nil {
panic(err)
}
return
}
vars := mux.Vars(r)
actorName := vars["actorName"]
if actorName == "" {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode("Missing Actor"); err != nil {
panic(err)
}
return
}
decoder := json.NewDecoder(r.Body)
err = decoder.Decode(&newActivity)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Could not parse JSON: %s", err)); err != nil {
panic(err)
}
return
}
if newActivity.Type == "Follow" {
err = CreateCollectionObject(utils.GenerateFollowersUrl(actorName), newActivity.Actor)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s", err)); err != nil {
panic(err)
}
return
}
log.Printf("%s follows %s", newActivity.Actor, newActivity.To)
} else if newActivity.Type == "Create" {
foundObject, err := object.ReadObject(newActivity.Object.Id)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Reading Object: %s", err)); err != nil {
panic(err)
}
return
}
if foundObject == nil {
_ = object.CreateObject(newActivity.Object)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Object: %s", err)); err != nil {
panic(err)
}
return
}
}
_ = CreateCollectionObject(utils.GenerateInboxUrl(actorName), newActivity.Object.Id)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s", err)); err != nil {
panic(err)
}
return
}
log.Printf("%s to %s: %s", newActivity.Actor, newActivity.To, newActivity.Object.Content)
} else {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode("Unsupported Activity Type"); err != nil {
panic(err)
}
return
}
w.WriteHeader(http.StatusOK)
}
func PostOutboxHandler(w http.ResponseWriter, r *http.Request) {
packetLossNumber := rand.Intn(100)
if packetLossNumber > config.Packetloss {
w.Header().Set("Content-Type", utils.GetContentTypeString())
newActivity := &activity.Activity{}
err := utils.CheckRequest(r)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(err); err != nil {
panic(err)
}
return
}
token, err := utils.GetAccessToken(r)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(err); err != nil {
panic(err)
}
return
}
foundUser, err := user.ReadUserFromToken(token)
if err != nil || foundUser == nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Unknown Token: %s", err)); err != nil {
panic(err)
}
return
}
buf := new(bytes.Buffer)
buf.ReadFrom(r.Body)
err = json.Unmarshal(buf.Bytes(), newActivity)
if err != nil || newActivity.Type == "Note" {
postedObject := &object.Object{}
err = json.Unmarshal(buf.Bytes(), postedObject)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Could not parse JSON: %s")); err != nil {
panic(err)
}
return
}
var newObject *object.Object
if postedObject.Id == "" {
err, newObject = object.New(
postedObject.Type,
postedObject.AttributedTo,
postedObject.Content,
postedObject.Published,
postedObject.To,
foundUser.Id,
)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Error Creating Object: %s")); err != nil {
panic(err)
}
return
}
err = object.CreateObject(newObject)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Object: %s")); err != nil {
panic(err)
}
return
}
} else {
newObject = postedObject
}
newActivity = activity.New(
newObject.Id,
newObject.AttributedTo,
newObject,
foundUser.Id,
)
}
var recipients []string
if newActivity.Type == "Follow" {
err = CreateCollectionObject(utils.GenerateFollowingUrl(foundUser.Id), newActivity.To)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s")); err != nil {
panic(err)
}
return
}
recipients = append(recipients, newActivity.To)
} else if newActivity.Type == "Create" {
err = CreateCollectionObject(utils.GenerateOutboxUrl(foundUser.Id), newActivity.Id)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
if err := json.NewEncoder(w).Encode(fmt.Sprintf("Database Error Creating Collection Object: %s")); err != nil {
panic(err)
}
return
}
err, recipients = GetReciepientsForActivity(newActivity)
}
for _, recipient := range recipients {
log.Printf("Send Activity to Recipient %s", recipient)
operation := func() error {
return SendActivity(newActivity, recipient, token)
}
notify := func(err error, duration time.Duration) {
log.Printf("Error Sending Activity, retrying in %ss: %s", duration/1000000000, err)
}
backoff.RetryNotify(operation, backoff.WithMaxRetries(backoff.NewExponentialBackOff(), 16), notify)
}
response := OutboxResponse{
Location: newActivity.Id,
}
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(response); err != nil {
panic(err)
}
}
}
func GetReciepientsForActivity(activityToSend *activity.Activity) (err error, recipientsWithoutDuplicates []string) {
recipients := []string{}
if strings.Contains(activityToSend.To, config.Homeserver) {
var foundActor *actor.Actor
foundActor, err = actor.ReadActor(activityToSend.To)
if err != nil {
return
}
if foundActor == nil {
var foundCollectionObjects []string
foundCollectionObjects, err = ReadCollectionObjects(activityToSend.To)
if err != nil {
return
}
if len(foundCollectionObjects) <= 0 {
err = errors.New("No Recipients")
return
}
recipients = append(recipients, foundCollectionObjects...)
} else {
recipients = append(recipients, foundActor.Id)
}
} else {
// Not Implemented
}
recipientsWithoutDuplicates = utils.RemoveDuplicates(recipients)
return
}
func SendActivity(activityToSend *activity.Activity, recipient string, token string) (err error) {
if strings.Contains(recipient, config.Homeserver) {
if activityToSend.Type == "Follow" {
followers := fmt.Sprintf("%sfollowers/", activityToSend.To)
err = CreateCollectionObject(followers, activityToSend.Actor)
if err != nil {
return
}
log.Printf("%s follows %s", activityToSend.Actor, activityToSend.To)
} else if activityToSend.Type == "Create" {
id := activityToSend.Id
if activityToSend.Object != nil {
id = activityToSend.Object.Id
}
inbox := fmt.Sprintf("%sinbox", recipient)
err = CreateCollectionObject(inbox, id)
if err != nil {
return
}
log.Printf("%s to %s: %s", activityToSend.Actor, recipient, activityToSend.Object.Content)
}
if err != nil {
return
}
} else {
activityToSend.To = recipient
var profile *actor.Actor
err, profile = actor.GetProfile(recipient)
if err != nil {
return
}
var reqBody []byte
reqBody, err = json.Marshal(activityToSend)
if err != nil {
return
}
client := &http.Client{Timeout: 2 * time.Second}
var req *http.Request
log.Printf("Inbox: %s", profile.Inbox)
req, err = http.NewRequest(http.MethodPost, profile.Inbox, bytes.NewBuffer(reqBody))
if err != nil {
return
}
req.Header["Content-Type"] = []string{utils.GetContentTypeString()}
req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s")}
var res *http.Response
res, err = client.Do(req)
if err != nil {
return
}
if res.StatusCode != http.StatusOK {
err = utils.HandleHTTPError(res)
return
}
}
return
}