From 580dec5b89215310ce34341e11ff17fe38bdb63a Mon Sep 17 00:00:00 2001 From: FChannel <> Date: Sun, 8 May 2022 14:57:40 -0700 Subject: more cleanup, logging and error logging everywhere things are mostly in place can work on "features" and polish --- activitypub/activity.go | 343 ++++++++++++++++++++- activitypub/actor.go | 774 +++++++++++++++++++++++++++++++++-------------- activitypub/object.go | 201 ++++++++++-- activitypub/pem.go | 5 +- activitypub/util.go | 12 + activitypub/webfinger.go | 180 +++++++++++ 6 files changed, 1245 insertions(+), 270 deletions(-) create mode 100644 activitypub/webfinger.go (limited to 'activitypub') diff --git a/activitypub/activity.go b/activitypub/activity.go index 06e061b..1650f14 100644 --- a/activitypub/activity.go +++ b/activitypub/activity.go @@ -1,26 +1,135 @@ package activitypub import ( + "bytes" + "encoding/json" "errors" "fmt" + "io/ioutil" + "net/http" + "regexp" "strings" + "time" "github.com/FChannel0/FChannel-Server/config" "github.com/FChannel0/FChannel-Server/util" ) -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) - } +func (activity Activity) AcceptFollow() Activity { + var accept Activity + accept.AtContext.Context = activity.AtContext.Context + accept.Type = "Accept" + var nActor Actor + accept.Actor = &nActor + accept.Actor.Id = activity.Object.Actor + var nObj ObjectBase + accept.Object = &nObj + 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.Object.Actor) + return accept } +func (activity Activity) AddFollowersTo() (Activity, error) { + activity.To = append(activity.To, activity.Actor.Id) + + for _, e := range activity.To { + reqActivity := Activity{Id: e + "/followers"} + aFollowers, err := reqActivity.GetCollection() + if err != nil { + return activity, util.MakeError(err, "AddFollowersTo") + } + + for _, k := range aFollowers.Items { + activity.To = append(activity.To, k.Id) + } + } + + var nActivity Activity + + for _, e := range activity.To { + var alreadyTo = false + for _, k := range nActivity.To { + if e == k || e == activity.Actor.Id { + alreadyTo = true + } + } + + if !alreadyTo { + nActivity.To = append(nActivity.To, e) + } + } + + activity.To = nActivity.To + + return activity, nil +} + +func (activity Activity) CheckValid() (Collection, bool, error) { + var respCollection Collection + + re := regexp.MustCompile(`.+\.onion(.+)?`) + if re.MatchString(activity.Id) { + activity.Id = strings.Replace(activity.Id, "https", "http", 1) + } + + req, err := http.NewRequest("GET", activity.Id, nil) + if err != nil { + return respCollection, false, util.MakeError(err, "CheckValid") + } + + req.Header.Set("Accept", config.ActivityStreams) + + resp, err := util.RouteProxy(req) + if err != nil { + return respCollection, false, util.MakeError(err, "CheckValid") + } + defer resp.Body.Close() + + body, _ := ioutil.ReadAll(resp.Body) + + if err := json.Unmarshal(body, &respCollection); err != nil { + return respCollection, false, util.MakeError(err, "CheckValid") + } + + if respCollection.AtContext.Context == "https://www.w3.org/ns/activitystreams" && respCollection.OrderedItems[0].Id != "" { + return respCollection, true, nil + } + + return respCollection, false, nil +} + +func (activity Activity) GetCollection() (Collection, error) { + var nColl Collection + + req, err := http.NewRequest("GET", activity.Id, nil) + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + req.Header.Set("Accept", config.ActivityStreams) + resp, err := util.RouteProxy(req) + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + if resp.StatusCode == 200 { + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + if len(body) > 0 { + if err := json.Unmarshal(body, &nColl); err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + } + } + + return nColl, nil +} + func (activity Activity) IsLocal() (bool, error) { for _, e := range activity.To { @@ -50,11 +159,11 @@ func (activity Activity) Process() error { if activityType == "Create" { for _, e := range activity.To { if res, err := GetActorFromDB(e); res.Id != "" { - fmt.Println("actor is in the database") + config.Log.Println("actor is in the database") } else if err != nil { return util.MakeError(err, "Process") } else { - fmt.Println("actor is NOT in the database") + config.Log.Println("actor is NOT in the database") } } } else if activityType == "Follow" { @@ -91,7 +200,9 @@ func (activity Activity) Report(reason string) (bool, error) { return false, util.MakeError(err, "Report") } - activityCol, err := activity.Object.GetCollection() + reqActivity := Activity{Id: activity.Object.Id} + activityCol, err := reqActivity.GetCollection() + if err != nil { return false, util.MakeError(err, "Report") } @@ -104,7 +215,7 @@ func (activity Activity) Report(reason string) (bool, error) { return true, nil } -func (activity Activity) SetFollower() (Activity, error) { +func (activity Activity) SetActorFollower() (Activity, error) { var query string alreadyFollow, err := activity.Actor.IsAlreadyFollower(activity.Object.Actor) @@ -140,3 +251,209 @@ func (activity Activity) SetFollower() (Activity, error) { activity.Type = "Accept" return activity, nil } + +func (activity Activity) SetActorFollowing() (Activity, error) { + var query string + + alreadyFollowing := false + alreadyFollower := false + objActor, _ := GetActor(activity.Object.Actor) + following, err := objActor.GetFollowing() + + if err != nil { + return activity, util.MakeError(err, "SetActorFollowing") + } + + actor, err := FingerActor(activity.Actor.Id) + + if err != nil { + return activity, util.MakeError(err, "SetActorFollowing") + } + + reqActivity := Activity{Id: actor.Followers} + remoteActorFollowerCol, err := reqActivity.GetCollection() + + if err != nil { + return activity, util.MakeError(err, "SetActorFollowing") + } + + for _, e := range following { + if e.Id == activity.Actor.Id { + alreadyFollowing = true + } + } + + for _, e := range remoteActorFollowerCol.Items { + if e.Id == activity.Object.Actor { + alreadyFollower = true + } + } + + activity.Type = "Reject" + + if activity.Actor.Id == activity.Object.Actor { + return activity, nil + } + + if alreadyFollowing && alreadyFollower { + query = `delete from following where id=$1 and following=$2` + activity.Summary = activity.Object.Actor + " Unfollowing " + activity.Actor.Id + + if res, err := activity.Actor.IsLocal(); err == nil && !res { + go activity.Actor.DeleteCache() + } else { + return activity, util.MakeError(err, "SetActorFollowing") + } + + if _, err := config.DB.Exec(query, activity.Object.Actor, activity.Actor.Id); err != nil { + return activity, util.MakeError(err, "SetActorFollowing") + } + + activity.Type = "Accept" + + return activity, nil + } + + if !alreadyFollowing && !alreadyFollower { + + query = `insert into following (id, following) values ($1, $2)` + activity.Summary = activity.Object.Actor + " Following " + activity.Actor.Id + + if res, err := activity.Actor.IsLocal(); err == nil && !res { + go activity.Actor.WriteCache() + } + if _, err := config.DB.Exec(query, activity.Object.Actor, activity.Actor.Id); err != nil { + return activity, util.MakeError(err, "SetActorFollowing") + } + + activity.Type = "Accept" + + return activity, nil + } + + return activity, nil +} + +func (activity Activity) MakeFollowingReq() (bool, error) { + actor, err := GetActor(activity.Object.Id) + + if err != nil { + return false, util.MakeError(err, "MakeFollowingReq") + } + + req, err := http.NewRequest("POST", actor.Inbox, nil) + + if err != nil { + return false, util.MakeError(err, "MakeFollowingReq") + } + + resp, err := util.RouteProxy(req) + + if err != nil { + return false, util.MakeError(err, "MakeFollowingReq") + } + + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + + var respActivity Activity + err = json.Unmarshal(body, &respActivity) + + return respActivity.Type == "Accept", util.MakeError(err, "MakeFollowingReq") +} + +func (activity Activity) MakeRequestInbox() error { + j, _ := json.MarshalIndent(activity, "", "\t") + + for _, e := range activity.To { + if e != activity.Actor.Id { + actor, err := FingerActor(e) + + if err != nil { + return util.MakeError(err, "MakeRequest") + } + + if actor.Id != "" { + _, instance := GetActorAndInstance(actor.Id) + + if actor.Inbox != "" { + req, err := http.NewRequest("POST", actor.Inbox, bytes.NewBuffer(j)) + + if err != nil { + return util.MakeError(err, "MakeRequest") + } + + date := time.Now().UTC().Format(time.RFC1123) + path := strings.Replace(actor.Inbox, instance, "", 1) + re := regexp.MustCompile("https?://(www.)?") + path = re.ReplaceAllString(path, "") + sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date) + encSig, err := activity.Actor.ActivitySign(sig) + + if err != nil { + return util.MakeError(err, "MakeRequest") + } + + signature := fmt.Sprintf("keyId=\"%s\",headers=\"(request-target) host date\",signature=\"%s\"", activity.Actor.PublicKey.Id, encSig) + + req.Header.Set("Content-Type", config.ActivityStreams) + req.Header.Set("Date", date) + req.Header.Set("Signature", signature) + req.Host = instance + + _, err = util.RouteProxy(req) + + if err != nil { + return util.MakeError(err, "MakeRequest") + } + } + } + } + } + + return nil +} + +func (activity Activity) MakeRequestOutbox() error { + j, _ := json.Marshal(activity) + + if activity.Actor.Outbox == "" { + return util.MakeError(errors.New("invalid outbox"), "MakeRequestOutbox") + } + + req, err := http.NewRequest("POST", activity.Actor.Outbox, bytes.NewBuffer(j)) + + if err != nil { + return util.MakeError(err, "MakeRequestOutbox") + } + + re := regexp.MustCompile("https?://(www.)?") + + var instance string + if activity.Actor.Id == config.Domain { + instance = re.ReplaceAllString(config.Domain, "") + } else { + _, instance = GetActorAndInstance(activity.Actor.Id) + } + + date := time.Now().UTC().Format(time.RFC1123) + path := strings.Replace(activity.Actor.Outbox, instance, "", 1) + path = re.ReplaceAllString(path, "") + sig := fmt.Sprintf("(request-target): %s %s\nhost: %s\ndate: %s", "post", path, instance, date) + encSig, err := activity.Actor.ActivitySign(sig) + + if err != nil { + return util.MakeError(err, "MakeRequestOutbox") + } + + signature := fmt.Sprintf("keyId=\"%s\",headers=\"(request-target) host date\",signature=\"%s\"", activity.Actor.PublicKey.Id, encSig) + + req.Header.Set("Content-Type", config.ActivityStreams) + req.Header.Set("Date", date) + req.Header.Set("Signature", signature) + req.Host = instance + + _, err = util.RouteProxy(req) + + return util.MakeError(err, "MakeRequestOutbox") +} diff --git a/activitypub/actor.go b/activitypub/actor.go index 120c9fd..bc7ba8a 100644 --- a/activitypub/actor.go +++ b/activitypub/actor.go @@ -11,16 +11,20 @@ import ( "encoding/json" "encoding/pem" "errors" - "fmt" "io/ioutil" + "net/http" "os" + "regexp" "strings" + "time" "github.com/FChannel0/FChannel-Server/config" "github.com/FChannel0/FChannel-Server/util" "github.com/gofiber/fiber/v2" ) +var ActorCache = make(map[string]Actor) + func (actor Actor) AddFollower(follower string) error { query := `insert into follower (id, follower) values ($1, $2)` _, err := config.DB.Exec(query, actor.Id, follower) @@ -39,7 +43,7 @@ func (actor Actor) ActivitySign(signature string) (string, error) { _, err := os.Stat(file) if err != nil { - fmt.Println(`\n Unable to locate private key. Now, + config.Log.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 @@ -65,6 +69,75 @@ accepting your posts from your board from this site. Good luck ;)`) return base64.StdEncoding.EncodeToString(cipher), nil } +func (actor Actor) ArchivePosts() error { + if actor.Id != "" && actor.Id != config.Domain { + col, err := actor.GetAllArchive(165) + + if err != nil { + return util.MakeError(err, "ArchivePosts") + } + + for _, e := range col.OrderedItems { + for _, k := range e.Replies.OrderedItems { + if err := k.UpdateType("Archive"); err != nil { + return util.MakeError(err, "ArchivePosts") + } + } + + if err := e.UpdateType("Archive"); err != nil { + return util.MakeError(err, "ArchivePosts") + } + } + } + + return nil +} + +func (actor Actor) AutoFollow() error { + nActor, _ := GetActor(actor.Id) + following, err := nActor.GetFollowing() + + if err != nil { + return util.MakeError(err, "AutoFollow") + } + + follower, err := nActor.GetFollow() + + if err != nil { + return util.MakeError(err, "AutoFollow") + } + + isFollowing := false + + for _, e := range follower { + for _, k := range following { + if e.Id == k.Id { + isFollowing = true + } + } + + if !isFollowing && e.Id != config.Domain && e.Id != nActor.Id { + followActivity, err := nActor.MakeFollowActivity(e.Id) + + if err != nil { + return util.MakeError(err, "AutoFollow") + } + + nActor, err := FingerActor(e.Id) + + if err != nil { + return util.MakeError(err, "AutoFollow") + } + + if nActor.Id != "" { + followActivity.MakeRequestOutbox() + } + } + } + + return nil +} + func (actor Actor) DeleteCache() error { query := `select id from cacheactivitystream where id in (select id from cacheactivitystream where actor=$1)` rows, err := config.DB.Query(query, actor.Id) @@ -88,6 +161,37 @@ func (actor Actor) DeleteCache() error { return nil } +func (actor Actor) GetAllArchive(offset int) (Collection, error) { + var nColl Collection + var result []ObjectBase + + query := `select x.id, x.updated from (select id, updated from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, updated from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, updated from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc offset $2` + rows, err := config.DB.Query(query, actor.Id, offset) + + if err != nil { + return nColl, util.MakeError(err, "GetAllArchive") + } + + defer rows.Close() + for rows.Next() { + var post ObjectBase + + if err := rows.Scan(&post.Id, &post.Updated); err != nil { + return nColl, util.MakeError(err, "GetAllArchive") + } + + post.Replies, _, _, err = post.GetReplies() + + result = append(result, post) + } + + nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + + nColl.OrderedItems = result + + return nColl, nil +} + func (actor Actor) GetAutoSubscribe() (bool, error) { var subscribed bool @@ -99,6 +203,188 @@ func (actor Actor) GetAutoSubscribe() (bool, error) { return subscribed, nil } +func (actor Actor) GetCatalogCollection() (Collection, error) { + var nColl Collection + var result []ObjectBase + + var err error + var rows *sql.Rows + + query := `select x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc limit 165` + if rows, err = config.DB.Query(query, actor.Id); err != nil { + return nColl, util.MakeError(err, "GetCatalogCollection") + } + + defer rows.Close() + for rows.Next() { + var post ObjectBase + var actor Actor + + var attch ObjectBase + post.Attachment = append(post.Attachment, attch) + + var prev NestedObjectBase + post.Preview = &prev + + err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive) + + if err != nil { + return nColl, util.MakeError(err, "GetCatalogCollection") + } + + post.Actor = actor.Id + + var replies CollectionBase + + post.Replies = replies + + post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetRepliesCount() + + if err != nil { + return nColl, util.MakeError(err, "GetCatalogCollection") + } + + post.Attachment, err = post.Attachment[0].GetAttachment() + + if err != nil { + return nColl, util.MakeError(err, "GetCatalogCollection") + } + + post.Preview, err = post.Preview.GetPreview() + + if err != nil { + return nColl, util.MakeError(err, "GetCatalogCollection") + } + + result = append(result, post) + } + + nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + + nColl.OrderedItems = result + + return nColl, nil +} + +func (actor Actor) GetCollectionPage(page int) (Collection, error) { + var nColl Collection + var result []ObjectBase + + var err error + var rows *sql.Rows + + query := `select count (x.id) over(), x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc limit 15 offset $2` + + if rows, err = config.DB.Query(query, actor.Id, page*15); err != nil { + return nColl, util.MakeError(err, "GetCollectionPage") + } + + var count int + defer rows.Close() + for rows.Next() { + var post ObjectBase + var actor Actor + + var attch ObjectBase + post.Attachment = append(post.Attachment, attch) + + var prev NestedObjectBase + post.Preview = &prev + + err = rows.Scan(&count, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive) + + if err != nil { + return nColl, util.MakeError(err, "GetCollectionPage") + } + + post.Actor = actor.Id + + post.Replies, post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetRepliesLimit(5) + + if err != nil { + return nColl, util.MakeError(err, "GetCollectionPage") + } + + post.Attachment, err = post.Attachment[0].GetAttachment() + + if err != nil { + return nColl, util.MakeError(err, "GetCollectionPage") + } + + post.Preview, err = post.Preview.GetPreview() + + if err != nil { + return nColl, util.MakeError(err, "GetCollectionPage") + } + + result = append(result, post) + } + + nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + + nColl.TotalItems = count + + nColl.OrderedItems = result + + return nColl, nil +} + +func (actor Actor) GetCollection() (Collection, error) { + var nColl Collection + var result []ObjectBase + + query := `select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' order by updated desc` + rows, err := config.DB.Query(query, actor.Id) + + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + defer rows.Close() + for rows.Next() { + var post ObjectBase + var actor Actor + + var attch ObjectBase + post.Attachment = append(post.Attachment, attch) + + var prev NestedObjectBase + post.Preview = &prev + + if err := rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive); err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + post.Actor = actor.Id + + post.Replies, post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetReplies() + + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + post.Attachment, err = post.Attachment[0].GetAttachment() + + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + post.Preview, err = post.Preview.GetPreview() + + if err != nil { + return nColl, util.MakeError(err, "GetCollection") + } + + result = append(result, post) + } + + nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + + nColl.OrderedItems = result + + return nColl, nil +} + func (actor Actor) GetCollectionType(nType string) (Collection, error) { var nColl Collection var result []ObjectBase @@ -232,6 +518,50 @@ func (actor Actor) GetFollow() ([]ObjectBase, error) { return followerCollection, nil } +func (actor Actor) GetFollowing() ([]ObjectBase, error) { + var followingCollection []ObjectBase + + query := `select following from following where id=$1` + rows, err := config.DB.Query(query, actor.Id) + + if err != nil { + return followingCollection, util.MakeError(err, "GetFollowing") + } + + defer rows.Close() + for rows.Next() { + var obj ObjectBase + + if err := rows.Scan(&obj.Id); err != nil { + return followingCollection, util.MakeError(err, "GetFollowing") + } + + followingCollection = append(followingCollection, obj) + } + + return followingCollection, nil +} + +func (actor Actor) GetFollowFromName(name string) ([]string, error) { + var followingActors []string + + activity := Activity{Id: actor.Following} + follow, err := activity.GetCollection() + if err != nil { + return followingActors, util.MakeError(err, "GetFollowFromName") + } + + re := regexp.MustCompile("\\w+?$") + + for _, e := range follow.Items { + if re.FindString(e.Id) == name { + followingActors = append(followingActors, e.Id) + } + } + + return followingActors, nil +} + func (actor Actor) GetFollowingTotal() (int, error) { var following int @@ -276,7 +606,7 @@ func (actor Actor) GetFollowersResp(ctx *fiber.Ctx) error { ctx.Response().Header.Set("Content-Type", config.ActivityStreams) _, err = ctx.Write(enc) - return err + return util.MakeError(err, "") } func (actor Actor) GetFollowingResp(ctx *fiber.Ctx) error { @@ -304,28 +634,15 @@ func (actor Actor) GetFollowingResp(ctx *fiber.Ctx) error { return util.MakeError(err, "GetFollowingResp") } -func (actor Actor) GetFollowing() ([]ObjectBase, error) { - var followingCollection []ObjectBase - - query := `select following from following where id=$1` - rows, err := config.DB.Query(query, actor.Id) - - if err != nil { - return followingCollection, util.MakeError(err, "GetFollowing") - } - - defer rows.Close() - for rows.Next() { - var obj ObjectBase - - if err := rows.Scan(&obj.Id); err != nil { - return followingCollection, util.MakeError(err, "GetFollowing") - } +func (actor Actor) GetImgTotal() (int, error) { + var count int - followingCollection = append(followingCollection, obj) + query := `select count(attachment) from activitystream where actor=$1 and id in (select id from replies where inreplyto='' and type='Note' )` + if err := config.DB.QueryRow(query, actor.Id).Scan(&count); err != nil { + return count, util.MakeError(err, "GetImgTotal") } - return followingCollection, nil + return count, nil } func (actor Actor) GetInfoResp(ctx *fiber.Ctx) error { @@ -337,60 +654,47 @@ func (actor Actor) GetInfoResp(ctx *fiber.Ctx) error { return util.MakeError(err, "GetInfoResp") } -func (actor Actor) GetCollection() (Collection, error) { - var nColl Collection - var result []ObjectBase - - query := `select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' order by updated desc` - rows, err := config.DB.Query(query, actor.Id) +func (actor Actor) GetPostTotal() (int, error) { + var count int - if err != nil { - return nColl, util.MakeError(err, "GetCollection") + query := `select count(id) from activitystream where actor=$1 and id in (select id from replies where inreplyto='' and type='Note')` + if err := config.DB.QueryRow(query, actor.Id).Scan(&count); err != nil { + return count, util.MakeError(err, "GetPostTotal") } - defer rows.Close() - for rows.Next() { - var post ObjectBase - var actor Actor - - var attch ObjectBase - post.Attachment = append(post.Attachment, attch) - - var prev NestedObjectBase - post.Preview = &prev - - if err := rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive); err != nil { - return nColl, util.MakeError(err, "GetCollection") - } - - post.Actor = actor.Id - - post.Replies, post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetReplies() + return count, nil +} - if err != nil { - return nColl, util.MakeError(err, "GetCollection") - } +func (actor Actor) GetOutbox(ctx *fiber.Ctx) error { + var collection Collection - post.Attachment, err = post.Attachment[0].GetAttachment() + c, err := actor.GetCollection() - if err != nil { - return nColl, util.MakeError(err, "GetCollection") - } + if err != nil { + return util.MakeError(err, "GetOutbox") + } - post.Preview, err = post.Preview.GetPreview() + collection.OrderedItems = c.OrderedItems + collection.AtContext.Context = "https://www.w3.org/ns/activitystreams" + collection.Actor = actor - if err != nil { - return nColl, util.MakeError(err, "GetCollection") - } + collection.TotalItems, err = actor.GetPostTotal() - result = append(result, post) + if err != nil { + return util.MakeError(err, "GetOutbox") } - nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + collection.TotalImgs, err = actor.GetImgTotal() - nColl.OrderedItems = result + if err != nil { + return util.MakeError(err, "GetOutbox") + } - return nColl, nil + enc, _ := json.Marshal(collection) + ctx.Response().Header.Set("Content-Type", config.ActivityStreams) + _, err = ctx.Write(enc) + + return util.MakeError(err, "GetOutbox") } func (actor Actor) GetRecentPosts() ([]ObjectBase, error) { @@ -467,35 +771,14 @@ func (actor Actor) GetReportedTotal() (int, error) { return count, nil } -func (actor Actor) GetAllArchive(offset int) (Collection, error) { - var nColl Collection - var result []ObjectBase - - query := `select x.id, x.updated from (select id, updated from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, updated from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, updated from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc offset $2` - rows, err := config.DB.Query(query, actor.Id, offset) - - if err != nil { - return nColl, util.MakeError(err, "GetAllArchive") - } - - defer rows.Close() - for rows.Next() { - var post ObjectBase - - if err := rows.Scan(&post.Id, &post.Updated); err != nil { - return nColl, util.MakeError(err, "GetAllArchive") - } - - post.Replies, _, _, err = post.GetReplies() +func (actor Actor) HasValidation(ctx *fiber.Ctx) bool { + id, _ := util.GetPasswordFromSession(ctx) - result = append(result, post) + if id == "" || (id != actor.Id && id != config.Domain) { + return false } - nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" - - nColl.OrderedItems = result - - return nColl, nil + return true } func (actor Actor) IsAlreadyFollowing(follow string) (bool, error) { @@ -530,6 +813,70 @@ func (actor Actor) IsAlreadyFollower(follow string) (bool, error) { return false, nil } +func (actor Actor) IsLocal() (bool, error) { + actor, err := GetActorFromDB(actor.Id) + return actor.Id != "", util.MakeError(err, "IsLocal") +} + +func (actor Actor) IsValid() (Actor, bool, error) { + actor, err := FingerActor(actor.Id) + return actor, actor.Id != "", util.MakeError(err, "IsValid") +} + +func (actor Actor) ReportedResp(ctx *fiber.Ctx) error { + var err error + + auth := ctx.Get("Authorization") + verification := strings.Split(auth, " ") + + if len(verification) < 2 { + ctx.Response().Header.SetStatusCode(http.StatusBadRequest) + _, err := ctx.Write([]byte("")) + return util.MakeError(err, "GetReported") + } + + if hasAuth, err := util.HasAuth(verification[1], actor.Id); !hasAuth { + ctx.Response().Header.SetStatusCode(http.StatusBadRequest) + _, err := ctx.Write([]byte("")) + return util.MakeError(err, "GetReported") + } else if err != nil { + return util.MakeError(err, "GetReported") + } + + actor, err = GetActorFromDB(actor.Id) + + if err != nil { + return util.MakeError(err, "GetReported") + } + + var following Collection + + following.AtContext.Context = "https://www.w3.org/ns/activitystreams" + following.Type = "Collection" + following.TotalItems, err = actor.GetReportedTotal() + + if err != nil { + return util.MakeError(err, "GetReported") + } + + following.Items, err = actor.GetReported() + + if err != nil { + return util.MakeError(err, "GetReported") + } + + enc, err := json.MarshalIndent(following, "", "\t") + + if err != nil { + return util.MakeError(err, "GetReported") + } + + ctx.Response().Header.Set("Content-Type", config.ActivityStreams) + _, err = ctx.Write(enc) + + return util.MakeError(err, "GetReported") +} + func (actor Actor) SetActorAutoSubscribeDB() error { current, err := actor.GetAutoSubscribe() @@ -543,36 +890,37 @@ func (actor Actor) SetActorAutoSubscribeDB() error { return util.MakeError(err, "SetActorAutoSubscribeDB") } -func (actor Actor) GetOutbox(ctx *fiber.Ctx) error { - var collection Collection - - c, err := actor.GetCollection() +func (actor Actor) SendToFollowers(activity Activity) error { + nActor, err := GetActorFromDB(actor.Id) if err != nil { - return util.MakeError(err, "GetOutbox") + return util.MakeError(err, "SendToFollowers") } - collection.OrderedItems = c.OrderedItems - collection.AtContext.Context = "https://www.w3.org/ns/activitystreams" - collection.Actor = actor - - collection.TotalItems, err = actor.GetPostTotal() + activity.Actor = &nActor + followers, err := nActor.GetFollow() if err != nil { - return util.MakeError(err, "GetOutbox") + return util.MakeError(err, "SendToFollowers") } - collection.TotalImgs, err = actor.GetImgTotal() + var to []string - if err != nil { - return util.MakeError(err, "GetOutbox") + for _, e := range followers { + for _, k := range activity.To { + if e.Id != k { + to = append(to, e.Id) + } + } } - enc, _ := json.Marshal(collection) - ctx.Response().Header.Set("Content-Type", config.ActivityStreams) - _, err = ctx.Write(enc) + activity.To = to - return util.MakeError(err, "GetOutbox") + if len(activity.Object.InReplyTo) > 0 { + err = activity.MakeRequestInbox() + } + + return util.MakeError(err, "SendToFollowers") } func (actor Actor) UnArchiveLast() error { @@ -597,135 +945,135 @@ func (actor Actor) UnArchiveLast() error { return nil } -func (actor Actor) IsLocal() (bool, error) { - actor, err := GetActorFromDB(actor.Id) - return actor.Id != "", util.MakeError(err, "IsLocal") -} - -func (actor Actor) GetCatalogCollection() (Collection, error) { - var nColl Collection - var result []ObjectBase - - var err error - var rows *sql.Rows - - query := `select x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc limit 165` - if rows, err = config.DB.Query(query, actor.Id); err != nil { - return nColl, util.MakeError(err, "GetCatalogCollection") - } - - defer rows.Close() - for rows.Next() { - var post ObjectBase - var actor Actor - - var attch ObjectBase - post.Attachment = append(post.Attachment, attch) - - var prev NestedObjectBase - post.Preview = &prev - - err = rows.Scan(&post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive) +func (actor Actor) Verify(signature string, verify string) error { + sig, _ := base64.StdEncoding.DecodeString(signature) + if actor.PublicKey.PublicKeyPem == "" { + _actor, err := FingerActor(actor.Id) if err != nil { - return nColl, util.MakeError(err, "GetCatalogCollection") + return util.MakeError(err, "Verify") } + actor = _actor + } - post.Actor = actor.Id - - var replies CollectionBase + block, _ := pem.Decode([]byte(actor.PublicKey.PublicKeyPem)) + pub, _ := x509.ParsePKIXPublicKey(block.Bytes) - post.Replies = replies + hashed := sha256.New() + hashed.Write([]byte(verify)) - post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetRepliesCount() + return rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), crypto.SHA256, hashed.Sum(nil), sig) +} - if err != nil { - return nColl, util.MakeError(err, "GetCatalogCollection") +func (actor Actor) VerifyHeaderSignature(ctx *fiber.Ctx) bool { + var sig string + var path string + var host string + var date string + var method string + var digest string + var contentLength string + + s := ParseHeaderSignature(ctx.Get("Signature")) + + for i, e := range s.Headers { + var nl string + if i < len(s.Headers)-1 { + nl = "\n" } - post.Attachment, err = post.Attachment[0].GetAttachment() - - if err != nil { - return nColl, util.MakeError(err, "GetCatalogCollection") + switch e { + case "(request-target)": + method = strings.ToLower(ctx.Method()) + path = ctx.Path() + sig += "(request-target): " + method + " " + path + "" + nl + break + case "host": + host = ctx.Hostname() + sig += "host: " + host + "" + nl + break + case "date": + date = ctx.Get("date") + sig += "date: " + date + "" + nl + break + case "digest": + digest = ctx.Get("digest") + sig += "digest: " + digest + "" + nl + break + case "content-length": + contentLength = ctx.Get("content-length") + sig += "content-length: " + contentLength + "" + nl + break } + } - post.Preview, err = post.Preview.GetPreview() + if s.KeyId != actor.PublicKey.Id { + return false + } - if err != nil { - return nColl, util.MakeError(err, "GetCatalogCollection") - } + t, _ := time.Parse(time.RFC1123, date) - result = append(result, post) + if time.Now().UTC().Sub(t).Seconds() > 75 { + return false } - nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" - - nColl.OrderedItems = result + if actor.Verify(s.Signature, sig) != nil { + return false + } - return nColl, nil + return true } -func (actor Actor) GetCollectionPage(page int) (Collection, error) { - var nColl Collection - var result []ObjectBase - - var err error - var rows *sql.Rows - - query := `select count (x.id) over(), x.id, x.name, x.content, x.type, x.published, x.updated, x.attributedto, x.attachment, x.preview, x.actor, x.tripcode, x.sensitive from (select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor=$1 and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from activitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note' union select id, name, content, type, published, updated, attributedto, attachment, preview, actor, tripcode, sensitive from cacheactivitystream where actor in (select following from following where id=$1) and id in (select id from replies where inreplyto='') and type='Note') as x order by x.updated desc limit 15 offset $2` +func (actor Actor) WriteCache() error { + actor, err := FingerActor(actor.Id) - if rows, err = config.DB.Query(query, actor.Id, page*15); err != nil { - return nColl, util.MakeError(err, "GetCollectionPage") + if err != nil { + return util.MakeError(err, "WriteCache") } - var count int - defer rows.Close() - for rows.Next() { - var post ObjectBase - var actor Actor - - var attch ObjectBase - post.Attachment = append(post.Attachment, attch) - - var prev NestedObjectBase - post.Preview = &prev - - err = rows.Scan(&count, &post.Id, &post.Name, &post.Content, &post.Type, &post.Published, &post.Updated, &post.AttributedTo, &post.Attachment[0].Id, &post.Preview.Id, &actor.Id, &post.TripCode, &post.Sensitive) - - if err != nil { - return nColl, util.MakeError(err, "GetCollectionPage") - } - - post.Actor = actor.Id + reqActivity := Activity{Id: actor.Outbox} + collection, err := reqActivity.GetCollection() - post.Replies, post.Replies.TotalItems, post.Replies.TotalImgs, err = post.GetRepliesLimit(5) + if err != nil { + return util.MakeError(err, "WriteCache") + } - if err != nil { - return nColl, util.MakeError(err, "GetCollectionPage") + for _, e := range collection.OrderedItems { + if err := e.WriteCache(); err != nil { + return util.MakeError(err, "WriteCache") } + } - post.Attachment, err = post.Attachment[0].GetAttachment() + return nil +} - if err != nil { - return nColl, util.MakeError(err, "GetCollectionPage") - } +func (actor Actor) MakeFollowActivity(follow string) (Activity, error) { + var followActivity Activity + var err error - post.Preview, err = post.Preview.GetPreview() + followActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams" + followActivity.Type = "Follow" - if err != nil { - return nColl, util.MakeError(err, "GetCollectionPage") - } + var obj ObjectBase + var nactor Actor - result = append(result, post) + if actor.Id == config.Domain { + nactor, err = GetActorFromDB(actor.Id) + } else { + nactor, err = FingerActor(actor.Id) } - nColl.AtContext.Context = "https://www.w3.org/ns/activitystreams" + if err != nil { + return followActivity, util.MakeError(err, "MakeFollowActivity") + } - nColl.TotalItems = count + followActivity.Actor = &nactor + followActivity.Object = &obj - nColl.OrderedItems = result + followActivity.Object.Actor = follow + followActivity.To = append(followActivity.To, follow) - return nColl, nil + return followActivity, nil } func (actor Actor) WantToServePage(page int) (Collection, error) { @@ -744,25 +1092,3 @@ func (actor Actor) WantToServePage(page int) (Collection, error) { return collection, nil } - -func (actor Actor) GetImgTotal() (int, error) { - var count int - - query := `select count(attachment) from activitystream where actor=$1 and id in (select id from replies where inreplyto='' and type='Note' )` - if err := config.DB.QueryRow(query, actor.Id).Scan(&count); err != nil { - return count, util.MakeError(err, "GetImgTotal") - } - - return count, nil -} - -func (actor Actor) GetPostTotal() (int, error) { - var count int - - query := `select count(id) from activitystream where actor=$1 and id in (select id from replies where inreplyto='' and type='Note')` - if err := config.DB.QueryRow(query, actor.Id).Scan(&count); err != nil { - return count, util.MakeError(err, "GetPostTotal") - } - - return count, nil -} diff --git a/activitypub/object.go b/activitypub/object.go index a873ce0..29a17e4 100644 --- a/activitypub/object.go +++ b/activitypub/object.go @@ -2,10 +2,7 @@ package activitypub import ( "database/sql" - "encoding/json" "fmt" - "io/ioutil" - "net/http" "os" "os/exec" "regexp" @@ -17,6 +14,35 @@ import ( "github.com/FChannel0/FChannel-Server/util" ) +func (obj ObjectBase) CreateActivity(activityType string) (Activity, error) { + var newActivity Activity + + actor, err := FingerActor(obj.Actor) + if err != nil { + return newActivity, util.MakeError(err, "CreateActivity") + } + + newActivity.AtContext.Context = "https://www.w3.org/ns/activitystreams" + newActivity.Type = activityType + newActivity.Published = obj.Published + newActivity.Actor = &actor + newActivity.Object = &obj + + for _, e := range obj.To { + if obj.Actor != e { + newActivity.To = append(newActivity.To, e) + } + } + + for _, e := range obj.Cc { + if obj.Actor != e { + newActivity.Cc = append(newActivity.Cc, e) + } + } + + return newActivity, nil +} + func (obj ObjectBase) CheckIfOP() (bool, error) { var count int @@ -63,6 +89,44 @@ func (obj ObjectBase) CreatePreview() *NestedObjectBase { return &nPreview } +func (obj ObjectBase) DeleteAndRepliesRequest() error { + activity, err := obj.CreateActivity("Delete") + + if err != nil { + return util.MakeError(err, "DeleteAndRepliesRequest") + } + + nObj, err := obj.GetCollectionFromPath() + if err != nil { + return util.MakeError(err, "DeleteAndRepliesRequest") + } + + activity.Actor.Id = nObj.OrderedItems[0].Actor + activity.Object = &nObj.OrderedItems[0] + objActor, _ := GetActor(nObj.OrderedItems[0].Actor) + followers, err := objActor.GetFollow() + + if err != nil { + return util.MakeError(err, "DeleteAndRepliesRequest") + } + for _, e := range followers { + activity.To = append(activity.To, e.Id) + } + + following, err := objActor.GetFollowing() + if err != nil { + return util.MakeError(err, "DeleteAndRepliesRequest") + } + + for _, e := range following { + activity.To = append(activity.To, e.Id) + } + + err = activity.MakeRequestInbox() + + return util.MakeError(err, "DeleteAndRepliesRequest") +} + //TODO break this off into seperate for Cache func (obj ObjectBase) DeleteAttachment() error { query := `delete from activitystream where id in (select attachment from activitystream where id=$1)` @@ -167,49 +231,65 @@ func (obj ObjectBase) Delete() error { return util.MakeError(err, "Delete") } -func (obj ObjectBase) DeleteRepliedTo() error { - query := `delete from replies where id=$1` - _, err := config.DB.Exec(query, obj.Id) - return util.MakeError(err, "DeleteRepliedTo") -} - func (obj ObjectBase) DeleteInReplyTo() error { query := `delete from replies where id in (select id from replies where inreplyto=$1)` _, err := config.DB.Exec(query, obj.Id) return util.MakeError(err, "DeleteInReplyTo") } -func (obj ObjectBase) DeleteReported() error { - query := `delete from reported where id=$1` +func (obj ObjectBase) DeleteRepliedTo() error { + query := `delete from replies where id=$1` _, err := config.DB.Exec(query, obj.Id) - return util.MakeError(err, "DeleteReported") + return util.MakeError(err, "DeleteRepliedTo") } -func (obj ObjectBase) GetCollection() (Collection, error) { - var nColl Collection +func (obj ObjectBase) DeleteRequest() error { + activity, err := obj.CreateActivity("Delete") - req, err := http.NewRequest("GET", obj.Id, nil) if err != nil { - return nColl, util.MakeError(err, "GetCollection") + return util.MakeError(err, "DeleteRequest") } - req.Header.Set("Accept", config.ActivityStreams) - resp, err := util.RouteProxy(req) + nObj, err := obj.GetFromPath() + if err != nil { - return nColl, util.MakeError(err, "GetCollection") + return util.MakeError(err, "DeleteRequest") } - if resp.StatusCode == 200 { - defer resp.Body.Close() - body, _ := ioutil.ReadAll(resp.Body) - if len(body) > 0 { - if err := json.Unmarshal(body, &nColl); err != nil { - return nColl, util.MakeError(err, "GetCollection") - } - } + actor, err := FingerActor(nObj.Actor) + + if err != nil { + return util.MakeError(err, "DeleteRequest") + } + activity.Actor = &actor + objActor, _ := GetActor(nObj.Actor) + followers, err := objActor.GetFollow() + if err != nil { + return util.MakeError(err, "DeleteRequest") } - return nColl, nil + for _, e := range followers { + activity.To = append(activity.To, e.Id) + } + + following, err := objActor.GetFollowing() + if err != nil { + return util.MakeError(err, "DeleteRequest") + } + for _, e := range following { + activity.To = append(activity.To, e.Id) + } + + err = activity.MakeRequestInbox() + + return util.MakeError(err, "DeleteRequest") +} + +func (obj ObjectBase) DeleteReported() error { + query := `delete from reported where id=$1` + _, err := config.DB.Exec(query, obj.Id) + + return util.MakeError(err, "DeleteReported") } func (obj ObjectBase) GetCollectionLocal() (Collection, error) { @@ -646,6 +726,23 @@ func (obj ObjectBase) IsLocal() (bool, error) { return true, nil } +func (obj ObjectBase) IsReplyInThread(id string) (bool, error) { + reqActivity := Activity{Id: obj.InReplyTo[0].Id} + coll, _, err := reqActivity.CheckValid() + + if err != nil { + return false, util.MakeError(err, "IsReplyInThread") + } + + for _, e := range coll.OrderedItems[0].Replies.OrderedItems { + if e.Id == id { + return true, nil + } + } + + return false, nil +} + //TODO break this off into seperate for Cache func (obj ObjectBase) MarkSensitive(sensitive bool) error { var query = `update activitystream set sensitive=$1 where id=$2` @@ -1151,6 +1248,50 @@ func (obj ObjectBase) WriteReply() error { return nil } +func (obj ObjectBase) WriteReplyCache() error { + for i, e := range obj.InReplyTo { + res, err := obj.InReplyTo[0].IsReplyInThread(e.Id) + if err != nil { + return util.MakeError(err, "WriteReplyCache") + } + + if i == 0 || res { + query := `select id from replies where id=$1` + + rows, err := config.DB.Query(query, obj.Id) + if err != nil { + return util.MakeError(err, "WriteReplyCache") + } + defer rows.Close() + + var id string + rows.Next() + err = rows.Scan(&id) + if err != nil { + return util.MakeError(err, "WriteReplyCache") + } else if id != "" { + return nil // TODO: error? + } + + query = `insert into cachereplies (id, inreplyto) values ($1, $2)` + + _, err = config.DB.Exec(query, obj.Id, e.Id) + if err != nil { + return util.MakeError(err, "WriteReplyCache") + } + } + } + + if len(obj.InReplyTo) < 1 { + query := `insert into cachereplies (id, inreplyto) values ($1, $2)` + + _, err := config.DB.Exec(query, obj.Id, "") + return util.MakeError(err, "WriteReplyCache") + } + + return nil +} + func (obj ObjectBase) WriteReplyLocal(replyto string) error { var nID string @@ -1179,7 +1320,7 @@ func (obj ObjectBase) WriteReplyLocal(replyto string) error { func (obj ObjectBase) WriteObjectToCache() (ObjectBase, error) { if isBlacklisted, err := util.IsPostBlacklist(obj.Content); err != nil || isBlacklisted { - fmt.Println("\n\nBlacklist post blocked\n\n") + config.Log.Println("\n\nBlacklist post blocked\n\n") return obj, util.MakeError(err, "WriteObjectToCache") } @@ -1243,7 +1384,7 @@ func (obj ObjectBase) WriteWithAttachment(attachment ObjectBase) { _, e := config.DB.Exec(query, obj.Id, obj.Type, obj.Name, obj.Content, attachment.Id, obj.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") + config.Log.Println("error inserting new activity with attachment") panic(e) } } diff --git a/activitypub/pem.go b/activitypub/pem.go index 408be0c..f99643e 100644 --- a/activitypub/pem.go +++ b/activitypub/pem.go @@ -6,7 +6,6 @@ import ( "crypto/x509" "encoding/pem" "errors" - "fmt" "io/ioutil" "os" "regexp" @@ -72,7 +71,7 @@ func CreatePem(actor Actor) error { return StorePemToDB(actor) } - fmt.Println(`Created PEM keypair for the "` + actor.Name + `" board. Please keep in mind that + config.Log.Println(`Created PEM keypair for the "` + actor.Name + `" board. Please keep in mind that the PEM key is crucial in identifying yourself as the legitimate owner of the board, so DO NOT LOSE IT!!! If you lose it, YOU WILL LOSE ACCESS TO YOUR BOARD!`) @@ -123,7 +122,7 @@ func CreatePublicKeyFromPrivate(actor *Actor, publicKeyPem string) error { return util.MakeError(err, "CreatePublicKeyFromPrivate") } } else { - fmt.Println(`\nUnable to locate private key from public key generation. Now, + config.Log.Println(`\nUnable to locate private key from public key generation. 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 diff --git a/activitypub/util.go b/activitypub/util.go index 7aee666..9dfa1ae 100644 --- a/activitypub/util.go +++ b/activitypub/util.go @@ -19,6 +19,18 @@ import ( // 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 CreateAttachmentObject(file multipart.File, header *multipart.FileHeader) ([]ObjectBase, *os.File, error) { contentType, err := util.GetFileContentType(file) if err != nil { diff --git a/activitypub/webfinger.go b/activitypub/webfinger.go new file mode 100644 index 0000000..276a791 --- /dev/null +++ b/activitypub/webfinger.go @@ -0,0 +1,180 @@ +package activitypub + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "strings" + "time" + + "github.com/FChannel0/FChannel-Server/config" + "github.com/FChannel0/FChannel-Server/util" +) + +type Webfinger struct { + Subject string `json:"subject,omitempty"` + Links []WebfingerLink `json:"links,omitempty"` +} + +type WebfingerLink struct { + Rel string `json:"rel,omitempty"` + Type string `json:"type,omitempty"` + Href string `json:"href,omitempty"` +} + +func GetActor(id string) (Actor, error) { + var respActor Actor + + if id == "" { + return respActor, nil + } + + actor, instance := GetActorAndInstance(id) + + if ActorCache[actor+"@"+instance].Id != "" { + respActor = ActorCache[actor+"@"+instance] + return respActor, nil + } + + req, err := http.NewRequest("GET", strings.TrimSpace(id), nil) + if err != nil { + return respActor, util.MakeError(err, "GetActor") + } + + req.Header.Set("Accept", config.ActivityStreams) + + resp, err := util.RouteProxy(req) + + if err != nil { + return respActor, util.MakeError(err, "GetActor") + } + + defer resp.Body.Close() + body, _ := ioutil.ReadAll(resp.Body) + + if err := json.Unmarshal(body, &respActor); err != nil { + return respActor, util.MakeError(err, "GetActor") + } + + ActorCache[actor+"@"+instance] = respActor + + return respActor, nil +} + +//looks for actor with pattern of board@instance +func FingerActor(path string) (Actor, error) { + var nActor Actor + + actor, instance := GetActorAndInstance(path) + + if actor == "" && instance == "" { + return nActor, nil + } + + if ActorCache[actor+"@"+instance].Id != "" { + nActor = ActorCache[actor+"@"+instance] + } else { + resp, _ := FingerRequest(actor, instance) + + if resp != nil && resp.StatusCode == 200 { + defer resp.Body.Close() + + body, _ := ioutil.ReadAll(resp.Body) + + if err := json.Unmarshal(body, &nActor); err != nil { + return nActor, util.MakeError(err, "FingerActor") + } + + ActorCache[actor+"@"+instance] = nActor + } + } + + return nActor, nil +} + +func FingerRequest(actor string, instance string) (*http.Response, error) { + acct := "acct:" + actor + "@" + instance + + // TODO: respect https + req, err := http.NewRequest("GET", "http://"+instance+"/.well-known/webfinger?resource="+acct, nil) + + if err != nil { + return nil, util.MakeError(err, "FingerRequest") + } + + resp, err := util.RouteProxy(req) + if err != nil { + return resp, nil + } + + var finger Webfinger + + if resp.StatusCode == 200 { + defer resp.Body.Close() + + body, _ := ioutil.ReadAll(resp.Body) + + if err := json.Unmarshal(body, &finger); err != nil { + return resp, util.MakeError(err, "FingerRequest") + } + } + + if len(finger.Links) > 0 { + for _, e := range finger.Links { + if e.Type == "application/activity+json" { + req, err := http.NewRequest("GET", e.Href, nil) + + if err != nil { + return resp, util.MakeError(err, "FingerRequest") + } + + req.Header.Set("Accept", config.ActivityStreams) + resp, _ := util.RouteProxy(req) + + return resp, nil + } + } + } + + return resp, nil +} + +func AddInstanceToIndexDB(actor string) error { + // TODO: completely disabling this until it is actually reasonable to turn it on + // only actually allow this when it more or less works, i.e. can post, make threads, manage boards, etc + return nil + + //sleep to be sure the webserver is fully initialized + //before making finger request + time.Sleep(15 * time.Second) + + nActor, err := FingerActor(actor) + if err != nil { + return util.MakeError(err, "IsValidActor") + } + + if nActor.Id == "" { + return nil + } + + // TODO: maybe allow different indexes? + reqActivity := Activity{Id: "https://fchan.xyz/followers"} + followers, err := reqActivity.GetCollection() + if err != nil { + return util.MakeError(err, "IsValidActor") + } + + var alreadyIndex = false + for _, e := range followers.Items { + if e.Id == nActor.Id { + alreadyIndex = true + } + } + + if !alreadyIndex { + actor := Actor{Id: "https://fchan.xyz"} + return actor.AddFollower(nActor.Id) + } + + return nil +} -- cgit v1.2.3