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.
 
 
 
 
 

666 lines
16 KiB

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