diff options
31 files changed, 816 insertions, 75 deletions
diff --git a/.gitignore b/.gitignore index 3fbf17ba8..9aad700ee 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,7 @@ erl_crash.dump /config/setup_db.psql .DS_Store -.env
\ No newline at end of file +.env + +# Editor config +/.vscode
\ No newline at end of file diff --git a/config/config.exs b/config/config.exs index 826dd07b7..3292bf29c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -54,7 +54,8 @@ config :pleroma, :instance, registrations_open: true, federating: true, rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy, - public: true + public: true, + quarantined_instances: [] config :pleroma, :activitypub, accept_blocks: true diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx index 42323dd95..a0a2d9f51 100644 --- a/installation/pleroma.nginx +++ b/installation/pleroma.nginx @@ -24,18 +24,28 @@ server { # } } +# Enable SSL session caching for improved performance +ssl_session_cache shared:ssl_session_cache:10m; + server { listen 443 ssl http2; ssl on; ssl_session_timeout 5m; + ssl_trusted_certificate /etc/letsencrypt/live/example.tld/fullchain.pem; ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; + # Add TLSv1.0 to support older devices + ssl_protocols TLSv1.2; + # Uncomment line below if you want to support older devices (Before Android 4.4.2, IE 8, etc.) + # ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; + ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; ssl_prefer_server_ciphers on; - + ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1; + ssl_stapling on; + ssl_stapling_verify on; + server_name example.tld; gzip_vary on; diff --git a/lib/mix/tasks/make_moderator.ex b/lib/mix/tasks/make_moderator.ex index 20f04c54c..4d427cfd8 100644 --- a/lib/mix/tasks/make_moderator.ex +++ b/lib/mix/tasks/make_moderator.ex @@ -19,7 +19,7 @@ defmodule Mix.Tasks.SetModerator do |> Map.put("is_moderator", !!moderator) cng = User.info_changeset(user, %{info: info}) - user = Repo.update!(cng) + {:ok, user} = User.update_and_set_cache(cng) IO.puts("Moderator status of #{nickname}: #{user.info["is_moderator"]}") else diff --git a/lib/mix/tasks/set_locked.ex b/lib/mix/tasks/set_locked.ex new file mode 100644 index 000000000..2b3b18b09 --- /dev/null +++ b/lib/mix/tasks/set_locked.ex @@ -0,0 +1,30 @@ +defmodule Mix.Tasks.SetLocked do + use Mix.Task + import Mix.Ecto + alias Pleroma.{Repo, User} + + @shortdoc "Set locked status" + def run([nickname | rest]) do + ensure_started(Repo, []) + + locked = + case rest do + [locked] -> locked == "true" + _ -> true + end + + with %User{local: true} = user <- User.get_by_nickname(nickname) do + info = + user.info + |> Map.put("locked", !!locked) + + cng = User.info_changeset(user, %{info: info}) + user = Repo.update!(cng) + + IO.puts("locked status of #{nickname}: #{user.info["locked"]}") + else + _ -> + IO.puts("No local user #{nickname}") + end + end +end diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex index 9d0b9285b..53d98665b 100644 --- a/lib/pleroma/list.ex +++ b/lib/pleroma/list.ex @@ -1,7 +1,7 @@ defmodule Pleroma.List do use Ecto.Schema import Ecto.{Changeset, Query} - alias Pleroma.{User, Repo} + alias Pleroma.{User, Repo, Activity} schema "lists" do belongs_to(:user, Pleroma.User) @@ -56,6 +56,19 @@ defmodule Pleroma.List do {:ok, Repo.all(q)} end + # Get lists the activity should be streamed to. + def get_lists_from_activity(%Activity{actor: ap_id}) do + actor = User.get_cached_by_ap_id(ap_id) + + query = + from( + l in Pleroma.List, + where: fragment("? && ?", l.following, ^[actor.follower_address]) + ) + + Repo.all(query) + end + def rename(%Pleroma.List{} = list, title) do list |> title_changeset(%{title: title}) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b1b935a0f..b27397e13 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -174,7 +174,7 @@ defmodule Pleroma.User do should_direct_follow = cond do # if the account is locked, don't pre-create the relationship - user_info["locked"] == true -> + user_info[:locked] == true -> false # if the users are blocking each other, we shouldn't even be here, but check for it anyway @@ -193,7 +193,15 @@ defmodule Pleroma.User do if should_direct_follow do follow(follower, followed) else - follower + {:ok, follower} + end + end + + def maybe_follow(%User{} = follower, %User{info: info} = followed) do + if not following?(follower, followed) do + follow(follower, followed) + else + {:ok, follower} end end @@ -252,6 +260,10 @@ defmodule Pleroma.User do Enum.member?(follower.following, followed.follower_address) end + def locked?(%User{} = user) do + user.info["locked"] || false + end + def get_by_ap_id(ap_id) do Repo.get_by(User, ap_id: ap_id) end @@ -349,6 +361,40 @@ defmodule Pleroma.User do {:ok, Repo.all(q)} end + def get_follow_requests_query(%User{} = user) do + from( + a in Activity, + where: + fragment( + "? ->> 'type' = 'Follow'", + a.data + ), + where: + fragment( + "? ->> 'state' = 'pending'", + a.data + ), + where: + fragment( + "? @> ?", + a.data, + ^%{"object" => user.ap_id} + ) + ) + end + + def get_follow_requests(%User{} = user) do + q = get_follow_requests_query(user) + reqs = Repo.all(q) + + users = + Enum.map(reqs, fn req -> req.actor end) + |> Enum.uniq() + |> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end) + + {:ok, users} + end + def increase_note_count(%User{} = user) do note_count = (user.info["note_count"] || 0) + 1 new_info = Map.put(user.info, "note_count", note_count) @@ -479,7 +525,31 @@ defmodule Pleroma.User do def blocks?(user, %{ap_id: ap_id}) do blocks = user.info["blocks"] || [] - Enum.member?(blocks, ap_id) + domain_blocks = user.info["domain_blocks"] || [] + %{host: host} = URI.parse(ap_id) + + Enum.member?(blocks, ap_id) || + Enum.any?(domain_blocks, fn domain -> + host == domain + end) + end + + def block_domain(user, domain) do + domain_blocks = user.info["domain_blocks"] || [] + new_blocks = Enum.uniq([domain | domain_blocks]) + new_info = Map.put(user.info, "domain_blocks", new_blocks) + + cs = User.info_changeset(user, %{info: new_info}) + update_and_set_cache(cs) + end + + def unblock_domain(user, domain) do + blocks = user.info["domain_blocks"] || [] + new_blocks = List.delete(blocks, domain) + new_info = Map.put(user.info, "domain_blocks", new_blocks) + + cs = User.info_changeset(user, %{info: new_info}) + update_and_set_cache(cs) end def local_user_query() do diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 3a03f5fe4..08e21069c 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -57,6 +57,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do if activity.data["type"] in ["Create", "Announce"] do Pleroma.Web.Streamer.stream("user", activity) + Pleroma.Web.Streamer.stream("list", activity) if Enum.member?(activity.data["to"], public) do Pleroma.Web.Streamer.stream("public", activity) @@ -214,6 +215,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def unfollow(follower, followed, activity_id \\ nil, local \\ true) do with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed), + {: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 @@ -439,11 +441,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do blocks = info["blocks"] || [] + domain_blocks = info["domain_blocks"] || [] from( activity in query, where: fragment("not (? = ANY(?))", activity.actor, ^blocks), - where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks) + where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks), + where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks) ) end @@ -562,6 +566,17 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do end end + @quarantined_instances Keyword.get(@instance, :quarantined_instances, []) + + def should_federate?(inbox, public) do + if public do + true + else + inbox_info = URI.parse(inbox) + inbox_info.host not in @quarantined_instances + end + end + def publish(actor, activity) do followers = if actor.follower_address in activity.recipients do @@ -571,6 +586,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do [] end + public = is_public?(activity) + remote_inboxes = (Pleroma.Web.Salmon.remote_users(activity) ++ followers) |> Enum.filter(fn user -> User.ap_enabled?(user) end) @@ -578,6 +595,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do (data["endpoints"] && data["endpoints"]["sharedInbox"]) || data["inbox"] end) |> Enum.uniq() + |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) json = Jason.encode!(data) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index ee5d319a7..d337532d0 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -15,6 +15,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do conn |> put_resp_header("content-type", "application/activity+json") |> json(UserView.render("user.json", %{user: user})) + else + nil -> {:error, :not_found} end end diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex new file mode 100644 index 000000000..879cbe6de --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex @@ -0,0 +1,29 @@ +defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do + alias Pleroma.User + @behaviour Pleroma.Web.ActivityPub.MRF + + @impl true + def filter(object) do + if object["type"] == "Create" do + user = User.get_cached_by_ap_id(object["actor"]) + public = "https://www.w3.org/ns/activitystreams#Public" + + # Determine visibility + visibility = + cond do + public in object["to"] -> "public" + public in object["cc"] -> "unlisted" + user.follower_address in object["to"] -> "followers" + true -> "direct" + end + + case visibility do + "public" -> {:ok, object} + "unlisted" -> {:ok, object} + _ -> {:reject, nil} + end + else + {:ok, object} + end + end +end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 3c9377be9..e7a3420d2 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -137,9 +137,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), %User{} = follower <- User.get_or_fetch_by_ap_id(follower), {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do - ActivityPub.accept(%{to: [follower.ap_id], actor: followed.ap_id, object: data, local: true}) + if not User.locked?(followed) do + ActivityPub.accept(%{ + to: [follower.ap_id], + actor: followed.ap_id, + object: data, + local: true + }) + + User.follow(follower, followed) + end - User.follow(follower, followed) {:ok, activity} else _e -> :error @@ -252,11 +260,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object) banner = new_user_data[:info]["banner"] + locked = new_user_data[:info]["locked"] || false update_data = new_user_data |> Map.take([:name, :bio, :avatar]) - |> Map.put(:info, Map.merge(actor.info, %{"banner" => banner})) + |> Map.put(:info, Map.merge(actor.info, %{"banner" => banner, "locked" => locked})) actor |> User.upgrade_changeset(update_data) @@ -431,6 +440,58 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do {:ok, data} end + # Mastodon Accept/Reject requires a non-normalized object containing the actor URIs, + # because of course it does. + def prepare_outgoing(%{"type" => "Accept"} = data) do + follow_activity_id = + if is_binary(data["object"]) do + data["object"] + else + data["object"]["id"] + end + + with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do + object = %{ + "actor" => follow_activity.actor, + "object" => follow_activity.data["object"], + "id" => follow_activity.data["id"], + "type" => "Follow" + } + + data = + data + |> Map.put("object", object) + |> Map.put("@context", "https://www.w3.org/ns/activitystreams") + + {:ok, data} + end + end + + def prepare_outgoing(%{"type" => "Reject"} = data) do + follow_activity_id = + if is_binary(data["object"]) do + data["object"] + else + data["object"]["id"] + end + + with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do + object = %{ + "actor" => follow_activity.actor, + "object" => follow_activity.data["object"], + "id" => follow_activity.data["id"], + "type" => "Follow" + } + + data = + data + |> Map.put("object", object) + |> Map.put("@context", "https://www.w3.org/ns/activitystreams") + + {:ok, data} + end + end + def prepare_outgoing(%{"type" => _type} = data) do data = data diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 56b80a8db..64329b710 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do alias Pleroma.Web.Endpoint alias Ecto.{Changeset, UUID} import Ecto.Query + require Logger # Some implementations send the actor URI as the actor field, others send the entire actor object, # so figure out what the actor's URI is based on what we have. @@ -217,9 +218,26 @@ defmodule Pleroma.Web.ActivityPub.Utils do #### Follow-related helpers @doc """ + Updates a follow activity's state (for locked accounts). + """ + def update_follow_state(%Activity{} = activity, state) do + with new_data <- + activity.data + |> Map.put("state", state), + changeset <- Changeset.change(activity, data: new_data), + {:ok, activity} <- Repo.update(changeset) do + {:ok, activity} + end + end + + @doc """ Makes a follow activity data for the given follower and followed """ - def make_follow_data(%User{ap_id: follower_id}, %User{ap_id: followed_id}, activity_id) do + def make_follow_data( + %User{ap_id: follower_id}, + %User{ap_id: followed_id} = followed, + activity_id + ) do data = %{ "type" => "Follow", "actor" => follower_id, @@ -228,7 +246,10 @@ defmodule Pleroma.Web.ActivityPub.Utils do "object" => followed_id } - if activity_id, do: Map.put(data, "id", activity_id), else: data + data = if activity_id, do: Map.put(data, "id", activity_id), else: data + data = if User.locked?(followed), do: Map.put(data, "state", "pending"), else: data + + data end def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 4252ac2fe..922b83ed0 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 alias Pleroma.Web alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView} alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.{CommonAPI, OStatus} alias Pleroma.Web.OAuth.{Authorization, Token, App} alias Comeonin.Pbkdf2 @@ -71,6 +72,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do user end + user = + if locked = params["locked"] do + with locked <- locked == "true", + new_info <- Map.put(user.info, "locked", locked), + change <- User.info_changeset(user, %{info: new_info}), + {:ok, user} <- User.update_and_set_cache(change) do + user + else + _e -> user + end + else + user + end + with changeset <- User.update_changeset(user, params), {:ok, user} <- User.update_and_set_cache(changeset) do if original_user != user do @@ -247,7 +262,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end - def dm_timeline(%{assigns: %{user: user}} = conn, params) do + def dm_timeline(%{assigns: %{user: user}} = conn, _params) do query = ActivityPub.fetch_activities_query([user.ap_id], %{"type" => "Create", visibility: "direct"}) @@ -300,6 +315,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params) + when length(media_ids) > 0 do + params = + params + |> Map.put("status", ".") + + post_status(conn, params) + end + def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do params = params @@ -467,6 +491,53 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def follow_requests(%{assigns: %{user: followed}} = conn, _params) do + with {:ok, follow_requests} <- User.get_follow_requests(followed) do + render(conn, AccountView, "accounts.json", %{users: follow_requests, as: :user}) + end + 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.ap_id, + object: follow_activity.data["id"], + type: "Accept" + }) do + render(conn, AccountView, "relationship.json", %{user: followed, target: follower}) + else + {:error, message} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(403, Jason.encode!(%{"error" => message})) + end + 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.ap_id, + object: follow_activity.data["id"], + type: "Reject" + }) do + render(conn, AccountView, "relationship.json", %{user: followed, target: follower}) + else + {:error, message} -> + conn + |> put_resp_content_type("application/json") + |> send_resp(403, Jason.encode!(%{"error" => message})) + end + end + def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do with %User{} = followed <- Repo.get(User, id), {:ok, follower} <- User.maybe_direct_follow(follower, followed), @@ -536,6 +607,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end end + def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do + json(conn, info["domain_blocks"] || []) + end + + def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do + User.block_domain(blocker, domain) + json(conn, %{}) + end + + def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do + User.unblock_domain(blocker, domain) + json(conn, %{}) + end + def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do accounts = User.search(query, params["resolve"] == "true") diff --git a/lib/pleroma/web/mastodon_api/mastodon_socket.ex b/lib/pleroma/web/mastodon_api/mastodon_socket.ex index 080f62b31..46648c366 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_socket.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_socket.ex @@ -15,10 +15,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do with token when not is_nil(token) <- params["access_token"], %Token{user_id: user_id} <- Repo.get_by(Token, token: token), %User{} = user <- Repo.get(User, user_id), - stream when stream in ["public", "public:local", "user", "direct"] <- params["stream"] do + stream when stream in ["public", "public:local", "user", "direct", "list"] <- + params["stream"] do + topic = if stream == "list", do: "list:#{params["list"]}", else: stream + socket = socket - |> assign(:topic, params["stream"]) + |> assign(:topic, topic) |> assign(:user, user) Pleroma.Web.Streamer.add_socket(params["stream"], socket) diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index f346cc9af..2f72fdb16 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -12,19 +12,24 @@ defmodule Pleroma.Web.OStatus.OStatusController do action_fallback(:errors) def feed_redirect(conn, %{"nickname" => nickname}) do - with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)} do - case get_format(conn) do - "html" -> Fallback.RedirectController.redirector(conn, nil) - "activity+json" -> ActivityPubController.call(conn, :user) - _ -> redirect(conn, external: OStatus.feed_path(user)) - end - else - {:user, nil} -> {:error, :not_found} + case get_format(conn) do + "html" -> + Fallback.RedirectController.redirector(conn, nil) + + "activity+json" -> + ActivityPubController.call(conn, :user) + + _ -> + with %User{} = user <- User.get_cached_by_nickname(nickname) do + redirect(conn, external: OStatus.feed_path(user)) + else + nil -> {:error, :not_found} + end end end def feed(conn, %{"nickname" => nickname} = params) do - with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)} do + with %User{} = user <- User.get_cached_by_nickname(nickname) do query_params = Map.take(params, ["max_id"]) |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id}) @@ -43,7 +48,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do |> put_resp_content_type("application/atom+xml") |> send_resp(200, response) else - {:user, nil} -> {:error, :not_found} + nil -> {:error, :not_found} end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 924254895..ee6a373d3 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -97,12 +97,14 @@ defmodule Pleroma.Web.Router do post("/accounts/:id/mute", MastodonAPIController, :relationship_noop) post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop) + get("/follow_requests", MastodonAPIController, :follow_requests) + post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request) + post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request) + post("/follows", MastodonAPIController, :follow) get("/blocks", MastodonAPIController, :blocks) - get("/domain_blocks", MastodonAPIController, :empty_array) - get("/follow_requests", MastodonAPIController, :empty_array) get("/mutes", MastodonAPIController, :empty_array) get("/timelines/home", MastodonAPIController, :home_timeline) @@ -134,6 +136,10 @@ defmodule Pleroma.Web.Router do get("/lists/:id/accounts", MastodonAPIController, :list_accounts) post("/lists/:id/accounts", MastodonAPIController, :add_to_list) delete("/lists/:id/accounts", MastodonAPIController, :remove_from_list) + + get("/domain_blocks", MastodonAPIController, :domain_blocks) + post("/domain_blocks", MastodonAPIController, :block_domain) + delete("/domain_blocks", MastodonAPIController, :unblock_domain) end scope "/api/web", Pleroma.Web.MastodonAPI do @@ -240,6 +246,10 @@ defmodule Pleroma.Web.Router do post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet) post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post) + get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests) + post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request) + post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request) + post("/friendships/create", TwitterAPI.Controller, :follow) post("/friendships/destroy", TwitterAPI.Controller, :unfollow) post("/blocks/create", TwitterAPI.Controller, :block) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index 6ed9035fb..0c9e95485 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -59,6 +59,19 @@ defmodule Pleroma.Web.Streamer do {:noreply, topics} end + def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do + recipient_topics = + Pleroma.List.get_lists_from_activity(item) + |> Enum.map(fn %{id: id} -> "list:#{id}" end) + + Enum.each(recipient_topics || [], fn list_topic -> + Logger.debug("Trying to push message to #{list_topic}\n\n") + push_to_socket(topics, list_topic, item) + end) + + {:noreply, topics} + end + def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do topic = "user:#{item.user_id}" diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index cc5146566..7a0c37ce9 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -189,7 +189,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do {:ok, follower} <- User.follow(follower, followed) do ActivityPub.follow(follower, followed) else - _e -> Logger.debug("follow_import: following #{account} failed") + err -> Logger.debug("follow_import: following #{account} failed with #{inspect(err)}") 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 d53dd0c44..b29687df5 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do alias Pleroma.Web.CommonAPI alias Pleroma.{Repo, Activity, User, Notification} alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.Utils alias Ecto.Changeset require Logger @@ -331,6 +332,54 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end end + def friend_requests(conn, params) do + with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), + {:ok, friend_requests} <- User.get_follow_requests(user) do + render(conn, UserView, "index.json", %{users: friend_requests, for: conn.assigns[:user]}) + else + _e -> bad_request_reply(conn, "Can't get friend requests") + end + end + + def approve_friend_request(conn, %{"user_id" => uid} = params) do + with followed <- conn.assigns[:user], + uid when is_number(uid) <- String.to_integer(uid), + %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.ap_id, + object: follow_activity.data["id"], + type: "Accept" + }) do + render(conn, UserView, "show.json", %{user: follower, for: followed}) + else + e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}") + end + end + + def deny_friend_request(conn, %{"user_id" => uid} = params) do + with followed <- conn.assigns[:user], + uid when is_number(uid) <- String.to_integer(uid), + %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.ap_id, + object: follow_activity.data["id"], + type: "Reject" + }) do + render(conn, UserView, "show.json", %{user: follower, for: followed}) + else + e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}") + end + end + def friends_ids(%{assigns: %{user: user}} = conn, _params) do with {:ok, friends} <- User.get_friends(user) do ids = @@ -357,6 +406,20 @@ defmodule Pleroma.Web.TwitterAPI.Controller do params end + user = + if locked = params["locked"] do + with locked <- locked == "true", + new_info <- Map.put(user.info, "locked", locked), + change <- User.info_changeset(user, %{info: new_info}), + {:ok, user} <- User.update_and_set_cache(change) do + user + else + _e -> user + end + else + user + end + with changeset <- User.update_changeset(user, params), {:ok, user} <- User.update_and_set_cache(changeset) do CommonAPI.update(user) diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index 31527caae..711008973 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -51,7 +51,8 @@ defmodule Pleroma.Web.TwitterAPI.UserView do "statusnet_profile_url" => user.ap_id, "cover_photo" => User.banner_url(user) |> MediaProxy.url(), "background_image" => image_url(user.info["background"]) |> MediaProxy.url(), - "is_local" => user.local + "is_local" => user.local, + "locked" => !!user.info["locked"] } if assigns[:token] do diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 9c6f1cb68..e7ee810f9 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -144,41 +144,50 @@ defmodule Pleroma.Web.WebFinger do end end - defp webfinger_from_xml(doc) do - magic_key = XML.string_from_xpath(~s{//Link[@rel="magic-public-key"]/@href}, doc) + defp get_magic_key(magic_key) do "data:application/magic-public-key," <> magic_key = magic_key + {:ok, magic_key} + rescue + MatchError -> {:error, "Missing magic key data."} + end - topic = - XML.string_from_xpath( - ~s{//Link[@rel="http://schemas.google.com/g/2010#updates-from"]/@href}, - doc - ) - - subject = XML.string_from_xpath("//Subject", doc) - salmon = XML.string_from_xpath(~s{//Link[@rel="salmon"]/@href}, doc) - - subscribe_address = - XML.string_from_xpath( - ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}, - doc - ) - - ap_id = - XML.string_from_xpath( - ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}, - doc - ) - - data = %{ - "magic_key" => magic_key, - "topic" => topic, - "subject" => subject, - "salmon" => salmon, - "subscribe_address" => subscribe_address, - "ap_id" => ap_id - } + defp webfinger_from_xml(doc) do + with magic_key <- XML.string_from_xpath(~s{//Link[@rel="magic-public-key"]/@href}, doc), + {:ok, magic_key} <- get_magic_key(magic_key), + topic <- + XML.string_from_xpath( + ~s{//Link[@rel="http://schemas.google.com/g/2010#updates-from"]/@href}, + doc + ), + subject <- XML.string_from_xpath("//Subject", doc), + salmon <- XML.string_from_xpath(~s{//Link[@rel="salmon"]/@href}, doc), + subscribe_address <- + XML.string_from_xpath( + ~s{//Link[@rel="http://ostatus.org/schema/1.0/subscribe"]/@template}, + doc + ), + ap_id <- + XML.string_from_xpath( + ~s{//Link[@rel="self" and @type="application/activity+json"]/@href}, + doc + ) do + data = %{ + "magic_key" => magic_key, + "topic" => topic, + "subject" => subject, + "salmon" => salmon, + "subscribe_address" => subscribe_address, + "ap_id" => ap_id + } - {:ok, data} + {:ok, data} + else + {:error, e} -> + {:error, e} + + e -> + {:error, e} + end end defp webfinger_from_json(doc) do @@ -253,7 +262,7 @@ defmodule Pleroma.Web.WebFinger do String.replace(template, "{uri}", URI.encode(account)) _ -> - "http://#{domain}/.well-known/webfinger?resource=acct:#{account}" + "https://#{domain}/.well-known/webfinger?resource=acct:#{account}" end with response <- @@ -268,8 +277,11 @@ defmodule Pleroma.Web.WebFinger do if doc != :error do webfinger_from_xml(doc) else - {:ok, doc} = Jason.decode(body) - webfinger_from_json(doc) + with {:ok, doc} <- Jason.decode(body) do + webfinger_from_json(doc) + else + {:error, e} -> e + end end else e -> diff --git a/lib/pleroma/web/xml/xml.ex b/lib/pleroma/web/xml/xml.ex index 36430a3fa..da3f68ecb 100644 --- a/lib/pleroma/web/xml/xml.ex +++ b/lib/pleroma/web/xml/xml.ex @@ -32,6 +32,10 @@ defmodule Pleroma.Web.XML do :exit, _error -> Logger.debug("Couldn't parse XML: #{inspect(text)}") :error + rescue + e -> + Logger.debug("Couldn't parse XML: #{inspect(text)}") + :error end end end diff --git a/priv/repo/migrations/20180530123448_add_list_follow_index.exs b/priv/repo/migrations/20180530123448_add_list_follow_index.exs new file mode 100644 index 000000000..d6603e916 --- /dev/null +++ b/priv/repo/migrations/20180530123448_add_list_follow_index.exs @@ -0,0 +1,7 @@ +defmodule Pleroma.Repo.Migrations.AddListFollowIndex do + use Ecto.Migration + + def change do + create index(:lists, [:following]) + end +end diff --git a/priv/repo/migrations/20180606173637_create_apid_host_extraction_index.exs b/priv/repo/migrations/20180606173637_create_apid_host_extraction_index.exs new file mode 100644 index 000000000..9831a1b82 --- /dev/null +++ b/priv/repo/migrations/20180606173637_create_apid_host_extraction_index.exs @@ -0,0 +1,8 @@ +defmodule Pleroma.Repo.Migrations.CreateApidHostExtractionIndex do + use Ecto.Migration + @disable_ddl_transaction true + + def change do + create index(:activities, ["(split_part(actor, '/', 3))"], concurrently: true, name: :activities_hosts) + end +end diff --git a/test/list_test.exs b/test/list_test.exs index ced012093..da3b88024 100644 --- a/test/list_test.exs +++ b/test/list_test.exs @@ -74,4 +74,20 @@ defmodule Pleroma.ListTest do assert list_two in lists refute list_three in lists end + + test "getting all lists the user is a member of" do + user = insert(:user) + other_user = insert(:user) + {:ok, list_one} = Pleroma.List.create("title", user) + {:ok, list_two} = Pleroma.List.create("other title", user) + {:ok, list_three} = Pleroma.List.create("third title", other_user) + {:ok, list_one} = Pleroma.List.follow(list_one, other_user) + {:ok, list_two} = Pleroma.List.follow(list_two, other_user) + {:ok, list_three} = Pleroma.List.follow(list_three, user) + + lists = Pleroma.List.get_lists_from_activity(%Pleroma.Activity{actor: other_user.ap_id}) + assert list_one in lists + assert list_two in lists + refute list_three in lists + end end diff --git a/test/support/httpoison_mock.ex b/test/support/httpoison_mock.ex index 6e8336a93..befebad8a 100644 --- a/test/support/httpoison_mock.ex +++ b/test/support/httpoison_mock.ex @@ -4,7 +4,7 @@ defmodule HTTPoisonMock do def get(url, body \\ [], headers \\ []) def get( - "http://gerzilla.de/.well-known/webfinger?resource=acct:kaniini@gerzilla.de", + "https://gerzilla.de/.well-known/webfinger?resource=acct:kaniini@gerzilla.de", [Accept: "application/xrd+xml,application/jrd+json"], follow_redirect: true ) do @@ -16,7 +16,7 @@ defmodule HTTPoisonMock do end def get( - "http://framatube.org/.well-known/webfinger?resource=acct:framasoft@framatube.org", + "https://framatube.org/.well-known/webfinger?resource=acct:framasoft@framatube.org", [Accept: "application/xrd+xml,application/jrd+json"], follow_redirect: true ) do @@ -28,7 +28,7 @@ defmodule HTTPoisonMock do end def get( - "http://gnusocial.de/.well-known/webfinger?resource=acct:winterdienst@gnusocial.de", + "https://gnusocial.de/.well-known/webfinger?resource=acct:winterdienst@gnusocial.de", [Accept: "application/xrd+xml,application/jrd+json"], follow_redirect: true ) do diff --git a/test/user_test.exs b/test/user_test.exs index 8c8cfd673..200352981 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -361,6 +361,27 @@ defmodule Pleroma.UserTest do end end + describe "domain blocking" do + test "blocks domains" do + user = insert(:user) + collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) + + {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com") + + assert User.blocks?(user, collateral_user) + end + + test "unblocks domains" do + user = insert(:user) + collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) + + {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com") + {:ok, user} = User.unblock_domain(user, "awful-and-rude-instance.com") + + refute User.blocks?(user, collateral_user) + end + end + test "get recipients from activity" do actor = insert(:user) user = insert(:user, local: true) diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 384844095..7e771b9f8 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -266,6 +266,29 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do assert user.bio == "<p>Some bio</p>" end + test "it works for incoming update activities which lock the account" do + data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + update_data = File.read!("test/fixtures/mastodon-update.json") |> Poison.decode!() + + object = + update_data["object"] + |> Map.put("actor", data["actor"]) + |> Map.put("id", data["actor"]) + |> Map.put("manuallyApprovesFollowers", true) + + update_data = + update_data + |> Map.put("actor", data["actor"]) + |> Map.put("object", object) + + {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(update_data) + + user = User.get_cached_by_ap_id(data["actor"]) + assert user.info["locked"] == true + end + test "it works for incoming deletes" do activity = insert(:note_activity) diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 1291c3693..d1812457d 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -4,6 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.{Repo, User, Activity, Notification} alias Pleroma.Web.{OStatus, CommonAPI} + alias Pleroma.Web.ActivityPub.ActivityPub import Pleroma.Factory import ExUnit.CaptureLog @@ -644,6 +645,73 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do end end + describe "locked accounts" do + test "/api/v1/follow_requests works" do + user = insert(:user, %{info: %{"locked" => true}}) + other_user = insert(:user) + + {:ok, activity} = ActivityPub.follow(other_user, user) + + user = Repo.get(User, user.id) + other_user = Repo.get(User, other_user.id) + + assert User.following?(other_user, user) == false + + conn = + build_conn() + |> assign(:user, user) + |> get("/api/v1/follow_requests") + + assert [relationship] = json_response(conn, 200) + assert to_string(other_user.id) == relationship["id"] + end + + test "/api/v1/follow_requests/:id/authorize works" do + user = insert(:user, %{info: %{"locked" => true}}) + other_user = insert(:user) + + {:ok, activity} = ActivityPub.follow(other_user, user) + + user = Repo.get(User, user.id) + other_user = Repo.get(User, other_user.id) + + assert User.following?(other_user, user) == false + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/v1/follow_requests/#{other_user.id}/authorize") + + assert relationship = json_response(conn, 200) + assert to_string(other_user.id) == relationship["id"] + + user = Repo.get(User, user.id) + other_user = Repo.get(User, other_user.id) + + assert User.following?(other_user, user) == true + end + + test "/api/v1/follow_requests/:id/reject works" do + user = insert(:user, %{info: %{"locked" => true}}) + other_user = insert(:user) + + {:ok, activity} = ActivityPub.follow(other_user, user) + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/v1/follow_requests/#{other_user.id}/reject") + + assert relationship = json_response(conn, 200) + assert to_string(other_user.id) == relationship["id"] + + user = Repo.get(User, user.id) + other_user = Repo.get(User, other_user.id) + + assert User.following?(other_user, user) == false + end + end + test "account fetching", %{conn: conn} do user = insert(:user) @@ -792,6 +860,46 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do assert [%{"id" => ^other_user_id}] = json_response(conn, 200) end + test "blocking / unblocking a domain", %{conn: conn} do + user = insert(:user) + other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"}) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + + assert %{} = json_response(conn, 200) + user = User.get_cached_by_ap_id(user.ap_id) + assert User.blocks?(user, other_user) + + conn = + build_conn() + |> assign(:user, user) + |> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"}) + + assert %{} = json_response(conn, 200) + user = User.get_cached_by_ap_id(user.ap_id) + refute User.blocks?(user, other_user) + end + + test "getting a list of domain blocks" do + user = insert(:user) + + {:ok, user} = User.block_domain(user, "bad.site") + {:ok, user} = User.block_domain(user, "even.worse.site") + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/domain_blocks") + + domain_blocks = json_response(conn, 200) + + assert "bad.site" in domain_blocks + assert "even.worse.site" in domain_blocks + end + test "unimplemented mute endpoints" do user = insert(:user) other_user = insert(:user) diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index 68f4331df..bd11551df 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -762,6 +762,38 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) end + + test "it locks an account", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/account/update_profile.json", %{ + "locked" => "true" + }) + + user = Repo.get!(User, user.id) + assert user.info["locked"] == true + + assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) + end + + test "it unlocks an account", %{conn: conn} do + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/account/update_profile.json", %{ + "locked" => "false" + }) + + user = Repo.get!(User, user.id) + assert user.info["locked"] == false + + assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) + end end defp valid_user(_context) do @@ -926,4 +958,72 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do :timer.sleep(1000) end end + + describe "GET /api/pleroma/friend_requests" do + test "it lists friend requests" do + user = insert(:user, %{info: %{"locked" => true}}) + other_user = insert(:user) + + {:ok, activity} = ActivityPub.follow(other_user, user) + + user = Repo.get(User, user.id) + other_user = Repo.get(User, other_user.id) + + assert User.following?(other_user, user) == false + + conn = + build_conn() + |> assign(:user, user) + |> get("/api/pleroma/friend_requests") + + assert [relationship] = json_response(conn, 200) + assert other_user.id == relationship["id"] + end + end + + describe "POST /api/pleroma/friendships/approve" do + test "it approves a friend request" do + user = insert(:user, %{info: %{"locked" => true}}) + other_user = insert(:user) + + {:ok, activity} = ActivityPub.follow(other_user, user) + + user = Repo.get(User, user.id) + other_user = Repo.get(User, other_user.id) + + assert User.following?(other_user, user) == false + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/pleroma/friendships/approve", %{"user_id" => to_string(other_user.id)}) + + assert relationship = json_response(conn, 200) + assert other_user.id == relationship["id"] + assert relationship["follows_you"] == true + end + end + + describe "POST /api/pleroma/friendships/deny" do + test "it denies a friend request" do + user = insert(:user, %{info: %{"locked" => true}}) + other_user = insert(:user) + + {:ok, activity} = ActivityPub.follow(other_user, user) + + user = Repo.get(User, user.id) + other_user = Repo.get(User, other_user.id) + + assert User.following?(other_user, user) == false + + conn = + build_conn() + |> assign(:user, user) + |> post("/api/pleroma/friendships/deny", %{"user_id" => to_string(other_user.id)}) + + assert relationship = json_response(conn, 200) + assert other_user.id == relationship["id"] + assert relationship["follows_you"] == false + end + end end diff --git a/test/web/twitter_api/views/user_view_test.exs b/test/web/twitter_api/views/user_view_test.exs index 9f8bf4cdc..eea743b32 100644 --- a/test/web/twitter_api/views/user_view_test.exs +++ b/test/web/twitter_api/views/user_view_test.exs @@ -59,7 +59,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do "statusnet_profile_url" => user.ap_id, "cover_photo" => banner, "background_image" => nil, - "is_local" => true + "is_local" => true, + "locked" => false } assert represented == UserView.render("show.json", %{user: user}) @@ -94,7 +95,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do "statusnet_profile_url" => user.ap_id, "cover_photo" => banner, "background_image" => nil, - "is_local" => true + "is_local" => true, + "locked" => false } assert represented == UserView.render("show.json", %{user: user, for: follower}) @@ -130,7 +132,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do "statusnet_profile_url" => follower.ap_id, "cover_photo" => banner, "background_image" => nil, - "is_local" => true + "is_local" => true, + "locked" => false } assert represented == UserView.render("show.json", %{user: follower, for: user}) @@ -173,7 +176,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do "statusnet_profile_url" => user.ap_id, "cover_photo" => banner, "background_image" => nil, - "is_local" => true + "is_local" => true, + "locked" => false } blocker = Repo.get(User, blocker.id) |