diff options
Diffstat (limited to 'lib')
32 files changed, 584 insertions, 128 deletions
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index c7c925c89..c88512567 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Notification do alias Pleroma.Notification alias Pleroma.Repo alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.CommonAPI import Ecto.Query @@ -117,7 +118,7 @@ defmodule Pleroma.Notification do # TODO move to sql, too. def create_notification(%Activity{} = activity, %User{} = user) do unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or - user.ap_id == activity.data["actor"] or + CommonAPI.thread_muted?(user, activity) or user.ap_id == activity.data["actor"] or (activity.data["type"] == "Follow" and Enum.any?(Notification.for_user(user), fn notif -> notif.activity.data["type"] == "Follow" and diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index dabb49536..5f1fc801b 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -20,29 +20,9 @@ defmodule Pleroma.Object do timestamps() end - def insert_or_get(cng) do - {_, data} = fetch_field(cng, :data) - id = data["id"] || data[:id] - key = "object:#{id}" - - fetcher = fn _ -> - with nil <- get_by_ap_id(id), - {:ok, object} <- Repo.insert(cng) do - {:commit, object} - else - %Object{} = object -> {:commit, object} - e -> {:ignore, e} - end - end - - with {state, object} when state in [:commit, :ok] <- Cachex.fetch(:object_cache, key, fetcher) do - {:ok, object} - end - end - def create(data) do Object.change(%Object{}, %{data: data}) - |> insert_or_get() + |> Repo.insert() end def change(struct, params \\ %{}) do diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex index 2a266c407..057553e24 100644 --- a/lib/pleroma/plugs/http_security_plug.ex +++ b/lib/pleroma/plugs/http_security_plug.ex @@ -33,7 +33,22 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do end defp csp_string do - protocol = Config.get([Pleroma.Web.Endpoint, :protocol]) + scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme] + websocket_url = String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws") + + connect_src = + if Mix.env() == :dev do + "connect-src 'self' http://localhost:3035/ " <> websocket_url + else + "connect-src 'self' " <> websocket_url + end + + script_src = + if Mix.env() == :dev do + "script-src 'self' 'unsafe-eval'" + else + "script-src 'self'" + end [ "default-src 'none'", @@ -43,10 +58,10 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do "media-src 'self' https:", "style-src 'self' 'unsafe-inline'", "font-src 'self'", - "script-src 'self'", - "connect-src 'self' " <> String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"), "manifest-src 'self'", - if protocol == "https" do + connect_src, + script_src, + if scheme == "https" do "upgrade-insecure-requests" end ] diff --git a/lib/pleroma/thread_mute.ex b/lib/pleroma/thread_mute.ex new file mode 100644 index 000000000..0b577113d --- /dev/null +++ b/lib/pleroma/thread_mute.ex @@ -0,0 +1,45 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ThreadMute do + use Ecto.Schema + alias Pleroma.{Repo, User, ThreadMute} + require Ecto.Query + + schema "thread_mutes" do + belongs_to(:user, User, type: Pleroma.FlakeId) + field(:context, :string) + end + + def changeset(mute, params \\ %{}) do + mute + |> Ecto.Changeset.cast(params, [:user_id, :context]) + |> Ecto.Changeset.foreign_key_constraint(:user_id) + |> Ecto.Changeset.unique_constraint(:user_id, name: :unique_index) + end + + def query(user_id, context) do + user_id = Pleroma.FlakeId.from_string(user_id) + + ThreadMute + |> Ecto.Query.where(user_id: ^user_id) + |> Ecto.Query.where(context: ^context) + end + + def add_mute(user_id, context) do + %ThreadMute{} + |> changeset(%{user_id: user_id, context: context}) + |> Repo.insert() + end + + def remove_mute(user_id, context) do + query(user_id, context) + |> Repo.delete_all() + end + + def check_muted(user_id, context) do + query(user_id, context) + |> Repo.all() + end +end diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex index 320b07abd..190ed9f3a 100644 --- a/lib/pleroma/uploaders/mdii.ex +++ b/lib/pleroma/uploaders/mdii.ex @@ -25,7 +25,7 @@ defmodule Pleroma.Uploaders.MDII do query = "#{cgi}?#{extension}" with {:ok, %{status: 200, body: body}} <- - @httpoison.post(query, file_data, adapter: [pool: :default]) do + @httpoison.post(query, file_data, [], adapter: [pool: :default]) do remote_file_name = String.split(body) |> List.first() public_url = "#{files}/#{remote_file_name}.#{extension}" {:ok, {:url, public_url}} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 0060d966b..ff84e7b0a 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -261,6 +261,7 @@ defmodule Pleroma.User do def register(%Ecto.Changeset{} = changeset) do with {:ok, user} <- Repo.insert(changeset), {:ok, user} <- autofollow_users(user), + {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user), {:ok, _} <- try_send_confirmation_email(user) do {:ok, user} end @@ -311,12 +312,12 @@ defmodule Pleroma.User do end end - @doc "A mass follow for local users. Respects blocks but does not create activities." + @doc "A mass follow for local users. Respects blocks in both directions but does not create activities." @spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()} def follow_all(follower, followeds) do followed_addresses = followeds - |> Enum.reject(fn %{ap_id: ap_id} -> ap_id in follower.info.blocks end) + |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end) |> Enum.map(fn %{follower_address: fa} -> fa end) q = @@ -618,6 +619,32 @@ defmodule Pleroma.User do ) end + def update_follow_request_count(%User{} = user) do + subquery = + user + |> User.get_follow_requests_query() + |> select([a], %{count: count(a.id)}) + + User + |> where(id: ^user.id) + |> join(:inner, [u], s in subquery(subquery)) + |> update([u, s], + set: [ + info: + fragment( + "jsonb_set(?, '{follow_request_count}', ?::varchar::jsonb, true)", + u.info, + s.count + ) + ] + ) + |> Repo.update_all([], returning: true) + |> case do + {1, [user]} -> {:ok, user} + _ -> {:error, user} + end + end + def get_follow_requests(%User{} = user) do q = get_follow_requests_query(user) reqs = Repo.all(q) @@ -731,7 +758,7 @@ defmodule Pleroma.User do # Strip the beginning @ off if there is a query query = String.trim_leading(query, "@") - if resolve, do: User.get_or_fetch_by_nickname(query) + if resolve, do: get_or_fetch(query) fts_results = do_search(fts_search_subquery(query), for_user) diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 9d8779fab..9099d7fbb 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -12,6 +12,7 @@ defmodule Pleroma.User.Info do field(:source_data, :map, default: %{}) field(:note_count, :integer, default: 0) field(:follower_count, :integer, default: 0) + field(:follow_request_count, :integer, default: 0) field(:locked, :boolean, default: false) field(:confirmation_pending, :boolean, default: false) field(:confirmation_token, :string, default: nil) @@ -34,6 +35,7 @@ defmodule Pleroma.User.Info do field(:hide_followers, :boolean, default: false) field(:hide_follows, :boolean, default: false) field(:pinned_activities, {:array, :string}, default: []) + field(:flavour, :string, default: nil) # Found in the wild # ap_id -> Where is this used? @@ -186,6 +188,14 @@ defmodule Pleroma.User.Info do |> validate_required([:settings]) end + def mastodon_flavour_update(info, flavour) do + params = %{flavour: flavour} + + info + |> cast(params, [:flavour]) + |> validate_required([:flavour]) + end + def set_source_data(info, source_data) do params = %{source_data: source_data} diff --git a/lib/pleroma/user/welcome_message.ex b/lib/pleroma/user/welcome_message.ex new file mode 100644 index 000000000..8018ac22f --- /dev/null +++ b/lib/pleroma/user/welcome_message.ex @@ -0,0 +1,30 @@ +defmodule Pleroma.User.WelcomeMessage do + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + def post_welcome_message_to_user(user) do + with %User{} = sender_user <- welcome_user(), + message when is_binary(message) <- welcome_message() do + CommonAPI.post(sender_user, %{ + "visibility" => "direct", + "status" => "@#{user.nickname}\n#{message}" + }) + else + _ -> {:ok, nil} + end + end + + defp welcome_user() do + with nickname when is_binary(nickname) <- + Pleroma.Config.get([:instance, :welcome_user_nickname]), + %User{local: true} = user <- User.get_cached_by_nickname(nickname) do + user + else + _ -> nil + end + end + + defp welcome_message() do + Pleroma.Config.get([:instance, :welcome_message]) + end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 501c8b845..8fd4c9899 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -172,9 +172,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do # only accept false as false value local = !(params[:local] == false) - with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object}, + with data <- %{"to" => to, "type" => "Accept", "actor" => actor.ap_id, "object" => object}, {:ok, activity} <- insert(data, local), - :ok <- maybe_federate(activity) do + :ok <- maybe_federate(activity), + _ <- User.update_follow_request_count(actor) do {:ok, activity} end end @@ -183,9 +184,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do # only accept false as false value local = !(params[:local] == false) - with data <- %{"to" => to, "type" => "Reject", "actor" => actor, "object" => object}, + with data <- %{"to" => to, "type" => "Reject", "actor" => actor.ap_id, "object" => object}, {:ok, activity} <- insert(data, local), - :ok <- maybe_federate(activity) do + :ok <- maybe_federate(activity), + _ <- User.update_follow_request_count(actor) do {:ok, activity} end end @@ -283,7 +285,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def follow(follower, followed, activity_id \\ nil, local \\ true) do with data <- make_follow_data(follower, followed, activity_id), {:ok, activity} <- insert(data, local), - :ok <- maybe_federate(activity) do + :ok <- maybe_federate(activity), + _ <- User.update_follow_request_count(followed) do {:ok, activity} end end @@ -293,7 +296,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"), unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), {:ok, activity} <- insert(unfollow_data, local), - :ok <- maybe_federate(activity) do + :ok <- maybe_federate(activity), + _ <- User.update_follow_request_count(followed) do {:ok, activity} end end @@ -816,8 +820,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do if object = Object.get_cached_by_ap_id(id) do {:ok, object} else - Logger.info("Fetching #{id} via AP") - with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), nil <- Object.normalize(data), params <- %{ @@ -849,7 +851,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end def fetch_and_contain_remote_object_from_id(id) do - Logger.info("Fetching #{id} via AP") + Logger.info("Fetching object #{id} via AP") with true <- String.starts_with?(id, "http"), {:ok, %{body: body, status: code}} when code in 200..299 <- diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex index 4c6e612b2..6736f3cb9 100644 --- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex @@ -6,40 +6,80 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do alias Pleroma.User @behaviour Pleroma.Web.ActivityPub.MRF - defp delist_message(message) do + defp delist_message(message, threshold) when threshold > 0 do follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address - message - |> Map.put("to", [follower_collection]) - |> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) + follower_collection? = Enum.member?(message["to"] ++ message["cc"], follower_collection) + + message = + case get_recipient_count(message) do + {:public, recipients} + when follower_collection? and recipients > threshold -> + message + |> Map.put("to", [follower_collection]) + |> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) + + {:public, recipients} when recipients > threshold -> + message + |> Map.put("to", []) + |> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) + + _ -> + message + end + + {:ok, message} + end + + defp delist_message(message, _threshold), do: {:ok, message} + + defp reject_message(message, threshold) when threshold > 0 do + with {_, recipients} <- get_recipient_count(message) do + if recipients > threshold do + {:reject, nil} + else + {:ok, message} + end + end + end + + defp reject_message(message, _threshold), do: {:ok, message} + + defp get_recipient_count(message) do + recipients = (message["to"] || []) ++ (message["cc"] || []) + follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address + + if Enum.member?(recipients, "https://www.w3.org/ns/activitystreams#Public") do + recipients = + recipients + |> List.delete("https://www.w3.org/ns/activitystreams#Public") + |> List.delete(follower_collection) + + {:public, length(recipients)} + else + recipients = + recipients + |> List.delete(follower_collection) + + {:not_public, length(recipients)} + end end @impl true def filter(%{"type" => "Create"} = message) do - delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold]) - reject_threshold = Pleroma.Config.get( [:mrf_hellthread, :reject_threshold], Pleroma.Config.get([:mrf_hellthread, :threshold]) ) - recipients = (message["to"] || []) ++ (message["cc"] || []) - - cond do - length(recipients) > reject_threshold and reject_threshold > 0 -> - {:reject, nil} - - length(recipients) > delist_threshold and delist_threshold > 0 -> - if Enum.member?(message["to"], "https://www.w3.org/ns/activitystreams#Public") or - Enum.member?(message["cc"], "https://www.w3.org/ns/activitystreams#Public") do - {:ok, delist_message(message)} - else - {:ok, message} - end + delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold]) - true -> - {:ok, message} + with {:ok, message} <- reject_message(message, reject_threshold), + {:ok, message} <- delist_message(message, delist_threshold) do + {:ok, message} + else + _e -> {:reject, nil} end end diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index ce6d2e529..5fdc03414 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -12,9 +12,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do String.match?(string, pattern) end - defp check_reject(%{"object" => %{"content" => content}} = message) do + defp check_reject(%{"object" => %{"content" => content, "summary" => summary}} = message) do if Enum.any?(Pleroma.Config.get([:mrf_keyword, :reject]), fn pattern -> - string_matches?(content, pattern) + string_matches?(content, pattern) or string_matches?(summary, pattern) end) do {:reject, nil} else @@ -22,10 +22,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do end end - defp check_ftl_removal(%{"to" => to, "object" => %{"content" => content}} = message) do + defp check_ftl_removal( + %{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message + ) do if "https://www.w3.org/ns/activitystreams#Public" in to and Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern -> - string_matches?(content, pattern) + string_matches?(content, pattern) or string_matches?(summary, pattern) end) do to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public") cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []] @@ -41,14 +43,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do end end - defp check_replace(%{"object" => %{"content" => content}} = message) do - content = - Enum.reduce(Pleroma.Config.get([:mrf_keyword, :replace]), content, fn {pattern, replacement}, - acc -> - String.replace(acc, pattern, replacement) + defp check_replace(%{"object" => %{"content" => content, "summary" => summary}} = message) do + {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) - {:ok, put_in(message["object"]["content"], content)} + {:ok, + message + |> put_in(["object", "content"], content) + |> put_in(["object", "summary"], summary)} end @impl true diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 98a2af819..41d89a02b 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -406,7 +406,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do if not User.locked?(followed) do ActivityPub.accept(%{ to: [follower.ap_id], - actor: followed.ap_id, + actor: followed, object: data, local: true }) @@ -432,7 +432,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do ActivityPub.accept(%{ to: follow_activity.data["to"], type: "Accept", - actor: followed.ap_id, + actor: followed, object: follow_activity.data["id"], local: false }) do @@ -458,7 +458,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do ActivityPub.reject(%{ to: follow_activity.data["to"], type: "Reject", - actor: followed.ap_id, + actor: followed, object: follow_activity.data["id"], local: false }) do @@ -649,7 +649,7 @@ 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) do + 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) @@ -765,12 +765,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do def add_hashtags(object) do tags = (object["tag"] || []) - |> Enum.map(fn tag -> - %{ - "href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", - "name" => "##{tag}", - "type" => "Hashtag" - } + |> Enum.map(fn + # Expand internal representation tags into AS2 tags. + tag when is_binary(tag) -> + %{ + "href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", + "name" => "##{tag}", + "type" => "Hashtag" + } + + # Do not process tags which are already AS2 tag objects. + tag when is_map(tag) -> + tag end) object diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index e34dd1d41..6a89374d0 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -142,8 +142,14 @@ defmodule Pleroma.Web.ActivityPub.Utils do context = context || generate_id("contexts") changeset = Object.context_mapping(context) - with {:ok, object} <- Object.insert_or_get(changeset) do - object + case Repo.insert(changeset) do + {:ok, object} -> + object + + # 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) end end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 15e6c1f68..c8e154989 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -12,9 +12,26 @@ defmodule Pleroma.Web.ActivityPub.UserView do alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.Router.Helpers + alias Pleroma.Web.Endpoint import Ecto.Query + def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do + %{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)} + end + + def render("endpoints.json", %{user: %User{local: true} = _user}) do + %{ + "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize), + "oauthRegistrationEndpoint" => Helpers.mastodon_api_url(Endpoint, :create_app), + "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange), + "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox) + } + end + + def render("endpoints.json", _), do: %{} + # the instance itself is not a Person, but instead an Application def render("user.json", %{user: %{nickname: nil} = user}) do {:ok, user} = WebFinger.ensure_keys_present(user) @@ -22,6 +39,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_encode([public_key]) + endpoints = render("endpoints.json", %{user: user}) + %{ "id" => user.ap_id, "type" => "Application", @@ -37,9 +56,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do "owner" => user.ap_id, "publicKeyPem" => public_key }, - "endpoints" => %{ - "sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox" - } + "endpoints" => endpoints } |> Map.merge(Utils.make_json_ld_header()) end @@ -50,6 +67,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_encode([public_key]) + endpoints = render("endpoints.json", %{user: user}) + %{ "id" => user.ap_id, "type" => "Person", @@ -67,9 +86,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do "owner" => user.ap_id, "publicKeyPem" => public_key }, - "endpoints" => %{ - "sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox" - }, + "endpoints" => endpoints, "icon" => %{ "type" => "Image", "url" => User.avatar_url(user) @@ -88,7 +105,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do query = from(user in query, select: [:ap_id]) following = Repo.all(query) - collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows) + total = + if !user.info.hide_follows do + length(following) + else + 0 + end + + collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total) |> Map.merge(Utils.make_json_ld_header()) end @@ -97,10 +121,17 @@ defmodule Pleroma.Web.ActivityPub.UserView do query = from(user in query, select: [:ap_id]) following = Repo.all(query) + total = + if !user.info.hide_follows do + length(following) + else + 0 + end + %{ "id" => "#{user.ap_id}/following", "type" => "OrderedCollection", - "totalItems" => length(following), + "totalItems" => total, "first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows) } |> Map.merge(Utils.make_json_ld_header()) @@ -111,7 +142,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do query = from(user in query, select: [:ap_id]) followers = Repo.all(query) - collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers) + total = + if !user.info.hide_followers do + length(followers) + else + 0 + end + + collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total) |> Map.merge(Utils.make_json_ld_header()) end @@ -120,19 +158,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do query = from(user in query, select: [:ap_id]) followers = Repo.all(query) + total = + if !user.info.hide_followers do + length(followers) + else + 0 + end + %{ "id" => "#{user.ap_id}/followers", "type" => "OrderedCollection", - "totalItems" => length(followers), - "first" => collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers) + "totalItems" => total, + "first" => + collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total) } |> Map.merge(Utils.make_json_ld_header()) end def render("outbox.json", %{user: user, max_id: max_qid}) do - # XXX: technically note_count is wrong for this, but it's better than nothing - info = User.user_info(user) - params = %{ "limit" => "10" } @@ -160,7 +203,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do "id" => "#{iri}?max_id=#{max_id}", "type" => "OrderedCollectionPage", "partOf" => iri, - "totalItems" => info.note_count, "orderedItems" => collection, "next" => "#{iri}?max_id=#{min_id}" } @@ -169,7 +211,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do %{ "id" => iri, "type" => "OrderedCollection", - "totalItems" => info.note_count, "first" => page } |> Map.merge(Utils.make_json_ld_header()) @@ -207,7 +248,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do "id" => "#{iri}?max_id=#{max_id}", "type" => "OrderedCollectionPage", "partOf" => iri, - "totalItems" => -1, "orderedItems" => collection, "next" => "#{iri}?max_id=#{min_id}" } @@ -216,7 +256,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do %{ "id" => iri, "type" => "OrderedCollection", - "totalItems" => -1, "first" => page } |> Map.merge(Utils.make_json_ld_header()) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index c0d6fb5c4..90b208e54 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Repo alias Pleroma.Activity alias Pleroma.Object + alias Pleroma.ThreadMute alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Formatter @@ -94,7 +95,7 @@ defmodule Pleroma.Web.CommonAPI do limit = Pleroma.Config.get([:instance, :limit]) with status <- String.trim(status), - attachments <- attachments_from_ids(data["media_ids"]), + attachments <- attachments_from_ids(data), mentions <- Formatter.parse_mentions(status), inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]), {to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility), @@ -219,4 +220,27 @@ defmodule Pleroma.Web.CommonAPI do {:error, "Could not unpin"} end end + + def add_mute(user, activity) do + with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]) do + {:ok, activity} + else + {:error, _} -> {:error, "conversation is already muted"} + end + end + + def remove_mute(user, activity) do + ThreadMute.remove_mute(user.id, activity.data["context"]) + {:ok, activity} + end + + def thread_muted?(%{id: nil} = _user, _activity), do: false + + def thread_muted?(user, activity) do + with [] <- ThreadMute.check_muted(user.id, activity.data["context"]) do + false + else + _ -> true + end + end end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 123107b56..abdeee947 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -35,12 +35,28 @@ defmodule Pleroma.Web.CommonAPI.Utils do def get_replied_to_activity(_), do: nil - def attachments_from_ids(ids) do + def attachments_from_ids(data) do + if Map.has_key?(data, "descriptions") do + attachments_from_ids_descs(data["media_ids"], data["descriptions"]) + else + attachments_from_ids_no_descs(data["media_ids"]) + end + end + + def attachments_from_ids_no_descs(ids) do Enum.map(ids || [], fn media_id -> Repo.get(Object, media_id).data end) end + def attachments_from_ids_descs(ids, descs_str) do + {_, descs} = Jason.decode(descs_str) + + Enum.map(ids || [], fn media_id -> + Map.put(Repo.get(Object, media_id).data, "name", descs[media_id]) + end) + end + def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 06f870393..e2715bd08 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -456,6 +456,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do + activity = Activity.get_by_id(id) + + with {:ok, activity} <- CommonAPI.add_mute(user, activity) do + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) + else + {:error, reason} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(:bad_request, Jason.encode!(%{"error" => reason})) + end + end + + def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do + activity = Activity.get_by_id(id) + + with {:ok, activity} <- CommonAPI.remove_mute(user, activity) do + conn + |> put_view(StatusView) + |> try_render("status.json", %{activity: activity, for: user, as: :activity}) + end + end + def notifications(%{assigns: %{user: user}} = conn, params) do notifications = Notification.for_user(user, params) @@ -655,7 +680,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do {:ok, _activity} <- ActivityPub.accept(%{ to: [follower.ap_id], - actor: followed.ap_id, + actor: followed, object: follow_activity.data["id"], type: "Accept" }) do @@ -677,7 +702,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do {:ok, _activity} <- ActivityPub.reject(%{ to: [follower.ap_id], - actor: followed.ap_id, + actor: followed, object: follow_activity.data["id"], type: "Reject" }) do @@ -1026,6 +1051,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do accounts = Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user})) + flavour = get_user_flavour(user) + initial_state = %{ meta: %{ @@ -1110,7 +1137,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do conn |> put_layout(false) |> put_view(MastodonView) - |> render("index.html", %{initial_state: initial_state}) + |> render("index.html", %{initial_state: initial_state, flavour: flavour}) else conn |> redirect(to: "/web/login") @@ -1132,6 +1159,43 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + @supported_flavours ["glitch", "vanilla"] + + def set_flavour(%{assigns: %{user: user}} = conn, %{"flavour" => flavour} = _params) + when flavour in @supported_flavours do + flavour_cng = User.Info.mastodon_flavour_update(user.info, flavour) + + with changeset <- Ecto.Changeset.change(user), + changeset <- Ecto.Changeset.put_embed(changeset, :info, flavour_cng), + {:ok, user} <- User.update_and_set_cache(changeset), + flavour <- user.info.flavour do + json(conn, flavour) + else + e -> + conn + |> put_resp_content_type("application/json") + |> send_resp(500, Jason.encode!(%{"error" => inspect(e)})) + end + end + + def set_flavour(conn, _params) do + conn + |> put_status(400) + |> json(%{error: "Unsupported flavour"}) + end + + def get_flavour(%{assigns: %{user: user}} = conn, _params) do + json(conn, get_user_flavour(user)) + end + + defp get_user_flavour(%User{info: %{flavour: flavour}}) when flavour in @supported_flavours do + flavour + end + + defp get_user_flavour(_) do + "glitch" + end + def login(conn, %{"code" => code}) do with {:ok, app} <- get_or_make_app(), %Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id), diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index f51a2ebb0..a49b381c9 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do alias Pleroma.HTML alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.StatusView @@ -160,12 +161,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do reblogged: present?(repeated), favourited: present?(favorited), bookmarked: present?(bookmarked), - muted: false, + muted: CommonAPI.thread_muted?(user, activity), pinned: pinned?(activity, user), sensitive: sensitive, spoiler_text: object["summary"] || "", visibility: get_visibility(object), - media_attachments: attachments |> Enum.take(4), + media_attachments: attachments, mentions: mentions, tags: build_tags(tags), application: %{ diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 1e9da7283..39a725a69 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -19,11 +19,16 @@ 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) + replacement = get_replacement(url, ":2F:") + # The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice. base64 = url + |> String.replace("%2F", replacement) |> URI.decode() |> URI.encode() + |> String.replace(replacement, "%2F") |> Base.url_encode64(@base64_opts) sig = :crypto.hmac(:sha, secret, base64) @@ -60,4 +65,12 @@ defmodule Pleroma.Web.MediaProxy do |> Enum.filter(fn value -> value end) |> Path.join() end + + defp get_replacement(url, replacement) do + if String.contains?(url, replacement) do + get_replacement(url, replacement <> replacement) + else + replacement + end + end end diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/oauth/app.ex index 3e8acde31..8b61bf3a4 100644 --- a/lib/pleroma/web/oauth/app.ex +++ b/lib/pleroma/web/oauth/app.ex @@ -25,8 +25,14 @@ defmodule Pleroma.Web.OAuth.App do if changeset.valid? do changeset - |> put_change(:client_id, :crypto.strong_rand_bytes(32) |> Base.url_encode64()) - |> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64()) + |> put_change( + :client_id, + :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) + ) + |> put_change( + :client_secret, + :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) + ) else changeset end diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex index 75c9ab9aa..9039b8b45 100644 --- a/lib/pleroma/web/oauth/authorization.ex +++ b/lib/pleroma/web/oauth/authorization.ex @@ -24,7 +24,7 @@ defmodule Pleroma.Web.OAuth.Authorization do end def create_authorization(%App{} = app, %User{} = user) do - token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) authorization = %Authorization{ token: token, diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index e4d0601f8..dddfcf299 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -173,7 +173,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do token |> URI.decode() |> Base.url_decode64!(padding: false) - |> Base.url_encode64() + |> Base.url_encode64(padding: false) end defp get_app_from_request(conn, params) do diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index b0bbeeb69..71fd1b874 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -31,8 +31,8 @@ defmodule Pleroma.Web.OAuth.Token do end def create_token(%App{} = app, %User{} = user) do - token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() - refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() + token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) + refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) token = %Token{ token: token, @@ -47,9 +47,27 @@ defmodule Pleroma.Web.OAuth.Token do def delete_user_tokens(%User{id: user_id}) do from( - t in Pleroma.Web.OAuth.Token, + t in Token, where: t.user_id == ^user_id ) |> Repo.delete_all() end + + def delete_user_token(%User{id: user_id}, token_id) do + from( + t in Token, + where: t.user_id == ^user_id, + where: t.id == ^token_id + ) + |> Repo.delete_all() + end + + def get_user_tokens(%User{id: user_id}) do + from( + t in Token, + where: t.user_id == ^user_id + ) + |> Repo.all() + |> Repo.preload(:app) + end end diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 38f1cdeec..4341141df 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -9,6 +9,13 @@ defmodule Pleroma.Web.RichMedia.Parser do Pleroma.Web.RichMedia.Parsers.OEmbed ] + @hackney_options [ + pool: :media, + timeout: 2_000, + recv_timeout: 2_000, + max_body: 2_000_000 + ] + def parse(nil), do: {:error, "No URL provided"} if Mix.env() == :test do @@ -28,7 +35,7 @@ defmodule Pleroma.Web.RichMedia.Parser do defp parse_url(url) do try do - {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media]) + {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options) html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data() rescue diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 7f606ac40..9a6cf2232 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -198,6 +198,8 @@ defmodule Pleroma.Web.Router do post("/statuses/:id/unpin", MastodonAPIController, :unpin_status) post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status) post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status) + post("/statuses/:id/mute", MastodonAPIController, :mute_conversation) + post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation) post("/notifications/clear", MastodonAPIController, :clear_notifications) post("/notifications/dismiss", MastodonAPIController, :dismiss_notification) @@ -234,6 +236,9 @@ defmodule Pleroma.Web.Router do get("/suggestions", MastodonAPIController, :suggestions) get("/endorsements", MastodonAPIController, :empty_array) + + post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour) + get("/pleroma/flavour", MastodonAPIController, :get_flavour) end scope "/api/web", Pleroma.Web.MastodonAPI do @@ -387,6 +392,9 @@ defmodule Pleroma.Web.Router do get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array) get("/externalprofile/show", TwitterAPI.Controller, :external_profile) + + get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens) + delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) end pipeline :ap_relay do @@ -466,8 +474,8 @@ defmodule Pleroma.Web.Router do scope "/", Pleroma.Web.ActivityPub do pipe_through(:activitypub) - post("/users/:nickname/inbox", ActivityPubController, :inbox) post("/inbox", ActivityPubController, :inbox) + post("/users/:nickname/inbox", ActivityPubController, :inbox) end scope "/.well-known", Pleroma.Web do diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 8dd3284d6..520e4b3d5 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -67,6 +67,32 @@ font-weight: 500; font-size: 16px; } + + .alert-danger { + box-sizing: border-box; + width: 100%; + color: #D8000C; + background-color: #FFD2D2; + border-radius: 4px; + border: none; + padding: 10px; + margin-top: 20px; + font-weight: 500; + font-size: 16px; + } + + .alert-info { + box-sizing: border-box; + width: 100%; + color: #00529B; + background-color: #BDE5F8; + border-radius: 4px; + border: none; + padding: 10px; + margin-top: 20px; + font-weight: 500; + font-size: 16px; + } </style> </head> <body> diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex index 9a725e420..5659c7828 100644 --- a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex +++ b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex @@ -8,7 +8,7 @@ </title> <link rel="icon" type="image/png" href="/favicon.png"/> <script crossorigin='anonymous' src="/packs/locales.js"></script> -<script crossorigin='anonymous' src="/packs/locales/glitch/en.js"></script> +<script crossorigin='anonymous' src="/packs/locales/<%= @flavour %>/en.js"></script> <link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/getting_started.js'> <link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/compose.js'> @@ -19,10 +19,10 @@ <script src="/packs/core/common.js"></script> <link rel="stylesheet" media="all" href="/packs/core/common.css" /> -<script src="/packs/flavours/glitch/common.js"></script> -<link rel="stylesheet" media="all" href="/packs/flavours/glitch/common.css" /> +<script src="/packs/flavours/<%= @flavour %>/common.js"></script> +<link rel="stylesheet" media="all" href="/packs/flavours/<%= @flavour %>/common.css" /> -<script src="/packs/flavours/glitch/home.js"></script> +<script src="/packs/flavours/<%= @flavour %>/home.js"></script> </head> <body class='app-body no-reduce-motion system-font'> <div class='app-holder' data-props='{"locale":"en"}' id='mastodon'> 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 de2241ec9..32c458f0c 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 @@ -1,5 +1,9 @@ +<%= if get_flash(@conn, :info) do %> <p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p> +<% end %> +<%= if get_flash(@conn, :error) do %> <p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> +<% end %> <h2>OAuth Authorization</h2> <%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> <%= label f, :name, "Name or email" %> diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 162beb9be..db521a3ad 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -310,8 +310,16 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do else _e -> changeset = Object.context_mapping(context) - {:ok, object} = Object.insert_or_get(changeset) - object.id + + 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 diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index c2f0dc2a9..b815379fd 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -8,6 +8,10 @@ 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.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.CommonAPI @@ -524,6 +528,9 @@ defmodule Pleroma.Web.TwitterAPI.Controller do def friends(%{assigns: %{user: for_user}} = conn, params) do {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1) + {:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false) + + page = if export, do: nil, else: page with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), {:ok, friends} <- User.get_friends(user, page) do @@ -542,6 +549,20 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end end + def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do + with oauth_tokens <- Token.get_user_tokens(user) do + conn + |> put_view(TokenView) + |> render("index.json", %{tokens: oauth_tokens}) + end + end + + def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + Token.delete_user_token(user, id) + + json_reply(conn, 201, "") + end + def blocks(%{assigns: %{user: user}} = conn, _params) do with blocked_users <- User.blocked_users(user) do conn @@ -570,7 +591,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do {:ok, _activity} <- ActivityPub.accept(%{ to: [follower.ap_id], - actor: followed.ap_id, + actor: followed, object: follow_activity.data["id"], type: "Accept" }) do @@ -590,7 +611,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do {:ok, _activity} <- ActivityPub.reject(%{ to: [follower.ap_id], - actor: followed.ap_id, + actor: followed, object: follow_activity.data["id"], type: "Reject" }) do diff --git a/lib/pleroma/web/twitter_api/views/token_view.ex b/lib/pleroma/web/twitter_api/views/token_view.ex new file mode 100644 index 000000000..3ff314913 --- /dev/null +++ b/lib/pleroma/web/twitter_api/views/token_view.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.TwitterAPI.TokenView do + use Pleroma.Web, :view + + def render("index.json", %{tokens: tokens}) do + tokens + |> render_many(Pleroma.Web.TwitterAPI.TokenView, "show.json") + |> Enum.filter(&Enum.any?/1) + end + + def render("show.json", %{token: token_entry}) do + %{ + id: token_entry.id, + valid_until: token_entry.valid_until, + app_name: token_entry.app.client_name + } + end +end diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index a09450df7..df7384476 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -113,10 +113,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do "fields" => fields, # Pleroma extension - "pleroma" => %{ - "confirmation_pending" => user_info.confirmation_pending, - "tags" => user.tags - } + "pleroma" => + %{ + "confirmation_pending" => user_info.confirmation_pending, + "tags" => user.tags + } + |> maybe_with_follow_request_count(user, for_user) } data = @@ -132,6 +134,14 @@ defmodule Pleroma.Web.TwitterAPI.UserView do end end + defp maybe_with_follow_request_count(data, %User{id: id, info: %{locked: true}} = user, %User{ + id: id + }) do + Map.put(data, "follow_request_count", user.info.follow_request_count) + end + + defp maybe_with_follow_request_count(data, _, _), do: data + defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role}) end |