A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

706 lines
18 KiB

  1. /*
  2. * Copyright © 2018-2019 A Bunch Tell LLC.
  3. *
  4. * This file is part of WriteFreely.
  5. *
  6. * WriteFreely is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU Affero General Public License, included
  8. * in the LICENSE file in this source code package.
  9. */
  10. package writefreely
  11. import (
  12. "bytes"
  13. "crypto/sha256"
  14. "database/sql"
  15. "encoding/base64"
  16. "encoding/json"
  17. "fmt"
  18. "io/ioutil"
  19. "net/http"
  20. "net/http/httputil"
  21. "net/url"
  22. "strconv"
  23. "time"
  24. "github.com/gorilla/mux"
  25. "github.com/writeas/activity/streams"
  26. "github.com/writeas/httpsig"
  27. "github.com/writeas/impart"
  28. "github.com/writeas/nerds/store"
  29. "github.com/writeas/web-core/activitypub"
  30. "github.com/writeas/web-core/activitystreams"
  31. "github.com/writeas/web-core/log"
  32. )
  33. const (
  34. // TODO: delete. don't use this!
  35. apCustomHandleDefault = "blog"
  36. )
  37. type RemoteUser struct {
  38. ID int64
  39. ActorID string
  40. Inbox string
  41. SharedInbox string
  42. }
  43. func (ru *RemoteUser) AsPerson() *activitystreams.Person {
  44. return &activitystreams.Person{
  45. BaseObject: activitystreams.BaseObject{
  46. Type: "Person",
  47. Context: []interface{}{
  48. activitystreams.Namespace,
  49. },
  50. ID: ru.ActorID,
  51. },
  52. Inbox: ru.Inbox,
  53. Endpoints: activitystreams.Endpoints{
  54. SharedInbox: ru.SharedInbox,
  55. },
  56. }
  57. }
  58. func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Request) error {
  59. w.Header().Set("Server", serverSoftware)
  60. vars := mux.Vars(r)
  61. alias := vars["alias"]
  62. // TODO: enforce visibility
  63. // Get base Collection data
  64. var c *Collection
  65. var err error
  66. if app.cfg.App.SingleUser {
  67. c, err = app.db.GetCollectionByID(1)
  68. } else {
  69. c, err = app.db.GetCollection(alias)
  70. }
  71. if err != nil {
  72. return err
  73. }
  74. c.hostName = app.cfg.App.Host
  75. p := c.PersonObject()
  76. return impart.RenderActivityJSON(w, p, http.StatusOK)
  77. }
  78. func handleFetchCollectionOutbox(app *App, w http.ResponseWriter, r *http.Request) error {
  79. w.Header().Set("Server", serverSoftware)
  80. vars := mux.Vars(r)
  81. alias := vars["alias"]
  82. // TODO: enforce visibility
  83. // Get base Collection data
  84. var c *Collection
  85. var err error
  86. if app.cfg.App.SingleUser {
  87. c, err = app.db.GetCollectionByID(1)
  88. } else {
  89. c, err = app.db.GetCollection(alias)
  90. }
  91. if err != nil {
  92. return err
  93. }
  94. c.hostName = app.cfg.App.Host
  95. if app.cfg.App.SingleUser {
  96. if alias != c.Alias {
  97. return ErrCollectionNotFound
  98. }
  99. }
  100. res := &CollectionObj{Collection: *c}
  101. app.db.GetPostsCount(res, false)
  102. accountRoot := c.FederatedAccount()
  103. page := r.FormValue("page")
  104. p, err := strconv.Atoi(page)
  105. if err != nil || p < 1 {
  106. // Return outbox
  107. oc := activitystreams.NewOrderedCollection(accountRoot, "outbox", res.TotalPosts)
  108. return impart.RenderActivityJSON(w, oc, http.StatusOK)
  109. }
  110. // Return outbox page
  111. ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "outbox", res.TotalPosts, p)
  112. ocp.OrderedItems = []interface{}{}
  113. posts, err := app.db.GetPosts(app.cfg, c, p, false, true, false)
  114. for _, pp := range *posts {
  115. pp.Collection = res
  116. o := pp.ActivityObject(app.cfg)
  117. a := activitystreams.NewCreateActivity(o)
  118. ocp.OrderedItems = append(ocp.OrderedItems, *a)
  119. }
  120. return impart.RenderActivityJSON(w, ocp, http.StatusOK)
  121. }
  122. func handleFetchCollectionFollowers(app *App, w http.ResponseWriter, r *http.Request) error {
  123. w.Header().Set("Server", serverSoftware)
  124. vars := mux.Vars(r)
  125. alias := vars["alias"]
  126. // TODO: enforce visibility
  127. // Get base Collection data
  128. var c *Collection
  129. var err error
  130. if app.cfg.App.SingleUser {
  131. c, err = app.db.GetCollectionByID(1)
  132. } else {
  133. c, err = app.db.GetCollection(alias)
  134. }
  135. if err != nil {
  136. return err
  137. }
  138. c.hostName = app.cfg.App.Host
  139. accountRoot := c.FederatedAccount()
  140. folls, err := app.db.GetAPFollowers(c)
  141. if err != nil {
  142. return err
  143. }
  144. page := r.FormValue("page")
  145. p, err := strconv.Atoi(page)
  146. if err != nil || p < 1 {
  147. // Return outbox
  148. oc := activitystreams.NewOrderedCollection(accountRoot, "followers", len(*folls))
  149. return impart.RenderActivityJSON(w, oc, http.StatusOK)
  150. }
  151. // Return outbox page
  152. ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "followers", len(*folls), p)
  153. ocp.OrderedItems = []interface{}{}
  154. /*
  155. for _, f := range *folls {
  156. ocp.OrderedItems = append(ocp.OrderedItems, f.ActorID)
  157. }
  158. */
  159. return impart.RenderActivityJSON(w, ocp, http.StatusOK)
  160. }
  161. func handleFetchCollectionFollowing(app *App, w http.ResponseWriter, r *http.Request) error {
  162. w.Header().Set("Server", serverSoftware)
  163. vars := mux.Vars(r)
  164. alias := vars["alias"]
  165. // TODO: enforce visibility
  166. // Get base Collection data
  167. var c *Collection
  168. var err error
  169. if app.cfg.App.SingleUser {
  170. c, err = app.db.GetCollectionByID(1)
  171. } else {
  172. c, err = app.db.GetCollection(alias)
  173. }
  174. if err != nil {
  175. return err
  176. }
  177. c.hostName = app.cfg.App.Host
  178. accountRoot := c.FederatedAccount()
  179. page := r.FormValue("page")
  180. p, err := strconv.Atoi(page)
  181. if err != nil || p < 1 {
  182. // Return outbox
  183. oc := activitystreams.NewOrderedCollection(accountRoot, "following", 0)
  184. return impart.RenderActivityJSON(w, oc, http.StatusOK)
  185. }
  186. // Return outbox page
  187. ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "following", 0, p)
  188. ocp.OrderedItems = []interface{}{}
  189. return impart.RenderActivityJSON(w, ocp, http.StatusOK)
  190. }
  191. func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request) error {
  192. w.Header().Set("Server", serverSoftware)
  193. vars := mux.Vars(r)
  194. alias := vars["alias"]
  195. var c *Collection
  196. var err error
  197. if app.cfg.App.SingleUser {
  198. c, err = app.db.GetCollectionByID(1)
  199. } else {
  200. c, err = app.db.GetCollection(alias)
  201. }
  202. if err != nil {
  203. // TODO: return Reject?
  204. return err
  205. }
  206. c.hostName = app.cfg.App.Host
  207. if debugging {
  208. dump, err := httputil.DumpRequest(r, true)
  209. if err != nil {
  210. log.Error("Can't dump: %v", err)
  211. } else {
  212. log.Info("Rec'd! %q", dump)
  213. }
  214. }
  215. var m map[string]interface{}
  216. if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
  217. return err
  218. }
  219. a := streams.NewAccept()
  220. p := c.PersonObject()
  221. var to *url.URL
  222. var isFollow, isUnfollow bool
  223. fullActor := &activitystreams.Person{}
  224. var remoteUser *RemoteUser
  225. res := &streams.Resolver{
  226. FollowCallback: func(f *streams.Follow) error {
  227. isFollow = true
  228. // 1) Use the Follow concrete type here
  229. // 2) Errors are propagated to res.Deserialize call below
  230. m["@context"] = []string{activitystreams.Namespace}
  231. b, _ := json.Marshal(m)
  232. if debugging {
  233. log.Info("Follow: %s", b)
  234. }
  235. _, followID := f.GetId()
  236. if followID == nil {
  237. log.Error("Didn't resolve follow ID")
  238. } else {
  239. aID := c.FederatedAccount() + "#accept-" + store.GenerateFriendlyRandomString(20)
  240. acceptID, err := url.Parse(aID)
  241. if err != nil {
  242. log.Error("Couldn't parse generated Accept URL '%s': %v", aID, err)
  243. }
  244. a.SetId(acceptID)
  245. }
  246. a.AppendObject(f.Raw())
  247. _, to = f.GetActor(0)
  248. obj := f.Raw().GetObjectIRI(0)
  249. a.AppendActor(obj)
  250. // First get actor information
  251. if to == nil {
  252. return fmt.Errorf("No valid `to` string")
  253. }
  254. fullActor, remoteUser, err = getActor(app, to.String())
  255. if err != nil {
  256. return err
  257. }
  258. return impart.RenderActivityJSON(w, m, http.StatusOK)
  259. },
  260. UndoCallback: func(u *streams.Undo) error {
  261. isUnfollow = true
  262. m["@context"] = []string{activitystreams.Namespace}
  263. b, _ := json.Marshal(m)
  264. if debugging {
  265. log.Info("Undo: %s", b)
  266. }
  267. a.AppendObject(u.Raw())
  268. _, to = u.GetActor(0)
  269. // TODO: get actor from object.object, not object
  270. obj := u.Raw().GetObjectIRI(0)
  271. a.AppendActor(obj)
  272. if to != nil {
  273. // Populate fullActor from DB?
  274. remoteUser, err = getRemoteUser(app, to.String())
  275. if err != nil {
  276. if iErr, ok := err.(*impart.HTTPError); ok {
  277. if iErr.Status == http.StatusNotFound {
  278. log.Error("No remoteuser info for Undo event!")
  279. }
  280. }
  281. return err
  282. } else {
  283. fullActor = remoteUser.AsPerson()
  284. }
  285. } else {
  286. log.Error("No to on Undo!")
  287. }
  288. return impart.RenderActivityJSON(w, m, http.StatusOK)
  289. },
  290. }
  291. if err := res.Deserialize(m); err != nil {
  292. // 3) Any errors from #2 can be handled, or the payload is an unknown type.
  293. log.Error("Unable to resolve Follow: %v", err)
  294. if debugging {
  295. log.Error("Map: %s", m)
  296. }
  297. return err
  298. }
  299. go func() {
  300. time.Sleep(2 * time.Second)
  301. am, err := a.Serialize()
  302. if err != nil {
  303. log.Error("Unable to serialize Accept: %v", err)
  304. return
  305. }
  306. am["@context"] = []string{activitystreams.Namespace}
  307. if to == nil {
  308. log.Error("No to! %v", err)
  309. return
  310. }
  311. err = makeActivityPost(app.cfg.App.Host, p, fullActor.Inbox, am)
  312. if err != nil {
  313. log.Error("Unable to make activity POST: %v", err)
  314. return
  315. }
  316. if isFollow {
  317. t, err := app.db.Begin()
  318. if err != nil {
  319. log.Error("Unable to start transaction: %v", err)
  320. return
  321. }
  322. var followerID int64
  323. if remoteUser != nil {
  324. followerID = remoteUser.ID
  325. } else {
  326. // Add follower locally, since it wasn't found before
  327. res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox) VALUES (?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox)
  328. if err != nil {
  329. if !app.db.isDuplicateKeyErr(err) {
  330. t.Rollback()
  331. log.Error("Couldn't add new remoteuser in DB: %v\n", err)
  332. return
  333. }
  334. }
  335. followerID, err = res.LastInsertId()
  336. if err != nil {
  337. t.Rollback()
  338. log.Error("no lastinsertid for followers, rolling back: %v", err)
  339. return
  340. }
  341. // Add in key
  342. _, err = t.Exec("INSERT INTO remoteuserkeys (id, remote_user_id, public_key) VALUES (?, ?, ?)", fullActor.PublicKey.ID, followerID, fullActor.PublicKey.PublicKeyPEM)
  343. if err != nil {
  344. if !app.db.isDuplicateKeyErr(err) {
  345. t.Rollback()
  346. log.Error("Couldn't add follower keys in DB: %v\n", err)
  347. return
  348. }
  349. }
  350. }
  351. // Add follow
  352. _, err = t.Exec("INSERT INTO remotefollows (collection_id, remote_user_id, created) VALUES (?, ?, "+app.db.now()+")", c.ID, followerID)
  353. if err != nil {
  354. if !app.db.isDuplicateKeyErr(err) {
  355. t.Rollback()
  356. log.Error("Couldn't add follower in DB: %v\n", err)
  357. return
  358. }
  359. }
  360. err = t.Commit()
  361. if err != nil {
  362. t.Rollback()
  363. log.Error("Rolling back after Commit(): %v\n", err)
  364. return
  365. }
  366. } else if isUnfollow {
  367. // Remove follower locally
  368. _, err = app.db.Exec("DELETE FROM remotefollows WHERE collection_id = ? AND remote_user_id = (SELECT id FROM remoteusers WHERE actor_id = ?)", c.ID, to.String())
  369. if err != nil {
  370. log.Error("Couldn't remove follower from DB: %v\n", err)
  371. }
  372. }
  373. }()
  374. return nil
  375. }
  376. func makeActivityPost(hostName string, p *activitystreams.Person, url string, m interface{}) error {
  377. log.Info("POST %s", url)
  378. b, err := json.Marshal(m)
  379. if err != nil {
  380. return err
  381. }
  382. r, _ := http.NewRequest("POST", url, bytes.NewBuffer(b))
  383. r.Header.Add("Content-Type", "application/activity+json")
  384. r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")")
  385. h := sha256.New()
  386. h.Write(b)
  387. r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil)))
  388. // Sign using the 'Signature' header
  389. privKey, err := activitypub.DecodePrivateKey(p.GetPrivKey())
  390. if err != nil {
  391. return err
  392. }
  393. signer := httpsig.NewSigner(p.PublicKey.ID, privKey, httpsig.RSASHA256, []string{"(request-target)", "date", "host", "digest"})
  394. err = signer.SignSigHeader(r)
  395. if err != nil {
  396. log.Error("Can't sign: %v", err)
  397. }
  398. if debugging {
  399. dump, err := httputil.DumpRequestOut(r, true)
  400. if err != nil {
  401. log.Error("Can't dump: %v", err)
  402. } else {
  403. log.Info("%s", dump)
  404. }
  405. }
  406. resp, err := http.DefaultClient.Do(r)
  407. if err != nil {
  408. return err
  409. }
  410. if resp != nil && resp.Body != nil {
  411. defer resp.Body.Close()
  412. }
  413. body, err := ioutil.ReadAll(resp.Body)
  414. if err != nil {
  415. return err
  416. }
  417. if debugging {
  418. log.Info("Status : %s", resp.Status)
  419. log.Info("Response: %s", body)
  420. }
  421. return nil
  422. }
  423. func resolveIRI(hostName, url string) ([]byte, error) {
  424. log.Info("GET %s", url)
  425. r, _ := http.NewRequest("GET", url, nil)
  426. r.Header.Add("Accept", "application/activity+json")
  427. r.Header.Set("User-Agent", "Go ("+serverSoftware+"/"+softwareVer+"; +"+hostName+")")
  428. if debugging {
  429. dump, err := httputil.DumpRequestOut(r, true)
  430. if err != nil {
  431. log.Error("Can't dump: %v", err)
  432. } else {
  433. log.Info("%s", dump)
  434. }
  435. }
  436. resp, err := http.DefaultClient.Do(r)
  437. if err != nil {
  438. return nil, err
  439. }
  440. if resp != nil && resp.Body != nil {
  441. defer resp.Body.Close()
  442. }
  443. body, err := ioutil.ReadAll(resp.Body)
  444. if err != nil {
  445. return nil, err
  446. }
  447. if debugging {
  448. log.Info("Status : %s", resp.Status)
  449. log.Info("Response: %s", body)
  450. }
  451. return body, nil
  452. }
  453. func deleteFederatedPost(app *App, p *PublicPost, collID int64) error {
  454. if debugging {
  455. log.Info("Deleting federated post!")
  456. }
  457. p.Collection.hostName = app.cfg.App.Host
  458. actor := p.Collection.PersonObject(collID)
  459. na := p.ActivityObject(app.cfg)
  460. // Add followers
  461. p.Collection.ID = collID
  462. followers, err := app.db.GetAPFollowers(&p.Collection.Collection)
  463. if err != nil {
  464. log.Error("Couldn't delete post (get followers)! %v", err)
  465. return err
  466. }
  467. inboxes := map[string][]string{}
  468. for _, f := range *followers {
  469. inbox := f.SharedInbox
  470. if inbox == "" {
  471. inbox = f.Inbox
  472. }
  473. if _, ok := inboxes[inbox]; ok {
  474. inboxes[inbox] = append(inboxes[inbox], f.ActorID)
  475. } else {
  476. inboxes[inbox] = []string{f.ActorID}
  477. }
  478. }
  479. for si, instFolls := range inboxes {
  480. na.CC = []string{}
  481. for _, f := range instFolls {
  482. na.CC = append(na.CC, f)
  483. }
  484. err = makeActivityPost(app.cfg.App.Host, actor, si, activitystreams.NewDeleteActivity(na))
  485. if err != nil {
  486. log.Error("Couldn't delete post! %v", err)
  487. }
  488. }
  489. return nil
  490. }
  491. func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error {
  492. if debugging {
  493. if isUpdate {
  494. log.Info("Federating updated post!")
  495. } else {
  496. log.Info("Federating new post!")
  497. }
  498. }
  499. actor := p.Collection.PersonObject(collID)
  500. na := p.ActivityObject(app.cfg)
  501. // Add followers
  502. p.Collection.ID = collID
  503. followers, err := app.db.GetAPFollowers(&p.Collection.Collection)
  504. if err != nil {
  505. log.Error("Couldn't post! %v", err)
  506. return err
  507. }
  508. log.Info("Followers for %d: %+v", collID, followers)
  509. inboxes := map[string][]string{}
  510. for _, f := range *followers {
  511. inbox := f.SharedInbox
  512. if inbox == "" {
  513. inbox = f.Inbox
  514. }
  515. if _, ok := inboxes[inbox]; ok {
  516. inboxes[inbox] = append(inboxes[inbox], f.ActorID)
  517. } else {
  518. inboxes[inbox] = []string{f.ActorID}
  519. }
  520. }
  521. for si, instFolls := range inboxes {
  522. na.CC = []string{}
  523. for _, f := range instFolls {
  524. na.CC = append(na.CC, f)
  525. }
  526. var activity *activitystreams.Activity
  527. if isUpdate {
  528. activity = activitystreams.NewUpdateActivity(na)
  529. } else {
  530. activity = activitystreams.NewCreateActivity(na)
  531. activity.To = na.To
  532. activity.CC = na.CC
  533. }
  534. err = makeActivityPost(app.cfg.App.Host, actor, si, activity)
  535. if err != nil {
  536. log.Error("Couldn't post! %v", err)
  537. }
  538. }
  539. return nil
  540. }
  541. func getRemoteUser(app *App, actorID string) (*RemoteUser, error) {
  542. u := RemoteUser{ActorID: actorID}
  543. err := app.db.QueryRow("SELECT id, inbox, shared_inbox FROM remoteusers WHERE actor_id = ?", actorID).Scan(&u.ID, &u.Inbox, &u.SharedInbox)
  544. switch {
  545. case err == sql.ErrNoRows:
  546. return nil, impart.HTTPError{http.StatusNotFound, "No remote user with that ID."}
  547. case err != nil:
  548. log.Error("Couldn't get remote user %s: %v", actorID, err)
  549. return nil, err
  550. }
  551. return &u, nil
  552. }
  553. func getActor(app *App, actorIRI string) (*activitystreams.Person, *RemoteUser, error) {
  554. log.Info("Fetching actor %s locally", actorIRI)
  555. actor := &activitystreams.Person{}
  556. remoteUser, err := getRemoteUser(app, actorIRI)
  557. if err != nil {
  558. if iErr, ok := err.(impart.HTTPError); ok {
  559. if iErr.Status == http.StatusNotFound {
  560. // Fetch remote actor
  561. log.Info("Not found; fetching actor %s remotely", actorIRI)
  562. actorResp, err := resolveIRI(app.cfg.App.Host, actorIRI)
  563. if err != nil {
  564. log.Error("Unable to get actor! %v", err)
  565. return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't fetch actor."}
  566. }
  567. if err := unmarshalActor(actorResp, actor); err != nil {
  568. log.Error("Unable to unmarshal actor! %v", err)
  569. return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actor."}
  570. }
  571. } else {
  572. return nil, nil, err
  573. }
  574. } else {
  575. return nil, nil, err
  576. }
  577. } else {
  578. actor = remoteUser.AsPerson()
  579. }
  580. return actor, remoteUser, nil
  581. }
  582. // unmarshal actor normalizes the actor response to conform to
  583. // the type Person from github.com/writeas/web-core/activitysteams
  584. //
  585. // some implementations return different context field types
  586. // this converts any non-slice contexts into a slice
  587. func unmarshalActor(actorResp []byte, actor *activitystreams.Person) error {
  588. // FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string
  589. // flexActor overrides the Context field to allow
  590. // all valid representations during unmarshal
  591. flexActor := struct {
  592. activitystreams.Person
  593. Context json.RawMessage `json:"@context,omitempty"`
  594. }{}
  595. if err := json.Unmarshal(actorResp, &flexActor); err != nil {
  596. return err
  597. }
  598. actor.Endpoints = flexActor.Endpoints
  599. actor.Followers = flexActor.Followers
  600. actor.Following = flexActor.Following
  601. actor.ID = flexActor.ID
  602. actor.Icon = flexActor.Icon
  603. actor.Inbox = flexActor.Inbox
  604. actor.Name = flexActor.Name
  605. actor.Outbox = flexActor.Outbox
  606. actor.PreferredUsername = flexActor.PreferredUsername
  607. actor.PublicKey = flexActor.PublicKey
  608. actor.Summary = flexActor.Summary
  609. actor.Type = flexActor.Type
  610. actor.URL = flexActor.URL
  611. func(val interface{}) {
  612. switch val.(type) {
  613. case []interface{}:
  614. // already a slice, do nothing
  615. actor.Context = val.([]interface{})
  616. default:
  617. actor.Context = []interface{}{val}
  618. }
  619. }(flexActor.Context)
  620. return nil
  621. }