First
[anni] / lib / pleroma / web / activity_pub / object_validators / create_generic_validator.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 # Code based on CreateChatMessageValidator
6 # NOTES
7 # - doesn't embed, will only get the object id
8 defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
9   use Ecto.Schema
10
11   alias Pleroma.EctoType.ActivityPub.ObjectValidators
12   alias Pleroma.Object
13   alias Pleroma.User
14   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
15   alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
16   alias Pleroma.Web.ActivityPub.Transmogrifier
17
18   import Ecto.Changeset
19
20   @primary_key false
21
22   embedded_schema do
23     quote do
24       unquote do
25         import Elixir.Pleroma.Web.ActivityPub.ObjectValidators.CommonFields
26         message_fields()
27         activity_fields()
28       end
29     end
30
31     field(:expires_at, ObjectValidators.DateTime)
32
33     # Should be moved to object, done for CommonAPI.Utils.make_context
34     field(:context, :string)
35   end
36
37   def cast_data(data, meta \\ []) do
38     data = fix(data, meta)
39
40     %__MODULE__{}
41     |> changeset(data)
42   end
43
44   def cast_and_apply(data) do
45     data
46     |> cast_data
47     |> apply_action(:insert)
48   end
49
50   def cast_and_validate(data, meta \\ []) do
51     data
52     |> cast_data(meta)
53     |> validate_data(meta)
54   end
55
56   def changeset(struct, data) do
57     struct
58     |> cast(data, __schema__(:fields))
59   end
60
61   # CommonFixes.fix_activity_addressing adapted for Create specific behavior
62   defp fix_addressing(data, object) do
63     %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["actor"])
64
65     data
66     |> CommonFixes.cast_and_filter_recipients("to", follower_collection, object["to"])
67     |> CommonFixes.cast_and_filter_recipients("cc", follower_collection, object["cc"])
68     |> CommonFixes.cast_and_filter_recipients("bto", follower_collection, object["bto"])
69     |> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"])
70     |> Transmogrifier.fix_implicit_addressing(follower_collection)
71   end
72
73   def fix(data, meta) do
74     object = meta[:object_data]
75
76     data
77     |> CommonFixes.fix_actor()
78     |> Map.put("context", object["context"])
79     |> fix_addressing(object)
80   end
81
82   defp validate_data(cng, meta) do
83     object = meta[:object_data]
84
85     cng
86     |> validate_required([:actor, :type, :object, :to, :cc])
87     |> validate_inclusion(:type, ["Create"])
88     |> CommonValidations.validate_actor_presence()
89     |> validate_actors_match(object)
90     |> validate_context_match(object)
91     |> validate_addressing_match(object)
92     |> validate_object_nonexistence()
93     |> validate_object_containment()
94   end
95
96   def validate_object_containment(cng) do
97     actor = get_field(cng, :actor)
98
99     cng
100     |> validate_change(:object, fn :object, object_id ->
101       %URI{host: object_id_host} = URI.parse(object_id)
102       %URI{host: actor_host} = URI.parse(actor)
103
104       if object_id_host == actor_host do
105         []
106       else
107         [{:object, "The host of the object id doesn't match with the host of the actor"}]
108       end
109     end)
110   end
111
112   def validate_object_nonexistence(cng) do
113     cng
114     |> validate_change(:object, fn :object, object_id ->
115       if Object.get_cached_by_ap_id(object_id) do
116         [{:object, "The object to create already exists"}]
117       else
118         []
119       end
120     end)
121   end
122
123   def validate_actors_match(cng, object) do
124     attributed_to = object["attributedTo"] || object["actor"]
125
126     cng
127     |> validate_change(:actor, fn :actor, actor ->
128       if actor == attributed_to do
129         []
130       else
131         [{:actor, "Actor doesn't match with object attributedTo"}]
132       end
133     end)
134   end
135
136   def validate_context_match(cng, %{"context" => object_context}) do
137     cng
138     |> validate_change(:context, fn :context, context ->
139       if context == object_context do
140         []
141       else
142         [{:context, "context field not matching between Create and object (#{object_context})"}]
143       end
144     end)
145   end
146
147   def validate_addressing_match(cng, object) do
148     [:to, :cc, :bcc, :bto]
149     |> Enum.reduce(cng, fn field, cng ->
150       object_data = object[to_string(field)]
151
152       validate_change(cng, field, fn field, data ->
153         if data == object_data do
154           []
155         else
156           [{field, "field doesn't match with object (#{inspect(object_data)})"}]
157         end
158       end)
159     end)
160   end
161 end