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