fix fetching AP objects with application/json
[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 "json" ->
37 ActivityPubController.call(conn, :user)
38
39 _ ->
40 with %User{} = user <- User.get_cached_by_nickname(nickname) do
41 redirect(conn, external: OStatus.feed_path(user))
42 else
43 nil -> {:error, :not_found}
44 end
45 end
46 end
47
48 def feed(conn, %{"nickname" => nickname} = params) do
49 with %User{} = user <- User.get_cached_by_nickname(nickname) do
50 query_params =
51 Map.take(params, ["max_id"])
52 |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
53
54 activities =
55 ActivityPub.fetch_public_activities(query_params)
56 |> Enum.reverse()
57
58 response =
59 user
60 |> FeedRepresenter.to_simple_form(activities, [user])
61 |> :xmerl.export_simple(:xmerl_xml)
62 |> to_string
63
64 conn
65 |> put_resp_content_type("application/atom+xml")
66 |> send_resp(200, response)
67 else
68 nil -> {:error, :not_found}
69 end
70 end
71
72 defp decode_or_retry(body) do
73 with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
74 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
75 {:ok, doc}
76 else
77 _e ->
78 with [decoded | _] <- Pleroma.Web.Salmon.decode(body),
79 doc <- XML.parse_document(decoded),
80 uri when not is_nil(uri) <- XML.string_from_xpath("/entry/author[1]/uri", doc),
81 {:ok, _} <- Pleroma.Web.OStatus.make_user(uri, true),
82 {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
83 {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do
84 {:ok, doc}
85 end
86 end
87 end
88
89 def salmon_incoming(conn, _) do
90 {:ok, body, _conn} = read_body(conn)
91 {:ok, doc} = decode_or_retry(body)
92
93 Federator.incoming_doc(doc)
94
95 conn
96 |> send_resp(200, "")
97 end
98
99 def object(conn, %{"uuid" => uuid}) do
100 if get_format(conn) in ["activity+json", "json"] do
101 ActivityPubController.call(conn, :object)
102 else
103 with id <- o_status_url(conn, :object, uuid),
104 {_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id(id)},
105 {_, true} <- {:public?, ActivityPub.is_public?(activity)},
106 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
107 case get_format(conn) do
108 "html" -> redirect(conn, to: "/notice/#{activity.id}")
109 _ -> represent_activity(conn, nil, activity, user)
110 end
111 else
112 {:public?, false} ->
113 {:error, :not_found}
114
115 {:activity, nil} ->
116 {:error, :not_found}
117
118 e ->
119 e
120 end
121 end
122 end
123
124 def activity(conn, %{"uuid" => uuid}) do
125 if get_format(conn) in ["activity+json", "json"] do
126 ActivityPubController.call(conn, :activity)
127 else
128 with id <- o_status_url(conn, :activity, uuid),
129 {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
130 {_, true} <- {:public?, ActivityPub.is_public?(activity)},
131 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
132 case format = get_format(conn) do
133 "html" -> redirect(conn, to: "/notice/#{activity.id}")
134 _ -> represent_activity(conn, format, activity, user)
135 end
136 else
137 {:public?, false} ->
138 {:error, :not_found}
139
140 {:activity, nil} ->
141 {:error, :not_found}
142
143 e ->
144 e
145 end
146 end
147 end
148
149 def notice(conn, %{"id" => id}) do
150 with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(id)},
151 {_, true} <- {:public?, ActivityPub.is_public?(activity)},
152 %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
153 case format = get_format(conn) do
154 "html" ->
155 if activity.data["type"] == "Create" do
156 %Object{} = object = Object.normalize(activity.data["object"])
157
158 Fallback.RedirectController.redirector_with_meta(conn, %{
159 object: object,
160 url:
161 Pleroma.Web.Router.Helpers.o_status_url(
162 Pleroma.Web.Endpoint,
163 :notice,
164 activity.id
165 ),
166 user: user
167 })
168 else
169 Fallback.RedirectController.redirector(conn, nil)
170 end
171
172 _ ->
173 represent_activity(conn, format, activity, user)
174 end
175 else
176 {:public?, false} ->
177 conn
178 |> put_status(404)
179 |> Fallback.RedirectController.redirector(nil, 404)
180
181 {:activity, nil} ->
182 conn
183 |> Fallback.RedirectController.redirector(nil, 404)
184
185 e ->
186 e
187 end
188 end
189
190 defp represent_activity(
191 conn,
192 "activity+json",
193 %Activity{data: %{"type" => "Create"}} = activity,
194 _user
195 ) do
196 object = Object.normalize(activity.data["object"])
197
198 conn
199 |> put_resp_header("content-type", "application/activity+json")
200 |> json(ObjectView.render("object.json", %{object: object}))
201 end
202
203 defp represent_activity(_conn, "activity+json", _, _) do
204 {:error, :not_found}
205 end
206
207 defp represent_activity(conn, _, activity, user) do
208 response =
209 activity
210 |> ActivityRepresenter.to_simple_form(user, true)
211 |> ActivityRepresenter.wrap_with_entry()
212 |> :xmerl.export_simple(:xmerl_xml)
213 |> to_string
214
215 conn
216 |> put_resp_content_type("application/atom+xml")
217 |> send_resp(200, response)
218 end
219
220 def errors(conn, {:error, :not_found}) do
221 conn
222 |> put_status(404)
223 |> text("Not found")
224 end
225
226 def errors(conn, _) do
227 conn
228 |> put_status(500)
229 |> text("Something went wrong")
230 end
231 end