aboutsummaryrefslogtreecommitdiff
path: root/activitypub/activity.go
diff options
context:
space:
mode:
authorFChannel <>2022-04-30 11:00:55 -0700
committerFChannel <>2022-06-19 12:53:29 -0700
commit1892327cee2c3fa1d3bea729bd08eb63c2189a96 (patch)
tree7b846f7d9caf46fba6c9d15ff81b9d89dcca9476 /activitypub/activity.go
parent5b52d269faa2ce2014d0feba603a2122361cf4eb (diff)
restructured code base to prevent circular dependicies
Diffstat (limited to 'activitypub/activity.go')
-rw-r--r--activitypub/activity.go524
1 files changed, 524 insertions, 0 deletions
diff --git a/activitypub/activity.go b/activitypub/activity.go
new file mode 100644
index 0000000..aef060b
--- /dev/null
+++ b/activitypub/activity.go
@@ -0,0 +1,524 @@
+package activitypub
+
+import (
+ "crypto"
+ crand "crypto/rand"
+ "crypto/rsa"
+ "crypto/sha256"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "regexp"
+ "strings"
+
+ "github.com/FChannel0/FChannel-Server/config"
+ "github.com/FChannel0/FChannel-Server/util"
+ "github.com/gofiber/fiber/v2"
+)
+
+// False positive for application/ld+ld, application/activity+ld, application/json+json
+var activityRegexp = regexp.MustCompile("application\\/(ld|json|activity)((\\+(ld|json))|$)")
+
+func AcceptActivity(header string) bool {
+ accept := false
+ if strings.Contains(header, ";") {
+ split := strings.Split(header, ";")
+ accept = accept || activityRegexp.MatchString(split[0])
+ accept = accept || strings.Contains(split[len(split)-1], "profile=\"https://www.w3.org/ns/activitystreams\"")
+ } else {
+ accept = accept || activityRegexp.MatchString(header)
+ }
+ return accept
+}
+
+func ActivitySign(actor Actor, signature string) (string, error) {
+ query := `select file from publicKeyPem where id=$1 `
+
+ rows, err := config.DB.Query(query, actor.PublicKey.Id)
+ if err != nil {
+ return "", err
+ }
+
+ defer rows.Close()
+
+ var file string
+ rows.Next()
+ rows.Scan(&file)
+
+ file = strings.ReplaceAll(file, "public.pem", "private.pem")
+ _, err = os.Stat(file)
+ if err != nil {
+ fmt.Println(`\n Unable to locate private key. Now,
+this means that you are now missing the proof that you are the
+owner of the "` + actor.Name + `" board. If you are the developer,
+then your job is just as easy as generating a new keypair, but
+if this board is live, then you'll also have to convince the other
+owners to switch their public keys for you so that they will start
+accepting your posts from your board from this site. Good luck ;)`)
+ return "", errors.New("unable to locate private key")
+ }
+
+ publickey, err := ioutil.ReadFile(file)
+ if err != nil {
+ return "", err
+ }
+
+ block, _ := pem.Decode(publickey)
+
+ pub, _ := x509.ParsePKCS1PrivateKey(block.Bytes)
+ rng := crand.Reader
+ hashed := sha256.New()
+ hashed.Write([]byte(signature))
+ cipher, _ := rsa.SignPKCS1v15(rng, pub, crypto.SHA256, hashed.Sum(nil))
+
+ return base64.StdEncoding.EncodeToString(cipher), nil
+}
+
+func DeleteReportActivity(id string) error {
+ query := `delete from reported where id=$1`
+
+ _, err := config.DB.Exec(query, id)
+ return err
+}
+
+func GetActivityFromDB(id string) (Collection, error) {
+ var nColl Collection
+ var nActor Actor
+ var result []ObjectBase
+
+ nColl.Actor = &nActor
+
+ query := `select actor, id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where id=$1 order by updated asc`
+
+ rows, err := config.DB.Query(query, id)
+ if err != nil {
+ return nColl, err
+ }
+
+ defer rows.Close()
+ for rows.Next() {
+ var post ObjectBase
+ var actor Actor
+ var attachID string
+ var previewID string
+
+ if err := rows.Scan(&nColl.Actor.Id, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &attachID, &previewID, &actor.Id, &post.TripCode, &post.Sensitive); err != nil {
+ return nColl, err
+ }
+
+ post.Actor = actor.Id
+
+ var postCnt int
+ var imgCnt int
+ var err error
+ post.Replies, postCnt, imgCnt, err = GetObjectRepliesDB(post)
+ if err != nil {
+ return nColl, err
+ }
+
+ post.Replies.TotalItems = postCnt
+ post.Replies.TotalImgs = imgCnt
+
+ post.Attachment, err = GetObjectAttachment(attachID)
+ if err != nil {
+ return nColl, err
+ }
+
+ post.Preview, err = GetObjectPreview(previewID)
+ if err != nil {
+ return nColl, err
+ }
+
+ result = append(result, post)
+ }
+
+ nColl.OrderedItems = result
+
+ return nColl, nil
+}
+
+func GetActivityFromJson(ctx *fiber.Ctx) (Activity, error) {
+
+ var respActivity ActivityRaw
+ var nActivity Activity
+ var nType string
+
+ if err := json.Unmarshal(ctx.Body(), &respActivity); err != nil {
+ return nActivity, err
+ }
+
+ if res, err := HasContextFromJson(respActivity.AtContextRaw.Context); err == nil && res {
+ var jObj ObjectBase
+
+ if respActivity.Type == "Note" {
+ jObj, err = GetObjectFromJson(ctx.Body())
+ if err != nil {
+ return nActivity, err
+ }
+
+ nType = "Create"
+ } else {
+ jObj, err = GetObjectFromJson(respActivity.ObjectRaw)
+ if err != nil {
+ return nActivity, err
+ }
+
+ nType = respActivity.Type
+ }
+
+ actor, err := GetActorFromJson(respActivity.ActorRaw)
+ if err != nil {
+ return nActivity, err
+ }
+
+ to, err := GetToFromJson(respActivity.ToRaw)
+ if err != nil {
+ return nActivity, err
+ }
+
+ cc, err := GetToFromJson(respActivity.CcRaw)
+ if err != nil {
+ return nActivity, err
+ }
+
+ nActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams"
+ nActivity.Type = nType
+ nActivity.Actor = &actor
+ nActivity.Published = respActivity.Published
+ nActivity.Auth = respActivity.Auth
+
+ if len(to) > 0 {
+ nActivity.To = to
+ }
+
+ if len(cc) > 0 {
+ nActivity.Cc = cc
+ }
+
+ nActivity.Name = respActivity.Name
+ nActivity.Object = &jObj
+ } else if err != nil {
+ return nActivity, err
+ }
+
+ return nActivity, nil
+}
+
+func HasContextFromJson(context []byte) (bool, error) {
+ var generic interface{}
+
+ err := json.Unmarshal(context, &generic)
+ if err != nil {
+ return false, err
+ }
+
+ hasContext := false
+
+ switch generic.(type) {
+ case []interface{}:
+ var arrContext AtContextArray
+ err = json.Unmarshal(context, &arrContext.Context)
+ if len(arrContext.Context) > 0 {
+ if arrContext.Context[0] == "https://www.w3.org/ns/activitystreams" {
+ hasContext = true
+ }
+ }
+ break
+
+ case string:
+ var arrContext AtContextString
+ err = json.Unmarshal(context, &arrContext.Context)
+ if arrContext.Context == "https://www.w3.org/ns/activitystreams" {
+ hasContext = true
+ }
+ break
+ }
+
+ return hasContext, err
+}
+
+func IsActivityLocal(activity Activity) (bool, error) {
+ for _, e := range activity.To {
+ if res, err := GetActorFromDB(e); err == nil && res.Id != "" {
+ return true, nil
+ } else if err != nil {
+ return false, err
+ }
+ }
+
+ for _, e := range activity.Cc {
+ if res, err := GetActorFromDB(e); err == nil && res.Id != "" {
+ return true, nil
+ } else if err != nil {
+ return false, err
+ }
+ }
+
+ if res, err := GetActorFromDB(activity.Actor.Id); err == nil && activity.Actor != nil && res.Id != "" {
+ return true, nil
+ } else if err != nil {
+ return false, err
+ }
+
+ return false, nil
+}
+
+func ProcessActivity(activity Activity) error {
+ activityType := activity.Type
+
+ if activityType == "Create" {
+ for _, e := range activity.To {
+ if res, err := GetActorFromDB(e); err == nil && res.Id != "" {
+ fmt.Println("actor is in the database")
+ } else if err != nil {
+ return err
+ } else {
+ fmt.Println("actor is NOT in the database")
+ }
+ }
+ } else if activityType == "Follow" {
+ // TODO: okay?
+ return errors.New("not implemented")
+ } else if activityType == "Delete" {
+ return errors.New("not implemented")
+ }
+
+ return nil
+}
+
+func RejectActivity(activity Activity) Activity {
+ var accept Activity
+ accept.AtContext.Context = activity.AtContext.Context
+ accept.Type = "Reject"
+ var nObj ObjectBase
+ accept.Object = &nObj
+ var nActor Actor
+ accept.Actor = &nActor
+ accept.Actor.Id = activity.Object.Actor
+ accept.Object.Actor = activity.Actor.Id
+ var nNested NestedObjectBase
+ accept.Object.Object = &nNested
+ accept.Object.Object.Actor = activity.Object.Actor
+ accept.Object.Object.Type = "Follow"
+ accept.To = append(accept.To, activity.Actor.Id)
+
+ return accept
+}
+
+func ReportActivity(id string, reason string) (bool, error) {
+ if res, err := IsIDLocal(id); err == nil && !res {
+ // TODO: not local error
+ return false, nil
+ } else if err != nil {
+ return false, err
+ }
+
+ actor, err := GetActivityFromDB(id)
+ if err != nil {
+ return false, err
+ }
+
+ query := `select count from reported where id=$1`
+
+ rows, err := config.DB.Query(query, id)
+ if err != nil {
+ return false, err
+ }
+ defer rows.Close()
+
+ var count int
+ for rows.Next() {
+ if err := rows.Scan(&count); err != nil {
+ return false, err
+ }
+ }
+
+ if count < 1 {
+ query = `insert into reported (id, count, board, reason) values ($1, $2, $3, $4)`
+
+ _, err := config.DB.Exec(query, id, 1, actor.Actor.Id, reason)
+ if err != nil {
+ return false, err
+ }
+ } else {
+ count = count + 1
+ query = `update reported set count=$1 where id=$2`
+
+ _, err := config.DB.Exec(query, count, id)
+ if err != nil {
+ return false, err
+ }
+ }
+
+ return true, nil
+}
+
+func WriteActivitytoCache(obj ObjectBase) error {
+ obj.Name = util.EscapeString(obj.Name)
+ obj.Content = util.EscapeString(obj.Content)
+ obj.AttributedTo = util.EscapeString(obj.AttributedTo)
+
+ query := `select id from cacheactivitystream where id=$1`
+
+ rows, err := config.DB.Query(query, obj.Id)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ err = rows.Scan(&id)
+ if err != nil {
+ return err
+ } else if id != "" {
+ return nil // TODO: error?
+ }
+
+ if obj.Updated.IsZero() {
+ obj.Updated = obj.Published
+ }
+
+ query = `insert into cacheactivitystream (id, type, name, content, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
+
+ _, err = config.DB.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
+ return err
+}
+
+func WriteActivitytoCacheWithAttachment(obj ObjectBase, attachment ObjectBase, preview NestedObjectBase) error {
+ obj.Name = util.EscapeString(obj.Name)
+ obj.Content = util.EscapeString(obj.Content)
+ obj.AttributedTo = util.EscapeString(obj.AttributedTo)
+
+ query := `select id from cacheactivitystream where id=$1`
+
+ rows, err := config.DB.Query(query, obj.Id)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ err = rows.Scan(&id)
+ if err != nil {
+ return err
+ } else if id != "" {
+ return nil // TODO: error?
+ }
+
+ if obj.Updated.IsZero() {
+ obj.Updated = obj.Published
+ }
+
+ query = `insert into cacheactivitystream (id, type, name, content, attachment, preview, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`
+
+ _, err = config.DB.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, attachment.Id, preview.Id, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
+ return err
+}
+
+func WriteActivitytoDB(obj ObjectBase) error {
+ obj.Name = util.EscapeString(obj.Name)
+ obj.Content = util.EscapeString(obj.Content)
+ obj.AttributedTo = util.EscapeString(obj.AttributedTo)
+
+ query := `insert into activitystream (id, type, name, content, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
+
+ _, err := config.DB.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
+ return err
+}
+
+func WriteActivitytoDBWithAttachment(obj ObjectBase, attachment ObjectBase, preview NestedObjectBase) {
+
+ obj.Name = util.EscapeString(obj.Name)
+ obj.Content = util.EscapeString(obj.Content)
+ obj.AttributedTo = util.EscapeString(obj.AttributedTo)
+
+ query := `insert into activitystream (id, type, name, content, attachment, preview, published, updated, attributedto, actor, tripcode, sensitive) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`
+
+ _, e := config.DB.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, attachment.Id, preview.Id, obj.Published, obj.Updated, obj.AttributedTo, obj.Actor, obj.TripCode, obj.Sensitive)
+
+ if e != nil {
+ fmt.Println("error inserting new activity with attachment")
+ panic(e)
+ }
+}
+
+func WriteAttachmentToCache(obj ObjectBase) error {
+ query := `select id from cacheactivitystream where id=$1`
+
+ rows, err := config.DB.Query(query, obj.Id)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ err = rows.Scan(&id)
+ if err != nil {
+ return err
+ } else if id != "" {
+ return nil // TODO: error?
+ }
+
+ if obj.Updated.IsZero() {
+ obj.Updated = obj.Published
+ }
+
+ query = `insert into cacheactivitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
+
+ _, err = config.DB.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+ return err
+}
+
+func WriteAttachmentToDB(obj ObjectBase) {
+ query := `insert into activitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
+
+ _, e := config.DB.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+
+ if e != nil {
+ fmt.Println("error inserting new attachment")
+ panic(e)
+ }
+}
+
+func WritePreviewToCache(obj NestedObjectBase) error {
+ query := `select id from cacheactivitystream where id=$1`
+
+ rows, err := config.DB.Query(query, obj.Id)
+ if err != nil {
+ return err
+ }
+ defer rows.Close()
+
+ var id string
+ rows.Next()
+ err = rows.Scan(&id)
+ if err != nil {
+ return err
+ } else if id != "" {
+ return nil // TODO: error?
+ }
+
+ if obj.Updated.IsZero() {
+ obj.Updated = obj.Published
+ }
+
+ query = `insert into cacheactivitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
+
+ _, err = config.DB.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+ return err
+}
+
+func WritePreviewToDB(obj NestedObjectBase) error {
+ query := `insert into activitystream (id, type, name, href, published, updated, attributedTo, mediatype, size) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)`
+
+ _, err := config.DB.Exec(query, obj.Id, obj.Type, obj.Name, obj.Href, obj.Published, obj.Updated, obj.AttributedTo, obj.MediaType, obj.Size)
+ return err
+}