SimplePolicy: filter string Objects
[akkoma] / lib / pleroma / web / admin_api / controllers / user_controller.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.AdminAPI.UserController do
6 use Pleroma.Web, :controller
7
8 import Pleroma.Web.ControllerHelper,
9 only: [fetch_integer_param: 3]
10
11 alias Pleroma.ModerationLog
12 alias Pleroma.User
13 alias Pleroma.Web.ActivityPub.Builder
14 alias Pleroma.Web.ActivityPub.Pipeline
15 alias Pleroma.Web.AdminAPI
16 alias Pleroma.Web.AdminAPI.AccountView
17 alias Pleroma.Web.AdminAPI.Search
18 alias Pleroma.Web.Plugs.OAuthScopesPlug
19
20 @users_page_size 50
21
22 plug(
23 OAuthScopesPlug,
24 %{scopes: ["admin:read:accounts"]}
25 when action in [:list, :show]
26 )
27
28 plug(
29 OAuthScopesPlug,
30 %{scopes: ["admin:write:accounts"]}
31 when action in [
32 :delete,
33 :create,
34 :toggle_activation,
35 :activate,
36 :deactivate,
37 :approve
38 ]
39 )
40
41 plug(
42 OAuthScopesPlug,
43 %{scopes: ["admin:write:follows"]}
44 when action in [:follow, :unfollow]
45 )
46
47 action_fallback(AdminAPI.FallbackController)
48
49 def delete(conn, %{"nickname" => nickname}) do
50 delete(conn, %{"nicknames" => [nickname]})
51 end
52
53 def delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
54 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
55
56 Enum.each(users, fn user ->
57 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
58 Pipeline.common_pipeline(delete_data, local: true)
59 end)
60
61 ModerationLog.insert_log(%{
62 actor: admin,
63 subject: users,
64 action: "delete"
65 })
66
67 json(conn, nicknames)
68 end
69
70 def follow(%{assigns: %{user: admin}} = conn, %{
71 "follower" => follower_nick,
72 "followed" => followed_nick
73 }) do
74 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
75 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
76 User.follow(follower, followed)
77
78 ModerationLog.insert_log(%{
79 actor: admin,
80 followed: followed,
81 follower: follower,
82 action: "follow"
83 })
84 end
85
86 json(conn, "ok")
87 end
88
89 def unfollow(%{assigns: %{user: admin}} = conn, %{
90 "follower" => follower_nick,
91 "followed" => followed_nick
92 }) do
93 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
94 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
95 User.unfollow(follower, followed)
96
97 ModerationLog.insert_log(%{
98 actor: admin,
99 followed: followed,
100 follower: follower,
101 action: "unfollow"
102 })
103 end
104
105 json(conn, "ok")
106 end
107
108 def create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
109 changesets =
110 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
111 user_data = %{
112 nickname: nickname,
113 name: nickname,
114 email: email,
115 password: password,
116 password_confirmation: password,
117 bio: "."
118 }
119
120 User.register_changeset(%User{}, user_data, need_confirmation: false)
121 end)
122 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
123 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
124 end)
125
126 case Pleroma.Repo.transaction(changesets) do
127 {:ok, users} ->
128 res =
129 users
130 |> Map.values()
131 |> Enum.map(fn user ->
132 {:ok, user} = User.post_register_action(user)
133
134 user
135 end)
136 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
137
138 ModerationLog.insert_log(%{
139 actor: admin,
140 subjects: Map.values(users),
141 action: "create"
142 })
143
144 json(conn, res)
145
146 {:error, id, changeset, _} ->
147 res =
148 Enum.map(changesets.operations, fn
149 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
150 AccountView.render("create-error.json", %{changeset: changeset})
151
152 {_, {:changeset, current_changeset, _}} ->
153 AccountView.render("create-error.json", %{changeset: current_changeset})
154 end)
155
156 conn
157 |> put_status(:conflict)
158 |> json(res)
159 end
160 end
161
162 def show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
163 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
164 conn
165 |> put_view(AccountView)
166 |> render("show.json", %{user: user})
167 else
168 _ -> {:error, :not_found}
169 end
170 end
171
172 def toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
173 user = User.get_cached_by_nickname(nickname)
174
175 {:ok, updated_user} = User.set_activation(user, !user.is_active)
176
177 action = if !user.is_active, do: "activate", else: "deactivate"
178
179 ModerationLog.insert_log(%{
180 actor: admin,
181 subject: [user],
182 action: action
183 })
184
185 conn
186 |> put_view(AccountView)
187 |> render("show.json", %{user: updated_user})
188 end
189
190 def activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
191 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
192 {:ok, updated_users} = User.set_activation(users, true)
193
194 ModerationLog.insert_log(%{
195 actor: admin,
196 subject: users,
197 action: "activate"
198 })
199
200 conn
201 |> put_view(AccountView)
202 |> render("index.json", %{users: Keyword.values(updated_users)})
203 end
204
205 def deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
206 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
207 {:ok, updated_users} = User.set_activation(users, false)
208
209 ModerationLog.insert_log(%{
210 actor: admin,
211 subject: users,
212 action: "deactivate"
213 })
214
215 conn
216 |> put_view(AccountView)
217 |> render("index.json", %{users: Keyword.values(updated_users)})
218 end
219
220 def approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
221 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
222 {:ok, updated_users} = User.approve(users)
223
224 ModerationLog.insert_log(%{
225 actor: admin,
226 subject: users,
227 action: "approve"
228 })
229
230 conn
231 |> put_view(AccountView)
232 |> render("index.json", %{users: updated_users})
233 end
234
235 def list(conn, params) do
236 {page, page_size} = page_params(params)
237 filters = maybe_parse_filters(params["filters"])
238
239 search_params =
240 %{
241 query: params["query"],
242 page: page,
243 page_size: page_size,
244 tags: params["tags"],
245 name: params["name"],
246 email: params["email"],
247 actor_types: params["actor_types"]
248 }
249 |> Map.merge(filters)
250
251 with {:ok, users, count} <- Search.user(search_params) do
252 json(
253 conn,
254 AccountView.render("index.json",
255 users: users,
256 count: count,
257 page_size: page_size
258 )
259 )
260 end
261 end
262
263 @filters ~w(local external active deactivated need_approval unconfirmed is_admin is_moderator)
264
265 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
266 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
267
268 defp maybe_parse_filters(filters) do
269 filters
270 |> String.split(",")
271 |> Enum.filter(&Enum.member?(@filters, &1))
272 |> Map.new(&{String.to_existing_atom(&1), true})
273 end
274
275 defp page_params(params) do
276 {
277 fetch_integer_param(params, "page", 1),
278 fetch_integer_param(params, "page_size", @users_page_size)
279 }
280 end
281 end