activity_pub.ex 23.8 KB
Newer Older
lain's avatar
lain committed
1
defmodule Pleroma.Web.ActivityPub.ActivityPub do
Thog's avatar
Thog committed
2
  alias Pleroma.{Activity, Repo, Object, Upload, User, Notification}
href's avatar
href committed
3
  alias Pleroma.Web.ActivityPub.{Transmogrifier, MRF}
lain's avatar
lain committed
4
  alias Pleroma.Web.WebFinger
lain's avatar
lain committed
5
  alias Pleroma.Web.Federator
6
  alias Pleroma.Web.OStatus
lain's avatar
lain committed
7
  import Ecto.Query
lain's avatar
lain committed
8
  import Pleroma.Web.ActivityPub.Utils
lain's avatar
lain committed
9
  require Logger
lain's avatar
lain committed
10

lain's avatar
lain committed
11
12
  @httpoison Application.get_env(:pleroma, :httpoison)

13
14
  @instance Application.get_env(:pleroma, :instance)

15
16
  # For Announce activities, we filter the recipients based on following status for any actors
  # that match actual users.  See issue #164 for more information about why this is necessary.
17
18
19
20
  defp get_recipients(%{"type" => "Announce"} = data) do
    to = data["to"] || []
    cc = data["cc"] || []
    recipients = to ++ cc
21
22
23
24
25
26
27
28
29
30
31
32
    actor = User.get_cached_by_ap_id(data["actor"])

    recipients
    |> Enum.filter(fn recipient ->
      case User.get_cached_by_ap_id(recipient) do
        nil ->
          true

        user ->
          User.following?(user, actor)
      end
    end)
33
34

    {recipients, to, cc}
35
36
  end

37
38
39
40
41
  defp get_recipients(data) do
    to = data["to"] || []
    cc = data["cc"] || []
    recipients = to ++ cc
    {recipients, to, cc}
lain's avatar
lain committed
42
43
  end

44
  defp check_actor_is_active(actor) do
45
46
    if not is_nil(actor) do
      with user <- User.get_cached_by_ap_id(actor),
scarlett's avatar
scarlett committed
47
           false <- !!user.info["deactivated"] do
48
49
50
51
        :ok
      else
        _e -> :reject
      end
52
53
54
55
56
    else
      :ok
    end
  end

lain's avatar
lain committed
57
  def insert(map, local \\ true) when is_map(map) do
58
    with nil <- Activity.normalize(map),
lain's avatar
lain committed
59
         map <- lazy_put_activity_defaults(map),
60
         :ok <- check_actor_is_active(map["actor"]),
href's avatar
href committed
61
         {:ok, map} <- MRF.filter(map),
lain's avatar
lain committed
62
         :ok <- insert_full_object(map) do
63
      {recipients, _, _} = get_recipients(map)
64

lain's avatar
lain committed
65
66
67
68
69
      {:ok, activity} =
        Repo.insert(%Activity{
          data: map,
          local: local,
          actor: map["actor"],
70
          recipients: recipients
lain's avatar
lain committed
71
72
        })

73
      Notification.create_notifications(activity)
lain's avatar
lain committed
74
      stream_out(activity)
75
      {:ok, activity}
lain's avatar
lain committed
76
77
78
    else
      %Activity{} = activity -> {:ok, activity}
      error -> {:error, error}
lain's avatar
lain committed
79
    end
lain's avatar
lain committed
80
  end
lain's avatar
lain committed
81

lain's avatar
lain committed
82
  def stream_out(activity) do
csaurus's avatar
csaurus committed
83
84
    public = "https://www.w3.org/ns/activitystreams#Public"

lain's avatar
lain committed
85
86
    if activity.data["type"] in ["Create", "Announce"] do
      Pleroma.Web.Streamer.stream("user", activity)
eal's avatar
eal committed
87
      Pleroma.Web.Streamer.stream("list", activity)
lain's avatar
lain committed
88

csaurus's avatar
csaurus committed
89
90
      if Enum.member?(activity.data["to"], public) do
        Pleroma.Web.Streamer.stream("public", activity)
91

csaurus's avatar
csaurus committed
92
93
94
        if activity.local do
          Pleroma.Web.Streamer.stream("public:local", activity)
        end
95

96
97
98
        activity.data["object"]
        |> Map.get("tag", [])
        |> Enum.filter(fn tag -> is_bitstring(tag) end)
99
100
        |> Enum.map(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end)

101
102
103
104
105
106
107
        if activity.data["object"]["attachment"] != [] do
          Pleroma.Web.Streamer.stream("public:media", activity)

          if activity.local do
            Pleroma.Web.Streamer.stream("public:local:media", activity)
          end
        end
csaurus's avatar
csaurus committed
108
109
110
111
112
      else
        if !Enum.member?(activity.data["cc"] || [], public) &&
             !Enum.member?(
               activity.data["to"],
               User.get_by_ap_id(activity.data["actor"]).follower_address
csaurus's avatar
csaurus committed
113
114
             ),
           do: Pleroma.Web.Streamer.stream("direct", activity)
lain's avatar
lain committed
115
      end
lain's avatar
lain committed
116
117
118
    end
  end

lain's avatar
lain committed
119
120
  def create(%{to: to, actor: actor, context: context, object: object} = params) do
    additional = params[:additional] || %{}
lain's avatar
lain committed
121
122
    # only accept false as false value
    local = !(params[:local] == false)
lain's avatar
lain committed
123
124
    published = params[:published]

lain's avatar
lain committed
125
126
127
128
129
    with create_data <-
           make_create_data(
             %{to: to, actor: actor, published: published, context: context, object: object},
             additional
           ),
lain's avatar
lain committed
130
         {:ok, activity} <- insert(create_data, local),
eal's avatar
eal committed
131
         :ok <- maybe_federate(activity),
feld's avatar
feld committed
132
         {:ok, _actor} <- User.increase_note_count(actor) do
133
134
      {:ok, activity}
    end
lain's avatar
lain committed
135
  end
lain's avatar
lain committed
136

137
  def accept(%{to: to, actor: actor, object: object} = params) do
lain's avatar
lain committed
138
139
    # only accept false as false value
    local = !(params[:local] == false)
140
141
142
143
144
145
146
147

    with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object},
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

148
149
150
151
152
153
154
155
156
157
158
  def reject(%{to: to, actor: actor, object: object} = params) do
    # only accept false as false value
    local = !(params[:local] == false)

    with data <- %{"to" => to, "type" => "Reject", "actor" => actor, "object" => object},
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

lain's avatar
lain committed
159
  def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
lain's avatar
lain committed
160
161
162
163
164
165
166
167
168
169
    # only accept false as false value
    local = !(params[:local] == false)

    with data <- %{
           "to" => to,
           "cc" => cc,
           "type" => "Update",
           "actor" => actor,
           "object" => object
         },
lain's avatar
lain committed
170
171
172
173
174
175
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

lain's avatar
lain committed
176
  # TODO: This is weird, maybe we shouldn't check here if we can make the activity.
lain's avatar
lain committed
177
178
179
180
181
182
  def like(
        %User{ap_id: ap_id} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
        local \\ true
      ) do
lain's avatar
lain committed
183
184
185
186
187
188
189
190
191
    with nil <- get_existing_like(ap_id, object),
         like_data <- make_like_data(user, object, activity_id),
         {:ok, activity} <- insert(like_data, local),
         {:ok, object} <- add_like_to_object(activity, object),
         :ok <- maybe_federate(activity) do
      {:ok, activity, object}
    else
      %Activity{} = activity -> {:ok, activity, object}
      error -> {:error, error}
lain's avatar
lain committed
192
    end
lain's avatar
lain committed
193
194
  end

Thog's avatar
Thog committed
195
196
197
198
199
200
201
202
203
204
205
206
207
  def unlike(
        %User{} = actor,
        %Object{} = object,
        activity_id \\ nil,
        local \\ true
      ) do
    with %Activity{} = like_activity <- get_existing_like(actor.ap_id, object),
         unlike_data <- make_unlike_data(actor, like_activity, activity_id),
         {:ok, unlike_activity} <- insert(unlike_data, local),
         {:ok, _activity} <- Repo.delete(like_activity),
         {:ok, object} <- remove_like_from_object(like_activity, object),
         :ok <- maybe_federate(unlike_activity) do
      {:ok, unlike_activity, like_activity, object}
lain's avatar
lain committed
208
209
    else
      _e -> {:ok, object}
lain's avatar
lain committed
210
211
212
    end
  end

lain's avatar
lain committed
213
214
215
216
217
218
  def announce(
        %User{ap_id: _} = user,
        %Object{data: %{"id" => _}} = object,
        activity_id \\ nil,
        local \\ true
      ) do
lain's avatar
lain committed
219
220
    with true <- is_public?(object),
         announce_data <- make_announce_data(user, object, activity_id),
lain's avatar
lain committed
221
222
223
224
225
226
227
         {:ok, activity} <- insert(announce_data, local),
         {:ok, object} <- add_announce_to_object(activity, object),
         :ok <- maybe_federate(activity) do
      {:ok, activity, object}
    else
      error -> {:error, error}
    end
lain's avatar
lain committed
228
229
  end

230
231
232
  def unannounce(
        %User{} = actor,
        %Object{} = object,
233
234
        activity_id \\ nil,
        local \\ true
235
      ) do
normandy's avatar
normandy committed
236
237
    with %Activity{} = announce_activity <- get_existing_announce(actor.ap_id, object),
         unannounce_data <- make_unannounce_data(actor, announce_activity, activity_id),
238
         {:ok, unannounce_activity} <- insert(unannounce_data, local),
normandy's avatar
normandy committed
239
240
241
         :ok <- maybe_federate(unannounce_activity),
         {:ok, _activity} <- Repo.delete(announce_activity),
         {:ok, object} <- remove_announce_from_object(announce_activity, object) do
242
      {:ok, unannounce_activity, object}
normandy's avatar
normandy committed
243
244
245
246
247
    else
      _e -> {:ok, object}
    end
  end

lain's avatar
lain committed
248
249
250
251
252
253
  def follow(follower, followed, activity_id \\ nil, local \\ true) do
    with data <- make_follow_data(follower, followed, activity_id),
         {:ok, activity} <- insert(data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
lain's avatar
lain committed
254
255
  end

normandy's avatar
normandy committed
256
  def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
lain's avatar
lain committed
257
    with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
258
         {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
normandy's avatar
normandy committed
259
         unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
lain's avatar
lain committed
260
         {:ok, activity} <- insert(unfollow_data, local),
normandy's avatar
normandy committed
261
         :ok <- maybe_federate(activity) do
lain's avatar
lain committed
262
263
      {:ok, activity}
    end
lain's avatar
lain committed
264
265
  end

lain's avatar
lain committed
266
267
  def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
    user = User.get_cached_by_ap_id(actor)
lain's avatar
lain committed
268

lain's avatar
lain committed
269
270
271
272
273
274
    data = %{
      "type" => "Delete",
      "actor" => actor,
      "object" => id,
      "to" => [user.follower_address, "https://www.w3.org/ns/activitystreams#Public"]
    }
lain's avatar
lain committed
275

276
    with {:ok, _} <- Object.delete(object),
lain's avatar
lain committed
277
         {:ok, activity} <- insert(data, local),
278
         :ok <- maybe_federate(activity),
feld's avatar
feld committed
279
         {:ok, _actor} <- User.decrease_note_count(user) do
lain's avatar
lain committed
280
281
282
283
      {:ok, activity}
    end
  end

normandy's avatar
normandy committed
284
  def block(blocker, blocked, activity_id \\ nil, local \\ true) do
285
286
287
    ap_config = Application.get_env(:pleroma, :activitypub)
    unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked)
    outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks)
normandy's avatar
normandy committed
288

289
    with true <- unfollow_blocked do
290
      follow_activity = fetch_latest_follow(blocker, blocked)
squidboi's avatar
squidboi committed
291

292
293
294
      if follow_activity do
        unfollow(blocker, blocked, nil, local)
      end
normandy's avatar
normandy committed
295
296
    end

297
298
    with true <- outgoing_blocks,
         block_data <- make_block_data(blocker, blocked, activity_id),
normandy's avatar
normandy committed
299
300
301
         {:ok, activity} <- insert(block_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
302
    else
squidboi's avatar
squidboi committed
303
      _e -> {:ok, nil}
normandy's avatar
normandy committed
304
305
306
    end
  end

normandy's avatar
normandy committed
307
  def unblock(blocker, blocked, activity_id \\ nil, local \\ true) do
normandy's avatar
normandy committed
308
    with %Activity{} = block_activity <- fetch_latest_block(blocker, blocked),
normandy's avatar
normandy committed
309
         unblock_data <- make_unblock_data(blocker, blocked, block_activity, activity_id),
normandy's avatar
normandy committed
310
311
312
313
314
315
         {:ok, activity} <- insert(unblock_data, local),
         :ok <- maybe_federate(activity) do
      {:ok, activity}
    end
  end

316
  def fetch_activities_for_context(context, opts \\ %{}) do
lain's avatar
lain committed
317
318
    public = ["https://www.w3.org/ns/activitystreams#Public"]

lain's avatar
lain committed
319
320
321
322
323
324
325
    recipients =
      if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public

    query = from(activity in Activity)

    query =
      query
lain's avatar
lain committed
326
      |> restrict_blocked(opts)
lain's avatar
lain committed
327
328
      |> restrict_recipients(recipients, opts["user"])

lain's avatar
lain committed
329
330
331
332
333
334
335
336
337
338
339
340
341
342
    query =
      from(
        activity in query,
        where:
          fragment(
            "?->>'type' = ? and ?->>'context' = ?",
            activity.data,
            "Create",
            activity.data,
            ^context
          ),
        order_by: [desc: :id]
      )

lain's avatar
lain committed
343
    Repo.all(query)
lain's avatar
lain committed
344
345
  end

lain's avatar
lain committed
346
  def fetch_public_activities(opts \\ %{}) do
lain's avatar
lain committed
347
    q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
lain's avatar
lain committed
348

lain's avatar
lain committed
349
    q
350
    |> restrict_unlisted()
lain's avatar
lain committed
351
352
    |> Repo.all()
    |> Enum.reverse()
lain's avatar
lain committed
353
354
  end

355
356
357
358
359
360
361
362
363
  @valid_visibilities ~w[direct unlisted public private]

  defp restrict_visibility(query, %{visibility: "direct"}) do
    public = "https://www.w3.org/ns/activitystreams#Public"

    from(
      activity in query,
      join: sender in User,
      on: sender.ap_id == activity.actor,
364
      # Are non-direct statuses with no to/cc possible?
365
      where:
lain's avatar
lain committed
366
367
368
369
370
        fragment(
          "not (? && ?)",
          [^public, sender.follower_address],
          activity.recipients
        )
371
372
373
374
375
376
377
378
379
380
    )
  end

  defp restrict_visibility(_query, %{visibility: visibility})
       when visibility not in @valid_visibilities do
    Logger.error("Could not restrict visibility to #{visibility}")
  end

  defp restrict_visibility(query, _visibility), do: query

381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
  def fetch_user_activities(user, reading_user, params \\ %{}) do
    params =
      params
      |> Map.put("type", ["Create", "Announce"])
      |> Map.put("actor_id", user.ap_id)
      |> Map.put("whole_db", true)

    recipients =
      if reading_user do
        ["https://www.w3.org/ns/activitystreams#Public"] ++
          [reading_user.ap_id | reading_user.following]
      else
        ["https://www.w3.org/ns/activitystreams#Public"]
      end

    fetch_activities(recipients, params)
    |> Enum.reverse()
  end

lain's avatar
lain committed
400
  defp restrict_since(query, %{"since_id" => since_id}) do
lain's avatar
lain committed
401
    from(activity in query, where: activity.id > ^since_id)
lain's avatar
lain committed
402
  end
lain's avatar
lain committed
403

lain's avatar
lain committed
404
  defp restrict_since(query, _), do: query
lain's avatar
lain committed
405

Roger Braun's avatar
Roger Braun committed
406
  defp restrict_tag(query, %{"tag" => tag}) do
lain's avatar
lain committed
407
408
    from(
      activity in query,
Roger Braun's avatar
Roger Braun committed
409
      where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
lain's avatar
lain committed
410
    )
Roger Braun's avatar
Roger Braun committed
411
  end
lain's avatar
lain committed
412

Roger Braun's avatar
Roger Braun committed
413
414
  defp restrict_tag(query, _), do: query

415
416
417
418
419
  defp restrict_to_cc(query, recipients_to, recipients_cc) do
    from(
      activity in query,
      where:
        fragment(
420
421
          "(?->'to' \\?| ?) or (?->'cc' \\?| ?)",
          activity.data,
422
          ^recipients_to,
423
424
          activity.data,
          ^recipients_cc
425
426
427
428
        )
    )
  end

feld's avatar
feld committed
429
  defp restrict_recipients(query, [], _user), do: query
lain's avatar
lain committed
430

lain's avatar
lain committed
431
  defp restrict_recipients(query, recipients, nil) do
lain's avatar
lain committed
432
    from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
lain's avatar
lain committed
433
  end
lain's avatar
lain committed
434

lain's avatar
lain committed
435
  defp restrict_recipients(query, recipients, user) do
lain's avatar
lain committed
436
437
    from(
      activity in query,
lain's avatar
lain committed
438
439
      where: fragment("? && ?", ^recipients, activity.recipients),
      or_where: activity.actor == ^user.ap_id
lain's avatar
lain committed
440
    )
lain's avatar
lain committed
441
  end
lain's avatar
lain committed
442

kaniini's avatar
kaniini committed
443
  defp restrict_limit(query, %{"limit" => limit}) do
lain's avatar
lain committed
444
    from(activity in query, limit: ^limit)
kaniini's avatar
kaniini committed
445
  end
lain's avatar
lain committed
446

kaniini's avatar
kaniini committed
447
448
  defp restrict_limit(query, _), do: query

lain's avatar
lain committed
449
  defp restrict_local(query, %{"local_only" => true}) do
lain's avatar
lain committed
450
    from(activity in query, where: activity.local == true)
lain's avatar
lain committed
451
  end
lain's avatar
lain committed
452

lain's avatar
lain committed
453
  defp restrict_local(query, _), do: query
lain's avatar
lain committed
454

lain's avatar
lain committed
455
  defp restrict_max(query, %{"max_id" => max_id}) do
lain's avatar
lain committed
456
    from(activity in query, where: activity.id < ^max_id)
457
  end
lain's avatar
lain committed
458

lain's avatar
lain committed
459
  defp restrict_max(query, _), do: query
460

lain's avatar
lain committed
461
  defp restrict_actor(query, %{"actor_id" => actor_id}) do
lain's avatar
lain committed
462
    from(activity in query, where: activity.actor == ^actor_id)
463
  end
lain's avatar
lain committed
464

lain's avatar
lain committed
465
  defp restrict_actor(query, _), do: query
466

lain's avatar
lain committed
467
468
469
  defp restrict_type(query, %{"type" => type}) when is_binary(type) do
    restrict_type(query, %{"type" => [type]})
  end
lain's avatar
lain committed
470

471
  defp restrict_type(query, %{"type" => type}) do
lain's avatar
lain committed
472
    from(activity in query, where: fragment("?->>'type' = ANY(?)", activity.data, ^type))
473
  end
lain's avatar
lain committed
474

475
476
  defp restrict_type(query, _), do: query

477
  defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do
lain's avatar
lain committed
478
479
    from(
      activity in query,
480
      where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data)
lain's avatar
lain committed
481
    )
482
  end
lain's avatar
lain committed
483

484
485
  defp restrict_favorited_by(query, _), do: query

eal's avatar
eal committed
486
  defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do
lain's avatar
lain committed
487
488
    from(
      activity in query,
eal's avatar
eal committed
489
      where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[])
lain's avatar
lain committed
490
    )
eal's avatar
eal committed
491
  end
lain's avatar
lain committed
492

eal's avatar
eal committed
493
494
  defp restrict_media(query, _), do: query

495
496
497
498
499
500
501
502
503
  defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
    from(
      activity in query,
      where: fragment("?->'object'->>'inReplyTo' is null", activity.data)
    )
  end

  defp restrict_replies(query, _), do: query

504
  # Only search through last 100_000 activities by default
505
  defp restrict_recent(query, %{"whole_db" => true}), do: query
lain's avatar
lain committed
506

507
  defp restrict_recent(query, _) do
lain's avatar
lain committed
508
    since = (Repo.aggregate(Activity, :max, :id) || 0) - 100_000
509

lain's avatar
lain committed
510
    from(activity in query, where: activity.id > ^since)
511
512
  end

lain's avatar
lain committed
513
514
  defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
    blocks = info["blocks"] || []
515
    domain_blocks = info["domain_blocks"] || []
lain's avatar
lain committed
516
517
518

    from(
      activity in query,
519
      where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
520
      where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks),
eal's avatar
eal committed
521
      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
522
    )
523
  end
lain's avatar
lain committed
524

525
526
  defp restrict_blocked(query, _), do: query

527
528
529
  defp restrict_unlisted(query) do
    from(
      activity in query,
lain's avatar
Format.    
lain committed
530
531
      where:
        fragment(
lain's avatar
lain committed
532
          "not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
lain's avatar
Format.    
lain committed
533
534
535
          activity.data,
          ^["https://www.w3.org/ns/activitystreams#Public"]
        )
536
537
538
    )
  end

lain's avatar
lain committed
539
  def fetch_activities_query(recipients, opts \\ %{}) do
lain's avatar
lain committed
540
541
542
543
544
545
    base_query =
      from(
        activity in Activity,
        limit: 20,
        order_by: [fragment("? desc nulls last", activity.id)]
      )
lain's avatar
lain committed
546

lain's avatar
lain committed
547
    base_query
lain's avatar
lain committed
548
    |> restrict_recipients(recipients, opts["user"])
Roger Braun's avatar
Roger Braun committed
549
    |> restrict_tag(opts)
lain's avatar
lain committed
550
551
    |> restrict_since(opts)
    |> restrict_local(opts)
kaniini's avatar
kaniini committed
552
    |> restrict_limit(opts)
lain's avatar
lain committed
553
554
    |> restrict_max(opts)
    |> restrict_actor(opts)
555
    |> restrict_type(opts)
556
    |> restrict_favorited_by(opts)
557
    |> restrict_recent(opts)
558
    |> restrict_blocked(opts)
eal's avatar
eal committed
559
    |> restrict_media(opts)
560
    |> restrict_visibility(opts)
561
    |> restrict_replies(opts)
lain's avatar
lain committed
562
563
564
565
  end

  def fetch_activities(recipients, opts \\ %{}) do
    fetch_activities_query(recipients, opts)
lain's avatar
lain committed
566
567
    |> Repo.all()
    |> Enum.reverse()
dtluna's avatar
dtluna committed
568
569
  end

570
571
572
573
574
575
576
  def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do
    fetch_activities_query([], opts)
    |> restrict_to_cc(recipients_to, recipients_cc)
    |> Repo.all()
    |> Enum.reverse()
  end

577
578
579
580
581
582
  def upload(file, size_limit \\ nil) do
    with data <-
           Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media], size_limit),
         false <- is_nil(data) do
      Repo.insert(%Object{data: data})
    end
lain's avatar
lain committed
583
  end
lain's avatar
lain committed
584

lain's avatar
lain committed
585
  def user_data_from_user_object(data) do
lain's avatar
lain committed
586
587
588
589
590
591
592
593
594
595
596
597
598
    avatar =
      data["icon"]["url"] &&
        %{
          "type" => "Image",
          "url" => [%{"href" => data["icon"]["url"]}]
        }

    banner =
      data["image"]["url"] &&
        %{
          "type" => "Image",
          "url" => [%{"href" => data["image"]["url"]}]
        }
lain's avatar
lain committed
599

600
    locked = data["manuallyApprovesFollowers"] || false
601
602
    data = Transmogrifier.maybe_fix_user_object(data)

lain's avatar
lain committed
603
604
605
606
607
    user_data = %{
      ap_id: data["id"],
      info: %{
        "ap_enabled" => true,
        "source_data" => data,
608
609
        "banner" => banner,
        "locked" => locked
lain's avatar
lain committed
610
611
612
613
614
615
616
      },
      avatar: avatar,
      name: data["name"],
      follower_address: data["followers"],
      bio: data["summary"]
    }

617
618
619
    # nickname can be nil because of virtual actors
    user_data =
      if data["preferredUsername"] do
kaniini's avatar
kaniini committed
620
621
622
623
624
        Map.put(
          user_data,
          :nickname,
          "#{data["preferredUsername"]}@#{URI.parse(data["id"]).host}"
        )
625
626
627
628
      else
        Map.put(user_data, :nickname, nil)
      end

lain's avatar
lain committed
629
630
631
    {:ok, user_data}
  end

lain's avatar
lain committed
632
  def fetch_and_prepare_user_from_ap_id(ap_id) do
lain's avatar
lain committed
633
    with {:ok, %{status_code: 200, body: body}} <-
634
           @httpoison.get(ap_id, [Accept: "application/activity+json"], follow_redirect: true),
lain's avatar
lain committed
635
         {:ok, data} <- Jason.decode(body) do
lain's avatar
lain committed
636
      user_data_from_user_object(data)
lain's avatar
lain committed
637
    else
638
      e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
lain's avatar
lain committed
639
640
641
642
    end
  end

  def make_user_from_ap_id(ap_id) do
feld's avatar
feld committed
643
    if _user = User.get_by_ap_id(ap_id) do
lain's avatar
lain committed
644
645
646
647
      Transmogrifier.upgrade_user_from_ap_id(ap_id)
    else
      with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
        User.insert_or_update_user(data)
lain's avatar
lain committed
648
      else
lain's avatar
lain committed
649
        e -> {:error, e}
lain's avatar
lain committed
650
      end
lain's avatar
lain committed
651
652
    end
  end
653

lain's avatar
lain committed
654
655
656
  def make_user_from_nickname(nickname) do
    with {:ok, %{"ap_id" => ap_id}} when not is_nil(ap_id) <- WebFinger.finger(nickname) do
      make_user_from_ap_id(ap_id)
lain's avatar
lain committed
657
    else
feld's avatar
feld committed
658
      _e -> {:error, "No AP id in WebFinger"}
lain's avatar
lain committed
659
660
661
    end
  end

662
663
664
665
666
667
668
669
670
671
672
  @quarantined_instances Keyword.get(@instance, :quarantined_instances, [])

  def should_federate?(inbox, public) do
    if public do
      true
    else
      inbox_info = URI.parse(inbox)
      inbox_info.host not in @quarantined_instances
    end
  end

673
  def publish(actor, activity) do
lain's avatar
lain committed
674
675
676
677
678
679
680
    followers =
      if actor.follower_address in activity.recipients do
        {:ok, followers} = User.get_followers(actor)
        followers |> Enum.filter(&(!&1.local))
      else
        []
      end
681

682
683
    public = is_public?(activity)

lain's avatar
lain committed
684
685
686
687
    remote_inboxes =
      (Pleroma.Web.Salmon.remote_users(activity) ++ followers)
      |> Enum.filter(fn user -> User.ap_enabled?(user) end)
      |> Enum.map(fn %{info: %{"source_data" => data}} ->
688
        (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
lain's avatar
lain committed
689
690
      end)
      |> Enum.uniq()
691
      |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
692

lain's avatar
lain committed
693
    {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
lain's avatar
lain committed
694
    json = Jason.encode!(data)
lain's avatar
lain committed
695
696
697
698
699
700
701
702
703

    Enum.each(remote_inboxes, fn inbox ->
      Federator.enqueue(:publish_single_ap, %{
        inbox: inbox,
        json: json,
        actor: actor,
        id: activity.data["id"]
      })
    end)
704
  end
705

lain's avatar
lain committed
706
707
708
  def publish_one(%{inbox: inbox, json: json, actor: actor, id: id}) do
    Logger.info("Federating #{id} to #{inbox}")
    host = URI.parse(inbox).host
lain's avatar
lain committed
709

710
711
    digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())

lain's avatar
lain committed
712
    signature =
713
714
715
716
717
      Pleroma.Web.HTTPSignatures.sign(actor, %{
        host: host,
        "content-length": byte_size(json),
        digest: digest
      })
lain's avatar
lain committed
718
719
720
721

    @httpoison.post(
      inbox,
      json,
722
723
724
725
726
      [
        {"Content-Type", "application/activity+json"},
        {"signature", signature},
        {"digest", digest}
      ],
lain's avatar
lain committed
727
728
      hackney: [pool: :default]
    )
lain's avatar
lain committed
729
730
  end

731
732
  # TODO:
  # This will create a Create activity, which we need internally at the moment.
733
734
735
736
  def fetch_object_from_id(id) do
    if object = Object.get_cached_by_ap_id(id) do
      {:ok, object}
    else
lain's avatar
lain committed
737
      Logger.info("Fetching #{id} via AP")
lain's avatar
lain committed
738

lain's avatar
lain committed
739
      with true <- String.starts_with?(id, "http"),
lain's avatar
lain committed
740
741
742
743
744
745
746
747
           {:ok, %{body: body, status_code: code}} when code in 200..299 <-
             @httpoison.get(
               id,
               [Accept: "application/activity+json"],
               follow_redirect: true,
               timeout: 10000,
               recv_timeout: 20000
             ),
lain's avatar
lain committed
748
           {:ok, data} <- Jason.decode(body),
749
           nil <- Object.normalize(data),
lain's avatar
lain committed
750
751
752
753
754
755
756
           params <- %{
             "type" => "Create",
             "to" => data["to"],
             "cc" => data["cc"],
             "actor" => data["attributedTo"],
             "object" => data
           },
757
           :ok <- Transmogrifier.contain_origin(id, params),
758
           {:ok, activity} <- Transmogrifier.handle_incoming(params) do
759
        {:ok, Object.normalize(activity.data["object"])}
760
      else
761
        {:error, {:reject, nil}} ->
762
763
          {:reject, nil}

lain's avatar
lain committed
764
765
766
        object = %Object{} ->
          {:ok, object}

feld's avatar
feld committed
767
        _e ->
lain's avatar
lain committed
768
          Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
lain's avatar
lain committed
769

lain's avatar
lain committed
770
          case OStatus.fetch_activity_from_url(id) do
771
            {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
lain's avatar
lain committed
772
773
            e -> e
          end
774
775
776
      end
    end
  end
lain's avatar
lain committed
777

lain's avatar
lain committed
778
  def is_public?(activity) do
lain's avatar
lain committed
779
780
    "https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++
                                                         (activity.data["cc"] || []))
lain's avatar
lain committed
781
  end
lain's avatar
lain committed
782
783
784
785

  def visible_for_user?(activity, nil) do
    is_public?(activity)
  end
lain's avatar
lain committed
786

lain's avatar
lain committed
787
788
  def visible_for_user?(activity, user) do
    x = [user.ap_id | user.following]
lain's avatar
lain committed
789
    y = activity.data["to"] ++ (activity.data["cc"] || [])
lain's avatar
lain committed
790
791
    visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))
  end
792
793
794
795
796
797

  # guard
  def entire_thread_visible_for_user?(nil, user), do: false

  # child
  def entire_thread_visible_for_user?(
798
        %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,
799
        user
800
801
      )
      when is_binary(parent_id) do
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
    parent = Activity.get_in_reply_to_activity(tail)
    visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user)
  end

  # root
  def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)

  # filter out broken threads
  def contain_broken_threads(%Activity{} = activity, %User{} = user) do
    entire_thread_visible_for_user?(activity, user)
  end

  # do post-processing on a specific activity
  def contain_activity(%Activity{} = activity, %User{} = user) do
    contain_broken_threads(activity, user)
  end

  # do post-processing on a timeline
  def contain_timeline(timeline, user) do
    timeline
    |> Enum.filter(fn activity ->
      contain_activity(activity, user)
    end)
  end
lain's avatar
lain committed
826
end