A clean, Markdown-based publishing platform made for writers. Write together, and build a community. https://writefreely.org
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 

652 řádky
16 KiB

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