Merge branch 'features/pleroma-tan' into 'develop'
[akkoma] / lib / pleroma / web / ostatus / ostatus_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.OStatus.OStatusController do
6 use Pleroma.Web, :controller
7
8 alias Pleroma.Activity
9 alias Pleroma.Object
10 alias Pleroma.User
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.ActivityPubController
13 alias Pleroma.Web.ActivityPub.ObjectView
14 alias Pleroma.Web.ActivityPub.Visibility
15 alias Pleroma.Web.Federator
16 alias Pleroma.Web.OStatus
17 alias Pleroma.Web.OStatus.ActivityRepresenter
18 alias Pleroma.Web.OStatus.FeedRepresenter
19 alias Pleroma.Web.XML
20
21 plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
22
23 action_fallback(:errors)
24
25 def feed_redirect(conn, %{"nickname" => nickname}) do
26 case get_format(conn) do
27 "html" ->
28 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
29 Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
30 else
31 nil -> {:error, :not_found}
32 end
33
34 "activity+json" ->
35 ActivityPubController.call(conn, :user)
36
37 "json" ->
38 ActivityPubController.call(conn, :user)
39
40 _ ->
41 with %User{} = user <- User.get_cached_by_nickname(nickname) do
42 redirect(conn, external: OStatus.feed_path(user))
43 else
44 nil -> {:error, :not_found}
45 end
46 end
47 end
48
49 def feed(conn, %{"nickname" => nickname} = params) do
50 with %User{} = user <- User.get_cached_by_nickname(nickname) do
51 query_params =
52 Map.take(params, ["max_id"])
53 |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
54
55 activities =
56 ActivityPub.fetch_public_activities(query_params)
57 |> Enum.reverse()
58
59 response =
60 user
61 |> FeedRepresenter.to_simple_form(activities, [user])
62 |> :xmerl.export_simple(:xmerl_xml)
63 |> to_string
64
65 conn
66 |> put_resp_content_type("application/atom+xml")
67 |> send_resp(200, response)
68 else
69 nil -> {:error, :not_found}
70 end
71 end
72
73 defp decode_or_retry(body) do
74 with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
75 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
76 {:ok, doc}
77 else
78 _e ->
79 with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
80 doc <- XML.parse_document(decoded),
81 uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
82 {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
83 {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
84 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
85 {:ok, doc}
86 end
87 end
88 end
89
90 def salmon_incoming(conn, _) do
91 {:ok, body, _conn} = read_body(conn)
92 {:ok, doc} = decode_or_retry(body)
93
94 Federator.incoming_doc(doc)
95
96 conn
97 |> send_resp(200, "")
98 end
99
100 def object(conn, %{"uuid" => uuid}) do
101 if get_format(conn) in ["activity+json", "json"] do
102 ActivityPubController.call(conn, :object)
103 else
104 with id <- o_status_url(conn, :object, uuid),
105 {_, %Activity{} = activity} <-
106 {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
107 {_, true} <- {:public?, Visibility.is_public?(activity)},
108 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
109 case get_format(conn) do
110 "html" -> redirect(conn, to: "/notice/#{activity.id}")
111 _ -> represent_activity(conn, nil, activity, user)
112 end
113 else
114 {:public?, false} ->
115 {:error, :not_found}
116
117 {:activity, nil} ->
118 {:error, :not_found}
119
120 e ->
121 e
122 end
123 end
124 end
125
126 def activity(conn, %{"uuid" => uuid}) do
127 if get_format(conn) in ["activity+json", "json"] do
128 ActivityPubController.call(conn, :activity)
129 else
130 with id <- o_status_url(conn, :activity, uuid),
131 {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
132 {_, true} <- {:public?, Visibility.is_public?(activity)},
133 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
134 case format = get_format(conn) do
135 "html" -> redirect(conn, to: "/notice/#{activity.id}")
136 _ -> represent_activity(conn, format, activity, user)
137 end
138 else
139 {:public?, false} ->
140 {:error, :not_found}
141
142 {:activity, nil} ->
143 {:error, :not_found}
144
145 e ->
146 e
147 end
148 end
149 end
150
151 def notice(conn, %{"id" => id}) do
152 with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
153 {_, true} <- {:public?, Visibility.is_public?(activity)},
154 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
155 case format = get_format(conn) do
156 "html" ->
157 if activity.data["type"] == "Create" do
158 %Object{} = object = Object.normalize(activity)
159
160 Fallback.RedirectController.redirector_with_meta(conn, %{
161 activity_id: activity.id,
162 object: object,
163 url:
164 Pleroma.Web.Router.Helpers.o_status_url(
165 Pleroma.Web.Endpoint,
166 :notice,
167 activity.id
168 ),
169 user: user
170 })
171 else
172 Fallback.RedirectController.redirector(conn, nil)
173 end
174
175 _ ->
176 represent_activity(conn, format, activity, user)
177 end
178 else
179 {:public?, false} ->
180 conn
181 |> put_status(404)
182 |> Fallback.RedirectController.redirector(nil, 404)
183
184 {:activity, nil} ->
185 conn
186 |> Fallback.RedirectController.redirector(nil, 404)
187
188 e ->
189 e
190 end
191 end
192
193 # Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
194 def notice_player(conn, %{"id" => id}) do
195 with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
196 true <- Visibility.is_public?(activity),
197 %Object{} = object <- Object.normalize(activity),
198 %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
199 true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
200 conn
201 |> put_layout(:metadata_player)
202 |> put_resp_header("x-frame-options", "ALLOW")
203 |> put_resp_header(
204 "content-security-policy",
205 "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
206 )
207 |> put_view(Pleroma.Web.Metadata.PlayerView)
208 |> render("player.html", url)
209 else
210 _error ->
211 conn
212 |> put_status(404)
213 |> Fallback.RedirectController.redirector(nil, 404)
214 end
215 end
216
217 defp represent_activity(
218 conn,
219 "activity+json",
220 %Activity{data: %{"type" => "Create"}} = activity,
221 _user
222 ) do
223 object = Object.normalize(activity)
224
225 conn
226 |> put_resp_header("content-type", "application/activity+json")
227 |> json(ObjectView.render("object.json", %{object: object}))
228 end
229
230 defp represent_activity(_conn, "activity+json", _, _) do
231 {:error, :not_found}
232 end
233
234 defp represent_activity(conn, _, activity, user) do
235 response =
236 activity
237 |> ActivityRepresenter.to_simple_form(user, true)
238 |> ActivityRepresenter.wrap_with_entry()
239 |> :xmerl.export_simple(:xmerl_xml)
240 |> to_string
241
242 conn
243 |> put_resp_content_type("application/atom+xml")
244 |> send_resp(200, response)
245 end
246
247 def errors(conn, {:error, :not_found}) do
248 conn
249 |> put_status(404)
250 |> text("Not found")
251 end
252
253 def errors(conn, _) do
254 conn
255 |> put_status(500)
256 |> text("Something went wrong")
257 end
258 end