332 lines
9.1 KiB
Go
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
|
|
}
|