diff options
Diffstat (limited to 'lib/pleroma/web')
66 files changed, 1363 insertions, 573 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index adb42b9ab..6e1ed7ec9 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -4,17 +4,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Activity - alias Pleroma.Repo + alias Pleroma.Instances + alias Pleroma.Notification alias Pleroma.Object + alias Pleroma.Repo alias Pleroma.Upload alias Pleroma.User - alias Pleroma.Notification - alias Pleroma.Instances - alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.MRF - alias Pleroma.Web.WebFinger + alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.Federator alias Pleroma.Web.OStatus + alias Pleroma.Web.WebFinger import Ecto.Query import Pleroma.Web.ActivityPub.Utils @@ -89,13 +89,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do if is_public?(object), do: User.decrease_note_count(actor), else: {:ok, actor} end + def increase_replies_count_if_reply(%{ + "object" => + %{"inReplyTo" => reply_ap_id, "inReplyToStatusId" => reply_status_id} = object, + "type" => "Create" + }) do + if is_public?(object) do + Activity.increase_replies_count(reply_status_id) + Object.increase_replies_count(reply_ap_id) + end + end + + def increase_replies_count_if_reply(_create_data), do: :noop + + def decrease_replies_count_if_reply(%Object{ + data: %{"inReplyTo" => reply_ap_id, "inReplyToStatusId" => reply_status_id} = object + }) do + if is_public?(object) do + Activity.decrease_replies_count(reply_status_id) + Object.decrease_replies_count(reply_ap_id) + end + end + + def decrease_replies_count_if_reply(_object), do: :noop + def insert(map, local \\ true) when is_map(map) do with nil <- Activity.normalize(map), map <- lazy_put_activity_defaults(map), :ok <- check_actor_is_active(map["actor"]), {_, true} <- {:remote_limit_error, check_remote_limit(map)}, {:ok, map} <- MRF.filter(map), - :ok <- insert_full_object(map) do + {:ok, object} <- insert_full_object(map) do {recipients, _, _} = get_recipients(map) {:ok, activity} = @@ -106,6 +130,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do recipients: recipients }) + # Splice in the child object if we have one. + activity = + if !is_nil(object) do + Map.put(activity, :object, object) + else + activity + end + Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) @@ -170,7 +202,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do additional ), {:ok, activity} <- insert(create_data, local), - # Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info + _ <- increase_replies_count_if_reply(create_data), + # Changing note count prior to enqueuing federation task in order to avoid + # race conditions on updating user.info {:ok, _actor} <- increase_note_count_if_public(actor, activity), :ok <- maybe_federate(activity) do {:ok, activity} @@ -309,18 +343,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do user = User.get_cached_by_ap_id(actor) - to = object.data["to"] || [] ++ object.data["cc"] || [] + to = (object.data["to"] || []) ++ (object.data["cc"] || []) - data = %{ - "type" => "Delete", - "actor" => actor, - "object" => id, - "to" => to - } - - with {:ok, _} <- Object.delete(object), + with {:ok, object, activity} <- Object.delete(object), + data <- %{ + "type" => "Delete", + "actor" => actor, + "object" => id, + "to" => to, + "deleted_activity_id" => activity && activity.id + }, {:ok, activity} <- insert(data, local), - # Changing note count prior to enqueuing federation task in order to avoid race conditions on updating user.info + _ <- decrease_replies_count_if_reply(object), + # Changing note count prior to enqueuing federation task in order to avoid + # race conditions on updating user.info {:ok, _actor} <- decrease_note_count_if_public(user, object), :ok <- maybe_federate(activity) do {:ok, activity} @@ -368,20 +404,38 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do content: content } = params ) do - additional = params[:additional] || %{} - # only accept false as false value local = !(params[:local] == false) + forward = !(params[:forward] == false) + + additional = params[:additional] || %{} - %{ + params = %{ actor: actor, context: context, account: account, statuses: statuses, content: content } - |> make_flag_data(additional) - |> insert(local) + + additional = + if forward do + Map.merge(additional, %{"to" => [], "cc" => [account.ap_id]}) + else + Map.merge(additional, %{"to" => [], "cc" => []}) + end + + with flag_data <- make_flag_data(params, additional), + {:ok, activity} <- insert(flag_data, local), + :ok <- maybe_federate(activity) do + Enum.each(User.all_superusers(), fn superuser -> + superuser + |> Pleroma.AdminEmail.report(actor, account, statuses, content) + |> Pleroma.Mailer.deliver_async() + end) + + {:ok, activity} + end end def fetch_activities_for_context(context, opts \\ %{}) do @@ -410,6 +464,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do ), order_by: [desc: :id] ) + |> Activity.with_preloaded_object() Repo.all(query) end @@ -502,7 +557,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do when is_list(tag_reject) and tag_reject != [] do from( activity in query, - where: fragment("(not (? #> '{\"object\",\"tag\"}') \\?| ?)", activity.data, ^tag_reject) + where: fragment(~s(\(not \(? #> '{"object","tag"}'\) \\?| ?\)), activity.data, ^tag_reject) ) end @@ -512,7 +567,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do when is_list(tag_all) and tag_all != [] do from( activity in query, - where: fragment("(? #> '{\"object\",\"tag\"}') \\?& ?", activity.data, ^tag_all) + where: fragment(~s(\(? #> '{"object","tag"}'\) \\?& ?), activity.data, ^tag_all) ) end @@ -521,14 +576,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do from( activity in query, - where: fragment("(? #> '{\"object\",\"tag\"}') \\?| ?", activity.data, ^tag) + where: fragment(~s(\(? #> '{"object","tag"}'\) \\?| ?), activity.data, ^tag) ) end defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do from( activity in query, - where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data) + where: fragment(~s(? <@ (? #> '{"object","tag"}'\)), ^tag, activity.data) ) end @@ -601,7 +656,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do from( activity in query, - where: fragment("? <@ (? #> '{\"object\",\"likes\"}')", ^ap_id, activity.data) + where: fragment(~s(? <@ (? #> '{"object","likes"}'\)), ^ap_id, activity.data) ) end @@ -610,7 +665,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do from( activity in query, - where: fragment("not (? #> '{\"object\",\"attachment\"}' = ?)", activity.data, ^[]) + where: fragment(~s(not (? #> '{"object","attachment"}' = ?\)), activity.data, ^[]) ) end @@ -677,6 +732,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_pinned(query, _), do: query + defp restrict_muted_reblogs(query, %{"muting_user" => %User{info: info}}) do + muted_reblogs = info.muted_reblogs || [] + + from( + activity in query, + where: + fragment( + "not ( ?->>'type' = 'Announce' and ? = ANY(?))", + activity.data, + activity.actor, + ^muted_reblogs + ) + ) + end + + defp restrict_muted_reblogs(query, _), do: query + + defp maybe_preload_objects(query, %{"skip_preload" => true}), do: query + + defp maybe_preload_objects(query, _) do + query + |> Activity.with_preloaded_object() + end + def fetch_activities_query(recipients, opts \\ %{}) do base_query = from( @@ -686,6 +765,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do ) base_query + |> maybe_preload_objects(opts) |> restrict_recipients(recipients, opts["user"]) |> restrict_tag(opts) |> restrict_tag_reject(opts) @@ -704,6 +784,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> restrict_replies(opts) |> restrict_reblogs(opts) |> restrict_pinned(opts) + |> restrict_muted_reblogs(opts) end def fetch_activities(recipients, opts \\ %{}) do @@ -907,7 +988,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do }, :ok <- Transmogrifier.contain_origin(id, params), {:ok, activity} <- Transmogrifier.handle_incoming(params) do - {:ok, Object.normalize(activity.data["object"])} + {:ok, Object.normalize(activity)} else {:error, {:reject, nil}} -> {:reject, nil} @@ -919,7 +1000,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do Logger.info("Couldn't get object via AP, trying out OStatus fetching...") case OStatus.fetch_activity_from_url(id) do - {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])} + {:ok, [activity | _]} -> {:ok, Object.normalize(activity)} e -> e end end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index ff924a536..7091d6927 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -6,15 +6,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do use Pleroma.Web, :controller alias Pleroma.Activity - alias Pleroma.User alias Pleroma.Object - alias Pleroma.Web.ActivityPub.ObjectView - alias Pleroma.Web.ActivityPub.UserView + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Federator require Logger diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index eebea207c..1aaa20050 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do end) end - def get_policies() do + def get_policies do Application.get_env(:pleroma, :instance, []) |> Keyword.get(:rewrite_policy, []) |> get_policies() diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index 5fdc03414..e8dfba672 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -4,6 +4,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do @behaviour Pleroma.Web.ActivityPub.MRF + defp string_matches?(string, _) when not is_binary(string) do + false + end + defp string_matches?(string, pattern) when is_binary(pattern) do String.contains?(string, pattern) end @@ -44,14 +48,29 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do end defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do + content = + if is_binary(content) do + content + else + "" + end + + summary = + if is_binary(summary) do + summary + else + "" + end + {content, summary} = - Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), {content, summary}, fn {pattern, - replacement}, - {content_acc, - summary_acc} -> - {String.replace(content_acc, pattern, replacement), - String.replace(summary_acc, pattern, replacement)} - end) + Enum.reduce( + Pleroma.Config.get([:mrf_keyword, :replace]), + {content, summary}, + fn {pattern, replacement}, {content_acc, summary_acc} -> + {String.replace(content_acc, pattern, replacement), + String.replace(summary_acc, pattern, replacement)} + end + ) {:ok, message @@ -60,11 +79,6 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do end @impl true - def filter(%{"object" => %{"content" => nil}} = message) do - {:ok, message} - end - - @impl true def filter(%{"type" => "Create", "object" => %{"content" => _content}} = message) do with {:ok, message} <- check_reject(message), {:ok, message} <- check_ftl_removal(message), diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex index c496063ea..a7a20ca37 100644 --- a/lib/pleroma/web/activity_pub/relay.ex +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -3,9 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Relay do - alias Pleroma.User - alias Pleroma.Object alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub require Logger @@ -41,7 +41,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do def publish(%Activity{data: %{"type" => "Create"}} = activity) do with %User{} = user <- get_actor(), - %Object{} = object <- Object.normalize(activity.data["object"]["id"]) do + %Object{} = object <- Object.normalize(activity) do ActivityPub.announce(user, object, nil, true, false) else e -> Logger.error("error: #{inspect(e)}") diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 88007aa16..f733ae7e1 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -7,9 +7,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do A module to handle coding from internal to wire ActivityPub and back. """ alias Pleroma.Activity - alias Pleroma.User alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility @@ -86,11 +86,15 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def fix_addressing_list(map, field) do - if is_binary(map[field]) do - map - |> Map.put(field, [map[field]]) - else - map + cond do + is_binary(map[field]) -> + Map.put(map, field, [map[field]]) + + is_nil(map[field]) -> + Map.put(map, field, []) + + true -> + map end end @@ -128,13 +132,42 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do |> fix_explicit_addressing(explicit_mentions) end + # if as:Public is addressed, then make sure the followers collection is also addressed + # so that the activities will be delivered to local users. + def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do + recipients = to ++ cc + + if followers_collection not in recipients do + cond do + "https://www.w3.org/ns/activitystreams#Public" in cc -> + to = to ++ [followers_collection] + Map.put(object, "to", to) + + "https://www.w3.org/ns/activitystreams#Public" in to -> + cc = cc ++ [followers_collection] + Map.put(object, "cc", cc) + + true -> + object + end + else + object + end + end + + def fix_implicit_addressing(object, _), do: object + def fix_addressing(object) do + %User{} = user = User.get_or_fetch_by_ap_id(object["actor"]) + followers_collection = User.ap_followers(user) + object |> fix_addressing_list("to") |> fix_addressing_list("cc") |> fix_addressing_list("bto") |> fix_addressing_list("bcc") |> fix_explicit_addressing + |> fix_implicit_addressing(followers_collection) end def fix_actor(%{"attributedTo" => actor} = object) do @@ -355,6 +388,40 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end end + # Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them + # with nil ID. + def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} = data) do + with context <- data["context"] || Utils.generate_context_id(), + content <- data["content"] || "", + %User{} = actor <- User.get_cached_by_ap_id(actor), + + # Reduce the object list to find the reported user. + %User{} = account <- + Enum.reduce_while(objects, nil, fn ap_id, _ -> + with %User{} = user <- User.get_cached_by_ap_id(ap_id) do + {:halt, user} + else + _ -> {:cont, nil} + end + end), + + # Remove the reported user from the object list. + statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do + params = %{ + actor: actor, + context: context, + account: account, + statuses: statuses, + content: content, + additional: %{ + "cc" => [account.ap_id] + } + } + + ActivityPub.flag(params) + end + end + # disallow objects with bogus IDs def handle_incoming(%{"id" => nil}), do: :error def handle_incoming(%{"id" => ""}), do: :error @@ -650,10 +717,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do if object = Object.normalize(id), do: {:ok, object}, else: nil end - def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) when is_binary(inReplyTo) do - with false <- String.starts_with?(inReplyTo, "http"), - {:ok, %{data: replied_to_object}} <- get_obj_helper(inReplyTo) do - Map.put(object, "inReplyTo", replied_to_object["external_url"] || inReplyTo) + def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do + with false <- String.starts_with?(in_reply_to, "http"), + {:ok, %{data: replied_to_object}} <- get_obj_helper(in_reply_to) do + Map.put(object, "inReplyTo", replied_to_object["external_url"] || in_reply_to) else _e -> object end @@ -736,6 +803,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def prepare_outgoing(%{"type" => _type} = data) do data = data + |> strip_internal_fields |> maybe_fix_object_url |> Map.merge(Utils.make_json_ld_header()) @@ -829,10 +897,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do end def add_attributed_to(object) do - attributedTo = object["attributedTo"] || object["actor"] + attributed_to = object["attributedTo"] || object["actor"] object - |> Map.put("attributedTo", attributedTo) + |> Map.put("attributedTo", attributed_to) end def add_likes(%{"id" => id, "like_count" => likes} = object) do @@ -870,7 +938,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do "announcements", "announcement_count", "emoji", - "context_id" + "context_id", + "deleted_activity_id" ]) end @@ -886,7 +955,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do defp strip_internal_tags(object), do: object defp user_upgrade_task(user) do - old_follower_address = User.ap_followers(user) + # we pass a fake user so that the followers collection is stripped away + old_follower_address = User.ap_followers(%User{nickname: user.nickname}) q = from( diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 9e50789db..77841278a 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -3,17 +3,17 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.Utils do + alias Ecto.Changeset + alias Ecto.UUID + alias Pleroma.Activity + alias Pleroma.Notification + alias Pleroma.Object alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web - alias Pleroma.Object - alias Pleroma.Activity alias Pleroma.Web.ActivityPub.Visibility - alias Pleroma.User - alias Pleroma.Notification - alias Pleroma.Web.Router.Helpers alias Pleroma.Web.Endpoint - alias Ecto.Changeset - alias Ecto.UUID + alias Pleroma.Web.Router.Helpers import Ecto.Query @@ -209,12 +209,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do """ def insert_full_object(%{"object" => %{"type" => type} = object_data}) when is_map(object_data) and type in @supported_object_types do - with {:ok, _} <- Object.create(object_data) do - :ok + with {:ok, object} <- Object.create(object_data) do + {:ok, object} end end - def insert_full_object(_), do: :ok + def insert_full_object(_), do: {:ok, nil} def update_object_in_activities(%{data: %{"id" => id}} = object) do # TODO @@ -354,7 +354,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do [state, actor, object] ) - activity = Repo.get(Activity, activity.id) + activity = Activity.get_by_id(activity.id) {:ok, activity} rescue e -> @@ -621,7 +621,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do #### Flag-related helpers def make_flag_data(params, additional) do - status_ap_ids = Enum.map(params.statuses || [], & &1.data["id"]) + status_ap_ids = + Enum.map(params.statuses || [], fn + %Activity{} = act -> act.data["id"] + act when is_map(act) -> act["id"] + act when is_binary(act) -> act + end) + object = [params.account.ap_id] ++ status_ap_ids %{ @@ -633,4 +639,43 @@ defmodule Pleroma.Web.ActivityPub.Utils do } |> Map.merge(additional) end + + @doc """ + Fetches the OrderedCollection/OrderedCollectionPage from `from`, limiting the amount of pages fetched after + the first one to `pages_left` pages. + If the amount of pages is higher than the collection has, it returns whatever was there. + """ + def fetch_ordered_collection(from, pages_left, acc \\ []) do + with {:ok, response} <- Tesla.get(from), + {:ok, collection} <- Poison.decode(response.body) do + case collection["type"] do + "OrderedCollection" -> + # If we've encountered the OrderedCollection and not the page, + # just call the same function on the page address + fetch_ordered_collection(collection["first"], pages_left) + + "OrderedCollectionPage" -> + if pages_left > 0 do + # There are still more pages + if Map.has_key?(collection, "next") do + # There are still more pages, go deeper saving what we have into the accumulator + fetch_ordered_collection( + collection["next"], + pages_left - 1, + acc ++ collection["orderedItems"] + ) + else + # No more pages left, just return whatever we already have + acc ++ collection["orderedItems"] + end + else + # Got the amount of pages needed, add them all to the accumulator + acc ++ collection["orderedItems"] + end + + _ -> + {:error, "Not an OrderedCollection or OrderedCollectionPage"} + end + end + end end diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex index 84fa94e32..6028b773c 100644 --- a/lib/pleroma/web/activity_pub/views/object_view.ex +++ b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do def render("object.json", %{object: %Activity{data: %{"type" => "Create"}} = activity}) do base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() - object = Object.normalize(activity.data["object"]) + object = Object.normalize(activity) additional = Transmogrifier.prepare_object(activity.data) @@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do def render("object.json", %{object: %Activity{} = activity}) do base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() - object = Object.normalize(activity.data["object"]) + object = Object.normalize(activity) additional = Transmogrifier.prepare_object(activity.data) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 415cbd47a..5926a3294 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -5,15 +5,15 @@ defmodule Pleroma.Web.ActivityPub.UserView do use Pleroma.Web, :view - alias Pleroma.Web.WebFinger - alias Pleroma.Web.Salmon - alias Pleroma.User alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.Router.Helpers alias Pleroma.Web.Endpoint + alias Pleroma.Web.Router.Helpers + alias Pleroma.Web.Salmon + alias Pleroma.Web.WebFinger import Ecto.Query @@ -87,16 +87,10 @@ defmodule Pleroma.Web.ActivityPub.UserView do "publicKeyPem" => public_key }, "endpoints" => endpoints, - "icon" => %{ - "type" => "Image", - "url" => User.avatar_url(user) - }, - "image" => %{ - "type" => "Image", - "url" => User.banner_url(user) - }, "tag" => user.info.source_data["tag"] || [] } + |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user)) + |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user)) |> Map.merge(Utils.make_json_ld_header()) end @@ -294,4 +288,17 @@ defmodule Pleroma.Web.ActivityPub.UserView do map end end + + defp maybe_make_image(func, key, user) do + if image = func.(user, no_default: true) do + %{ + key => %{ + "type" => "Image", + "url" => image + } + } + else + %{} + end + end end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 75c2c6061..b3a09e49e 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -3,17 +3,18 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.AdminAPIController do - @users_page_size 50 - use Pleroma.Web, :controller alias Pleroma.User alias Pleroma.Web.ActivityPub.Relay - alias Pleroma.Web.MastodonAPI.Admin.AccountView + alias Pleroma.Web.AdminAPI.AccountView + alias Pleroma.Web.AdminAPI.Search import Pleroma.Web.ControllerHelper, only: [json_response: 3] require Logger + @users_page_size 50 + action_fallback(:errors) def user_delete(conn, %{"nickname" => nickname}) do @@ -44,6 +45,15 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do |> json(user.nickname) end + def user_show(conn, %{"nickname" => nickname}) do + with %User{} = user <- User.get_by_nickname(nickname) do + conn + |> json(AccountView.render("show.json", %{user: user})) + else + _ -> {:error, :not_found} + end + end + def user_toggle_activation(conn, %{"nickname" => nickname}) do user = User.get_by_nickname(nickname) @@ -63,17 +73,17 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do do: json_response(conn, :no_content, "") end - def list_users(%{assigns: %{user: admin}} = conn, params) do + def list_users(conn, params) do {page, page_size} = page_params(params) + filters = maybe_parse_filters(params["filters"]) - with {:ok, users, count} <- - User.search_for_admin(%{ - query: params["query"], - admin: admin, - local: params["local_only"] == "true", - page: page, - page_size: page_size - }), + search_params = %{ + query: params["query"], + page: page, + page_size: page_size + } + + with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)), do: conn |> json( @@ -85,6 +95,19 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do ) end + @filters ~w(local external active deactivated) + + defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{} + + @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{} + defp maybe_parse_filters(filters) do + filters + |> String.split(",") + |> Enum.filter(&Enum.member?(@filters, &1)) + |> Enum.map(&String.to_atom(&1)) + |> Enum.into(%{}, &{&1, true}) + end + def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname}) when permission_group in ["moderator", "admin"] do user = User.get_by_nickname(nickname) @@ -217,6 +240,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do |> json(token.token) end + def errors(conn, {:error, :not_found}) do + conn + |> put_status(404) + |> json("Not found") + end + def errors(conn, {:param_cast, _}) do conn |> put_status(400) diff --git a/lib/pleroma/web/admin_api/search.ex b/lib/pleroma/web/admin_api/search.ex new file mode 100644 index 000000000..9a8e41c2a --- /dev/null +++ b/lib/pleroma/web/admin_api/search.ex @@ -0,0 +1,54 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.Search do + import Ecto.Query + + alias Pleroma.Repo + alias Pleroma.User + + @page_size 50 + + def user(%{query: term} = params) when is_nil(term) or term == "" do + query = maybe_filtered_query(params) + + paginated_query = + maybe_filtered_query(params) + |> paginate(params[:page] || 1, params[:page_size] || @page_size) + + count = query |> Repo.aggregate(:count, :id) + + results = Repo.all(paginated_query) + + {:ok, results, count} + end + + def user(%{query: term} = params) when is_binary(term) do + search_query = from(u in maybe_filtered_query(params), where: ilike(u.nickname, ^"%#{term}%")) + + count = search_query |> Repo.aggregate(:count, :id) + + results = + search_query + |> paginate(params[:page] || 1, params[:page_size] || @page_size) + |> Repo.all() + + {:ok, results, count} + end + + defp maybe_filtered_query(params) do + from(u in User, order_by: u.nickname) + |> User.maybe_local_user_query(params[:local]) + |> User.maybe_external_user_query(params[:external]) + |> User.maybe_active_user_query(params[:active]) + |> User.maybe_deactivated_user_query(params[:deactivated]) + end + + defp paginate(query, page, page_size) do + from(u in query, + limit: ^page_size, + offset: ^((page - 1) * page_size) + ) + end +end diff --git a/lib/pleroma/web/mastodon_api/views/admin/account_view.ex b/lib/pleroma/web/admin_api/views/account_view.ex index 74ca13564..4d6f921ef 100644 --- a/lib/pleroma/web/mastodon_api/views/admin/account_view.ex +++ b/lib/pleroma/web/admin_api/views/account_view.ex @@ -2,10 +2,11 @@ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.MastodonAPI.Admin.AccountView do +defmodule Pleroma.Web.AdminAPI.AccountView do use Pleroma.Web, :view - alias Pleroma.Web.MastodonAPI.Admin.AccountView + alias Pleroma.User.Info + alias Pleroma.Web.AdminAPI.AccountView def render("index.json", %{users: users, count: count, page_size: page_size}) do %{ @@ -19,7 +20,10 @@ defmodule Pleroma.Web.MastodonAPI.Admin.AccountView do %{ "id" => user.id, "nickname" => user.nickname, - "deactivated" => user.info.deactivated + "deactivated" => user.info.deactivated, + "local" => user.local, + "roles" => Info.roles(user.info), + "tags" => user.tags || [] } end end diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex new file mode 100644 index 000000000..88217aab8 --- /dev/null +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -0,0 +1,143 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.LDAPAuthenticator do + alias Pleroma.User + + require Logger + + @behaviour Pleroma.Web.Auth.Authenticator + + @connection_timeout 10_000 + @search_timeout 10_000 + + def get_user(%Plug.Conn{} = conn) do + if Pleroma.Config.get([:ldap, :enabled]) do + {name, password} = + case conn.params do + %{"authorization" => %{"name" => name, "password" => password}} -> + {name, password} + + %{"grant_type" => "password", "username" => name, "password" => password} -> + {name, password} + end + + case ldap_user(name, password) do + %User{} = user -> + {:ok, user} + + {:error, {:ldap_connection_error, _}} -> + # When LDAP is unavailable, try default authenticator + Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn) + + error -> + error + end + else + # Fall back to default authenticator + Pleroma.Web.Auth.PleromaAuthenticator.get_user(conn) + end + end + + def handle_error(%Plug.Conn{} = _conn, error) do + error + end + + def auth_template, do: nil + + defp ldap_user(name, password) do + ldap = Pleroma.Config.get(:ldap, []) + host = Keyword.get(ldap, :host, "localhost") + port = Keyword.get(ldap, :port, 389) + ssl = Keyword.get(ldap, :ssl, false) + sslopts = Keyword.get(ldap, :sslopts, []) + + options = + [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++ + if sslopts != [], do: [{:sslopts, sslopts}], else: [] + + case :eldap.open([to_charlist(host)], options) do + {:ok, connection} -> + try do + if Keyword.get(ldap, :tls, false) do + :application.ensure_all_started(:ssl) + + case :eldap.start_tls( + connection, + Keyword.get(ldap, :tlsopts, []), + @connection_timeout + ) do + :ok -> + :ok + + error -> + Logger.error("Could not start TLS: #{inspect(error)}") + end + end + + bind_user(connection, ldap, name, password) + after + :eldap.close(connection) + end + + {:error, error} -> + Logger.error("Could not open LDAP connection: #{inspect(error)}") + {:error, {:ldap_connection_error, error}} + end + end + + defp bind_user(connection, ldap, name, password) do + uid = Keyword.get(ldap, :uid, "cn") + base = Keyword.get(ldap, :base) + + case :eldap.simple_bind(connection, "#{uid}=#{name},#{base}", password) do + :ok -> + case User.get_by_nickname_or_email(name) do + %User{} = user -> + user + + _ -> + register_user(connection, base, uid, name, password) + end + + error -> + error + end + end + + defp register_user(connection, base, uid, name, password) do + case :eldap.search(connection, [ + {:base, to_charlist(base)}, + {:filter, :eldap.equalityMatch(to_charlist(uid), to_charlist(name))}, + {:scope, :eldap.wholeSubtree()}, + {:attributes, ['mail', 'email']}, + {:timeout, @search_timeout} + ]) do + {:ok, {:eldap_search_result, [{:eldap_entry, _, attributes}], _}} -> + with {_, [mail]} <- List.keyfind(attributes, 'mail', 0) do + params = %{ + email: :erlang.list_to_binary(mail), + name: name, + nickname: name, + password: password, + password_confirmation: password + } + + changeset = User.register_changeset(%User{}, params) + + case User.register(changeset) do + {:ok, user} -> user + error -> error + end + else + _ -> + Logger.error("Could not find LDAP attribute mail: #{inspect(attributes)}") + {:error, :ldap_registration_missing_attributes} + end + + error -> + error + end + end +end diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index 3cc19af01..94a19ad49 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -3,13 +3,20 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Auth.PleromaAuthenticator do - alias Pleroma.User alias Comeonin.Pbkdf2 + alias Pleroma.User @behaviour Pleroma.Web.Auth.Authenticator def get_user(%Plug.Conn{} = conn) do - %{"authorization" => %{"name" => name, "password" => password}} = conn.params + {name, password} = + case conn.params do + %{"authorization" => %{"name" => name, "password" => password}} -> + {name, password} + + %{"grant_type" => "password", "username" => name, "password" => password} -> + {name, password} + end with {_, %User{} = user} <- {:user, User.get_by_nickname_or_email(name)}, {_, true} <- {:checkpw, Pbkdf2.checkpw(password, user.password_hash)} do diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex index aed8475fd..6503979a1 100644 --- a/lib/pleroma/web/channels/user_socket.ex +++ b/lib/pleroma/web/channels/user_socket.ex @@ -23,8 +23,8 @@ defmodule Pleroma.Web.UserSocket do # performing token verification on connect. def connect(%{"token" => token}, socket) do with true <- Pleroma.Config.get([:chat, :enabled]), - {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84600), - %User{} = user <- Pleroma.Repo.get(User, user_id) do + {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600), + %User{} = user <- Pleroma.User.get_by_id(user_id) do {:ok, assign(socket, :user_name, user.nickname)} else _e -> :error diff --git a/lib/pleroma/web/chat_channel.ex b/lib/pleroma/web/chat_channel.ex index fe63ede66..f63f4bda1 100644 --- a/lib/pleroma/web/chat_channel.ex +++ b/lib/pleroma/web/chat_channel.ex @@ -4,8 +4,8 @@ defmodule Pleroma.Web.ChatChannel do use Phoenix.Channel - alias Pleroma.Web.ChatChannel.ChatChannelState alias Pleroma.User + alias Pleroma.Web.ChatChannel.ChatChannelState def join("chat:public", _message, socket) do send(self(), :after_join) @@ -48,7 +48,7 @@ defmodule Pleroma.Web.ChatChannel.ChatChannelState do end) end - def messages() do + def messages do Agent.get(__MODULE__, fn state -> state[:messages] |> Enum.reverse() end) end end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 55a9c2572..25b990677 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -3,14 +3,13 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.CommonAPI do - alias Pleroma.User - alias Pleroma.Repo alias Pleroma.Activity + alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.ThreadMute + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Formatter import Pleroma.Web.CommonAPI.Utils @@ -27,10 +26,47 @@ defmodule Pleroma.Web.CommonAPI do end end + def unfollow(follower, unfollowed) do + with {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed), + {:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do + {:ok, follower} + end + end + + def accept_follow_request(follower, followed) do + with {:ok, follower} <- User.maybe_follow(follower, followed), + %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), + {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"), + {:ok, _activity} <- + ActivityPub.accept(%{ + to: [follower.ap_id], + actor: followed, + object: follow_activity.data["id"], + type: "Accept" + }) do + {:ok, follower} + end + end + + def reject_follow_request(follower, followed) do + with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), + {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"), + {:ok, _activity} <- + ActivityPub.reject(%{ + to: [follower.ap_id], + actor: followed, + object: follow_activity.data["id"], + type: "Reject" + }) do + {:ok, follower} + end + end + def delete(activity_id, user) do - with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id), - %Object{} = object <- Object.normalize(object_id), - true <- user.info.is_moderator || user.ap_id == object.data["actor"], + with %Activity{data: %{"object" => _}} = activity <- + Activity.get_by_id_with_object(activity_id), + %Object{} = object <- Object.normalize(activity), + true <- User.superuser?(user) || user.ap_id == object.data["actor"], {:ok, _} <- unpin(activity_id, user), {:ok, delete} <- ActivityPub.delete(object) do {:ok, delete} @@ -39,7 +75,7 @@ defmodule Pleroma.Web.CommonAPI do def repeat(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - object <- Object.normalize(activity.data["object"]["id"]), + object <- Object.normalize(activity), nil <- Utils.get_existing_announce(user.ap_id, object) do ActivityPub.announce(user, object) else @@ -50,7 +86,7 @@ defmodule Pleroma.Web.CommonAPI do def unrepeat(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - object <- Object.normalize(activity.data["object"]["id"]) do + object <- Object.normalize(activity) do ActivityPub.unannounce(user, object) else _ -> @@ -60,7 +96,7 @@ defmodule Pleroma.Web.CommonAPI do def favorite(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - object <- Object.normalize(activity.data["object"]["id"]), + object <- Object.normalize(activity), nil <- Utils.get_existing_like(user.ap_id, object) do ActivityPub.like(user, object) else @@ -71,7 +107,7 @@ defmodule Pleroma.Web.CommonAPI do def unfavorite(id_or_ap_id, user) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), - object <- Object.normalize(activity.data["object"]["id"]) do + object <- Object.normalize(activity) do ActivityPub.unlike(user, object) else _ -> @@ -88,8 +124,8 @@ defmodule Pleroma.Web.CommonAPI do nil -> "public" - inReplyTo -> - Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"]) + in_reply_to -> + Pleroma.Web.MastodonAPI.StatusView.get_visibility(in_reply_to.data["object"]) end end @@ -101,15 +137,16 @@ defmodule Pleroma.Web.CommonAPI do with status <- String.trim(status), attachments <- attachments_from_ids(data), - inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]), + in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]), {content_html, mentions, tags} <- make_content_html( status, attachments, - data + data, + visibility ), - {to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility), - context <- make_context(inReplyTo), + {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility), + context <- make_context(in_reply_to), cw <- data["spoiler_text"], full_payload <- String.trim(status <> (data["spoiler_text"] || "")), length when length in 1..limit <- String.length(full_payload), @@ -120,7 +157,7 @@ defmodule Pleroma.Web.CommonAPI do context, content_html, attachments, - inReplyTo, + in_reply_to, tags, cw, cc @@ -248,14 +285,9 @@ defmodule Pleroma.Web.CommonAPI do actor: user, account: account, statuses: statuses, - content: content_html + content: content_html, + forward: data["forward"] || false }) do - Enum.each(User.all_superusers(), fn superuser -> - superuser - |> Pleroma.AdminEmail.report(user, account, statuses, content_html) - |> Pleroma.Mailer.deliver_async() - end) - {:ok, activity} else {:error, err} -> {:error, err} @@ -263,4 +295,24 @@ defmodule Pleroma.Web.CommonAPI do {:account, nil} -> {:error, "Account not found"} end end + + def hide_reblogs(user, muted) do + ap_id = muted.ap_id + + if ap_id not in user.info.muted_reblogs do + info_changeset = User.Info.add_reblog_mute(user.info, ap_id) + changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset) + User.update_and_set_cache(changeset) + end + end + + def show_reblogs(user, muted) do + ap_id = muted.ap_id + + if ap_id in user.info.muted_reblogs do + info_changeset = User.Info.remove_reblog_mute(user.info, ap_id) + changeset = Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset) + User.update_and_set_cache(changeset) + end + end end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index e4b9102c5..40cea3090 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -6,31 +6,32 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Calendar.Strftime alias Comeonin.Pbkdf2 alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User - alias Pleroma.Config + alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Endpoint alias Pleroma.Web.MediaProxy - alias Pleroma.Web.ActivityPub.Utils # This is a hack for twidere. def get_by_id_or_ap_id(id) do - activity = Repo.get(Activity, id) || Activity.get_create_by_object_ap_id(id) + activity = + Activity.get_by_id_with_object(id) || Activity.get_create_by_object_ap_id_with_object(id) activity && if activity.data["type"] == "Create" do activity else - Activity.get_create_by_object_ap_id(activity.data["object"]) + Activity.get_create_by_object_ap_id_with_object(activity.data["object"]) end end def get_replied_to_activity(""), do: nil def get_replied_to_activity(id) when not is_nil(id) do - Repo.get(Activity, id) + Activity.get_by_id(id) end def get_replied_to_activity(_), do: nil @@ -101,7 +102,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do def make_content_html( status, attachments, - data + data, + visibility ) do no_attachment_links = data @@ -110,8 +112,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do content_type = get_content_type(data["content_type"]) + options = + if visibility == "direct" && Config.get([:instance, :safe_dm_mentions]) do + [safe_mention: true] + else + [] + end + status - |> format_input(content_type) + |> format_input(content_type, options) |> maybe_add_attachments(attachments, no_attachment_links) |> maybe_add_nsfw_tag(data) end @@ -266,7 +275,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do end def confirm_current_password(user, password) do - with %User{local: true} = db_user <- Repo.get(User, user.id), + with %User{local: true} = db_user <- User.get_by_id(user.id), true <- Pbkdf2.checkpw(password, db_user.password_hash) do {:ok, db_user} else @@ -294,10 +303,10 @@ defmodule Pleroma.Web.CommonAPI.Utils do def maybe_notify_mentioned_recipients( recipients, - %Activity{data: %{"to" => _to, "type" => type} = data} = _activity + %Activity{data: %{"to" => _to, "type" => type} = data} = activity ) when type == "Create" do - object = Object.normalize(data["object"]) + object = Object.normalize(activity) object_data = cond do @@ -344,4 +353,33 @@ defmodule Pleroma.Web.CommonAPI.Utils do end def get_report_statuses(_, _), do: {:ok, nil} + + # DEPRECATED mostly, context objects are now created at insertion time. + def context_to_conversation_id(context) do + with %Object{id: id} <- Object.get_cached_by_ap_id(context) do + id + else + _e -> + changeset = Object.context_mapping(context) + + case Repo.insert(changeset) do + {:ok, %{id: id}} -> + id + + # This should be solved by an upsert, but it seems ecto + # has problems accessing the constraint inside the jsonb. + {:error, _} -> + Object.get_cached_by_ap_id(context).id + end + end + end + + def conversation_id_to_context(id) do + with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do + context + else + _e -> + {:error, "No such conversation"} + end + end end diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 5915ea40e..181483664 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -5,8 +5,14 @@ defmodule Pleroma.Web.ControllerHelper do use Pleroma.Web, :controller + # As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html + @falsy_param_values [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"] + def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil + def truthy_param?(value), do: value not in @falsy_param_values + def oauth_scopes(params, default) do - # Note: `scopes` is used by Mastodon — supporting it but sticking to OAuth's standard `scope` wherever we control it + # Note: `scopes` is used by Mastodon — supporting it but sticking to + # OAuth's standard `scope` wherever we control it Pleroma.Web.OAuth.parse_scopes(params["scope"] || params["scopes"], default) end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 3eed047ca..fa2d1cbe7 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -25,7 +25,8 @@ defmodule Pleroma.Web.Endpoint do at: "/", from: :pleroma, only: - ~w(index.html static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc) + ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc) + # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength ) # Code reloading can be explicitly enabled under the diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index fbfe97dbc..c47328e13 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -5,65 +5,64 @@ defmodule Pleroma.Web.Federator do alias Pleroma.Activity alias Pleroma.User - alias Pleroma.Web.WebFinger - alias Pleroma.Web.Websub - alias Pleroma.Web.Salmon alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Federator.RetryQueue alias Pleroma.Web.OStatus - alias Pleroma.Jobs + alias Pleroma.Web.Salmon + alias Pleroma.Web.WebFinger + alias Pleroma.Web.Websub require Logger @websub Application.get_env(:pleroma, :websub) @ostatus Application.get_env(:pleroma, :ostatus) - def init() do + def init do # 1 minute - Process.sleep(1000 * 60 * 1) + Process.sleep(1000 * 60) refresh_subscriptions() end # Client API def incoming_doc(doc) do - Jobs.enqueue(:federator_incoming, __MODULE__, [:incoming_doc, doc]) + PleromaJobQueue.enqueue(:federator_incoming, __MODULE__, [:incoming_doc, doc]) end def incoming_ap_doc(params) do - Jobs.enqueue(:federator_incoming, __MODULE__, [:incoming_ap_doc, params]) + PleromaJobQueue.enqueue(:federator_incoming, __MODULE__, [:incoming_ap_doc, params]) end def publish(activity, priority \\ 1) do - Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority) + PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority) end def publish_single_ap(params) do - Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params]) + PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params]) end def publish_single_websub(websub) do - Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub]) + PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub]) end def verify_websub(websub) do - Jobs.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub]) + PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub]) end def request_subscription(sub) do - Jobs.enqueue(:federator_outgoing, __MODULE__, [:request_subscription, sub]) + PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:request_subscription, sub]) end - def refresh_subscriptions() do - Jobs.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions]) + def refresh_subscriptions do + PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions]) end def publish_single_salmon(params) do - Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params]) + PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params]) end # Job Worker Callbacks diff --git a/lib/pleroma/web/federator/retry_queue.ex b/lib/pleroma/web/federator/retry_queue.ex index e0ce251d2..71e49494f 100644 --- a/lib/pleroma/web/federator/retry_queue.ex +++ b/lib/pleroma/web/federator/retry_queue.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do {:ok, %{args | queue_table: queue_table, running_jobs: :sets.new()}} end - def start_link() do + def start_link do enabled = if Mix.env() == :test, do: true, else: Pleroma.Config.get([__MODULE__, :enabled], false) @@ -39,11 +39,11 @@ defmodule Pleroma.Web.Federator.RetryQueue do GenServer.cast(__MODULE__, {:maybe_enqueue, data, transport, retries + 1}) end - def get_stats() do + def get_stats do GenServer.call(__MODULE__, :get_stats) end - def reset_stats() do + def reset_stats do GenServer.call(__MODULE__, :reset_stats) end @@ -55,7 +55,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do end end - def get_retry_timer_interval() do + def get_retry_timer_interval do Pleroma.Config.get([:retry_queue, :interval], 1000) end @@ -231,7 +231,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do end end - defp maybe_kickoff_timer() do + defp maybe_kickoff_timer do GenServer.cast(__MODULE__, :kickoff_timer) end end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 8b1378917..08ea5f967 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -1 +1,51 @@ +defmodule Pleroma.Web.MastodonAPI.MastodonAPI do + import Ecto.Query + import Ecto.Changeset + alias Pleroma.Activity + alias Pleroma.Notification + alias Pleroma.Pagination + alias Pleroma.User + + def get_followers(user, params \\ %{}) do + user + |> User.get_followers_query() + |> Pagination.fetch_paginated(params) + end + + def get_friends(user, params \\ %{}) do + user + |> User.get_friends_query() + |> Pagination.fetch_paginated(params) + end + + def get_notifications(user, params \\ %{}) do + options = cast_params(params) + + user + |> Notification.for_user_query() + |> restrict(:exclude_types, options) + |> Pagination.fetch_paginated(params) + end + + defp cast_params(params) do + param_types = %{ + exclude_types: {:array, :string} + } + + changeset = cast({%{}, param_types}, params, Map.keys(param_types)) + changeset.changes + end + + defp restrict(query, :exclude_types, %{exclude_types: mastodon_types = [_ | _]}) do + ap_types = + mastodon_types + |> Enum.map(&Activity.from_mastodon_notification_type/1) + |> Enum.filter(& &1) + + query + |> where([q, a], not fragment("? @> ARRAY[?->>'type']::varchar[]", ^ap_types, a.data)) + end + + defp restrict(query, _, _), do: query +end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 8c58f4545..0141186d8 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller + alias Pleroma.Activity alias Pleroma.Config alias Pleroma.Filter @@ -13,18 +14,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do alias Pleroma.Stats alias Pleroma.User alias Pleroma.Web + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.CommonAPI - alias Pleroma.Web.MediaProxy - alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.MastodonAPI.AppView alias Pleroma.Web.MastodonAPI.FilterView alias Pleroma.Web.MastodonAPI.ListView + alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonView - alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.ReportView - alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.MediaProxy alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Token @@ -50,16 +52,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do with cs <- App.register_changeset(%App{}, app_attrs), false <- cs.changes[:client_name] == @local_mastodon_name, {:ok, app} <- Repo.insert(cs) do - res = %{ - id: app.id |> to_string, - name: app.client_name, - client_id: app.client_id, - client_secret: app.client_secret, - redirect_uri: app.redirect_uris, - website: app.website - } - - json(conn, res) + conn + |> put_view(AppView) + |> render("show.json", %{app: app}) end end @@ -131,8 +126,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, account) end - def user(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do - with %User{} = user <- Repo.get(User, id), + def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do + with %Token{app: %App{} = app} <- Repo.preload(token, :app) do + conn + |> put_view(AppView) + |> render("short.json", %{app: app}) + end + end + + def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do + with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id), true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do account = AccountView.render("account.json", %{user: user, for: for_user}) json(conn, account) @@ -160,6 +163,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do }, stats: Stats.get_stats(), thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg", + languages: ["en"], + registrations: Pleroma.Config.get([:instance, :registrations_open]), + # Extra (not present in Mastodon): max_toot_chars: Keyword.get(instance, :limit) } @@ -279,7 +285,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do - with %User{} = user <- Repo.get(User, params["id"]) do + with %User{} = user <- User.get_by_id(params["id"]) do activities = ActivityPub.fetch_user_activities(user, reading_user, params) conn @@ -313,7 +319,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Repo.get(Activity, id), + with %Activity{} = activity <- Activity.get_by_id(id), true <- Visibility.visible_for_user?(activity, user) do conn |> put_view(StatusView) @@ -322,7 +328,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def get_context(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Repo.get(Activity, id), + with %Activity{} = activity <- Activity.get_by_id(id), activities <- ActivityPub.fetch_activities_for_context(activity.data["context"], %{ "blocking_user" => user, @@ -454,7 +460,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Repo.get(Activity, id), + with %Activity{} = activity <- Activity.get_by_id(id), %User{} = user <- User.get_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do @@ -465,7 +471,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Repo.get(Activity, id), + with %Activity{} = activity <- Activity.get_by_id(id), %User{} = user <- User.get_by_nickname(user.nickname), true <- Visibility.visible_for_user?(activity, user), {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do @@ -501,21 +507,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def notifications(%{assigns: %{user: user}} = conn, params) do - notifications = Notification.for_user(user, params) - - result = - notifications - |> Enum.map(fn x -> render_notification(user, x) end) - |> Enum.filter(& &1) + notifications = MastodonAPI.get_notifications(user, params) conn |> add_link_headers(:notifications, notifications) - |> json(result) + |> put_view(NotificationView) + |> render("index.json", %{notifications: notifications, for: user}) end def get_notification(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do with {:ok, notification} <- Notification.get(user, id) do - json(conn, render_notification(user, notification)) + conn + |> put_view(NotificationView) + |> render("show.json", %{notification: notification, for: user}) else {:error, reason} -> conn @@ -589,7 +593,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def favourited_by(conn, %{"id" => id}) do - with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do + with %Activity{data: %{"object" => %{"likes" => likes}}} <- Activity.get_by_id(id) do q = from(u in User, where: u.ap_id in ^likes) users = Repo.all(q) @@ -602,7 +606,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def reblogged_by(conn, %{"id" => id}) do - with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Repo.get(Activity, id) do + with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Activity.get_by_id(id) do q = from(u in User, where: u.ap_id in ^announces) users = Repo.all(q) @@ -652,9 +656,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> render("index.json", %{activities: activities, for: user, as: :activity}) end - def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do - with %User{} = user <- Repo.get(User, id), - {:ok, followers} <- User.get_followers(user) do + def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do + with %User{} = user <- User.get_by_id(id), + followers <- MastodonAPI.get_followers(user, params) do followers = cond do for_user && user.id == for_user.id -> followers @@ -663,14 +667,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end conn + |> add_link_headers(:followers, followers, user) |> put_view(AccountView) |> render("accounts.json", %{users: followers, as: :user}) end end - def following(%{assigns: %{user: for_user}} = conn, %{"id" => id}) do - with %User{} = user <- Repo.get(User, id), - {:ok, followers} <- User.get_friends(user) do + def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do + with %User{} = user <- User.get_by_id(id), + followers <- MastodonAPI.get_friends(user, params) do followers = cond do for_user && user.id == for_user.id -> followers @@ -679,6 +684,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end conn + |> add_link_headers(:following, followers, user) |> put_view(AccountView) |> render("accounts.json", %{users: followers, as: :user}) end @@ -693,17 +699,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do - with %User{} = follower <- Repo.get(User, id), - {:ok, follower} <- User.maybe_follow(follower, followed), - %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), - {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"), - {:ok, _activity} <- - ActivityPub.accept(%{ - to: [follower.ap_id], - actor: followed, - object: follow_activity.data["id"], - type: "Accept" - }) do + with %User{} = follower <- User.get_by_id(id), + {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do conn |> put_view(AccountView) |> render("relationship.json", %{user: followed, target: follower}) @@ -716,16 +713,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do - with %User{} = follower <- Repo.get(User, id), - %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), - {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"), - {:ok, _activity} <- - ActivityPub.reject(%{ - to: [follower.ap_id], - actor: followed, - object: follow_activity.data["id"], - type: "Reject" - }) do + with %User{} = follower <- User.get_by_id(id), + {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do conn |> put_view(AccountView) |> render("relationship.json", %{user: followed, target: follower}) @@ -738,12 +727,26 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do - with %User{} = followed <- Repo.get(User, id), + with %User{} = followed <- User.get_by_id(id), + false <- User.following?(follower, followed), {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do conn |> put_view(AccountView) |> render("relationship.json", %{user: follower, target: followed}) else + true -> + followed = User.get_cached_by_id(id) + + {:ok, follower} = + case conn.params["reblogs"] do + true -> CommonAPI.show_reblogs(follower, followed) + false -> CommonAPI.hide_reblogs(follower, followed) + end + + conn + |> put_view(AccountView) + |> render("relationship.json", %{user: follower, target: followed}) + {:error, message} -> conn |> put_resp_content_type("application/json") @@ -766,9 +769,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do - with %User{} = followed <- Repo.get(User, id), - {:ok, _activity} <- ActivityPub.unfollow(follower, followed), - {:ok, follower, _} <- User.unfollow(follower, followed) do + with %User{} = followed <- User.get_by_id(id), + {:ok, follower} <- CommonAPI.unfollow(follower, followed) do conn |> put_view(AccountView) |> render("relationship.json", %{user: follower, target: followed}) @@ -776,7 +778,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do - with %User{} = muted <- Repo.get(User, id), + with %User{} = muted <- User.get_by_id(id), {:ok, muter} <- User.mute(muter, muted) do conn |> put_view(AccountView) @@ -790,7 +792,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do - with %User{} = muted <- Repo.get(User, id), + with %User{} = muted <- User.get_by_id(id), {:ok, muter} <- User.unmute(muter, muted) do conn |> put_view(AccountView) @@ -811,7 +813,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do - with %User{} = blocked <- Repo.get(User, id), + with %User{} = blocked <- User.get_by_id(id), {:ok, blocker} <- User.block(blocker, blocked), {:ok, _activity} <- ActivityPub.block(blocker, blocked) do conn @@ -826,7 +828,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do - with %User{} = blocked <- Repo.get(User, id), + with %User{} = blocked <- User.get_by_id(id), {:ok, blocker} <- User.unblock(blocker, blocked), {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do conn @@ -947,12 +949,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def favourites(%{assigns: %{user: user}} = conn, params) do - activities = + params = params |> Map.put("type", "Create") |> Map.put("favorited_by", user.ap_id) |> Map.put("blocking_user", user) - |> ActivityPub.fetch_public_activities() + + activities = + ActivityPub.fetch_activities([], params) |> Enum.reverse() conn @@ -962,7 +966,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def bookmarks(%{assigns: %{user: user}} = conn, _) do - user = Repo.get(User, user.id) + user = User.get_by_id(user.id) activities = user.bookmarks @@ -1019,7 +1023,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do accounts |> Enum.each(fn account_id -> with %Pleroma.List{} = list <- Pleroma.List.get(id, user), - %User{} = followed <- Repo.get(User, account_id) do + %User{} = followed <- User.get_by_id(account_id) do Pleroma.List.follow(list, followed) end end) @@ -1031,7 +1035,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do accounts |> Enum.each(fn account_id -> with %Pleroma.List{} = list <- Pleroma.List.get(id, user), - %User{} = followed <- Repo.get(Pleroma.User, account_id) do + %User{} = followed <- Pleroma.User.get_by_id(account_id) do Pleroma.List.unfollow(list, followed) end end) @@ -1126,7 +1130,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do compose: %{ me: "#{user.id}", default_privacy: user.info.default_scope, - default_sensitive: false + default_sensitive: false, + allow_content_types: Config.get([:instance, :allowed_post_formats]) }, media_attachments: %{ accept_content_types: [ @@ -1244,16 +1249,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do "glitch" end - def login(conn, %{"code" => code}) do + def login(%{assigns: %{user: %User{}}} = conn, _params) do + redirect(conn, to: local_mastodon_root_path(conn)) + end + + @doc "Local Mastodon FE login init action" + def login(conn, %{"code" => auth_token}) do with {:ok, app} <- get_or_make_app(), - %Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id), + %Authorization{} = auth <- Repo.get_by(Authorization, token: auth_token, app_id: app.id), {:ok, token} <- Token.exchange_token(app, auth) do conn |> put_session(:oauth_token, token.token) - |> redirect(to: "/web/getting-started") + |> redirect(to: local_mastodon_root_path(conn)) end end + @doc "Local Mastodon FE callback action" def login(conn, _) do with {:ok, app} <- get_or_make_app() do path = @@ -1271,7 +1282,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - defp get_or_make_app() do + defp local_mastodon_root_path(conn), do: mastodon_api_path(conn, :index, ["getting-started"]) + + defp get_or_make_app do find_attrs = %{client_name: @local_mastodon_name, redirect_uris: "."} scopes = ["read", "write", "follow", "push"] @@ -1307,7 +1320,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do Logger.debug("Unimplemented, returning unmodified relationship") - with %User{} = target <- Repo.get(User, id) do + with %User{} = target <- User.get_by_id(id) do conn |> put_view(AccountView) |> render("relationship.json", %{user: user, target: target}) @@ -1324,45 +1337,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, %{}) end - def render_notification(user, %{id: id, activity: activity, inserted_at: created_at} = _params) do - actor = User.get_cached_by_ap_id(activity.data["actor"]) - parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) - mastodon_type = Activity.mastodon_notification_type(activity) - - response = %{ - id: to_string(id), - type: mastodon_type, - created_at: CommonAPI.Utils.to_masto_date(created_at), - account: AccountView.render("account.json", %{user: actor, for: user}) - } - - case mastodon_type do - "mention" -> - response - |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: activity, for: user}) - }) - - "favourite" -> - response - |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: parent_activity, for: user}) - }) - - "reblog" -> - response - |> Map.merge(%{ - status: StatusView.render("status.json", %{activity: parent_activity, for: user}) - }) - - "follow" -> - response - - _ -> - nil - end - end - def get_filters(%{assigns: %{user: user}} = conn, _) do filters = Filter.get_filters(user) res = FilterView.render("filters.json", filters: filters) @@ -1452,7 +1426,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do url, [], adapter: [ - timeout: timeout, recv_timeout: timeout, pool: :default ] @@ -1489,7 +1462,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do - with %Activity{} = activity <- Repo.get(Activity, status_id), + with %Activity{} = activity <- Activity.get_by_id(status_id), true <- Visibility.visible_for_user?(activity, user) do data = StatusView.render( diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index c32f27be2..b5f3bbb9d 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -55,7 +55,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do muting_notifications: false, requested: requested, domain_blocking: false, - showing_reblogs: false, + showing_reblogs: User.showing_reblogs?(user, target), endorsed: false } end diff --git a/lib/pleroma/web/mastodon_api/views/app_view.ex b/lib/pleroma/web/mastodon_api/views/app_view.ex new file mode 100644 index 000000000..f52b693a6 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/app_view.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.AppView do + use Pleroma.Web, :view + + alias Pleroma.Web.OAuth.App + + @vapid_key :web_push_encryption + |> Application.get_env(:vapid_details, []) + |> Keyword.get(:public_key) + + def render("show.json", %{app: %App{} = app}) do + %{ + id: app.id |> to_string, + name: app.client_name, + client_id: app.client_id, + client_secret: app.client_secret, + redirect_uri: app.redirect_uris, + website: app.website + } + |> with_vapid_key() + end + + def render("short.json", %{app: %App{website: webiste, client_name: name}}) do + %{ + name: name, + website: webiste + } + |> with_vapid_key() + end + + defp with_vapid_key(data) do + if @vapid_key do + Map.put(data, "vapid_key", @vapid_key) + else + data + end + end +end diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex new file mode 100644 index 000000000..27e9cab06 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -0,0 +1,64 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.NotificationView do + use Pleroma.Web, :view + + alias Pleroma.Activity + alias Pleroma.Notification + alias Pleroma.User + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI.AccountView + alias Pleroma.Web.MastodonAPI.NotificationView + alias Pleroma.Web.MastodonAPI.StatusView + + def render("index.json", %{notifications: notifications, for: user}) do + render_many(notifications, NotificationView, "show.json", %{for: user}) + end + + def render("show.json", %{ + notification: %Notification{activity: activity} = notification, + for: user + }) do + actor = User.get_cached_by_ap_id(activity.data["actor"]) + parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) + mastodon_type = Activity.mastodon_notification_type(activity) + + response = %{ + id: to_string(notification.id), + type: mastodon_type, + created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at), + account: AccountView.render("account.json", %{user: actor, for: user}), + pleroma: %{ + is_seen: notification.seen + } + } + + case mastodon_type do + "mention" -> + response + |> Map.merge(%{ + status: StatusView.render("status.json", %{activity: activity, for: user}) + }) + + "favourite" -> + response + |> Map.merge(%{ + status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + }) + + "reblog" -> + response + |> Map.merge(%{ + status: StatusView.render("status.json", %{activity: parent_activity, for: user}) + }) + + "follow" -> + response + + _ -> + nil + end + end +end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3468c0e1c..200bb453d 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -46,6 +46,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do end end + defp get_context_id(%{data: %{"context_id" => context_id}}) when not is_nil(context_id), + do: context_id + + defp get_context_id(%{data: %{"context" => context}}) when is_binary(context), + do: Utils.context_to_conversation_id(context) + + defp get_context_id(_), do: nil + def render("index.json", opts) do replied_to_activities = get_replied_to_activities(opts.activities) @@ -102,7 +110,10 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do website: nil }, language: nil, - emojis: [] + emojis: [], + pleroma: %{ + local: activity.local + } } end @@ -163,7 +174,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do content: content, created_at: created_at, reblogs_count: announcement_count, - replies_count: 0, + replies_count: object["repliesCount"] || 0, favourites_count: like_count, reblogged: present?(repeated), favourited: present?(favorited), @@ -181,7 +192,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do website: nil }, language: nil, - emojis: build_emojis(activity.data["object"]["emoji"]) + emojis: build_emojis(activity.data["object"]["emoji"]), + pleroma: %{ + local: activity.local, + conversation_id: get_context_id(activity) + } } end @@ -251,7 +266,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do preview_url: href, text_url: href, type: type, - description: attachment["name"] + description: attachment["name"], + pleroma: %{mime_type: media_type} } end diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 8efe2efd5..1b3721e2b 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -5,9 +5,9 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do require Logger - alias Pleroma.Web.OAuth.Token alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.OAuth.Token @behaviour :cowboy_websocket @@ -90,7 +90,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do # Authenticated streams. defp allow_request(stream, {"access_token", access_token}) when stream in @streams do with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token), - user = %User{} <- Repo.get(User, user_id) do + user = %User{} <- User.get_by_id(user_id) do {:ok, user} else _ -> {:error, 403} diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 39a725a69..3bd2affe9 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -19,7 +19,8 @@ defmodule Pleroma.Web.MediaProxy do else secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] - # Must preserve `%2F` for compatibility with S3 (https://git.pleroma.social/pleroma/pleroma/issues/580) + # Must preserve `%2F` for compatibility with S3 + # https://git.pleroma.social/pleroma/pleroma/issues/580 replacement = get_replacement(url, ":2F:") # The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice. diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/opengraph.ex index cafb8134b..357b80a2d 100644 --- a/lib/pleroma/web/metadata/opengraph.ex +++ b/lib/pleroma/web/metadata/opengraph.ex @@ -88,7 +88,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do # TODO: Add additional properties to objects when we have the data available. # Also, Whatsapp only wants JPEG or PNGs. It seems that if we add a second og:image - # object when a Video or GIF is attached it will display that in the Whatsapp Rich Preview. + # object when a Video or GIF is attached it will display that in Whatsapp Rich Preview. case media_type do "audio" -> [ diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/twitter_card.ex index a0be383e5..040b872e7 100644 --- a/lib/pleroma/web/metadata/twitter_card.ex +++ b/lib/pleroma/web/metadata/twitter_card.ex @@ -97,7 +97,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do | acc ] - # TODO: Need the true width and height values here or Twitter renders an iFrame with a bad aspect ratio + # TODO: Need the true width and height values here or Twitter renders an iFrame with + # a bad aspect ratio "video" -> [ {:meta, [property: "twitter:card", content: "player"], []}, diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 5fc9c9e7b..23bbde1a6 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -1,10 +1,10 @@ # Pleroma: A lightweight social networking server -# Copyright \xc2\xa9 2017-2019 Pleroma Authors <https://pleroma.social/> +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Utils do - alias Pleroma.HTML alias Pleroma.Formatter + alias Pleroma.HTML alias Pleroma.Web.MediaProxy def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex deleted file mode 100644 index 8b1378917..000000000 --- a/lib/pleroma/web/nodeinfo/nodeinfo.ex +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 8c775ce24..216a962bd 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -124,6 +124,9 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do end, if Keyword.get(instance, :allow_relay) do "relay" + end, + if Keyword.get(instance, :safe_dm_mentions) do + "safe_dm_mentions" end ] |> Enum.filter(& &1) diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex index d37c2cb83..3461f9983 100644 --- a/lib/pleroma/web/oauth/authorization.ex +++ b/lib/pleroma/web/oauth/authorization.ex @@ -5,10 +5,10 @@ defmodule Pleroma.Web.OAuth.Authorization do use Ecto.Schema - alias Pleroma.User alias Pleroma.Repo - alias Pleroma.Web.OAuth.Authorization + alias Pleroma.User alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Authorization import Ecto.Changeset import Ecto.Query @@ -16,7 +16,7 @@ defmodule Pleroma.Web.OAuth.Authorization do schema "oauth_authorizations" do field(:token, :string) field(:scopes, {:array, :string}, default: []) - field(:valid_until, :naive_datetime) + field(:valid_until, :naive_datetime_usec) field(:used, :boolean, default: false) belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId) belongs_to(:app, App) diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 36318d69b..26d53df1a 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -5,13 +5,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do use Pleroma.Web, :controller + alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.Auth.Authenticator + alias Pleroma.Web.ControllerHelper + alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Token - alias Pleroma.Web.OAuth.App - alias Pleroma.Repo - alias Pleroma.User - alias Comeonin.Pbkdf2 import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2] @@ -20,7 +20,28 @@ defmodule Pleroma.Web.OAuth.OAuthController do action_fallback(Pleroma.Web.OAuth.FallbackController) - def authorize(conn, params) do + def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do + if ControllerHelper.truthy_param?(params["force_login"]) do + do_authorize(conn, params) + else + redirect_uri = + if is_binary(params["redirect_uri"]) do + params["redirect_uri"] + else + app = Repo.preload(token, :app).app + + app.redirect_uris + |> String.split() + |> Enum.at(0) + end + + redirect(conn, external: redirect_uri(conn, redirect_uri)) + end + end + + def authorize(conn, params), do: do_authorize(conn, params) + + defp do_authorize(conn, params) do app = Repo.get_by(App, client_id: params["client_id"]) available_scopes = (app && app.scopes) || [] scopes = oauth_scopes(params, nil) || available_scopes @@ -52,13 +73,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do {:missing_scopes, false} <- {:missing_scopes, scopes == []}, {:auth_active, true} <- {:auth_active, User.auth_active?(user)}, {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do - redirect_uri = - if redirect_uri == "." do - # Special case: Local MastodonFE - mastodon_api_url(conn, :login) - else - redirect_uri - end + redirect_uri = redirect_uri(conn, redirect_uri) cond do redirect_uri == "urn:ietf:wg:oauth:2.0:oob" -> @@ -84,14 +99,18 @@ defmodule Pleroma.Web.OAuth.OAuthController do end else {scopes_issue, _} when scopes_issue in [:unsupported_scopes, :missing_scopes] -> + # Per https://github.com/tootsuite/mastodon/blob/ + # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39 conn - |> put_flash(:error, "Permissions not specified.") + |> put_flash(:error, "This action is outside the authorized scopes") |> put_status(:unauthorized) |> authorize(auth_params) {:auth_active, false} -> + # Per https://github.com/tootsuite/mastodon/blob/ + # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 conn - |> put_flash(:error, "Account confirmation pending.") + |> put_flash(:error, "Your login is missing a confirmed e-mail address") |> put_status(:forbidden) |> authorize(auth_params) @@ -105,6 +124,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do fixed_token = fix_padding(params["code"]), %Authorization{} = auth <- Repo.get_by(Authorization, token: fixed_token, app_id: app.id), + %User{} = user <- User.get_by_id(auth.user_id), {:ok, token} <- Token.exchange_token(app, auth), {:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do response = %{ @@ -113,7 +133,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do refresh_token: token.refresh_token, created_at: DateTime.to_unix(inserted_at), expires_in: 60 * 10, - scope: Enum.join(token.scopes, " ") + scope: Enum.join(token.scopes, " "), + me: user.ap_id } json(conn, response) @@ -126,11 +147,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do def token_exchange( conn, - %{"grant_type" => "password", "username" => name, "password" => password} = params + %{"grant_type" => "password"} = params ) do - with %App{} = app <- get_app_from_request(conn, params), - %User{} = user <- User.get_by_nickname_or_email(name), - true <- Pbkdf2.checkpw(password, user.password_hash), + with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)}, + %App{} = app <- get_app_from_request(conn, params), {:auth_active, true} <- {:auth_active, User.auth_active?(user)}, scopes <- oauth_scopes(params, app.scopes), [] <- scopes -- app.scopes, @@ -142,15 +162,18 @@ defmodule Pleroma.Web.OAuth.OAuthController do access_token: token.token, refresh_token: token.refresh_token, expires_in: 60 * 10, - scope: Enum.join(token.scopes, " ") + scope: Enum.join(token.scopes, " "), + me: user.ap_id } json(conn, response) else {:auth_active, false} -> + # Per https://github.com/tootsuite/mastodon/blob/ + # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 conn |> put_status(:forbidden) - |> json(%{error: "Account confirmation pending"}) + |> json(%{error: "Your login is missing a confirmed e-mail address"}) _error -> put_status(conn, 400) @@ -214,4 +237,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do nil end end + + # Special case: Local MastodonFE + defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login) + + defp redirect_uri(_conn, redirect_uri), do: redirect_uri end diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index ca67632ba..2b5ad9b94 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -7,17 +7,17 @@ defmodule Pleroma.Web.OAuth.Token do import Ecto.Query - alias Pleroma.User alias Pleroma.Repo - alias Pleroma.Web.OAuth.Token + alias Pleroma.User alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization + alias Pleroma.Web.OAuth.Token schema "oauth_tokens" do field(:token, :string) field(:refresh_token, :string) field(:scopes, {:array, :string}, default: []) - field(:valid_until, :naive_datetime) + field(:valid_until, :naive_datetime_usec) belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId) belongs_to(:app, App) @@ -27,7 +27,7 @@ defmodule Pleroma.Web.OAuth.Token do def exchange_token(app, auth) do with {:ok, auth} <- Authorization.use_token(auth), true <- auth.app_id == app.id do - create_token(app, Repo.get(User, auth.user_id), auth.scopes) + create_token(app, User.get_by_id(auth.user_id), auth.scopes) end end diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex index 9e1f24bc4..1a1b74bb0 100644 --- a/lib/pleroma/web/ostatus/activity_representer.ex +++ b/lib/pleroma/web/ostatus/activity_representer.ex @@ -4,8 +4,8 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do alias Pleroma.Activity - alias Pleroma.User alias Pleroma.Object + alias Pleroma.User alias Pleroma.Web.OStatus.UserRepresenter require Logger diff --git a/lib/pleroma/web/ostatus/feed_representer.ex b/lib/pleroma/web/ostatus/feed_representer.ex index 025d4731c..b7b97e505 100644 --- a/lib/pleroma/web/ostatus/feed_representer.ex +++ b/lib/pleroma/web/ostatus/feed_representer.ex @@ -4,8 +4,8 @@ defmodule Pleroma.Web.OStatus.FeedRepresenter do alias Pleroma.User - alias Pleroma.Web.OStatus alias Pleroma.Web.MediaProxy + alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus.ActivityRepresenter alias Pleroma.Web.OStatus.UserRepresenter diff --git a/lib/pleroma/web/ostatus/handlers/delete_handler.ex b/lib/pleroma/web/ostatus/handlers/delete_handler.ex index 01b52f08f..b2f9f3946 100644 --- a/lib/pleroma/web/ostatus/handlers/delete_handler.ex +++ b/lib/pleroma/web/ostatus/handlers/delete_handler.ex @@ -4,9 +4,9 @@ defmodule Pleroma.Web.OStatus.DeleteHandler do require Logger - alias Pleroma.Web.XML alias Pleroma.Object alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.XML def handle_delete(entry, _doc \\ nil) do with id <- XML.string_from_xpath("//id", entry), diff --git a/lib/pleroma/web/ostatus/handlers/follow_handler.ex b/lib/pleroma/web/ostatus/handlers/follow_handler.ex index 91ad4bc40..263d3b2dc 100644 --- a/lib/pleroma/web/ostatus/handlers/follow_handler.ex +++ b/lib/pleroma/web/ostatus/handlers/follow_handler.ex @@ -3,10 +3,10 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OStatus.FollowHandler do - alias Pleroma.Web.XML - alias Pleroma.Web.OStatus - alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.OStatus + alias Pleroma.Web.XML def handle(entry, doc) do with {:ok, actor} <- OStatus.find_make_or_update_user(doc), diff --git a/lib/pleroma/web/ostatus/handlers/note_handler.ex b/lib/pleroma/web/ostatus/handlers/note_handler.ex index c2e585cac..db995ec77 100644 --- a/lib/pleroma/web/ostatus/handlers/note_handler.ex +++ b/lib/pleroma/web/ostatus/handlers/note_handler.ex @@ -4,13 +4,14 @@ defmodule Pleroma.Web.OStatus.NoteHandler do require Logger - alias Pleroma.Web.OStatus - alias Pleroma.Web.XML + alias Pleroma.Activity alias Pleroma.Object alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI + alias Pleroma.Web.OStatus + alias Pleroma.Web.XML @doc """ Get the context for this note. Uses this: @@ -18,13 +19,13 @@ defmodule Pleroma.Web.OStatus.NoteHandler do 2. The conversation reference in the ostatus xml 3. A newly generated context id. """ - def get_context(entry, inReplyTo) do + def get_context(entry, in_reply_to) do context = (XML.string_from_xpath("//ostatus:conversation[1]", entry) || XML.string_from_xpath("//ostatus:conversation[1]/@ref", entry) || "") |> String.trim() - with %{data: %{"context" => context}} <- Object.get_cached_by_ap_id(inReplyTo) do + with %{data: %{"context" => context}} <- Object.get_cached_by_ap_id(in_reply_to) do context else _e -> @@ -87,14 +88,14 @@ defmodule Pleroma.Web.OStatus.NoteHandler do Map.put(note, "external_url", url) end - def fetch_replied_to_activity(entry, inReplyTo) do - with %Activity{} = activity <- Activity.get_create_by_object_ap_id(inReplyTo) do + def fetch_replied_to_activity(entry, in_reply_to) do + with %Activity{} = activity <- Activity.get_create_by_object_ap_id(in_reply_to) do activity else _e -> - with inReplyToHref when not is_nil(inReplyToHref) <- + with in_reply_to_href when not is_nil(in_reply_to_href) <- XML.string_from_xpath("//thr:in-reply-to[1]/@href", entry), - {:ok, [activity | _]} <- OStatus.fetch_activity_from_url(inReplyToHref) do + {:ok, [activity | _]} <- OStatus.fetch_activity_from_url(in_reply_to_href) do activity else _e -> nil @@ -105,16 +106,17 @@ defmodule Pleroma.Web.OStatus.NoteHandler do # TODO: Clean this up a bit. def handle_note(entry, doc \\ nil) do with id <- XML.string_from_xpath("//id", entry), - activity when is_nil(activity) <- Activity.get_create_by_object_ap_id(id), + activity when is_nil(activity) <- Activity.get_create_by_object_ap_id_with_object(id), [author] <- :xmerl_xpath.string('//author[1]', doc), {:ok, actor} <- OStatus.find_make_or_update_user(author), content_html <- OStatus.get_content(entry), cw <- OStatus.get_cw(entry), - inReplyTo <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry), - inReplyToActivity <- fetch_replied_to_activity(entry, inReplyTo), - inReplyTo <- (inReplyToActivity && inReplyToActivity.data["object"]["id"]) || inReplyTo, + in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry), + in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to), + in_reply_to <- + (in_reply_to_activity && in_reply_to_activity.data["object"]["id"]) || in_reply_to, attachments <- OStatus.get_attachments(entry), - context <- get_context(entry, inReplyTo), + context <- get_context(entry, in_reply_to), tags <- OStatus.get_tags(entry), mentions <- get_mentions(entry), to <- make_to_list(actor, mentions), @@ -128,7 +130,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do context, content_html, attachments, - inReplyToActivity, + in_reply_to_activity, [], cw ), @@ -140,8 +142,8 @@ defmodule Pleroma.Web.OStatus.NoteHandler do # TODO: Handle this case in make_note_data note <- if( - inReplyTo && !inReplyToActivity, - do: note |> Map.put("inReplyTo", inReplyTo), + in_reply_to && !in_reply_to_activity, + do: note |> Map.put("inReplyTo", in_reply_to), else: note ) do ActivityPub.create(%{ diff --git a/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex b/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex index c9085894d..6596ada3b 100644 --- a/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex +++ b/lib/pleroma/web/ostatus/handlers/unfollow_handler.ex @@ -3,10 +3,10 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.OStatus.UnfollowHandler do - alias Pleroma.Web.XML - alias Pleroma.Web.OStatus - alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.OStatus + alias Pleroma.Web.XML def handle(entry, doc) do with {:ok, actor} <- OStatus.find_make_or_update_user(doc), diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index b4f5761ac..9a34d7ad5 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -9,22 +9,22 @@ defmodule Pleroma.Web.OStatus do import Pleroma.Web.XML require Logger + alias Pleroma.Activity + alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web - alias Pleroma.Object - alias Pleroma.Activity alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier - alias Pleroma.Web.WebFinger - alias Pleroma.Web.Websub + alias Pleroma.Web.OStatus.DeleteHandler alias Pleroma.Web.OStatus.FollowHandler - alias Pleroma.Web.OStatus.UnfollowHandler alias Pleroma.Web.OStatus.NoteHandler - alias Pleroma.Web.OStatus.DeleteHandler + alias Pleroma.Web.OStatus.UnfollowHandler + alias Pleroma.Web.WebFinger + alias Pleroma.Web.Websub - def is_representable?(%Activity{data: data}) do - object = Object.normalize(data["object"]) + def is_representable?(%Activity{} = activity) do + object = Object.normalize(activity) cond do is_nil(object) -> @@ -119,7 +119,7 @@ defmodule Pleroma.Web.OStatus do def make_share(entry, doc, retweeted_activity) do with {:ok, actor} <- find_make_or_update_user(doc), - %Object{} = object <- Object.normalize(retweeted_activity.data["object"]), + %Object{} = object <- Object.normalize(retweeted_activity), id when not is_nil(id) <- string_from_xpath("/entry/id", entry), {:ok, activity, _object} = ActivityPub.announce(actor, object, id, false) do {:ok, activity} @@ -137,7 +137,7 @@ defmodule Pleroma.Web.OStatus do def make_favorite(entry, doc, favorited_activity) do with {:ok, actor} <- find_make_or_update_user(doc), - %Object{} = object <- Object.normalize(favorited_activity.data["object"]), + %Object{} = object <- Object.normalize(favorited_activity), id when not is_nil(id) <- string_from_xpath("/entry/id", entry), {:ok, activity, _object} = ActivityPub.like(actor, object, id, false) do {:ok, activity} @@ -159,7 +159,7 @@ defmodule Pleroma.Web.OStatus do Logger.debug("Trying to get entry from db") with id when not is_nil(id) <- string_from_xpath("//activity:object[1]/id", entry), - %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do + %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do {:ok, activity} else _ -> diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 4e963774a..2fb6ce41b 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -9,13 +9,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do alias Pleroma.Object alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.ObjectView - alias Pleroma.Web.OStatus.ActivityRepresenter - alias Pleroma.Web.OStatus.FeedRepresenter + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Federator alias Pleroma.Web.OStatus + alias Pleroma.Web.OStatus.ActivityRepresenter + alias Pleroma.Web.OStatus.FeedRepresenter alias Pleroma.Web.XML plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming]) @@ -102,7 +102,8 @@ defmodule Pleroma.Web.OStatus.OStatusController do ActivityPubController.call(conn, :object) else with id <- o_status_url(conn, :object, uuid), - {_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id(id)}, + {_, %Activity{} = activity} <- + {:activity, Activity.get_create_by_object_ap_id_with_object(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)}, %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do case get_format(conn) do @@ -148,13 +149,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do end def notice(conn, %{"id" => id}) do - with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(id)}, + with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)}, %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do case format = get_format(conn) do "html" -> if activity.data["type"] == "Create" do - %Object{} = object = Object.normalize(activity.data["object"]) + %Object{} = object = Object.normalize(activity) Fallback.RedirectController.redirector_with_meta(conn, %{ activity_id: activity.id, @@ -191,9 +192,9 @@ defmodule Pleroma.Web.OStatus.OStatusController do # Returns an HTML embedded <audio> or <video> player suitable for embed iframes. def notice_player(conn, %{"id" => id}) do - with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id), + with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id), true <- Visibility.is_public?(activity), - %Object{} = object <- Object.normalize(activity.data["object"]), + %Object{} = object <- Object.normalize(activity), %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object, true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do conn @@ -219,7 +220,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do %Activity{data: %{"type" => "Create"}} = activity, _user ) do - object = Object.normalize(activity.data["object"]) + object = Object.normalize(activity) conn |> put_resp_header("content-type", "application/activity+json") diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 33f912d34..863573185 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -5,13 +5,13 @@ defmodule Pleroma.Web.Push.Impl do @moduledoc "The module represents implementation push web notification" - alias Pleroma.Repo - alias Pleroma.User alias Pleroma.Activity + alias Pleroma.Notification alias Pleroma.Object - alias Pleroma.Web.Push.Subscription + alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.Metadata.Utils - alias Pleroma.Notification + alias Pleroma.Web.Push.Subscription require Logger import Ecto.Query @@ -20,7 +20,10 @@ defmodule Pleroma.Web.Push.Impl do @doc "Performs sending notifications for user subscriptions" @spec perform_send(Notification.t()) :: list(any) - def perform_send(%{activity: %{data: %{"type" => activity_type}}, user_id: user_id} = notif) + def perform_send( + %{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} = + notif + ) when activity_type in @types do actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) @@ -37,7 +40,10 @@ defmodule Pleroma.Web.Push.Impl do notification_id: notif.id, notification_type: type, icon: avatar_url, - preferred_locale: "en" + preferred_locale: "en", + pleroma: %{ + activity_id: activity_id + } } |> Jason.encode!() |> push_message(build_sub(subscription), gcm_api_key, subscription) diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex index 951dab535..5259e8e33 100644 --- a/lib/pleroma/web/push/push.ex +++ b/lib/pleroma/web/push/push.ex @@ -13,15 +13,15 @@ defmodule Pleroma.Web.Push do # Client API # ############## - def start_link() do + def start_link do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) end - def vapid_config() do + def vapid_config do Application.get_env(:web_push_encryption, :vapid_details, []) end - def enabled() do + def enabled do case vapid_config() do [] -> false list when is_list(list) -> true diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex index c90bd2bda..da301fbbc 100644 --- a/lib/pleroma/web/push/subscription.ex +++ b/lib/pleroma/web/push/subscription.ex @@ -82,8 +82,8 @@ defmodule Pleroma.Web.Push.Subscription do end # Some webpush clients (e.g. iOS Toot!) use an non urlsafe base64 as an encoding for the key. - # However, the web push rfs specify to use base64 urlsafe, and the `web_push_encryption` library we use - # requires the key to be properly encoded. So we just convert base64 to urlsafe base64. + # However, the web push rfs specify to use base64 urlsafe, and the `web_push_encryption` library + # we use requires the key to be properly encoded. So we just convert base64 to urlsafe base64. defp ensure_base64_urlsafe(string) do string |> String.replace("+", "-") diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex index ab29a36e3..eaca41132 100644 --- a/lib/pleroma/web/rel_me.ex +++ b/lib/pleroma/web/rel_me.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.RelMe do @hackney_options [ pool: :media, - timeout: 2_000, recv_timeout: 2_000, max_body: 2_000_000 ] diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 8317a1162..f67aaf58b 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -4,8 +4,8 @@ defmodule Pleroma.Web.RichMedia.Helpers do alias Pleroma.Activity - alias Pleroma.Object alias Pleroma.HTML + alias Pleroma.Object alias Pleroma.Web.RichMedia.Parser defp validate_page_url(page_url) when is_binary(page_url) do @@ -21,9 +21,9 @@ defmodule Pleroma.Web.RichMedia.Helpers do defp validate_page_url(%URI{}), do: :ok defp validate_page_url(_), do: :error - def fetch_data_for_activity(%Activity{} = activity) do + def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do with true <- Pleroma.Config.get([:rich_media, :enabled]), - %Object{} = object <- Object.normalize(activity.data["object"]), + %Object{} = object <- Object.normalize(activity), {:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]), :ok <- validate_page_url(page_url), {:ok, rich_media} <- Parser.parse(page_url) do @@ -32,4 +32,6 @@ defmodule Pleroma.Web.RichMedia.Helpers do _ -> %{} end end + + def fetch_data_for_activity(_), do: %{} end diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 4341141df..4bd271d8e 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.RichMedia.Parser do @hackney_options [ pool: :media, - timeout: 2_000, recv_timeout: 2_000, max_body: 2_000_000 ] diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 65a90e31e..605a327fc 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -5,6 +5,11 @@ defmodule Pleroma.Web.Router do use Pleroma.Web, :router + pipeline :oauth do + plug(:fetch_session) + plug(Pleroma.Plugs.OAuthPlug) + end + pipeline :api do plug(:accepts, ["json"]) plug(:fetch_session) @@ -105,10 +110,6 @@ defmodule Pleroma.Web.Router do plug(:accepts, ["json", "xml"]) end - pipeline :oauth do - plug(:accepts, ["html", "json"]) - end - pipeline :pleroma_api do plug(:accepts, ["html", "json"]) end @@ -140,6 +141,7 @@ defmodule Pleroma.Web.Router do pipe_through([:admin_api, :oauth_write]) get("/users", AdminAPIController, :list_users) + get("/users/:nickname", AdminAPIController, :user_show) delete("/user", AdminAPIController, :user_delete) patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) post("/user", AdminAPIController, :user_create) @@ -190,10 +192,20 @@ defmodule Pleroma.Web.Router do post("/blocks_import", UtilController, :blocks_import) post("/follow_import", UtilController, :follow_import) end + + scope [] do + pipe_through(:oauth_read) + + post("/notifications/read", UtilController, :notifications_read) + end end scope "/oauth", Pleroma.Web.OAuth do - get("/authorize", OAuthController, :authorize) + scope [] do + pipe_through(:oauth) + get("/authorize", OAuthController, :authorize) + end + post("/authorize", OAuthController, :create_authorization) post("/token", OAuthController, :token_exchange) post("/revoke", OAuthController, :token_revoke) @@ -211,6 +223,7 @@ defmodule Pleroma.Web.Router do get("/accounts/search", MastodonAPIController, :account_search) get("/accounts/:id/lists", MastodonAPIController, :account_lists) + get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array) get("/follow_requests", MastodonAPIController, :follow_requests) get("/blocks", MastodonAPIController, :blocks) @@ -322,6 +335,7 @@ defmodule Pleroma.Web.Router do get("/instance", MastodonAPIController, :masto_instance) get("/instance/peers", MastodonAPIController, :peers) post("/apps", MastodonAPIController, :create_app) + get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials) get("/custom_emojis", MastodonAPIController, :custom_emojis) get("/statuses/:id/card", MastodonAPIController, :status_card) @@ -631,8 +645,8 @@ end defmodule Fallback.RedirectController do use Pleroma.Web, :controller - alias Pleroma.Web.Metadata alias Pleroma.User + alias Pleroma.Web.Metadata def redirector(conn, _params, code \\ 200) do conn diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 0a69aa1ec..0a9e51656 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -9,8 +9,8 @@ defmodule Pleroma.Web.Salmon do alias Pleroma.Instances alias Pleroma.User - alias Pleroma.Web.XML alias Pleroma.Web.OStatus.ActivityRepresenter + alias Pleroma.Web.XML require Logger @@ -86,10 +86,10 @@ defmodule Pleroma.Web.Salmon do # Native generation of RSA keys is only available since OTP 20+ and in default build conditions # We try at compile time to generate natively an RSA key otherwise we fallback on the old way. try do - _ = :public_key.generate_key({:rsa, 2048, 65537}) + _ = :public_key.generate_key({:rsa, 2048, 65_537}) def generate_rsa_pem do - key = :public_key.generate_key({:rsa, 2048, 65537}) + key = :public_key.generate_key({:rsa, 2048, 65_537}) entry = :public_key.pem_entry_encode(:RSAPrivateKey, key) pem = :public_key.pem_encode([entry]) |> String.trim_trailing() {:ok, pem} diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 27e8020f4..a82109f92 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -5,12 +5,13 @@ defmodule Pleroma.Web.Streamer do use GenServer require Logger - alias Pleroma.User - alias Pleroma.Notification alias Pleroma.Activity + alias Pleroma.Notification alias Pleroma.Object - alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility + alias Pleroma.Web.MastodonAPI.NotificationView @keepalive_interval :timer.seconds(30) @@ -80,7 +81,7 @@ defmodule Pleroma.Web.Streamer do _ -> Pleroma.List.get_lists_from_activity(item) |> Enum.filter(fn list -> - owner = Repo.get(User, list.user_id) + owner = User.get_by_id(list.user_id) Visibility.visible_for_user?(item, owner) end) @@ -106,10 +107,10 @@ defmodule Pleroma.Web.Streamer do %{ event: "notification", payload: - Pleroma.Web.MastodonAPI.MastodonAPIController.render_notification( - socket.assigns["user"], - item - ) + NotificationView.render("show.json", %{ + notification: item, + for: socket.assigns["user"] + }) |> Jason.encode!() } |> Jason.encode!() @@ -198,10 +199,12 @@ defmodule Pleroma.Web.Streamer do user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) blocks = user.info.blocks || [] mutes = user.info.mutes || [] + reblog_mutes = user.info.muted_reblogs || [] - parent = Object.normalize(item.data["object"]) + parent = Object.normalize(item) unless is_nil(parent) or item.actor in blocks or item.actor in mutes or + item.actor in reblog_mutes or not ActivityPub.contain_activity(item, user) or parent.data["actor"] in blocks or parent.data["actor"] in mutes do send(socket.transport_pid, {:text, represent_update(item, user)}) end @@ -211,15 +214,19 @@ defmodule Pleroma.Web.Streamer do end) end - def push_to_socket(topics, topic, %Activity{id: id, data: %{"type" => "Delete"}}) do + def push_to_socket(topics, topic, %Activity{ + data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id} + }) do Enum.each(topics[topic] || [], fn socket -> send( socket.transport_pid, - {:text, %{event: "delete", payload: to_string(id)} |> Jason.encode!()} + {:text, %{event: "delete", payload: to_string(deleted_activity_id)} |> Jason.encode!()} ) end) end + def push_to_socket(_topics, _topic, %Activity{data: %{"type" => "Delete"}}), do: :noop + def push_to_socket(topics, topic, item) do Enum.each(topics[topic] || [], fn socket -> # Get the current user so we have up-to-date blocks etc. @@ -228,7 +235,8 @@ defmodule Pleroma.Web.Streamer do blocks = user.info.blocks || [] mutes = user.info.mutes || [] - unless item.actor in blocks or item.actor in mutes do + unless item.actor in blocks or item.actor in mutes or + not ActivityPub.contain_activity(item, user) do send(socket.transport_pid, {:text, represent_update(item, user)}) end else diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index db97ccac2..8333bc921 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -8,75 +8,145 @@ </title> <style> body { - background-color: #282c37; + background-color: #121a24; font-family: sans-serif; - color:white; + color: #b9b9ba; text-align: center; } .container { - margin: 50px auto; - max-width: 320px; - padding: 0; - padding: 40px 40px 40px 40px; - background-color: #313543; + max-width: 420px; + padding: 20px; + background-color: #182230; border-radius: 4px; + margin: auto; + margin-top: 10vh; + box-shadow: 0 1px 4px 0px rgba(0, 0, 0, 0.5); } h1 { margin: 0; + font-size: 24px; } h2 { - color: #9baec8; + color: #b9b9ba; font-weight: normal; - font-size: 20px; - margin-bottom: 40px; + font-size: 18px; + margin-bottom: 20px; } form { width: 100%; } + .input { + text-align: left; + color: #89898a; + display: flex; + flex-direction: column; + } + input { - box-sizing: border-box; - width: 100%; + box-sizing: content-box; padding: 10px; - margin-top: 20px; - background-color: rgba(0,0,0,.1); - color: white; + margin-top: 5px; + margin-bottom: 10px; + background-color: #121a24; + color: #b9b9ba; border: 0; - border-bottom: 2px solid #9baec8; + transition-property: border-bottom; + transition-duration: 0.35s; + border-bottom: 2px solid #2a384a; font-size: 14px; } - input:focus { - border-bottom: 2px solid #4b8ed8; + .scopes-input { + display: flex; + margin-top: 1em; + text-align: left; + color: #89898a; + } + + .scopes-input label:first-child { + flex-basis: 40%; } - input[type="checkbox"] { - width: auto; + .scopes { + display: flex; + flex-wrap: wrap; + text-align: left; + color: #b9b9ba; + } + + .scope { + flex-basis: 100%; + display: flex; + height: 2em; + align-items: center; + } + + [type="checkbox"] + label { + margin: 0.5em; + } + + [type="checkbox"] { + display: none; + } + + [type="checkbox"] + label:before { + display: inline-block; + color: white; + background-color: #121a24; + border: 4px solid #121a24; + box-sizing: border-box; + width: 1.2em; + height: 1.2em; + margin-right: 1.0em; + content: ""; + transition-property: background-color; + transition-duration: 0.35s; + color: #121a24; + margin-bottom: -0.2em; + border-radius: 2px; + } + + [type="checkbox"]:checked + label:before { + background-color: #d8a070; + } + + input:focus { + outline: none; + border-bottom: 2px solid #d8a070; } button { box-sizing: border-box; width: 100%; - color: white; - background-color: #419bdd; + background-color: #1c2a3a; + color: #b9b9ba; border-radius: 4px; border: none; padding: 10px; margin-top: 30px; text-transform: uppercase; - font-weight: 500; font-size: 16px; + box-shadow: 0px 0px 2px 0px black, + 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, + 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; + } + + button:hover { + cursor: pointer; + box-shadow: 0px 0px 0px 1px #d8a070, + 0px 1px 0px 0px rgba(255, 255, 255, 0.2) inset, + 0px -1px 0px 0px rgba(0, 0, 0, 0.2) inset; } .alert-danger { box-sizing: border-box; width: 100%; - color: #D8000C; - background-color: #FFD2D2; + background-color: #931014; border-radius: 4px; border: none; padding: 10px; @@ -88,20 +158,32 @@ .alert-info { box-sizing: border-box; width: 100%; - color: #00529B; - background-color: #BDE5F8; border-radius: 4px; - border: none; + border: 1px solid #7d796a; padding: 10px; margin-top: 20px; font-weight: 500; font-size: 16px; } + + @media all and (max-width: 440px) { + .container { + margin-top: 0 + } + + .scopes-input { + flex-direction: column; + } + + .scope { + flex-basis: 50%; + } + } </style> </head> <body> <div class="container"> - <h1>Pleroma</h1> + <h1><%= Application.get_env(:pleroma, :instance)[:name] %></h1> <%= render @view_module, @view_template, assigns %> </div> </body> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index f50599bdb..161333847 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -6,23 +6,26 @@ <% end %> <h2>OAuth Authorization</h2> <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> -<%= label f, :name, "Name or email" %> -<%= text_input f, :name %> -<br> -<br> -<%= label f, :password, "Password" %> -<%= password_input f, :password %> -<br> -<br> - +<div class="input"> + <%= label f, :name, "Name or email" %> + <%= text_input f, :name %> +</div> +<div class="input"> + <%= label f, :password, "Password" %> + <%= password_input f, :password %> +</div> +<div class="scopes-input"> <%= label f, :scope, "Permissions" %> -<br> -<%= for scope <- @available_scopes do %> - <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %> - <%= checkbox f, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %> - <%= label f, :"scope_#{scope}", String.capitalize(scope) %> - <br> -<% end %> + <div class="scopes"> + <%= for scope <- @available_scopes do %> + <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %> + <div class="scope"> + <%= checkbox f, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %> + <%= label f, :"scope_#{scope}", String.capitalize(scope) %> + </div> + <% end %> + </div> +</div> <%= hidden_input f, :client_id, value: @client_id %> <%= hidden_input f, :response_type, value: @response_type %> diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index e2fdedb25..e817f0d79 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -9,18 +9,19 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Comeonin.Pbkdf2 alias Pleroma.Emoji + alias Pleroma.Notification alias Pleroma.PasswordResetToken - alias Pleroma.User alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web + alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.CommonAPI alias Pleroma.Web.OStatus alias Pleroma.Web.WebFinger - alias Pleroma.Web.ActivityPub.ActivityPub def show_password_reset(conn, %{"token" => token}) do with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), - %User{} = user <- Repo.get(User, token.user_id) do + %User{} = user <- User.get_by_id(token.user_id) do render(conn, "password_reset.html", %{ token: token, user: user @@ -95,13 +96,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do def do_remote_follow(conn, %{ "authorization" => %{"name" => username, "password" => password, "id" => id} }) do - followee = Repo.get(User, id) + followee = User.get_by_id(id) avatar = User.avatar_url(followee) name = followee.nickname with %User{} = user <- User.get_cached_by_nickname(username), true <- Pbkdf2.checkpw(password, user.password_hash), - %User{} = _followed <- Repo.get(User, id), + %User{} = _followed <- User.get_by_id(id), {:ok, follower} <- User.follow(user, followee), {:ok, _activity} <- ActivityPub.follow(follower, followee) do conn @@ -123,7 +124,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do - with %User{} = followee <- Repo.get(User, id), + with %User{} = followee <- User.get_by_id(id), {:ok, follower} <- User.follow(user, followee), {:ok, _activity} <- ActivityPub.follow(follower, followee) do conn @@ -142,6 +143,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do end end + def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do + with {:ok, _} <- Notification.read_one(user, notification_id) do + json(conn, %{status: "success"}) + else + {:error, message} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(403, Jason.encode!(%{"error" => message})) + end + end + def config(conn, _params) do instance = Pleroma.Config.get(:instance) instance_fe = Pleroma.Config.get(:fe) @@ -185,7 +197,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do vapidPublicKey: vapid_public_key, accountActivationRequired: if(Keyword.get(instance, :account_activation_required, false), do: "1", else: "0"), - invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0") + invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0"), + safeDMMentionsEnabled: + if(Pleroma.Config.get([:instance, :safe_dm_mentions]), do: "1", else: "0") } pleroma_fe = diff --git a/lib/pleroma/web/twitter_api/representers/activity_representer.ex b/lib/pleroma/web/twitter_api/representers/activity_representer.ex deleted file mode 100644 index 55c612ddd..000000000 --- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex +++ /dev/null @@ -1,15 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -# FIXME: Remove this module? -# THIS MODULE IS DEPRECATED! DON'T USE IT! -# USE THE Pleroma.Web.TwitterAPI.Views.ActivityView MODULE! -defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do - def to_map(activity, opts) do - Pleroma.Web.TwitterAPI.ActivityView.render( - "activity.json", - Map.put(opts, :activity, activity) - ) - end -end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index dcb15b9a9..d0e58e71b 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -3,16 +3,15 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.TwitterAPI do - alias Pleroma.UserInviteToken - alias Pleroma.User alias Pleroma.Activity + alias Pleroma.Mailer alias Pleroma.Repo - alias Pleroma.Object + alias Pleroma.User alias Pleroma.UserEmail - alias Pleroma.Mailer + alias Pleroma.UserInviteToken alias Pleroma.Web.ActivityPub.ActivityPub - alias Pleroma.Web.TwitterAPI.UserView alias Pleroma.Web.CommonAPI + alias Pleroma.Web.TwitterAPI.UserView import Ecto.Query @@ -21,7 +20,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end def delete(%User{} = user, id) do - with %Activity{data: %{"type" => _type}} <- Repo.get(Activity, id), + with %Activity{data: %{"type" => _type}} <- Activity.get_by_id(id), {:ok, activity} <- CommonAPI.delete(id, user) do {:ok, activity} end @@ -35,11 +34,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def unfollow(%User{} = follower, params) do with {:ok, %User{} = unfollowed} <- get_user(params), - {:ok, follower, _follow_activity} <- User.unfollow(follower, unfollowed), - {:ok, _activity} <- ActivityPub.unfollow(follower, unfollowed) do + {:ok, follower} <- CommonAPI.unfollow(follower, unfollowed) do {:ok, follower, unfollowed} - else - err -> err end end @@ -133,7 +129,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end def register_user(params) do - tokenString = params["token"] + token_string = params["token"] params = %{ nickname: params["nickname"], @@ -170,8 +166,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do # no need to query DB if registration is open token = - unless registrations_open || is_nil(tokenString) do - Repo.get_by(UserInviteToken, %{token: tokenString}) + unless registrations_open || is_nil(token_string) do + Repo.get_by(UserInviteToken, %{token: token_string}) end cond do @@ -285,35 +281,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do _activities = Repo.all(q) end - # DEPRECATED mostly, context objects are now created at insertion time. - def context_to_conversation_id(context) do - with %Object{id: id} <- Object.get_cached_by_ap_id(context) do - id - else - _e -> - changeset = Object.context_mapping(context) - - case Repo.insert(changeset) do - {:ok, %{id: id}} -> - id - - # This should be solved by an upsert, but it seems ecto - # has problems accessing the constraint inside the jsonb. - {:error, _} -> - Object.get_cached_by_ap_id(context).id - end - end - end - - def conversation_id_to_context(id) do - with %Object{data: %{"id" => context}} <- Repo.get(Object, id) do - context - else - _e -> - {:error, "No such conversation"} - end - end - def get_external_profile(for_user, uri) do with %User{} = user <- User.get_or_fetch(uri) do {:ok, UserView.render("show.json", %{user: user, for: for_user})} diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 5e4ebb8e8..a7ec9949c 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -8,23 +8,21 @@ defmodule Pleroma.Web.TwitterAPI.Controller do import Pleroma.Web.ControllerHelper, only: [json_response: 3] alias Ecto.Changeset - alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView, TokenView} - alias Pleroma.Web.CommonAPI - alias Pleroma.{Repo, Activity, Object, User, Notification} - alias Pleroma.Web.OAuth.Token + alias Pleroma.Activity + alias Pleroma.Notification + alias Pleroma.Object + alias Pleroma.Repo + alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility - alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI + alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.OAuth.Token alias Pleroma.Web.TwitterAPI.ActivityView alias Pleroma.Web.TwitterAPI.NotificationView + alias Pleroma.Web.TwitterAPI.TokenView alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.UserView - alias Pleroma.Activity - alias Pleroma.Object - alias Pleroma.Notification - alias Pleroma.Repo - alias Pleroma.User require Logger @@ -272,7 +270,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with %Activity{} = activity <- Repo.get(Activity, id), + with %Activity{} = activity <- Activity.get_by_id(id), true <- Visibility.visible_for_user?(activity, user) do conn |> put_view(ActivityView) @@ -281,7 +279,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with context when is_binary(context) <- TwitterAPI.conversation_id_to_context(id), + with context when is_binary(context) <- Utils.conversation_id_to_context(id), activities <- ActivityPub.fetch_activities_for_context(context, %{ "blocking_user" => user, @@ -344,7 +342,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def get_by_id_or_ap_id(id) do - activity = Repo.get(Activity, id) || Activity.get_create_by_object_ap_id(id) + activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id) if activity.data["type"] == "Create" do activity @@ -436,7 +434,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def confirm_email(conn, %{"user_id" => uid, "token" => token}) do - with %User{} = user <- Repo.get(User, uid), + with %User{} = user <- User.get_by_id(uid), true <- user.local, true <- user.info.confirmation_pending, true <- user.info.confirmation_token == token, @@ -589,17 +587,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def approve_friend_request(conn, %{"user_id" => uid} = _params) do with followed <- conn.assigns[:user], - %User{} = follower <- Repo.get(User, uid), - {:ok, follower} <- User.maybe_follow(follower, followed), - %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), - {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"), - {:ok, _activity} <- - ActivityPub.accept(%{ - to: [follower.ap_id], - actor: followed, - object: follow_activity.data["id"], - type: "Accept" - }) do + %User{} = follower <- User.get_by_id(uid), + {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do conn |> put_view(UserView) |> render("show.json", %{user: follower, for: followed}) @@ -610,16 +599,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def deny_friend_request(conn, %{"user_id" => uid} = _params) do with followed <- conn.assigns[:user], - %User{} = follower <- Repo.get(User, uid), - %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), - {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"), - {:ok, _activity} <- - ActivityPub.reject(%{ - to: [follower.ap_id], - actor: followed, - object: follow_activity.data["id"], - type: "Reject" - }) do + %User{} = follower <- User.get_by_id(uid), + {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do conn |> put_view(UserView) |> render("show.json", %{user: follower, for: followed}) diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index 02ca4ee42..aa1d41fa2 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -14,9 +14,8 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.TwitterAPI.ActivityView - alias Pleroma.Web.TwitterAPI.TwitterAPI - alias Pleroma.Web.TwitterAPI.UserView alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter + alias Pleroma.Web.TwitterAPI.UserView import Ecto.Query require Logger @@ -78,7 +77,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do defp get_context_id(%{data: %{"context" => context}}, options) do cond do id = options[:context_ids][context] -> id - true -> TwitterAPI.context_to_conversation_id(context) + true -> Utils.context_to_conversation_id(context) end end @@ -267,6 +266,8 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do content |> String.replace(~r/<br\s?\/?>/, "\n") |> HTML.get_cached_stripped_html_for_object(activity, __MODULE__) + else + "" end reply_parent = Activity.get_in_reply_to_activity(activity) diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 5ea5ae48e..32c3455f5 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -6,11 +6,11 @@ defmodule Pleroma.Web.WebFinger do @httpoison Application.get_env(:pleroma, :httpoison) alias Pleroma.User - alias Pleroma.XmlBuilder alias Pleroma.Web - alias Pleroma.Web.XML - alias Pleroma.Web.Salmon alias Pleroma.Web.OStatus + alias Pleroma.Web.Salmon + alias Pleroma.Web.XML + alias Pleroma.XmlBuilder require Jason require Logger diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex index c00ec0858..3ffa6b416 100644 --- a/lib/pleroma/web/websub/websub.ex +++ b/lib/pleroma/web/websub/websub.ex @@ -6,14 +6,14 @@ defmodule Pleroma.Web.Websub do alias Ecto.Changeset alias Pleroma.Instances alias Pleroma.Repo - alias Pleroma.Web.Websub.WebsubServerSubscription - alias Pleroma.Web.Websub.WebsubClientSubscription - alias Pleroma.Web.OStatus.FeedRepresenter - alias Pleroma.Web.XML alias Pleroma.Web.Endpoint + alias Pleroma.Web.Federator alias Pleroma.Web.OStatus + alias Pleroma.Web.OStatus.FeedRepresenter alias Pleroma.Web.Router.Helpers - alias Pleroma.Web.Federator + alias Pleroma.Web.Websub.WebsubClientSubscription + alias Pleroma.Web.Websub.WebsubServerSubscription + alias Pleroma.Web.XML require Logger import Ecto.Query @@ -200,8 +200,8 @@ defmodule Pleroma.Web.Websub do uri when not is_nil(uri) <- XML.string_from_xpath("/feed/author[1]/uri", doc), hub when not is_nil(hub) <- XML.string_from_xpath(~S{/feed/link[@rel="hub"]/@href}, doc) do name = XML.string_from_xpath("/feed/author[1]/name", doc) - preferredUsername = XML.string_from_xpath("/feed/author[1]/poco:preferredUsername", doc) - displayName = XML.string_from_xpath("/feed/author[1]/poco:displayName", doc) + preferred_username = XML.string_from_xpath("/feed/author[1]/poco:preferredUsername", doc) + display_name = XML.string_from_xpath("/feed/author[1]/poco:displayName", doc) avatar = OStatus.make_avatar_object(doc) bio = XML.string_from_xpath("/feed/author[1]/summary", doc) @@ -209,8 +209,8 @@ defmodule Pleroma.Web.Websub do %{ "uri" => uri, "hub" => hub, - "nickname" => preferredUsername || name, - "name" => displayName || name, + "nickname" => preferred_username || name, + "name" => display_name || name, "host" => URI.parse(uri).host, "avatar" => avatar, "bio" => bio diff --git a/lib/pleroma/web/websub/websub_client_subscription.ex b/lib/pleroma/web/websub/websub_client_subscription.ex index 969ee0684..77703c496 100644 --- a/lib/pleroma/web/websub/websub_client_subscription.ex +++ b/lib/pleroma/web/websub/websub_client_subscription.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.Websub.WebsubClientSubscription do schema "websub_client_subscriptions" do field(:topic, :string) field(:secret, :string) - field(:valid_until, :naive_datetime) + field(:valid_until, :naive_datetime_usec) field(:state, :string) field(:subscribers, {:array, :string}, default: []) field(:hub, :string) diff --git a/lib/pleroma/web/websub/websub_controller.ex b/lib/pleroma/web/websub/websub_controller.ex index ad40f1b94..9e8b48b80 100644 --- a/lib/pleroma/web/websub/websub_controller.ex +++ b/lib/pleroma/web/websub/websub_controller.ex @@ -7,8 +7,8 @@ defmodule Pleroma.Web.Websub.WebsubController do alias Pleroma.Repo alias Pleroma.User - alias Pleroma.Web.Websub alias Pleroma.Web.Federator + alias Pleroma.Web.Websub alias Pleroma.Web.Websub.WebsubClientSubscription require Logger |