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