activity_pub.ex 22.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
47
48
49
50
51
    if not is_nil(actor) do
      with user <- User.get_cached_by_ap_id(actor),
           nil <- user.info["deactivated"] do
        :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

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

normandy's avatar
normandy committed
285
  def block(blocker, blocked, activity_id \\ nil, local \\ true) do
286
287
288
    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
289

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

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

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

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

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

lain's avatar
lain committed
320
321
322
323
324
325
326
    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
327
      |> restrict_blocked(opts)
lain's avatar
lain committed
328
329
      |> restrict_recipients(recipients, opts["user"])

lain's avatar
lain committed
330
331
332
333
334
335
336
337
338
339
340
341
342
343
    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
344
    Repo.all(query)
lain's avatar
lain committed
345
346
  end

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

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

356
357
358
359
360
361
362
363
364
  @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,
365
      # Are non-direct statuses with no to/cc possible?
366
      where:
lain's avatar
lain committed
367
368
369
370
371
        fragment(
          "not (? && ?)",
          [^public, sender.follower_address],
          activity.recipients
        )
372
373
374
375
376
377
378
379
380
381
    )
  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

382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
  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
401
  defp restrict_since(query, %{"since_id" => since_id}) do
lain's avatar
lain committed
402
    from(activity in query, where: activity.id > ^since_id)
lain's avatar
lain committed
403
  end
lain's avatar
lain committed
404

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

496
497
498
499
500
501
502
503
504
  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

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

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

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

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

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

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

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

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

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

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

571
572
573
574
575
576
577
  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

lain's avatar
lain committed
578
  def upload(file) do
Sir_Boops's avatar
Sir_Boops committed
579
    data = Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media])
lain's avatar
lain committed
580
581
    Repo.insert(%Object{data: data})
  end
lain's avatar
lain committed
582

lain's avatar
lain committed
583
  def user_data_from_user_object(data) do
lain's avatar
lain committed
584
585
586
587
588
589
590
591
592
593
594
595
596
    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
597

598
    locked = data["manuallyApprovesFollowers"] || false
599
600
    data = Transmogrifier.maybe_fix_user_object(data)

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

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

lain's avatar
lain committed
627
628
629
    {:ok, user_data}
  end

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

  def make_user_from_ap_id(ap_id) do
feld's avatar
feld committed
641
    if _user = User.get_by_ap_id(ap_id) do
lain's avatar
lain committed
642
643
644
645
      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
646
      else
lain's avatar
lain committed
647
        e -> {:error, e}
lain's avatar
lain committed
648
      end
lain's avatar
lain committed
649
650
    end
  end
651

lain's avatar
lain committed
652
653
654
  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
655
    else
feld's avatar
feld committed
656
      _e -> {:error, "No AP id in WebFinger"}
lain's avatar
lain committed
657
658
659
    end
  end

660
661
662
663
664
665
666
667
668
669
670
  @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

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

680
681
    public = is_public?(activity)

lain's avatar
lain committed
682
683
684
685
    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}} ->
686
        (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
lain's avatar
lain committed
687
688
      end)
      |> Enum.uniq()
689
      |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)
690

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

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

lain's avatar
lain committed
704
705
706
  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
707

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

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

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

729
730
  # TODO:
  # This will create a Create activity, which we need internally at the moment.
731
732
733
734
  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
735
      Logger.info("Fetching #{id} via AP")
lain's avatar
lain committed
736

lain's avatar
lain committed
737
      with true <- String.starts_with?(id, "http"),
lain's avatar
lain committed
738
739
740
741
742
743
744
745
           {: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
746
           {:ok, data} <- Jason.decode(body),
747
           nil <- Object.normalize(data),
lain's avatar
lain committed
748
749
750
751
752
753
754
           params <- %{
             "type" => "Create",
             "to" => data["to"],
             "cc" => data["cc"],
             "actor" => data["attributedTo"],
             "object" => data
           },
755
           :ok <- Transmogrifier.contain_origin(id, params),
756
           {:ok, activity} <- Transmogrifier.handle_incoming(params) do
757
        {:ok, Object.normalize(activity.data["object"])}
758
      else
759
        {:error, {:reject, nil}} ->
760
761
          {:reject, nil}

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

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

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

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

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

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