From: eal Date: Mon, 11 Jun 2018 19:59:30 +0000 (+0000) Subject: Merge branch 'fix/mix-task-caching' into 'develop' X-Git-Url: http://git.squeep.com/?a=commitdiff_plain;h=cdf5a668f2194d98ac2020babc4b10e4b01ab957;hp=18837c2fedfc5dc79e78751d5b1b16ea1f0dce87;p=akkoma Merge branch 'fix/mix-task-caching' into 'develop' make_moderator.ex: set cache on update See merge request pleroma/pleroma!206 --- 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/caddyfile-pleroma.example b/installation/caddyfile-pleroma.example new file mode 100644 index 000000000..e0f9dc917 --- /dev/null +++ b/installation/caddyfile-pleroma.example @@ -0,0 +1,18 @@ +social.domain.tld { + tls user@domain.tld + + log /var/log/caddy/pleroma.log + + cors / { + origin https://halcyon.domain.tld + origin https://pinafore.domain.tld + methods POST,PUT,DELETE,GET,PATCH,OPTIONS + allowed_headers Authorization,Content-Type,Idempotency-Key + exposed_headers Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id + } + + proxy / localhost:4000 { + websocket + transparent + } +} diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index c7502981e..dd6805125 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -72,8 +72,10 @@ defmodule Pleroma.Activity do ) end - def get_create_activity_by_object_ap_id(ap_id) do + def get_create_activity_by_object_ap_id(ap_id) when is_binary(ap_id) do create_activity_by_object_id_query([ap_id]) |> Repo.one() end + + def get_create_activity_by_object_ap_id(_), do: nil end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b1b935a0f..00cac153d 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,7 @@ defmodule Pleroma.User do if should_direct_follow do follow(follower, followed) else - follower + {:ok, follower} end end @@ -479,7 +479,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 4e0be5ba2..43e96fe37 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -439,11 +439,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 +564,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 +584,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 +593,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 a6a9b99ef..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 @@ -27,9 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do |> json(ObjectView.render("object.json", %{object: object})) else {:public?, false} -> - conn - |> put_status(404) - |> json("Not found") + {:error, :not_found} end end @@ -107,6 +107,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do json(conn, "ok") end + def errors(conn, {:error, :not_found}) do + conn + |> put_status(404) + |> json("Not found") + end + def errors(conn, _e) do conn |> put_status(500) 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..75ba36729 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -252,11 +252,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"] 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) diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 9c9951371..30089f553 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -9,11 +9,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do def get_by_id_or_ap_id(id) do activity = Repo.get(Activity, id) || Activity.get_create_activity_by_object_ap_id(id) - if activity.data["type"] == "Create" do - activity - else - Activity.get_create_activity_by_object_ap_id(activity.data["object"]) - end + activity && + if activity.data["type"] == "Create" do + activity + else + Activity.get_create_activity_by_object_ap_id(activity.data["object"]) + end end def get_replied_to_activity(id) when not is_nil(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 5fb51e8fa..0f7d4bb6d 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -10,6 +10,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do import Ecto.Query require Logger + action_fallback(:errors) + def create_app(conn, params) do with cs <- App.register_changeset(%App{}, params) |> IO.inspect(), {:ok, app} <- Repo.insert(cs) |> IO.inspect() do @@ -134,6 +136,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do %{ "shortcode" => shortcode, "static_url" => url, + "visible_in_picker" => true, "url" => url } end) @@ -244,7 +247,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"}) @@ -297,6 +300,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 @@ -327,27 +339,27 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, announce, _activity} = CommonAPI.repeat(ap_id_or_id, user) do + with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user) do render(conn, StatusView, "status.json", %{activity: announce, for: user, as: :activity}) end end def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, _, _, %{data: %{"id" => id}}} = CommonAPI.unrepeat(ap_id_or_id, user), + with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) end end def fav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user), + with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) end end def unfav_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, _, _, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), + with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) end @@ -533,6 +545,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") @@ -919,4 +945,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do nil end end + + def errors(conn, _) do + conn + |> put_status(500) + |> json("Something went wrong") + end end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 11dc1806f..3dd87d0ab 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -56,12 +56,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do # TODO # - proper scope handling def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do - with %App{} = app <- - Repo.get_by( - App, - client_id: params["client_id"], - client_secret: params["client_secret"] - ), + with %App{} = app <- get_app_from_request(conn, params), fixed_token = fix_padding(params["code"]), %Authorization{} = auth <- Repo.get_by(Authorization, token: fixed_token, app_id: app.id), @@ -76,7 +71,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do json(conn, response) else - _error -> json(conn, %{error: "Invalid credentials"}) + _error -> + put_status(conn, 400) + |> json(%{error: "Invalid credentials"}) end end @@ -86,12 +83,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do conn, %{"grant_type" => "password", "name" => name, "password" => password} = params ) do - with %App{} = app <- - Repo.get_by( - App, - client_id: params["client_id"], - client_secret: params["client_secret"] - ), + with %App{} = app <- get_app_from_request(conn, params), %User{} = user <- User.get_cached_by_nickname(name), true <- Pbkdf2.checkpw(password, user.password_hash), {:ok, auth} <- Authorization.create_authorization(app, user), @@ -106,7 +98,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do json(conn, response) else - _error -> json(conn, %{error: "Invalid credentials"}) + _error -> + put_status(conn, 400) + |> json(%{error: "Invalid credentials"}) end end @@ -115,4 +109,28 @@ defmodule Pleroma.Web.OAuth.OAuthController do |> Base.url_decode64!(padding: false) |> Base.url_encode64() end + + defp get_app_from_request(conn, params) do + # Per RFC 6749, HTTP Basic is preferred to body params + {client_id, client_secret} = + with ["Basic " <> encoded] <- get_req_header(conn, "authorization"), + {:ok, decoded} <- Base.decode64(encoded), + [id, secret] <- + String.split(decoded, ":") + |> Enum.map(fn s -> URI.decode_www_form(s) end) do + {id, secret} + else + _ -> {params["client_id"], params["client_secret"]} + end + + if client_id && client_secret do + Repo.get_by( + App, + client_id: client_id, + client_secret: client_secret + ) + else + nil + end + end end diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex index 53278431e..2f72fdb16 100644 --- a/lib/pleroma/web/ostatus/ostatus_controller.ex +++ b/lib/pleroma/web/ostatus/ostatus_controller.ex @@ -9,36 +9,47 @@ defmodule Pleroma.Web.OStatus.OStatusController do alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.ActivityPub - def feed_redirect(conn, %{"nickname" => nickname} = params) do - user = User.get_cached_by_nickname(nickname) + action_fallback(:errors) + def feed_redirect(conn, %{"nickname" => nickname}) do case get_format(conn) do - "html" -> Fallback.RedirectController.redirector(conn, nil) - "activity+json" -> ActivityPubController.user(conn, params) - _ -> redirect(conn, external: OStatus.feed_path(user)) + "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 - user = User.get_cached_by_nickname(nickname) - - query_params = - Map.take(params, ["max_id"]) - |> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id}) - - activities = - ActivityPub.fetch_public_activities(query_params) - |> Enum.reverse() - - response = - user - |> FeedRepresenter.to_simple_form(activities, [user]) - |> :xmerl.export_simple(:xmerl_xml) - |> to_string - - conn - |> put_resp_content_type("application/atom+xml") - |> send_resp(200, response) + 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}) + + activities = + ActivityPub.fetch_public_activities(query_params) + |> Enum.reverse() + + response = + user + |> FeedRepresenter.to_simple_form(activities, [user]) + |> :xmerl.export_simple(:xmerl_xml) + |> to_string + + conn + |> put_resp_content_type("application/atom+xml") + |> send_resp(200, response) + else + nil -> {:error, :not_found} + end end defp decode_or_retry(body) do @@ -68,12 +79,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do |> send_resp(200, "") end - def object(conn, %{"uuid" => uuid} = params) do + def object(conn, %{"uuid" => uuid}) do if get_format(conn) == "activity+json" do - ActivityPubController.object(conn, params) + ActivityPubController.call(conn, :object) else with id <- o_status_url(conn, :object, uuid), - %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id), + {_, %Activity{} = activity} <- + {:activity, Activity.get_create_activity_by_object_ap_id(id)}, {_, true} <- {:public?, ActivityPub.is_public?(activity)}, %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do case get_format(conn) do @@ -82,16 +94,20 @@ defmodule Pleroma.Web.OStatus.OStatusController do end else {:public?, false} -> - conn - |> put_status(404) - |> json("Not found") + {:error, :not_found} + + {:activity, nil} -> + {:error, :not_found} + + e -> + e end end end def activity(conn, %{"uuid" => uuid}) do with id <- o_status_url(conn, :activity, uuid), - %Activity{} = activity <- Activity.get_by_ap_id(id), + {_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(id)}, {_, true} <- {:public?, ActivityPub.is_public?(activity)}, %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do case get_format(conn) do @@ -100,14 +116,18 @@ defmodule Pleroma.Web.OStatus.OStatusController do end else {:public?, false} -> - conn - |> put_status(404) - |> json("Not found") + {:error, :not_found} + + {:activity, nil} -> + {:error, :not_found} + + e -> + e end end def notice(conn, %{"id" => id}) do - with %Activity{} = activity <- Repo.get(Activity, id), + with {_, %Activity{} = activity} <- {:activity, Repo.get(Activity, id)}, {_, true} <- {:public?, ActivityPub.is_public?(activity)}, %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do case get_format(conn) do @@ -121,9 +141,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do end else {:public?, false} -> - conn - |> put_status(404) - |> json("Not found") + {:error, :not_found} + + {:activity, nil} -> + {:error, :not_found} + + e -> + e end end @@ -139,4 +163,16 @@ defmodule Pleroma.Web.OStatus.OStatusController do |> put_resp_content_type("application/atom+xml") |> send_resp(200, response) end + + def errors(conn, {:error, :not_found}) do + conn + |> put_status(404) + |> text("Not found") + end + + def errors(conn, _) do + conn + |> put_status(500) + |> text("Something went wrong") + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 924254895..57b10bff1 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -101,7 +101,6 @@ defmodule Pleroma.Web.Router do get("/blocks", MastodonAPIController, :blocks) - get("/domain_blocks", MastodonAPIController, :empty_array) get("/follow_requests", MastodonAPIController, :empty_array) get("/mutes", MastodonAPIController, :empty_array) @@ -134,6 +133,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 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.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 331efa90b..ccc6fe8e7 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -64,7 +64,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end def repeat(%User{} = user, ap_id_or_id) do - with {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(ap_id_or_id, user), + with {:ok, _announce, %{data: %{"id" => id}}} <- CommonAPI.repeat(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do {:ok, activity} end @@ -77,14 +77,14 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do end def fav(%User{} = user, ap_id_or_id) do - with {:ok, _fav, %{data: %{"id" => id}}} = CommonAPI.favorite(ap_id_or_id, user), + with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do {:ok, activity} end end def unfav(%User{} = user, ap_id_or_id) do - with {:ok, _unfav, _fav, %{data: %{"id" => id}}} = CommonAPI.unfavorite(ap_id_or_id, user), + with {:ok, _unfav, _fav, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do {:ok, activity} end diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 320f2fcf4..d53dd0c44 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Web.TwitterAPI.Controller do require Logger + action_fallback(:errors) + def verify_credentials(%{assigns: %{user: user}} = conn, _params) do token = Phoenix.Token.sign(conn, "user socket", user.id) render(conn, UserView, "show.json", %{user: user, token: token}) @@ -218,19 +220,22 @@ defmodule Pleroma.Web.TwitterAPI.Controller do end def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, activity} <- TwitterAPI.fav(user, id) do + with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, + {:ok, activity} <- TwitterAPI.fav(user, id) do render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) end end def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, activity} <- TwitterAPI.unfav(user, id) do + with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, + {:ok, activity} <- TwitterAPI.unfav(user, id) do render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) end end def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, activity} <- TwitterAPI.repeat(user, id) do + with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)}, + {:ok, activity} <- TwitterAPI.repeat(user, id) do render(conn, ActivityView, "activity.json", %{activity: activity, for: user}) end end @@ -389,4 +394,16 @@ defmodule Pleroma.Web.TwitterAPI.Controller do defp error_json(conn, error_message) do %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!() end + + def errors(conn, {:param_cast, _}) do + conn + |> put_status(400) + |> json("Invalid parameters") + end + + def errors(conn, _) do + conn + |> put_status(500) + |> json("Something went wrong") + end end 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/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/support/factory.ex b/test/support/factory.ex index 5cf456e3c..6c48d390f 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -146,4 +146,15 @@ defmodule Pleroma.Factory do subscribers: [] } end + + def oauth_app_factory do + %Pleroma.Web.OAuth.App{ + client_name: "Some client", + redirect_uris: "https://example.com/callback", + scopes: "read", + website: "https://example.com", + client_id: "aaabbb==", + client_secret: "aaa;/&bbb" + } + 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/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 305f9d0e0..bbf89136b 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -4,7 +4,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do alias Pleroma.Web.ActivityPub.{UserView, ObjectView} alias Pleroma.{Repo, User} alias Pleroma.Activity - alias Pleroma.Web.CommonAPI describe "/users/:nickname" do test "it returns a json representation of the user", %{conn: conn} do 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 == "

Some bio

" 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 2abcf0dfe..566f5acfc 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -505,6 +505,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do assert to_string(activity.id) == id end + + test "returns 500 for a wrong id", %{conn: conn} do + user = insert(:user) + + resp = + conn + |> assign(:user, user) + |> post("/api/v1/statuses/1/favourite") + |> json_response(500) + + assert resp == "Something went wrong" + end end describe "unfavoriting" do @@ -780,6 +792,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/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs new file mode 100644 index 000000000..3a902f128 --- /dev/null +++ b/test/web/oauth/oauth_controller_test.exs @@ -0,0 +1,113 @@ +defmodule Pleroma.Web.OAuth.OAuthControllerTest do + use Pleroma.Web.ConnCase + import Pleroma.Factory + + alias Pleroma.Repo + alias Pleroma.Web.OAuth.{Authorization, Token} + + test "redirects with oauth authorization" do + user = insert(:user) + app = insert(:oauth_app) + + conn = + build_conn() + |> post("/oauth/authorize", %{ + "authorization" => %{ + "name" => user.nickname, + "password" => "test", + "client_id" => app.client_id, + "redirect_uri" => app.redirect_uris, + "state" => "statepassed" + } + }) + + target = redirected_to(conn) + assert target =~ app.redirect_uris + + query = URI.parse(target).query |> URI.query_decoder() |> Map.new() + + assert %{"state" => "statepassed", "code" => code} = query + assert Repo.get_by(Authorization, token: code) + end + + test "issues a token for an all-body request" do + user = insert(:user) + app = insert(:oauth_app) + + {:ok, auth} = Authorization.create_authorization(app, user) + + conn = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "authorization_code", + "code" => auth.token, + "redirect_uri" => app.redirect_uris, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + + assert %{"access_token" => token} = json_response(conn, 200) + assert Repo.get_by(Token, token: token) + end + + test "issues a token for request with HTTP basic auth client credentials" do + user = insert(:user) + app = insert(:oauth_app) + + {:ok, auth} = Authorization.create_authorization(app, user) + + app_encoded = + (URI.encode_www_form(app.client_id) <> ":" <> URI.encode_www_form(app.client_secret)) + |> Base.encode64() + + conn = + build_conn() + |> put_req_header("authorization", "Basic " <> app_encoded) + |> post("/oauth/token", %{ + "grant_type" => "authorization_code", + "code" => auth.token, + "redirect_uri" => app.redirect_uris + }) + + assert %{"access_token" => token} = json_response(conn, 200) + assert Repo.get_by(Token, token: token) + end + + test "rejects token exchange with invalid client credentials" do + user = insert(:user) + app = insert(:oauth_app) + + {:ok, auth} = Authorization.create_authorization(app, user) + + conn = + build_conn() + |> put_req_header("authorization", "Basic JTIxOiVGMCU5RiVBNCVCNwo=") + |> post("/oauth/token", %{ + "grant_type" => "authorization_code", + "code" => auth.token, + "redirect_uri" => app.redirect_uris + }) + + assert resp = json_response(conn, 400) + assert %{"error" => _} = resp + refute Map.has_key?(resp, "access_token") + end + + test "rejects an invalid authorization code" do + app = insert(:oauth_app) + + conn = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "authorization_code", + "code" => "Imobviouslyinvalid", + "redirect_uri" => app.redirect_uris, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + + assert resp = json_response(conn, 400) + assert %{"error" => _} = json_response(conn, 400) + refute Map.has_key?(resp, "access_token") + end +end diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/web/ostatus/ostatus_controller_test.exs index faee4fc3e..d5adf3bf3 100644 --- a/test/web/ostatus/ostatus_controller_test.exs +++ b/test/web/ostatus/ostatus_controller_test.exs @@ -53,11 +53,21 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do conn = conn + |> put_req_header("content-type", "application/atom+xml") |> get("/users/#{user.nickname}/feed.atom") assert response(conn, 200) =~ note_activity.data["object"]["content"] end + test "returns 404 for a missing feed", %{conn: conn} do + conn = + conn + |> put_req_header("content-type", "application/atom+xml") + |> get("/users/nonexisting/feed.atom") + + assert response(conn, 404) + end + test "gets an object", %{conn: conn} do note_activity = insert(:note_activity) user = User.get_by_ap_id(note_activity.data["actor"]) @@ -90,6 +100,16 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do assert response(conn, 404) end + test "404s on nonexisting objects", %{conn: conn} do + url = "/objects/123" + + conn = + conn + |> get(url) + + assert response(conn, 404) + end + test "gets an activity", %{conn: conn} do note_activity = insert(:note_activity) [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) @@ -114,6 +134,16 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do assert response(conn, 404) end + test "404s on nonexistent activities", %{conn: conn} do + url = "/activities/123" + + conn = + conn + |> get(url) + + assert response(conn, 404) + end + test "gets a notice", %{conn: conn} do note_activity = insert(:note_activity) url = "/notice/#{note_activity.id}" @@ -135,4 +165,14 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do assert response(conn, 404) end + + test "404s a nonexisting notice", %{conn: conn} do + url = "/notice/123" + + conn = + conn + |> get(url) + + assert response(conn, 404) + end end diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/web/twitter_api/twitter_api_controller_test.exs index 03e5824a9..68f4331df 100644 --- a/test/web/twitter_api/twitter_api_controller_test.exs +++ b/test/web/twitter_api/twitter_api_controller_test.exs @@ -260,7 +260,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do test "with credentials", %{conn: conn, user: current_user} do other_user = insert(:user) - {:ok, activity} = + {:ok, _activity} = ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user}) conn = @@ -510,6 +510,24 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do assert json_response(conn, 200) end + + test "with credentials, invalid param", %{conn: conn, user: current_user} do + conn = + conn + |> with_credentials(current_user.nickname, "test") + |> post("/api/favorites/create/wrong.json") + + assert json_response(conn, 400) + end + + test "with credentials, invalid activity", %{conn: conn, user: current_user} do + conn = + conn + |> with_credentials(current_user.nickname, "test") + |> post("/api/favorites/create/1.json") + + assert json_response(conn, 500) + end end describe "POST /api/favorites/destroy/:id" do @@ -793,7 +811,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do test "Convert newlines to
in bio", %{conn: conn} do user = insert(:user) - conn = + _conn = conn |> assign(:user, user) |> post("/api/account/update_profile.json", %{ @@ -904,6 +922,8 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do |> post("/api/pleroma/delete_account", %{"password" => "test"}) assert json_response(conn, 200) == %{"status" => "success"} + # Wait a second for the started task to end + :timer.sleep(1000) end end end