1c5b1a059a11f45daaf7794dbc52f14e7c7fb190
[anni] / lib / pleroma / web / activity_pub / object_validators / common_validations.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 defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
6   import Ecto.Changeset
7
8   alias Pleroma.Activity
9   alias Pleroma.Object
10   alias Pleroma.User
11
12   @spec validate_any_presence(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
13   def validate_any_presence(cng, fields) do
14     non_empty =
15       fields
16       |> Enum.map(fn field -> get_field(cng, field) end)
17       |> Enum.any?(fn
18         nil -> false
19         [] -> false
20         _ -> true
21       end)
22
23     if non_empty do
24       cng
25     else
26       fields
27       |> Enum.reduce(cng, fn field, cng ->
28         cng
29         |> add_error(field, "none of #{inspect(fields)} present")
30       end)
31     end
32   end
33
34   @spec validate_actor_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
35   def validate_actor_presence(cng, options \\ []) do
36     field_name = Keyword.get(options, :field_name, :actor)
37
38     cng
39     |> validate_change(field_name, fn field_name, actor ->
40       case User.get_cached_by_ap_id(actor) do
41         %User{is_active: false} ->
42           [{field_name, "user is deactivated"}]
43
44         %User{} ->
45           []
46
47         _ ->
48           [{field_name, "can't find user"}]
49       end
50     end)
51   end
52
53   @spec validate_object_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
54   def validate_object_presence(cng, options \\ []) do
55     field_name = Keyword.get(options, :field_name, :object)
56     allowed_types = Keyword.get(options, :allowed_types, false)
57
58     cng
59     |> validate_change(field_name, fn field_name, object_id ->
60       object = Object.get_cached_by_ap_id(object_id) || Activity.get_by_ap_id(object_id)
61
62       cond do
63         !object ->
64           [{field_name, "can't find object"}]
65
66         object && allowed_types && object.data["type"] not in allowed_types ->
67           [{field_name, "object not in allowed types"}]
68
69         true ->
70           []
71       end
72     end)
73   end
74
75   @spec validate_object_or_user_presence(Ecto.Changeset.t(), keyword()) :: Ecto.Changeset.t()
76   def validate_object_or_user_presence(cng, options \\ []) do
77     field_name = Keyword.get(options, :field_name, :object)
78     options = Keyword.put(options, :field_name, field_name)
79
80     actor_cng =
81       cng
82       |> validate_actor_presence(options)
83
84     object_cng =
85       cng
86       |> validate_object_presence(options)
87
88     if actor_cng.valid?, do: actor_cng, else: object_cng
89   end
90
91   @spec validate_host_match(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
92   def validate_host_match(cng, fields \\ [:id, :actor]) do
93     if same_domain?(cng, fields) do
94       cng
95     else
96       fields
97       |> Enum.reduce(cng, fn field, cng ->
98         cng
99         |> add_error(field, "hosts of #{inspect(fields)} aren't matching")
100       end)
101     end
102   end
103
104   @spec validate_fields_match(Ecto.Changeset.t(), [atom()]) :: Ecto.Changeset.t()
105   def validate_fields_match(cng, fields) do
106     if map_unique?(cng, fields) do
107       cng
108     else
109       fields
110       |> Enum.reduce(cng, fn field, cng ->
111         cng
112         |> add_error(field, "Fields #{inspect(fields)} aren't matching")
113       end)
114     end
115   end
116
117   defp map_unique?(cng, fields, func \\ & &1) do
118     Enum.reduce_while(fields, nil, fn field, acc ->
119       value =
120         cng
121         |> get_field(field)
122         |> func.()
123
124       case {value, acc} do
125         {value, nil} -> {:cont, value}
126         {value, value} -> {:cont, value}
127         _ -> {:halt, false}
128       end
129     end)
130   end
131
132   @spec same_domain?(Ecto.Changeset.t(), [atom()]) :: boolean()
133   def same_domain?(cng, fields \\ [:actor, :object]) do
134     map_unique?(cng, fields, fn value -> URI.parse(value).host end)
135   end
136
137   # This figures out if a user is able to create, delete or modify something
138   # based on the domain and superuser status
139   @spec validate_modification_rights(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
140   def validate_modification_rights(cng, privilege) do
141     actor = User.get_cached_by_ap_id(get_field(cng, :actor))
142
143     if User.privileged?(actor, privilege) || same_domain?(cng) do
144       cng
145     else
146       cng
147       |> add_error(:actor, "is not allowed to modify object")
148     end
149   end
150 end