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ů.
 
 
 
 
 

955 řádky
25 KiB

  1. /*
  2. * Copyright © 2018-2021 Musing Studio 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"
  19. "net/http"
  20. "net/http/httputil"
  21. "net/url"
  22. "path/filepath"
  23. "strconv"
  24. "strings"
  25. "time"
  26. "github.com/gorilla/mux"
  27. "github.com/writeas/activity/streams"
  28. "github.com/writeas/activityserve"
  29. "github.com/writeas/httpsig"
  30. "github.com/writeas/impart"
  31. "github.com/writeas/web-core/activitypub"
  32. "github.com/writeas/web-core/activitystreams"
  33. "github.com/writeas/web-core/id"
  34. "github.com/writeas/web-core/log"
  35. "github.com/writeas/web-core/silobridge"
  36. )
  37. const (
  38. // TODO: delete. don't use this!
  39. apCustomHandleDefault = "blog"
  40. apCacheTime = time.Minute
  41. )
  42. var instanceColl *Collection
  43. func initActivityPub(app *App) {
  44. ur, _ := url.Parse(app.cfg.App.Host)
  45. instanceColl = &Collection{
  46. ID: 0,
  47. Alias: ur.Host,
  48. Title: ur.Host,
  49. db: app.db,
  50. hostName: app.cfg.App.Host,
  51. }
  52. }
  53. type RemoteUser struct {
  54. ID int64
  55. ActorID string
  56. Inbox string
  57. SharedInbox string
  58. URL string
  59. Handle string
  60. Created time.Time
  61. }
  62. func (ru *RemoteUser) CreatedFriendly() string {
  63. return ru.Created.Format("January 2, 2006")
  64. }
  65. func (ru *RemoteUser) EstimatedHandle() string {
  66. if ru.Handle != "" {
  67. return ru.Handle
  68. }
  69. username := filepath.Base(ru.ActorID)
  70. host, _ := url.Parse(ru.ActorID)
  71. return username + "@" + host.Host
  72. }
  73. func (ru *RemoteUser) AsPerson() *activitystreams.Person {
  74. return &activitystreams.Person{
  75. BaseObject: activitystreams.BaseObject{
  76. Type: "Person",
  77. Context: []interface{}{
  78. activitystreams.Namespace,
  79. },
  80. ID: ru.ActorID,
  81. },
  82. Inbox: ru.Inbox,
  83. Endpoints: activitystreams.Endpoints{
  84. SharedInbox: ru.SharedInbox,
  85. },
  86. }
  87. }
  88. func activityPubClient() *http.Client {
  89. return &http.Client{
  90. Timeout: 15 * time.Second,
  91. }
  92. }
  93. func handleFetchCollectionActivities(app *App, w http.ResponseWriter, r *http.Request) error {
  94. w.Header().Set("Server", serverSoftware)
  95. vars := mux.Vars(r)
  96. alias := vars["alias"]
  97. if alias == "" {
  98. alias = filepath.Base(r.RequestURI)
  99. }
  100. // TODO: enforce visibility
  101. // Get base Collection data
  102. var c *Collection
  103. var err error
  104. if alias == r.Host {
  105. c = instanceColl
  106. } else if app.cfg.App.SingleUser {
  107. c, err = app.db.GetCollectionByID(1)
  108. } else {
  109. c, err = app.db.GetCollection(alias)
  110. }
  111. if err != nil {
  112. return err
  113. }
  114. c.hostName = app.cfg.App.Host
  115. if !c.IsInstanceColl() {
  116. silenced, err := app.db.IsUserSilenced(c.OwnerID)
  117. if err != nil {
  118. log.Error("fetch collection activities: %v", err)
  119. return ErrInternalGeneral
  120. }
  121. if silenced {
  122. return ErrCollectionNotFound
  123. }
  124. }
  125. p := c.PersonObject()
  126. setCacheControl(w, apCacheTime)
  127. return impart.RenderActivityJSON(w, p, http.StatusOK)
  128. }
  129. func handleFetchCollectionOutbox(app *App, w http.ResponseWriter, r *http.Request) error {
  130. w.Header().Set("Server", serverSoftware)
  131. vars := mux.Vars(r)
  132. alias := vars["alias"]
  133. // TODO: enforce visibility
  134. // Get base Collection data
  135. var c *Collection
  136. var err error
  137. if app.cfg.App.SingleUser {
  138. c, err = app.db.GetCollectionByID(1)
  139. } else {
  140. c, err = app.db.GetCollection(alias)
  141. }
  142. if err != nil {
  143. return err
  144. }
  145. silenced, err := app.db.IsUserSilenced(c.OwnerID)
  146. if err != nil {
  147. log.Error("fetch collection outbox: %v", err)
  148. return ErrInternalGeneral
  149. }
  150. if silenced {
  151. return ErrCollectionNotFound
  152. }
  153. c.hostName = app.cfg.App.Host
  154. if app.cfg.App.SingleUser {
  155. if alias != c.Alias {
  156. return ErrCollectionNotFound
  157. }
  158. }
  159. res := &CollectionObj{Collection: *c}
  160. app.db.GetPostsCount(res, false)
  161. accountRoot := c.FederatedAccount()
  162. page := r.FormValue("page")
  163. p, err := strconv.Atoi(page)
  164. if err != nil || p < 1 {
  165. // Return outbox
  166. oc := activitystreams.NewOrderedCollection(accountRoot, "outbox", res.TotalPosts)
  167. return impart.RenderActivityJSON(w, oc, http.StatusOK)
  168. }
  169. // Return outbox page
  170. ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "outbox", res.TotalPosts, p)
  171. ocp.OrderedItems = []interface{}{}
  172. posts, err := app.db.GetPosts(app.cfg, c, p, false, true, false)
  173. for _, pp := range *posts {
  174. pp.Collection = res
  175. o := pp.ActivityObject(app)
  176. a := activitystreams.NewCreateActivity(o)
  177. a.Context = nil
  178. ocp.OrderedItems = append(ocp.OrderedItems, *a)
  179. }
  180. setCacheControl(w, apCacheTime)
  181. return impart.RenderActivityJSON(w, ocp, http.StatusOK)
  182. }
  183. func handleFetchCollectionFollowers(app *App, w http.ResponseWriter, r *http.Request) error {
  184. w.Header().Set("Server", serverSoftware)
  185. vars := mux.Vars(r)
  186. alias := vars["alias"]
  187. // TODO: enforce visibility
  188. // Get base Collection data
  189. var c *Collection
  190. var err error
  191. if app.cfg.App.SingleUser {
  192. c, err = app.db.GetCollectionByID(1)
  193. } else {
  194. c, err = app.db.GetCollection(alias)
  195. }
  196. if err != nil {
  197. return err
  198. }
  199. silenced, err := app.db.IsUserSilenced(c.OwnerID)
  200. if err != nil {
  201. log.Error("fetch collection followers: %v", err)
  202. return ErrInternalGeneral
  203. }
  204. if silenced {
  205. return ErrCollectionNotFound
  206. }
  207. c.hostName = app.cfg.App.Host
  208. accountRoot := c.FederatedAccount()
  209. folls, err := app.db.GetAPFollowers(c)
  210. if err != nil {
  211. return err
  212. }
  213. page := r.FormValue("page")
  214. p, err := strconv.Atoi(page)
  215. if err != nil || p < 1 {
  216. // Return outbox
  217. oc := activitystreams.NewOrderedCollection(accountRoot, "followers", len(*folls))
  218. return impart.RenderActivityJSON(w, oc, http.StatusOK)
  219. }
  220. // Return outbox page
  221. ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "followers", len(*folls), p)
  222. ocp.OrderedItems = []interface{}{}
  223. /*
  224. for _, f := range *folls {
  225. ocp.OrderedItems = append(ocp.OrderedItems, f.ActorID)
  226. }
  227. */
  228. setCacheControl(w, apCacheTime)
  229. return impart.RenderActivityJSON(w, ocp, http.StatusOK)
  230. }
  231. func handleFetchCollectionFollowing(app *App, w http.ResponseWriter, r *http.Request) error {
  232. w.Header().Set("Server", serverSoftware)
  233. vars := mux.Vars(r)
  234. alias := vars["alias"]
  235. // TODO: enforce visibility
  236. // Get base Collection data
  237. var c *Collection
  238. var err error
  239. if app.cfg.App.SingleUser {
  240. c, err = app.db.GetCollectionByID(1)
  241. } else {
  242. c, err = app.db.GetCollection(alias)
  243. }
  244. if err != nil {
  245. return err
  246. }
  247. silenced, err := app.db.IsUserSilenced(c.OwnerID)
  248. if err != nil {
  249. log.Error("fetch collection following: %v", err)
  250. return ErrInternalGeneral
  251. }
  252. if silenced {
  253. return ErrCollectionNotFound
  254. }
  255. c.hostName = app.cfg.App.Host
  256. accountRoot := c.FederatedAccount()
  257. page := r.FormValue("page")
  258. p, err := strconv.Atoi(page)
  259. if err != nil || p < 1 {
  260. // Return outbox
  261. oc := activitystreams.NewOrderedCollection(accountRoot, "following", 0)
  262. return impart.RenderActivityJSON(w, oc, http.StatusOK)
  263. }
  264. // Return outbox page
  265. ocp := activitystreams.NewOrderedCollectionPage(accountRoot, "following", 0, p)
  266. ocp.OrderedItems = []interface{}{}
  267. setCacheControl(w, apCacheTime)
  268. return impart.RenderActivityJSON(w, ocp, http.StatusOK)
  269. }
  270. func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request) error {
  271. w.Header().Set("Server", serverSoftware)
  272. vars := mux.Vars(r)
  273. alias := vars["alias"]
  274. var c *Collection
  275. var err error
  276. if app.cfg.App.SingleUser {
  277. c, err = app.db.GetCollectionByID(1)
  278. } else {
  279. c, err = app.db.GetCollection(alias)
  280. }
  281. if err != nil {
  282. // TODO: return Reject?
  283. return err
  284. }
  285. silenced, err := app.db.IsUserSilenced(c.OwnerID)
  286. if err != nil {
  287. log.Error("fetch collection inbox: %v", err)
  288. return ErrInternalGeneral
  289. }
  290. if silenced {
  291. return ErrCollectionNotFound
  292. }
  293. c.hostName = app.cfg.App.Host
  294. if debugging {
  295. dump, err := httputil.DumpRequest(r, true)
  296. if err != nil {
  297. log.Error("Can't dump: %v", err)
  298. } else {
  299. log.Info("Rec'd! %q", dump)
  300. }
  301. }
  302. var m map[string]interface{}
  303. if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
  304. return err
  305. }
  306. a := streams.NewAccept()
  307. p := c.PersonObject()
  308. var to *url.URL
  309. var isFollow, isUnfollow bool
  310. fullActor := &activitystreams.Person{}
  311. var remoteUser *RemoteUser
  312. res := &streams.Resolver{
  313. FollowCallback: func(f *streams.Follow) error {
  314. isFollow = true
  315. // 1) Use the Follow concrete type here
  316. // 2) Errors are propagated to res.Deserialize call below
  317. m["@context"] = []string{activitystreams.Namespace}
  318. b, _ := json.Marshal(m)
  319. if debugging {
  320. log.Info("Follow: %s", b)
  321. }
  322. _, followID := f.GetId()
  323. if followID == nil {
  324. log.Error("Didn't resolve follow ID")
  325. } else {
  326. aID := c.FederatedAccount() + "#accept-" + id.GenerateFriendlyRandomString(20)
  327. acceptID, err := url.Parse(aID)
  328. if err != nil {
  329. log.Error("Couldn't parse generated Accept URL '%s': %v", aID, err)
  330. }
  331. a.SetId(acceptID)
  332. }
  333. a.AppendObject(f.Raw())
  334. _, to = f.GetActor(0)
  335. obj := f.Raw().GetObjectIRI(0)
  336. a.AppendActor(obj)
  337. // First get actor information
  338. if to == nil {
  339. return fmt.Errorf("No valid `to` string")
  340. }
  341. fullActor, remoteUser, err = getActor(app, to.String())
  342. if err != nil {
  343. return err
  344. }
  345. return impart.RenderActivityJSON(w, m, http.StatusOK)
  346. },
  347. UndoCallback: func(u *streams.Undo) error {
  348. isUnfollow = true
  349. m["@context"] = []string{activitystreams.Namespace}
  350. b, _ := json.Marshal(m)
  351. if debugging {
  352. log.Info("Undo: %s", b)
  353. }
  354. a.AppendObject(u.Raw())
  355. _, to = u.GetActor(0)
  356. // TODO: get actor from object.object, not object
  357. obj := u.Raw().GetObjectIRI(0)
  358. a.AppendActor(obj)
  359. if to != nil {
  360. // Populate fullActor from DB?
  361. remoteUser, err = getRemoteUser(app, to.String())
  362. if err != nil {
  363. if iErr, ok := err.(*impart.HTTPError); ok {
  364. if iErr.Status == http.StatusNotFound {
  365. log.Error("No remoteuser info for Undo event!")
  366. }
  367. }
  368. return err
  369. } else {
  370. fullActor = remoteUser.AsPerson()
  371. }
  372. } else {
  373. log.Error("No to on Undo!")
  374. }
  375. return impart.RenderActivityJSON(w, m, http.StatusOK)
  376. },
  377. }
  378. if err := res.Deserialize(m); err != nil {
  379. // 3) Any errors from #2 can be handled, or the payload is an unknown type.
  380. log.Error("Unable to resolve Follow: %v", err)
  381. if debugging {
  382. log.Error("Map: %s", m)
  383. }
  384. return err
  385. }
  386. go func() {
  387. if to == nil {
  388. if debugging {
  389. log.Error("No `to` value!")
  390. }
  391. return
  392. }
  393. time.Sleep(2 * time.Second)
  394. am, err := a.Serialize()
  395. if err != nil {
  396. log.Error("Unable to serialize Accept: %v", err)
  397. return
  398. }
  399. am["@context"] = []string{activitystreams.Namespace}
  400. err = makeActivityPost(app.cfg.App.Host, p, fullActor.Inbox, am)
  401. if err != nil {
  402. log.Error("Unable to make activity POST: %v", err)
  403. return
  404. }
  405. if isFollow {
  406. t, err := app.db.Begin()
  407. if err != nil {
  408. log.Error("Unable to start transaction: %v", err)
  409. return
  410. }
  411. var followerID int64
  412. if remoteUser != nil {
  413. followerID = remoteUser.ID
  414. } else {
  415. // Add follower locally, since it wasn't found before
  416. res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, url) VALUES (?, ?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox, fullActor.URL)
  417. if err != nil {
  418. // if duplicate key, res will be nil and panic on
  419. // res.LastInsertId below
  420. t.Rollback()
  421. log.Error("Couldn't add new remoteuser in DB: %v\n", err)
  422. return
  423. }
  424. followerID, err = res.LastInsertId()
  425. if err != nil {
  426. t.Rollback()
  427. log.Error("no lastinsertid for followers, rolling back: %v", err)
  428. return
  429. }
  430. // Add in key
  431. _, err = t.Exec("INSERT INTO remoteuserkeys (id, remote_user_id, public_key) VALUES (?, ?, ?)", fullActor.PublicKey.ID, followerID, fullActor.PublicKey.PublicKeyPEM)
  432. if err != nil {
  433. if !app.db.isDuplicateKeyErr(err) {
  434. t.Rollback()
  435. log.Error("Couldn't add follower keys in DB: %v\n", err)
  436. return
  437. }
  438. }
  439. }
  440. // Add follow
  441. _, err = t.Exec("INSERT INTO remotefollows (collection_id, remote_user_id, created) VALUES (?, ?, "+app.db.now()+")", c.ID, followerID)
  442. if err != nil {
  443. if !app.db.isDuplicateKeyErr(err) {
  444. t.Rollback()
  445. log.Error("Couldn't add follower in DB: %v\n", err)
  446. return
  447. }
  448. }
  449. err = t.Commit()
  450. if err != nil {
  451. t.Rollback()
  452. log.Error("Rolling back after Commit(): %v\n", err)
  453. return
  454. }
  455. } else if isUnfollow {
  456. // Remove follower locally
  457. _, 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())
  458. if err != nil {
  459. log.Error("Couldn't remove follower from DB: %v\n", err)
  460. }
  461. }
  462. }()
  463. return nil
  464. }
  465. func makeActivityPost(hostName string, p *activitystreams.Person, url string, m interface{}) error {
  466. log.Info("POST %s", url)
  467. b, err := json.Marshal(m)
  468. if err != nil {
  469. return err
  470. }
  471. r, _ := http.NewRequest("POST", url, bytes.NewBuffer(b))
  472. r.Header.Add("Content-Type", "application/activity+json")
  473. r.Header.Set("User-Agent", ServerUserAgent(hostName))
  474. h := sha256.New()
  475. h.Write(b)
  476. r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil)))
  477. // Sign using the 'Signature' header
  478. privKey, err := activitypub.DecodePrivateKey(p.GetPrivKey())
  479. if err != nil {
  480. return err
  481. }
  482. signer := httpsig.NewSigner(p.PublicKey.ID, privKey, httpsig.RSASHA256, []string{"(request-target)", "date", "host", "digest"})
  483. err = signer.SignSigHeader(r)
  484. if err != nil {
  485. log.Error("Can't sign: %v", err)
  486. }
  487. if debugging {
  488. dump, err := httputil.DumpRequestOut(r, true)
  489. if err != nil {
  490. log.Error("Can't dump: %v", err)
  491. } else {
  492. log.Info("%s", dump)
  493. }
  494. }
  495. resp, err := activityPubClient().Do(r)
  496. if err != nil {
  497. return err
  498. }
  499. if resp != nil && resp.Body != nil {
  500. defer resp.Body.Close()
  501. }
  502. body, err := io.ReadAll(resp.Body)
  503. if err != nil {
  504. return err
  505. }
  506. if debugging {
  507. log.Info("Status : %s", resp.Status)
  508. log.Info("Response: %s", body)
  509. }
  510. return nil
  511. }
  512. func resolveIRI(hostName, url string) ([]byte, error) {
  513. log.Info("GET %s", url)
  514. r, _ := http.NewRequest("GET", url, nil)
  515. r.Header.Add("Accept", "application/activity+json")
  516. r.Header.Set("User-Agent", ServerUserAgent(hostName))
  517. p := instanceColl.PersonObject()
  518. h := sha256.New()
  519. h.Write([]byte{})
  520. r.Header.Add("Digest", "SHA-256="+base64.StdEncoding.EncodeToString(h.Sum(nil)))
  521. // Sign using the 'Signature' header
  522. privKey, err := activitypub.DecodePrivateKey(p.GetPrivKey())
  523. if err != nil {
  524. return nil, err
  525. }
  526. signer := httpsig.NewSigner(p.PublicKey.ID, privKey, httpsig.RSASHA256, []string{"(request-target)", "date", "host", "digest"})
  527. err = signer.SignSigHeader(r)
  528. if err != nil {
  529. log.Error("Can't sign: %v", err)
  530. }
  531. if debugging {
  532. dump, err := httputil.DumpRequestOut(r, true)
  533. if err != nil {
  534. log.Error("Can't dump: %v", err)
  535. } else {
  536. log.Info("%s", dump)
  537. }
  538. }
  539. resp, err := activityPubClient().Do(r)
  540. if err != nil {
  541. return nil, err
  542. }
  543. if resp != nil && resp.Body != nil {
  544. defer resp.Body.Close()
  545. }
  546. body, err := io.ReadAll(resp.Body)
  547. if err != nil {
  548. return nil, err
  549. }
  550. if debugging {
  551. log.Info("Status : %s", resp.Status)
  552. log.Info("Response: %s", body)
  553. }
  554. return body, nil
  555. }
  556. func deleteFederatedPost(app *App, p *PublicPost, collID int64) error {
  557. if debugging {
  558. log.Info("Deleting federated post!")
  559. }
  560. p.Collection.hostName = app.cfg.App.Host
  561. actor := p.Collection.PersonObject(collID)
  562. na := p.ActivityObject(app)
  563. // Add followers
  564. p.Collection.ID = collID
  565. followers, err := app.db.GetAPFollowers(&p.Collection.Collection)
  566. if err != nil {
  567. log.Error("Couldn't delete post (get followers)! %v", err)
  568. return err
  569. }
  570. inboxes := map[string][]string{}
  571. for _, f := range *followers {
  572. inbox := f.SharedInbox
  573. if inbox == "" {
  574. inbox = f.Inbox
  575. }
  576. if _, ok := inboxes[inbox]; ok {
  577. inboxes[inbox] = append(inboxes[inbox], f.ActorID)
  578. } else {
  579. inboxes[inbox] = []string{f.ActorID}
  580. }
  581. }
  582. for si, instFolls := range inboxes {
  583. na.CC = []string{}
  584. na.CC = append(na.CC, instFolls...)
  585. da := activitystreams.NewDeleteActivity(na)
  586. // Make the ID unique to ensure it works in Pleroma
  587. // See: https://git.pleroma.social/pleroma/pleroma/issues/1481
  588. da.ID += "#Delete"
  589. err = makeActivityPost(app.cfg.App.Host, actor, si, da)
  590. if err != nil {
  591. log.Error("Couldn't delete post! %v", err)
  592. }
  593. }
  594. return nil
  595. }
  596. func federatePost(app *App, p *PublicPost, collID int64, isUpdate bool) error {
  597. // If app is private, do not federate
  598. if app.cfg.App.Private {
  599. return nil
  600. }
  601. // Do not federate posts from private or protected blogs
  602. if p.Collection.Visibility == CollPrivate || p.Collection.Visibility == CollProtected {
  603. return nil
  604. }
  605. if debugging {
  606. if isUpdate {
  607. log.Info("Federating updated post!")
  608. } else {
  609. log.Info("Federating new post!")
  610. }
  611. }
  612. actor := p.Collection.PersonObject(collID)
  613. na := p.ActivityObject(app)
  614. // Add followers
  615. p.Collection.ID = collID
  616. followers, err := app.db.GetAPFollowers(&p.Collection.Collection)
  617. if err != nil {
  618. log.Error("Couldn't post! %v", err)
  619. return err
  620. }
  621. log.Info("Followers for %d: %+v", collID, followers)
  622. inboxes := map[string][]string{}
  623. for _, f := range *followers {
  624. inbox := f.SharedInbox
  625. if inbox == "" {
  626. inbox = f.Inbox
  627. }
  628. if _, ok := inboxes[inbox]; ok {
  629. // check if we're already sending to this shared inbox
  630. inboxes[inbox] = append(inboxes[inbox], f.ActorID)
  631. } else {
  632. // add the new shared inbox to the list
  633. inboxes[inbox] = []string{f.ActorID}
  634. }
  635. }
  636. var activity *activitystreams.Activity
  637. // for each one of the shared inboxes
  638. for si, instFolls := range inboxes {
  639. // add all followers from that instance
  640. // to the CC field
  641. na.CC = []string{}
  642. na.CC = append(na.CC, instFolls...)
  643. // create a new "Create" activity
  644. // with our article as object
  645. if isUpdate {
  646. na.Updated = &p.Updated
  647. activity = activitystreams.NewUpdateActivity(na)
  648. } else {
  649. activity = activitystreams.NewCreateActivity(na)
  650. activity.To = na.To
  651. activity.CC = na.CC
  652. }
  653. // and post it to that sharedInbox
  654. err = makeActivityPost(app.cfg.App.Host, actor, si, activity)
  655. if err != nil {
  656. log.Error("Couldn't post! %v", err)
  657. }
  658. }
  659. // re-create the object so that the CC list gets reset and has
  660. // the mentioned users. This might seem wasteful but the code is
  661. // cleaner than adding the mentioned users to CC here instead of
  662. // in p.ActivityObject()
  663. na = p.ActivityObject(app)
  664. for _, tag := range na.Tag {
  665. if tag.Type == "Mention" {
  666. activity = activitystreams.NewCreateActivity(na)
  667. activity.To = na.To
  668. activity.CC = na.CC
  669. // This here might be redundant in some cases as we might have already
  670. // sent this to the sharedInbox of this instance above, but we need too
  671. // much logic to catch this at the expense of the odd extra request.
  672. // I don't believe we'd ever have too many mentions in a single post that this
  673. // could become a burden.
  674. remoteUser, err := getRemoteUser(app, tag.HRef)
  675. if err != nil {
  676. log.Error("Unable to find remote user %s. Skipping: %v", tag.HRef, err)
  677. continue
  678. }
  679. err = makeActivityPost(app.cfg.App.Host, actor, remoteUser.Inbox, activity)
  680. if err != nil {
  681. log.Error("Couldn't post! %v", err)
  682. }
  683. }
  684. }
  685. return nil
  686. }
  687. func getRemoteUser(app *App, actorID string) (*RemoteUser, error) {
  688. u := RemoteUser{ActorID: actorID}
  689. var urlVal, handle sql.NullString
  690. err := app.db.QueryRow("SELECT id, inbox, shared_inbox, url, handle FROM remoteusers WHERE actor_id = ?", actorID).Scan(&u.ID, &u.Inbox, &u.SharedInbox, &urlVal, &handle)
  691. switch {
  692. case err == sql.ErrNoRows:
  693. return nil, impart.HTTPError{http.StatusNotFound, "No remote user with that ID."}
  694. case err != nil:
  695. log.Error("Couldn't get remote user %s: %v", actorID, err)
  696. return nil, err
  697. }
  698. u.URL = urlVal.String
  699. u.Handle = handle.String
  700. return &u, nil
  701. }
  702. // getRemoteUserFromHandle retrieves the profile page of a remote user
  703. // from the @user@server.tld handle
  704. func getRemoteUserFromHandle(app *App, handle string) (*RemoteUser, error) {
  705. u := RemoteUser{Handle: handle}
  706. var urlVal sql.NullString
  707. err := app.db.QueryRow("SELECT id, actor_id, inbox, shared_inbox, url FROM remoteusers WHERE handle = ?", handle).Scan(&u.ID, &u.ActorID, &u.Inbox, &u.SharedInbox, &urlVal)
  708. switch {
  709. case err == sql.ErrNoRows:
  710. return nil, ErrRemoteUserNotFound
  711. case err != nil:
  712. log.Error("Couldn't get remote user %s: %v", handle, err)
  713. return nil, err
  714. }
  715. u.URL = urlVal.String
  716. return &u, nil
  717. }
  718. func getActor(app *App, actorIRI string) (*activitystreams.Person, *RemoteUser, error) {
  719. log.Info("Fetching actor %s locally", actorIRI)
  720. actor := &activitystreams.Person{}
  721. remoteUser, err := getRemoteUser(app, actorIRI)
  722. if err != nil {
  723. if iErr, ok := err.(impart.HTTPError); ok {
  724. if iErr.Status == http.StatusNotFound {
  725. // Fetch remote actor
  726. log.Info("Not found; fetching actor %s remotely", actorIRI)
  727. actorResp, err := resolveIRI(app.cfg.App.Host, actorIRI)
  728. if err != nil {
  729. log.Error("Unable to get actor! %v", err)
  730. return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't fetch actor."}
  731. }
  732. if err := unmarshalActor(actorResp, actor); err != nil {
  733. log.Error("Unable to unmarshal actor! %v", err)
  734. return nil, nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't parse actor."}
  735. }
  736. } else {
  737. return nil, nil, err
  738. }
  739. } else {
  740. return nil, nil, err
  741. }
  742. } else {
  743. actor = remoteUser.AsPerson()
  744. }
  745. return actor, remoteUser, nil
  746. }
  747. func GetProfileURLFromHandle(app *App, handle string) (string, error) {
  748. handle = strings.TrimLeft(handle, "@")
  749. actorIRI := ""
  750. parts := strings.Split(handle, "@")
  751. if len(parts) != 2 {
  752. return "", fmt.Errorf("invalid handle format")
  753. }
  754. domain := parts[1]
  755. // Check non-AP instances
  756. if siloProfileURL := silobridge.Profile(parts[0], domain); siloProfileURL != "" {
  757. return siloProfileURL, nil
  758. }
  759. remoteUser, err := getRemoteUserFromHandle(app, handle)
  760. if err != nil {
  761. // can't find using handle in the table but the table may already have this user without
  762. // handle from a previous version
  763. // TODO: Make this determination. We should know whether a user exists without a handle, or doesn't exist at all
  764. actorIRI = RemoteLookup(handle)
  765. _, errRemoteUser := getRemoteUser(app, actorIRI)
  766. // if it exists then we need to update the handle
  767. if errRemoteUser == nil {
  768. _, err := app.db.Exec("UPDATE remoteusers SET handle = ? WHERE actor_id = ?", handle, actorIRI)
  769. if err != nil {
  770. log.Error("Couldn't update handle '%s' for user %s", handle, actorIRI)
  771. }
  772. } else {
  773. // this probably means we don't have the user in the table so let's try to insert it
  774. // here we need to ask the server for the inboxes
  775. remoteActor, err := activityserve.NewRemoteActor(actorIRI)
  776. if err != nil {
  777. log.Error("Couldn't fetch remote actor: %v", err)
  778. }
  779. if debugging {
  780. log.Info("Got remote actor: %s %s %s %s %s", actorIRI, remoteActor.GetInbox(), remoteActor.GetSharedInbox(), remoteActor.URL(), handle)
  781. }
  782. _, err = app.db.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox, url, handle) VALUES(?, ?, ?, ?, ?)", actorIRI, remoteActor.GetInbox(), remoteActor.GetSharedInbox(), remoteActor.URL(), handle)
  783. if err != nil {
  784. log.Error("Couldn't insert remote user: %v", err)
  785. return "", err
  786. }
  787. actorIRI = remoteActor.URL()
  788. }
  789. } else if remoteUser.URL == "" {
  790. log.Info("Remote user %s URL empty, fetching", remoteUser.ActorID)
  791. newRemoteActor, err := activityserve.NewRemoteActor(remoteUser.ActorID)
  792. if err != nil {
  793. log.Error("Couldn't fetch remote actor: %v", err)
  794. } else {
  795. _, err := app.db.Exec("UPDATE remoteusers SET url = ? WHERE actor_id = ?", newRemoteActor.URL(), remoteUser.ActorID)
  796. if err != nil {
  797. log.Error("Couldn't update handle '%s' for user %s", handle, actorIRI)
  798. } else {
  799. actorIRI = newRemoteActor.URL()
  800. }
  801. }
  802. } else {
  803. actorIRI = remoteUser.URL
  804. }
  805. return actorIRI, nil
  806. }
  807. // unmarshal actor normalizes the actor response to conform to
  808. // the type Person from github.com/writeas/web-core/activitysteams
  809. //
  810. // some implementations return different context field types
  811. // this converts any non-slice contexts into a slice
  812. func unmarshalActor(actorResp []byte, actor *activitystreams.Person) error {
  813. // FIXME: Hubzilla has an object for the Actor's url: cannot unmarshal object into Go struct field Person.url of type string
  814. // flexActor overrides the Context field to allow
  815. // all valid representations during unmarshal
  816. flexActor := struct {
  817. activitystreams.Person
  818. Context json.RawMessage `json:"@context,omitempty"`
  819. }{}
  820. if err := json.Unmarshal(actorResp, &flexActor); err != nil {
  821. return err
  822. }
  823. actor.Endpoints = flexActor.Endpoints
  824. actor.Followers = flexActor.Followers
  825. actor.Following = flexActor.Following
  826. actor.ID = flexActor.ID
  827. actor.Icon = flexActor.Icon
  828. actor.Inbox = flexActor.Inbox
  829. actor.Name = flexActor.Name
  830. actor.Outbox = flexActor.Outbox
  831. actor.PreferredUsername = flexActor.PreferredUsername
  832. actor.PublicKey = flexActor.PublicKey
  833. actor.Summary = flexActor.Summary
  834. actor.Type = flexActor.Type
  835. actor.URL = flexActor.URL
  836. func(val interface{}) {
  837. switch val.(type) {
  838. case []interface{}:
  839. // already a slice, do nothing
  840. actor.Context = val.([]interface{})
  841. default:
  842. actor.Context = []interface{}{val}
  843. }
  844. }(flexActor.Context)
  845. return nil
  846. }
  847. func setCacheControl(w http.ResponseWriter, ttl time.Duration) {
  848. w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%.0f", ttl.Seconds()))
  849. }