User: generate private keys on user creation
[akkoma] / lib / pleroma / web / activity_pub / views / user_view.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.UserView do
6 use Pleroma.Web, :view
7
8 alias Pleroma.Keys
9 alias Pleroma.Object
10 alias Pleroma.Repo
11 alias Pleroma.User
12 alias Pleroma.Web.ActivityPub.ObjectView
13 alias Pleroma.Web.ActivityPub.Transmogrifier
14 alias Pleroma.Web.ActivityPub.Utils
15 alias Pleroma.Web.Endpoint
16 alias Pleroma.Web.Router.Helpers
17
18 import Ecto.Query
19
20 def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
21 %{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)}
22 end
23
24 def render("endpoints.json", %{user: %User{local: true} = _user}) do
25 %{
26 "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
27 "oauthRegistrationEndpoint" => Helpers.app_url(Endpoint, :create),
28 "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
29 "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox),
30 "uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media)
31 }
32 end
33
34 def render("endpoints.json", _), do: %{}
35
36 def render("service.json", %{user: user}) do
37 {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
38 public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
39 public_key = :public_key.pem_encode([public_key])
40
41 endpoints = render("endpoints.json", %{user: user})
42
43 %{
44 "id" => user.ap_id,
45 "type" => "Application",
46 "following" => "#{user.ap_id}/following",
47 "followers" => "#{user.ap_id}/followers",
48 "inbox" => "#{user.ap_id}/inbox",
49 "name" => "Pleroma",
50 "summary" =>
51 "An internal service actor for this Pleroma instance. No user-serviceable parts inside.",
52 "url" => user.ap_id,
53 "manuallyApprovesFollowers" => false,
54 "publicKey" => %{
55 "id" => "#{user.ap_id}#main-key",
56 "owner" => user.ap_id,
57 "publicKeyPem" => public_key
58 },
59 "endpoints" => endpoints,
60 "invisible" => User.invisible?(user)
61 }
62 |> Map.merge(Utils.make_json_ld_header())
63 end
64
65 # the instance itself is not a Person, but instead an Application
66 def render("user.json", %{user: %User{nickname: nil} = user}),
67 do: render("service.json", %{user: user})
68
69 def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
70 do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
71
72 def render("user.json", %{user: user}) do
73 {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
74 public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
75 public_key = :public_key.pem_encode([public_key])
76 user = User.sanitize_html(user)
77
78 endpoints = render("endpoints.json", %{user: user})
79
80 emoji_tags = Transmogrifier.take_emoji_tags(user)
81
82 fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue"))
83
84 capabilities = %{}
85
86 %{
87 "id" => user.ap_id,
88 "type" => user.actor_type,
89 "following" => "#{user.ap_id}/following",
90 "followers" => "#{user.ap_id}/followers",
91 "inbox" => "#{user.ap_id}/inbox",
92 "outbox" => "#{user.ap_id}/outbox",
93 "featured" => "#{user.ap_id}/collections/featured",
94 "preferredUsername" => user.nickname,
95 "name" => user.name,
96 "summary" => user.bio,
97 "url" => user.ap_id,
98 "manuallyApprovesFollowers" => user.is_locked,
99 "publicKey" => %{
100 "id" => "#{user.ap_id}#main-key",
101 "owner" => user.ap_id,
102 "publicKeyPem" => public_key
103 },
104 "endpoints" => endpoints,
105 "attachment" => fields,
106 "tag" => emoji_tags,
107 # Note: key name is indeed "discoverable" (not an error)
108 "discoverable" => user.is_discoverable,
109 "capabilities" => capabilities,
110 "alsoKnownAs" => user.also_known_as
111 }
112 |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
113 |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
114 |> Map.merge(Utils.make_json_ld_header())
115 end
116
117 def render("following.json", %{user: user, page: page} = opts) do
118 showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
119 showing_count = showing_items || !user.hide_follows_count
120
121 query = User.get_friends_query(user)
122 query = from(user in query, select: [:ap_id])
123 following = Repo.all(query)
124
125 total =
126 if showing_count do
127 length(following)
128 else
129 0
130 end
131
132 collection(following, "#{user.ap_id}/following", page, showing_items, total)
133 |> Map.merge(Utils.make_json_ld_header())
134 end
135
136 def render("following.json", %{user: user} = opts) do
137 showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
138 showing_count = showing_items || !user.hide_follows_count
139
140 query = User.get_friends_query(user)
141 query = from(user in query, select: [:ap_id])
142 following = Repo.all(query)
143
144 total =
145 if showing_count do
146 length(following)
147 else
148 0
149 end
150
151 %{
152 "id" => "#{user.ap_id}/following",
153 "type" => "OrderedCollection",
154 "totalItems" => total,
155 "first" =>
156 if showing_items do
157 collection(following, "#{user.ap_id}/following", 1, !user.hide_follows)
158 else
159 "#{user.ap_id}/following?page=1"
160 end
161 }
162 |> Map.merge(Utils.make_json_ld_header())
163 end
164
165 def render("followers.json", %{user: user, page: page} = opts) do
166 showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
167 showing_count = showing_items || !user.hide_followers_count
168
169 query = User.get_followers_query(user)
170 query = from(user in query, select: [:ap_id])
171 followers = Repo.all(query)
172
173 total =
174 if showing_count do
175 length(followers)
176 else
177 0
178 end
179
180 collection(followers, "#{user.ap_id}/followers", page, showing_items, total)
181 |> Map.merge(Utils.make_json_ld_header())
182 end
183
184 def render("followers.json", %{user: user} = opts) do
185 showing_items = (opts[:for] && opts[:for] == user) || !user.hide_followers
186 showing_count = showing_items || !user.hide_followers_count
187
188 query = User.get_followers_query(user)
189 query = from(user in query, select: [:ap_id])
190 followers = Repo.all(query)
191
192 total =
193 if showing_count do
194 length(followers)
195 else
196 0
197 end
198
199 %{
200 "id" => "#{user.ap_id}/followers",
201 "type" => "OrderedCollection",
202 "first" =>
203 if showing_items do
204 collection(followers, "#{user.ap_id}/followers", 1, showing_items, total)
205 else
206 "#{user.ap_id}/followers?page=1"
207 end
208 }
209 |> maybe_put_total_items(showing_count, total)
210 |> Map.merge(Utils.make_json_ld_header())
211 end
212
213 def render("activity_collection.json", %{iri: iri}) do
214 %{
215 "id" => iri,
216 "type" => "OrderedCollection",
217 "first" => "#{iri}?page=true"
218 }
219 |> Map.merge(Utils.make_json_ld_header())
220 end
221
222 def render("activity_collection_page.json", %{
223 activities: activities,
224 iri: iri,
225 pagination: pagination
226 }) do
227 collection =
228 Enum.map(activities, fn activity ->
229 {:ok, data} = Transmogrifier.prepare_outgoing(activity.data)
230 data
231 end)
232
233 %{
234 "type" => "OrderedCollectionPage",
235 "partOf" => iri,
236 "orderedItems" => collection
237 }
238 |> Map.merge(Utils.make_json_ld_header())
239 |> Map.merge(pagination)
240 end
241
242 def render("featured.json", %{
243 user: %{featured_address: featured_address, pinned_objects: pinned_objects}
244 }) do
245 objects =
246 pinned_objects
247 |> Enum.sort_by(fn {_, pinned_at} -> pinned_at end, &>=/2)
248 |> Enum.map(fn {id, _} ->
249 ObjectView.render("object.json", %{object: Object.get_cached_by_ap_id(id)})
250 end)
251
252 %{
253 "id" => featured_address,
254 "type" => "OrderedCollection",
255 "orderedItems" => objects,
256 "totalItems" => length(objects)
257 }
258 |> Map.merge(Utils.make_json_ld_header())
259 end
260
261 defp maybe_put_total_items(map, false, _total), do: map
262
263 defp maybe_put_total_items(map, true, total) do
264 Map.put(map, "totalItems", total)
265 end
266
267 def collection(collection, iri, page, show_items \\ true, total \\ nil) do
268 offset = (page - 1) * 10
269 items = Enum.slice(collection, offset, 10)
270 items = Enum.map(items, fn user -> user.ap_id end)
271 total = total || length(collection)
272
273 map = %{
274 "id" => "#{iri}?page=#{page}",
275 "type" => "OrderedCollectionPage",
276 "partOf" => iri,
277 "totalItems" => total,
278 "orderedItems" => if(show_items, do: items, else: [])
279 }
280
281 if offset < total do
282 Map.put(map, "next", "#{iri}?page=#{page + 1}")
283 else
284 map
285 end
286 end
287
288 defp maybe_make_image(func, key, user) do
289 if image = func.(user, no_default: true) do
290 %{
291 key => %{
292 "type" => "Image",
293 "url" => image
294 }
295 }
296 else
297 %{}
298 end
299 end
300 end