transmogrifier.ex 32.2 KB
Newer Older
1
# Pleroma: A lightweight social networking server
kaniini's avatar
kaniini committed
2
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3
4
# SPDX-License-Identifier: AGPL-3.0-only

5
6
7
8
defmodule Pleroma.Web.ActivityPub.Transmogrifier do
  @moduledoc """
  A module to handle coding from internal to wire ActivityPub and back.
  """
Haelwenn's avatar
Haelwenn committed
9
  alias Pleroma.Activity
10
  alias Pleroma.FollowingRelationship
Haelwenn's avatar
Haelwenn committed
11
  alias Pleroma.Object
rinpatch's avatar
rinpatch committed
12
  alias Pleroma.Object.Containment
Haelwenn's avatar
Haelwenn committed
13
  alias Pleroma.Repo
14
  alias Pleroma.User
Haelwenn's avatar
Haelwenn committed
15
16
  alias Pleroma.Web.ActivityPub.ActivityPub
  alias Pleroma.Web.ActivityPub.Utils
lain's avatar
lain committed
17
  alias Pleroma.Web.ActivityPub.Visibility
18
  alias Pleroma.Web.Federator
19
  alias Pleroma.Workers.TransmogrifierWorker
20

lain's avatar
lain committed
21
22
  import Ecto.Query

23
  require Logger
24
  require Pleroma.Constants
25

26
27
28
  @doc """
  Modifies an incoming AP object (mastodon format) to our internal format.
  """
29
  def fix_object(object, options \\ []) do
30
    object
31
    |> strip_internal_fields
32
    |> fix_actor
33
    |> fix_url
34
    |> fix_attachments
lain's avatar
lain committed
35
    |> fix_context
36
    |> fix_in_reply_to(options)
lain's avatar
lain committed
37
    |> fix_emoji
38
    |> fix_tag
39
    |> fix_content_map
40
    |> fix_addressing
41
    |> fix_summary
42
    |> fix_type(options)
43
44
45
  end

  def fix_summary(%{"summary" => nil} = object) do
46
    Map.put(object, "summary", "")
47
48
49
50
51
52
53
  end

  def fix_summary(%{"summary" => _} = object) do
    # summary is present, nothing to do
    object
  end

54
  def fix_summary(object), do: Map.put(object, "summary", "")
55
56

  def fix_addressing_list(map, field) do
57
58
59
60
61
62
63
64
65
    cond do
      is_binary(map[field]) ->
        Map.put(map, field, [map[field]])

      is_nil(map[field]) ->
        Map.put(map, field, [])

      true ->
        map
66
67
68
    end
  end

69
70
71
72
73
  def fix_explicit_addressing(
        %{"to" => to, "cc" => cc} = object,
        explicit_mentions,
        follower_collection
      ) do
74
    explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
75

76
    explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end)
77
78
79

    final_cc =
      (cc ++ explicit_cc)
80
      |> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)
81
82
83
84
85
86
87
      |> Enum.uniq()

    object
    |> Map.put("to", explicit_to)
    |> Map.put("cc", final_cc)
  end

88
  def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), do: object
89

90
91
  # if directMessage flag is set to true, leave the addressing alone
  def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
92

93
  def fix_explicit_addressing(object) do
94
    explicit_mentions = Utils.determine_explicit_mentions(object)
95

96
97
98
99
    %User{follower_address: follower_collection} =
      object
      |> Containment.get_actor()
      |> User.get_cached_by_ap_id()
100

101
102
103
104
105
106
    explicit_mentions =
      explicit_mentions ++
        [
          Pleroma.Constants.as_public(),
          follower_collection
        ]
107

108
    fix_explicit_addressing(object, explicit_mentions, follower_collection)
lain's avatar
lain committed
109
110
  end

111
112
113
114
115
116
117
  # if as:Public is addressed, then make sure the followers collection is also addressed
  # so that the activities will be delivered to local users.
  def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
    recipients = to ++ cc

    if followers_collection not in recipients do
      cond do
118
        Pleroma.Constants.as_public() in cc ->
119
120
121
          to = to ++ [followers_collection]
          Map.put(object, "to", to)

122
        Pleroma.Constants.as_public() in to ->
123
124
125
126
127
128
          cc = cc ++ [followers_collection]
          Map.put(object, "cc", cc)

        true ->
          object
      end
129
    else
130
      object
131
132
133
    end
  end

134
135
  def fix_implicit_addressing(object, _), do: object

136
  def fix_addressing(object) do
Alexander Strizhakov's avatar
Alexander Strizhakov committed
137
    {:ok, %User{} = user} = User.get_or_fetch_by_ap_id(object["actor"])
138
139
    followers_collection = User.ap_followers(user)

140
    object
141
142
143
144
    |> fix_addressing_list("to")
    |> fix_addressing_list("cc")
    |> fix_addressing_list("bto")
    |> fix_addressing_list("bcc")
145
    |> fix_explicit_addressing()
146
    |> fix_implicit_addressing(followers_collection)
lain's avatar
lain committed
147
148
  end

149
  def fix_actor(%{"attributedTo" => actor} = object) do
150
    Map.put(object, "actor", Containment.get_actor(%{"actor" => actor}))
151
152
  end

153
154
155
  def fix_in_reply_to(object, options \\ [])

  def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
156
      when not is_nil(in_reply_to) do
157
    in_reply_to_id = prepare_in_reply_to(in_reply_to)
158
    object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
lain's avatar
lain committed
159

160
    if Federator.allowed_incoming_reply_depth?(options[:depth]) do
161
162
163
164
165
166
167
168
      with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options),
           %Activity{} = _ <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
        object
        |> Map.put("inReplyTo", replied_object.data["id"])
        |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
        |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
        |> Map.put("context", replied_object.data["context"] || object["conversation"])
      else
169
        e ->
feld's avatar
feld committed
170
          Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
171
172
173
174
          object
      end
    else
      object
lain's avatar
lain committed
175
176
    end
  end
lain's avatar
lain committed
177

178
  def fix_in_reply_to(object, _options), do: object
lain's avatar
lain committed
179

180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
  defp prepare_in_reply_to(in_reply_to) do
    cond do
      is_bitstring(in_reply_to) ->
        in_reply_to

      is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
        in_reply_to["id"]

      is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) ->
        Enum.at(in_reply_to, 0)

      true ->
        ""
    end
  end

lain's avatar
lain committed
196
  def fix_context(object) do
Haelwenn's avatar
Haelwenn committed
197
198
    context = object["context"] || object["conversation"] || Utils.generate_context_id()

lain's avatar
lain committed
199
    object
Haelwenn's avatar
Haelwenn committed
200
201
    |> Map.put("context", context)
    |> Map.put("conversation", context)
lain's avatar
lain committed
202
203
  end

204
  def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
lain's avatar
lain committed
205
    attachments =
206
      Enum.map(attachment, fn data ->
207
208
209
210
211
212
213
        media_type = data["mediaType"] || data["mimeType"]
        href = data["url"] || data["href"]
        url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}]

        data
        |> Map.put("mediaType", media_type)
        |> Map.put("url", url)
lain's avatar
lain committed
214
      end)
lain's avatar
lain committed
215

216
    Map.put(object, "attachment", attachments)
217
218
  end

219
  def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do
Maksim's avatar
Maksim committed
220
221
222
    object
    |> Map.put("attachment", [attachment])
    |> fix_attachments()
223
224
  end

225
  def fix_attachments(object), do: object
226

227
  def fix_url(%{"url" => url} = object) when is_map(url) do
228
    Map.put(object, "url", url["href"])
229
230
  end

231
232
233
  def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
    first_element = Enum.at(url, 0)

234
    link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
235
236
237
238
239
240
241
242

    object
    |> Map.put("attachment", [first_element])
    |> Map.put("url", link_element["href"])
  end

  def fix_url(%{"type" => object_type, "url" => url} = object)
      when object_type != "Video" and is_list(url) do
243
244
245
246
247
248
249
250
251
    first_element = Enum.at(url, 0)

    url_string =
      cond do
        is_bitstring(first_element) -> first_element
        is_map(first_element) -> first_element["href"] || ""
        true -> ""
      end

252
    Map.put(object, "url", url_string)
253
254
255
256
  end

  def fix_url(object), do: object

257
  def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
lain's avatar
lain committed
258
    emoji =
259
260
      tags
      |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
lain's avatar
lain committed
261
      |> Enum.reduce(%{}, fn data, mapping ->
262
        name = String.trim(data["name"], ":")
lain's avatar
lain committed
263

264
        Map.put(mapping, name, data["icon"]["url"])
lain's avatar
lain committed
265
      end)
lain's avatar
lain committed
266
267
268
269

    # we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats
    emoji = Map.merge(object["emoji"] || %{}, emoji)

270
    Map.put(object, "emoji", emoji)
lain's avatar
lain committed
271
272
  end

273
274
275
276
  def fix_emoji(%{"tag" => %{"type" => "Emoji"} = tag} = object) do
    name = String.trim(tag["name"], ":")
    emoji = %{name => tag["icon"]["url"]}

277
    Map.put(object, "emoji", emoji)
278
279
  end

280
  def fix_emoji(object), do: object
281

282
  def fix_tag(%{"tag" => tag} = object) when is_list(tag) do
lain's avatar
lain committed
283
    tags =
284
      tag
lain's avatar
lain committed
285
286
      |> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
      |> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
287

288
    Map.put(object, "tag", tag ++ tags)
289
290
  end

291
292
  def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do
    combined = [tag, String.slice(hashtag, 1..-1)]
293

294
    Map.put(object, "tag", combined)
295
296
  end

297
298
  def fix_tag(%{"tag" => %{} = tag} = object), do: Map.put(object, "tag", [tag])

299
  def fix_tag(object), do: object
300

301
302
303
304
305
  # content map usually only has one language so this will do for now.
  def fix_content_map(%{"contentMap" => content_map} = object) do
    content_groups = Map.to_list(content_map)
    {_, content} = Enum.at(content_groups, 0)

306
    Map.put(object, "content", content)
307
308
309
310
  end

  def fix_content_map(object), do: object

311
312
  def fix_type(object, options \\ [])

313
314
  def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
      when is_binary(reply_id) do
315
316
    with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
         {:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
317
318
      Map.put(object, "type", "Answer")
    else
319
      _ -> object
320
321
322
    end
  end

323
  def fix_type(object, _), do: object
324

Maksim's avatar
Maksim committed
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
  defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do
    with true <- id =~ "follows",
         %User{local: true} = follower <- User.get_cached_by_ap_id(follower_id),
         %Activity{} = activity <- Utils.fetch_latest_follow(follower, followed) do
      {:ok, activity}
    else
      _ -> {:error, nil}
    end
  end

  defp mastodon_follow_hack(_, _), do: {:error, nil}

  defp get_follow_activity(follow_object, followed) do
    with object_id when not is_nil(object_id) <- Utils.get_ap_id(follow_object),
         {_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(object_id)} do
      {:ok, activity}
    else
      # Can't find the activity. This might a Mastodon 2.3 "Accept"
      {:activity, nil} ->
        mastodon_follow_hack(follow_object, followed)

      _ ->
        {:error, nil}
    end
  end

351
352
353
354
355
356
357
358
359
360
361
  # Reduce the object list to find the reported user.
  defp get_reported(objects) do
    Enum.reduce_while(objects, nil, fn ap_id, _ ->
      with %User{} = user <- User.get_cached_by_ap_id(ap_id) do
        {:halt, user}
      else
        _ -> {:cont, nil}
      end
    end)
  end

362
363
  def handle_incoming(data, options \\ [])

364
365
  # Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
  # with nil ID.
366
  def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data, _options) do
367
368
369
370
    with context <- data["context"] || Utils.generate_context_id(),
         content <- data["content"] || "",
         %User{} = actor <- User.get_cached_by_ap_id(actor),
         # Reduce the object list to find the reported user.
371
         %User{} = account <- get_reported(objects),
372
373
         # Remove the reported user from the object list.
         statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do
374
      %{
375
376
377
378
379
        actor: actor,
        context: context,
        account: account,
        statuses: statuses,
        content: content,
380
        additional: %{"cc" => [account.ap_id]}
381
      }
382
      |> ActivityPub.flag()
383
384
385
    end
  end

386
  # disallow objects with bogus IDs
387
388
  def handle_incoming(%{"id" => nil}, _options), do: :error
  def handle_incoming(%{"id" => ""}, _options), do: :error
389
  # length of https:// = 8, should validate better, but good enough for now.
390
391
  def handle_incoming(%{"id" => id}, _options) when not (is_binary(id) and length(id) > 8),
    do: :error
392

393
394
395
  # TODO: validate those with a Ecto scheme
  # - tags
  # - emoji
396
397
398
399
  def handle_incoming(
        %{"type" => "Create", "object" => %{"type" => objtype} = object} = data,
        options
      )
400
      when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do
401
    actor = Containment.get_actor(data)
402
403
404
405

    data =
      Map.put(data, "actor", actor)
      |> fix_addressing
406

407
    with nil <- Activity.get_create_by_object_ap_id(object["id"]),
408
         {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
409
410
      options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
      object = fix_object(data["object"], options)
411

412
413
414
415
      params = %{
        to: data["to"],
        object: object,
        actor: user,
lain's avatar
lain committed
416
        context: object["conversation"],
417
418
        local: false,
        published: data["published"],
lain's avatar
lain committed
419
420
421
        additional:
          Map.take(data, [
            "cc",
422
            "directMessage",
lain's avatar
lain committed
423
424
            "id"
          ])
425
426
427
428
      }

      ActivityPub.create(params)
    else
lain's avatar
lain committed
429
      %Activity{} = activity -> {:ok, activity}
430
431
432
433
      _e -> :error
    end
  end

434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
  def handle_incoming(
        %{"type" => "Listen", "object" => %{"type" => "Audio"} = object} = data,
        options
      ) do
    actor = Containment.get_actor(data)

    data =
      Map.put(data, "actor", actor)
      |> fix_addressing

    with {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
      options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
      object = fix_object(object, options)

      params = %{
        to: data["to"],
        object: object,
        actor: user,
        context: nil,
        local: false,
        published: data["published"],
        additional: Map.take(data, ["cc", "id"])
      }

      ActivityPub.listen(params)
    else
      _e -> :error
    end
  end

lain's avatar
lain committed
464
  def handle_incoming(
465
466
        %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,
        _options
lain's avatar
lain committed
467
      ) do
468
469
470
471
    with %User{local: true} = followed <-
           User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})),
         {:ok, %User{} = follower} <-
           User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),
472
         {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
473
      with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
474
           {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
475
476
           {_, false} <- {:user_locked, User.locked?(followed)},
           {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
477
           {_, {:ok, _}} <-
478
479
             {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
           {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
kaniini's avatar
kaniini committed
480
481
        ActivityPub.accept(%{
          to: [follower.ap_id],
482
          actor: followed,
kaniini's avatar
kaniini committed
483
484
485
          object: data,
          local: true
        })
486
487
      else
        {:user_blocked, true} ->
488
          {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
489
          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
490
491
492
493
494
495
496
497
498

          ActivityPub.reject(%{
            to: [follower.ap_id],
            actor: followed,
            object: data,
            local: true
          })

        {:follow, {:error, _}} ->
499
          {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
500
          {:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
501
502
503
504
505
506
507
508
509
510

          ActivityPub.reject(%{
            to: [follower.ap_id],
            actor: followed,
            object: data,
            local: true
          })

        {:user_locked, true} ->
          :noop
511
      end
lain's avatar
lain committed
512

513
514
      {:ok, activity}
    else
515
516
      _e ->
        :error
517
518
519
    end
  end

520
  def handle_incoming(
521
        %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,
522
        _options
523
      ) do
524
    with actor <- Containment.get_actor(data),
525
         {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
526
         {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
527
         {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
528
         %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
529
         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
530
531
532
533
534
      ActivityPub.accept(%{
        to: follow_activity.data["to"],
        type: "Accept",
        actor: followed,
        object: follow_activity.data["id"],
535
536
        local: false,
        activity_id: id
537
      })
538
539
    else
      _e -> :error
540
541
542
543
    end
  end

  def handle_incoming(
544
        %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => id} = data,
545
        _options
546
      ) do
547
    with actor <- Containment.get_actor(data),
548
         {:ok, %User{} = followed} <- User.get_or_fetch_by_ap_id(actor),
549
         {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
550
         {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
551
         %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
552
         {:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
lain's avatar
lain committed
553
         {:ok, activity} <-
554
           ActivityPub.reject(%{
lain's avatar
lain committed
555
             to: follow_activity.data["to"],
556
             type: "Reject",
557
             actor: followed,
lain's avatar
lain committed
558
             object: follow_activity.data["id"],
559
560
             local: false,
             activity_id: id
lain's avatar
lain committed
561
           }) do
562
      {:ok, activity}
563
564
    else
      _e -> :error
565
566
567
    end
  end

lain's avatar
lain committed
568
  def handle_incoming(
569
570
        %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data,
        _options
lain's avatar
lain committed
571
      ) do
572
    with actor <- Containment.get_actor(data),
573
         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
574
         {:ok, object} <- get_obj_helper(object_id),
feld's avatar
feld committed
575
         {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
lain's avatar
lain committed
576
577
578
579
580
581
      {:ok, activity}
    else
      _e -> :error
    end
  end

lain's avatar
lain committed
582
  def handle_incoming(
583
584
        %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data,
        _options
lain's avatar
lain committed
585
      ) do
586
    with actor <- Containment.get_actor(data),
587
         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
588
         {:ok, object} <- get_embedded_obj_helper(object_id, actor),
lain's avatar
lain committed
589
         public <- Visibility.is_public?(data),
590
         {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
lain's avatar
lain committed
591
592
593
594
595
596
      {:ok, activity}
    else
      _e -> :error
    end
  end

lain's avatar
lain committed
597
  def handle_incoming(
598
        %{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
599
600
          data,
        _options
601
      )
602
603
604
605
      when object_type in [
             "Person",
             "Application",
             "Service",
606
             "Organization"
607
           ] do
minibikini's avatar
minibikini committed
608
    with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
lain's avatar
lain committed
609
610
      {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)

611
612
      locked = new_user_data[:locked] || false
      attachment = get_in(new_user_data, [:source_data, "attachment"]) || []
613
      invisible = new_user_data[:invisible] || false
614
615
616
617
618

      fields =
        attachment
        |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)
        |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end)
lain's avatar
lain committed
619
620
621

      update_data =
        new_user_data
622
        |> Map.take([:avatar, :banner, :bio, :name, :also_known_as])
623
624
        |> Map.put(:fields, fields)
        |> Map.put(:locked, locked)
625
        |> Map.put(:invisible, invisible)
lain's avatar
lain committed
626
627

      actor
628
      |> User.upgrade_changeset(update_data, true)
lain's avatar
lain committed
629
      |> User.update_and_set_cache()
lain's avatar
lain committed
630

lain's avatar
lain committed
631
632
633
634
635
      ActivityPub.update(%{
        local: false,
        to: data["to"] || [],
        cc: data["cc"] || [],
        object: object,
636
637
        actor: actor_id,
        activity_id: data["id"]
lain's avatar
lain committed
638
      })
lain's avatar
lain committed
639
640
641
642
643
644
645
    else
      e ->
        Logger.error(e)
        :error
    end
  end

646
647
648
649
650
  # TODO: We presently assume that any actor on the same origin domain as the object being
  # deleted has the rights to delete that object.  A better way to validate whether or not
  # the object should be deleted is to refetch the object URI, which should return either
  # an error or a tombstone.  This would allow us to verify that a deletion actually took
  # place.
lain's avatar
lain committed
651
  def handle_incoming(
652
        %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data,
653
        _options
lain's avatar
lain committed
654
      ) do
lain's avatar
lain committed
655
    object_id = Utils.get_ap_id(object_id)
lain's avatar
lain committed
656

657
    with actor <- Containment.get_actor(data),
658
         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
659
         {:ok, object} <- get_obj_helper(object_id),
660
         :ok <- Containment.contain_origin(actor.ap_id, object.data),
661
662
         {:ok, activity} <-
           ActivityPub.delete(object, local: false, activity_id: id, actor: actor.ap_id) do
lain's avatar
lain committed
663
664
      {:ok, activity}
    else
665
666
667
      nil ->
        case User.get_cached_by_ap_id(object_id) do
          %User{ap_id: ^actor} = user ->
668
            User.delete(user)
669
670
671
672
673
674
675

          nil ->
            :error
        end

      _e ->
        :error
lain's avatar
lain committed
676
677
678
    end
  end

679
  def handle_incoming(
680
681
        %{
          "type" => "Undo",
682
          "object" => %{"type" => "Announce", "object" => object_id},
Maksim's avatar
Maksim committed
683
          "actor" => _actor,
684
          "id" => id
685
686
        } = data,
        _options
687
      ) do
688
    with actor <- Containment.get_actor(data),
689
         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
690
         {:ok, object} <- get_obj_helper(object_id),
691
         {:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
692
693
      {:ok, activity}
    else
Thog's avatar
Thog committed
694
      _e -> :error
695
696
697
    end
  end

normandy's avatar
normandy committed
698
699
700
701
702
703
  def handle_incoming(
        %{
          "type" => "Undo",
          "object" => %{"type" => "Follow", "object" => followed},
          "actor" => follower,
          "id" => id
704
705
        } = _data,
        _options
normandy's avatar
normandy committed
706
      ) do
normandy's avatar
normandy committed
707
    with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
708
         {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
normandy's avatar
normandy committed
709
         {:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do
normandy's avatar
normandy committed
710
711
712
      User.unfollow(follower, followed)
      {:ok, activity}
    else
Maksim's avatar
Maksim committed
713
      _e -> :error
normandy's avatar
normandy committed
714
715
716
    end
  end

normandy's avatar
normandy committed
717
718
719
720
721
722
  def handle_incoming(
        %{
          "type" => "Undo",
          "object" => %{"type" => "Block", "object" => blocked},
          "actor" => blocker,
          "id" => id
723
724
        } = _data,
        _options
normandy's avatar
normandy committed
725
      ) do
726
    with %User{local: true} = blocked <- User.get_cached_by_ap_id(blocked),
727
         {:ok, %User{} = blocker} <- User.get_or_fetch_by_ap_id(blocker),
normandy's avatar
normandy committed
728
         {:ok, activity} <- ActivityPub.unblock(blocker, blocked, id, false) do
normandy's avatar
normandy committed
729
      User.unblock(blocker, blocked)
normandy's avatar
normandy committed
730
731
      {:ok, activity}
    else
Maksim's avatar
Maksim committed
732
      _e -> :error
normandy's avatar
normandy committed
733
734
735
    end
  end

736
  def handle_incoming(
737
738
        %{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
        _options
739
      ) do
740
    with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
0x1C3B00DA's avatar
0x1C3B00DA committed
741
         {:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
normandy's avatar
normandy committed
742
         {:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
743
      User.unfollow(blocker, blocked)
744
      User.block(blocker, blocked)
normandy's avatar
normandy committed
745
746
      {:ok, activity}
    else
Maksim's avatar
Maksim committed
747
      _e -> :error
normandy's avatar
normandy committed
748
749
    end
  end
750

Thog's avatar
Thog committed
751
752
753
754
  def handle_incoming(
        %{
          "type" => "Undo",
          "object" => %{"type" => "Like", "object" => object_id},
Maksim's avatar
Maksim committed
755
          "actor" => _actor,
Thog's avatar
Thog committed
756
          "id" => id
757
758
        } = data,
        _options
Thog's avatar
Thog committed
759
      ) do
760
    with actor <- Containment.get_actor(data),
761
         {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
762
         {:ok, object} <- get_obj_helper(object_id),
Thog's avatar
Thog committed
763
764
765
         {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
      {:ok, activity}
    else
Thog's avatar
Thog committed
766
      _e -> :error
Thog's avatar
Thog committed
767
768
769
    end
  end

770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
  # For Undos that don't have the complete object attached, try to find it in our database.
  def handle_incoming(
        %{
          "type" => "Undo",
          "object" => object
        } = activity,
        options
      )
      when is_binary(object) do
    with %Activity{data: data} <- Activity.get_by_ap_id(object) do
      activity
      |> Map.put("object", data)
      |> handle_incoming(options)
    else
      _e -> :error
    end
  end

788
  def handle_incoming(_, _), do: :error
789

790
  @spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
791
  def get_obj_helper(id, options \\ []) do
Maksim's avatar
Maksim committed
792
793
794
    case Object.normalize(id, true, options) do
      %Object{} = object -> {:ok, object}
      _ -> nil
795
    end
796
797
  end

798
  @spec get_embedded_obj_helper(String.t() | Object.t(), User.t()) :: {:ok, Object.t()} | nil
Thibaut Girka's avatar
Thibaut Girka committed
799
  def get_embedded_obj_helper(%{"attributedTo" => attributed_to, "id" => object_id} = data, %User{
800
801
        ap_id: ap_id
      })
Thibaut Girka's avatar
Thibaut Girka committed
802
      when attributed_to == ap_id do
803
804
805
806
807
    with {:ok, activity} <-
           handle_incoming(%{
             "type" => "Create",
             "to" => data["to"],
             "cc" => data["cc"],
Thibaut Girka's avatar
Thibaut Girka committed
808
             "actor" => attributed_to,
809
810
811
812
813
814
815
816
817
818
819
820
             "object" => data
           }) do
      {:ok, Object.normalize(activity)}
    else
      _ -> get_obj_helper(object_id)
    end
  end

  def get_embedded_obj_helper(object_id, _) do
    get_obj_helper(object_id)
  end

821
822
823
824
  def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
    with false <- String.starts_with?(in_reply_to, "http"),
         {:ok, %{data: replied_to_object}} <- get_obj_helper(in_reply_to) do
      Map.put(object, "inReplyTo", replied_to_object["external_url"] || in_reply_to)
825
826
827
828
    else
      _e -> object
    end
  end
lain's avatar
lain committed
829

830
831
832
  def set_reply_to_uri(obj), do: obj

  # Prepares the object of an outgoing create activity.
lain's avatar
lain committed
833
834
  def prepare_object(object) do
    object
lain's avatar
lain committed
835
    |> set_sensitive
lain's avatar
lain committed
836
    |> add_hashtags
lain's avatar
lain committed
837
    |> add_mention_tags
lain's avatar
lain committed
838
    |> add_emoji_tags
lain's avatar
lain committed
839
    |> add_attributed_to
lain's avatar
lain committed
840
    |> prepare_attachments
lain's avatar
lain committed
841
    |> set_conversation
842
    |> set_reply_to_uri
843
844
    |> strip_internal_fields
    |> strip_internal_tags
845
    |> set_type
lain's avatar
lain committed
846
847
  end

feld's avatar
feld committed
848
849
850
851
  #  @doc
  #  """
  #  internal -> Mastodon
  #  """
lain's avatar
lain committed
852

853
854
  def prepare_outgoing(%{"type" => activity_type, "object" => object_id} = data)
      when activity_type in ["Create", "Listen"] do
lain's avatar
lain committed
855
    object =
minibikini's avatar
minibikini committed
856
857
858
      object_id
      |> Object.normalize()
      |> Map.get(:data)
lain's avatar
lain committed
859
860
861
862
863
      |> prepare_object

    data =
      data
      |> Map.put("object", object)
lain's avatar
lain committed
864
      |> Map.merge(Utils.make_json_ld_header())
minibikini's avatar
minibikini committed
865
      |> Map.delete("bcc")
lain's avatar
lain committed
866
867
868
869

    {:ok, data}
  end

870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
  def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
    object =
      object_id
      |> Object.normalize()

    data =
      if Visibility.is_private?(object) && object.data["actor"] == ap_id do
        data |> Map.put("object", object |> Map.get(:data) |> prepare_object)
      else
        data |> maybe_fix_object_url
      end

    data =
      data
      |> strip_internal_fields
      |> Map.merge(Utils.make_json_ld_header())
      |> Map.delete("bcc")

    {:ok, data}
  end

kaniini's avatar
kaniini committed
891
892
893
  # Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
  # because of course it does.
  def prepare_outgoing(%{"type" => "Accept"} = data) do
894
    with follow_activity <- Activity.normalize(data["object"]) do
kaniini's avatar
kaniini committed
895
896
897
898
899
900
901
902
903
904
      object = %{
        "actor" => follow_activity.actor,
        "object" => follow_activity.data["object"],
        "id" => follow_activity.data["id"],
        "type" => "Follow"
      }

      data =
        data
        |> Map.put("object", object)
lain's avatar
lain committed
905
        |> Map.merge(Utils.make_json_ld_header())
kaniini's avatar
kaniini committed
906
907
908
909
910

      {:ok, data}
    end
  end

911
  def prepare_outgoing(%{"type" => "Reject"} = data) do
912
    with follow_activity <- Activity.normalize(data["object"]) do
913
914
915
916
917
918
919
920
921
922
      object = %{
        "actor" => follow_activity.actor,
        "object" => follow_activity.data["object"],
        "id" => follow_activity.data["id"],
        "type" => "Follow"
      }

      data =
        data
        |> Map.put("object", object)
lain's avatar
lain committed
923
        |> Map.merge(Utils.make_json_ld_header())
924
925
926
927
928

      {:ok, data}
    end
  end

feld's avatar
feld committed
929
  def prepare_outgoing(%{"type" => _type} = data) do
lain's avatar
lain committed
930
931
    data =
      data