diff options
26 files changed, 517 insertions, 119 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/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 aecf96c36..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 diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a5e42d9d0..43e96fe37 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -564,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 @@ -573,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) @@ -580,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 12f9b5f7c..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 @@ -933,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/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/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/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 == "<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 eaae0b54f..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 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 <br> 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 | 
