diff options
Diffstat (limited to 'lib')
33 files changed, 396 insertions, 247 deletions
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 86409738a..91440b453 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -145,7 +145,7 @@ defmodule Mix.Tasks.Pleroma.Instance do            options,            :uploads_dir,            "What directory should media uploads go in (when using the local uploader)?", -          Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads]) +          Config.get([Pleroma.Uploaders.Local, :uploads])          )          |> Path.expand() @@ -154,7 +154,7 @@ defmodule Mix.Tasks.Pleroma.Instance do            options,            :static_dir,            "What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?", -          Pleroma.Config.get([:instance, :static_dir]) +          Config.get([:instance, :static_dir])          )          |> Path.expand() diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 9615af122..32773d3c9 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -35,7 +35,7 @@ defmodule Pleroma.Application do    # See http://elixir-lang.org/docs/stable/elixir/Application.html    # for more information on OTP Applications    def start(_type, _args) do -    Pleroma.Config.Holder.save_default() +    Config.Holder.save_default()      Pleroma.HTML.compile_scrubbers()      Config.DeprecationWarnings.warn()      Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled() diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index 55f61024e..aa0b2a66b 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Emails.AdminEmail do    alias Pleroma.Config    alias Pleroma.Web.Router.Helpers -  defp instance_config, do: Pleroma.Config.get(:instance) +  defp instance_config, do: Config.get(:instance)    defp instance_name, do: instance_config()[:name]    defp instance_notify_email do @@ -72,6 +72,8 @@ defmodule Pleroma.Emails.AdminEmail do      <p>Reported Account: <a href="#{user_url(account)}">#{account.nickname}</a></p>      #{comment_html}      #{statuses_html} +    <p> +    <a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/reports/index">View Reports in AdminFE</a>      """      new() diff --git a/lib/pleroma/emoji/loader.ex b/lib/pleroma/emoji/loader.ex index 3de2dc762..03a6bca0b 100644 --- a/lib/pleroma/emoji/loader.ex +++ b/lib/pleroma/emoji/loader.ex @@ -108,7 +108,7 @@ defmodule Pleroma.Emoji.Loader do        if File.exists?(emoji_txt) do          load_from_file(emoji_txt, emoji_groups)        else -        extensions = Pleroma.Config.get([:emoji, :pack_extensions]) +        extensions = Config.get([:emoji, :pack_extensions])          Logger.info(            "No emoji.txt found for pack \"#{pack_name}\", assuming all #{ diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 74458c09a..a1f935232 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -17,6 +17,8 @@ defmodule Pleroma.Instances.Instance do    schema "instances" do      field(:host, :string)      field(:unreachable_since, :naive_datetime_usec) +    field(:favicon, :string) +    field(:favicon_updated_at, :naive_datetime)      timestamps()    end @@ -25,7 +27,7 @@ defmodule Pleroma.Instances.Instance do    def changeset(struct, params \\ %{}) do      struct -    |> cast(params, [:host, :unreachable_since]) +    |> cast(params, [:host, :unreachable_since, :favicon, :favicon_updated_at])      |> validate_required([:host])      |> unique_constraint(:host)    end @@ -120,4 +122,48 @@ defmodule Pleroma.Instances.Instance do    end    defp parse_datetime(datetime), do: datetime + +  def get_or_update_favicon(%URI{host: host} = instance_uri) do +    existing_record = Repo.get_by(Instance, %{host: host}) +    now = NaiveDateTime.utc_now() + +    if existing_record && existing_record.favicon_updated_at && +         NaiveDateTime.diff(now, existing_record.favicon_updated_at) < 86_400 do +      existing_record.favicon +    else +      favicon = scrape_favicon(instance_uri) + +      if existing_record do +        existing_record +        |> changeset(%{favicon: favicon, favicon_updated_at: now}) +        |> Repo.update() +      else +        %Instance{} +        |> changeset(%{host: host, favicon: favicon, favicon_updated_at: now}) +        |> Repo.insert() +      end + +      favicon +    end +  end + +  defp scrape_favicon(%URI{} = instance_uri) do +    try do +      with {:ok, %Tesla.Env{body: html}} <- +             Pleroma.HTTP.get(to_string(instance_uri), [{:Accept, "text/html"}]), +           favicon_rel <- +             html +             |> Floki.parse_document!() +             |> Floki.attribute("link[rel=icon]", "href") +             |> List.first(), +           favicon <- URI.merge(instance_uri, favicon_rel) |> to_string(), +           true <- is_binary(favicon) do +        favicon +      else +        _ -> nil +      end +    rescue +      _ -> nil +    end +  end  end diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index fcb2144ae..32bcfcaba 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -497,6 +497,10 @@ defmodule Pleroma.Notification do      end    end +  def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => object_id}}) do +    [object_id] +  end +    def get_potential_receiver_ap_ids(activity) do      []      |> Utils.maybe_notify_to_recipients(activity) diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex index 1420a9611..7d65cf078 100644 --- a/lib/pleroma/plugs/http_security_plug.ex +++ b/lib/pleroma/plugs/http_security_plug.ex @@ -69,10 +69,11 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do      img_src = "img-src 'self' data: blob:"      media_src = "media-src 'self'" +    # Strict multimedia CSP enforcement only when MediaProxy is enabled      {img_src, media_src} =        if Config.get([:media_proxy, :enabled]) &&             !Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do -        sources = get_proxy_and_attachment_sources() +        sources = build_csp_multimedia_source_list()          {[img_src, sources], [media_src, sources]}        else          {[img_src, " https:"], [media_src, " https:"]} @@ -81,14 +82,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do      connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]      connect_src = -      if Pleroma.Config.get(:env) == :dev do +      if Config.get(:env) == :dev do          [connect_src, " http://localhost:3035/"]        else          connect_src        end      script_src = -      if Pleroma.Config.get(:env) == :dev do +      if Config.get(:env) == :dev do          "script-src 'self' 'unsafe-eval'"        else          "script-src 'self'" @@ -107,29 +108,28 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do      |> :erlang.iolist_to_binary()    end -  defp get_proxy_and_attachment_sources do +  defp build_csp_multimedia_source_list do      media_proxy_whitelist =        Enum.reduce(Config.get([:media_proxy, :whitelist]), [], fn host, acc ->          add_source(acc, host)        end) -    media_proxy_base_url = -      if Config.get([:media_proxy, :base_url]), -        do: URI.parse(Config.get([:media_proxy, :base_url])).host +    media_proxy_base_url = build_csp_param(Config.get([:media_proxy, :base_url])) -    upload_base_url = -      if Config.get([Pleroma.Upload, :base_url]), -        do: URI.parse(Config.get([Pleroma.Upload, :base_url])).host +    upload_base_url = build_csp_param(Config.get([Pleroma.Upload, :base_url])) -    s3_endpoint = -      if Config.get([Pleroma.Upload, :uploader]) == Pleroma.Uploaders.S3, -        do: URI.parse(Config.get([Pleroma.Uploaders.S3, :public_endpoint])).host +    s3_endpoint = build_csp_param(Config.get([Pleroma.Uploaders.S3, :public_endpoint])) + +    captcha_method = Config.get([Pleroma.Captcha, :method]) + +    captcha_endpoint = build_csp_param(Config.get([captcha_method, :endpoint]))      []      |> add_source(media_proxy_base_url)      |> add_source(upload_base_url)      |> add_source(s3_endpoint)      |> add_source(media_proxy_whitelist) +    |> add_source(captcha_endpoint)    end    defp add_source(iodata, nil), do: iodata @@ -139,6 +139,16 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do    defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata] +  defp build_csp_param(nil), do: nil + +  defp build_csp_param(url) when is_binary(url) do +    %{host: host, scheme: scheme} = URI.parse(url) + +    if scheme do +      [scheme, "://", host] +    end +  end +    def warn_if_disabled do      unless Config.get([:http_security, :enabled]) do        Logger.warn(" diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index e98332744..0078f9831 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -388,8 +388,8 @@ defmodule Pleroma.User do    defp fix_follower_address(params), do: params    def remote_user_changeset(struct \\ %User{local: false}, params) do -    bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000) -    name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) +    bio_limit = Config.get([:instance, :user_bio_length], 5000) +    name_limit = Config.get([:instance, :user_name_length], 100)      name =        case params[:name] do @@ -448,8 +448,8 @@ defmodule Pleroma.User do    end    def update_changeset(struct, params \\ %{}) do -    bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000) -    name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) +    bio_limit = Config.get([:instance, :user_bio_length], 5000) +    name_limit = Config.get([:instance, :user_name_length], 100)      struct      |> cast( @@ -618,12 +618,12 @@ defmodule Pleroma.User do    def force_password_reset(user), do: update_password_reset_pending(user, true)    def register_changeset(struct, params \\ %{}, opts \\ []) do -    bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000) -    name_limit = Pleroma.Config.get([:instance, :user_name_length], 100) +    bio_limit = Config.get([:instance, :user_bio_length], 5000) +    name_limit = Config.get([:instance, :user_name_length], 100)      need_confirmation? =        if is_nil(opts[:need_confirmation]) do -        Pleroma.Config.get([:instance, :account_activation_required]) +        Config.get([:instance, :account_activation_required])        else          opts[:need_confirmation]        end @@ -644,7 +644,7 @@ defmodule Pleroma.User do      |> validate_confirmation(:password)      |> unique_constraint(:email)      |> unique_constraint(:nickname) -    |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames])) +    |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))      |> validate_format(:nickname, local_nickname_regex())      |> validate_format(:email, @email_regex)      |> validate_length(:bio, max: bio_limit) @@ -659,7 +659,7 @@ defmodule Pleroma.User do    def maybe_validate_required_email(changeset, true), do: changeset    def maybe_validate_required_email(changeset, _) do -    if Pleroma.Config.get([:instance, :account_activation_required]) do +    if Config.get([:instance, :account_activation_required]) do        validate_required(changeset, [:email])      else        changeset @@ -679,7 +679,7 @@ defmodule Pleroma.User do    end    defp autofollow_users(user) do -    candidates = Pleroma.Config.get([:instance, :autofollowed_nicknames]) +    candidates = Config.get([:instance, :autofollowed_nicknames])      autofollowed_users =        User.Query.build(%{nickname: candidates, local: true, deactivated: false}) @@ -706,7 +706,7 @@ defmodule Pleroma.User do    def try_send_confirmation_email(%User{} = user) do      if user.confirmation_pending && -         Pleroma.Config.get([:instance, :account_activation_required]) do +         Config.get([:instance, :account_activation_required]) do        user        |> Pleroma.Emails.UserEmail.account_confirmation_email()        |> Pleroma.Emails.Mailer.deliver_async() @@ -763,7 +763,7 @@ defmodule Pleroma.User do    defdelegate following(user), to: FollowingRelationship    def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do -    deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) +    deny_follow_blocked = Config.get([:user, :deny_follow_blocked])      cond do        followed.deactivated -> @@ -964,7 +964,7 @@ defmodule Pleroma.User do    end    def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do -    restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) +    restrict_to_local = Config.get([:instance, :limit_to_local_content])      cond do        is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) -> @@ -1160,7 +1160,7 @@ defmodule Pleroma.User do    @spec update_follower_count(User.t()) :: {:ok, User.t()}    def update_follower_count(%User{} = user) do -    if user.local or !Pleroma.Config.get([:instance, :external_user_synchronization]) do +    if user.local or !Config.get([:instance, :external_user_synchronization]) do        follower_count = FollowingRelationship.follower_count(user)        user @@ -1173,7 +1173,7 @@ defmodule Pleroma.User do    @spec update_following_count(User.t()) :: {:ok, User.t()}    def update_following_count(%User{local: false} = user) do -    if Pleroma.Config.get([:instance, :external_user_synchronization]) do +    if Config.get([:instance, :external_user_synchronization]) do        {:ok, maybe_fetch_follow_information(user)}      else        {:ok, user} @@ -1260,7 +1260,7 @@ defmodule Pleroma.User do    end    def subscribe(%User{} = subscriber, %User{} = target) do -    deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) +    deny_follow_blocked = Config.get([:user, :deny_follow_blocked])      if blocks?(target, subscriber) and deny_follow_blocked do        {:error, "Could not subscribe: #{target.nickname} is blocking you"} @@ -1543,7 +1543,7 @@ defmodule Pleroma.User do        fn followed_identifier ->          with {:ok, %User{} = followed} <- get_or_fetch(followed_identifier),               {:ok, follower} <- maybe_direct_follow(follower, followed), -             {:ok, _} <- ActivityPub.follow(follower, followed) do +             {:ok, _, _, _} <- CommonAPI.follow(follower, followed) do            followed          else            err -> @@ -1651,7 +1651,7 @@ defmodule Pleroma.User do      Pleroma.HTML.Scrubber.TwitterText    end -  def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy]) +  def html_filter_policy(_), do: Config.get([:markup, :scrub_policy])    def fetch_by_ap_id(ap_id), do: ActivityPub.make_user_from_ap_id(ap_id) @@ -1833,7 +1833,7 @@ defmodule Pleroma.User do    end    defp local_nickname_regex do -    if Pleroma.Config.get([:instance, :extended_nickname_format]) do +    if Config.get([:instance, :extended_nickname_format]) do        @extended_local_nickname_regex      else        @strict_local_nickname_regex @@ -1961,8 +1961,8 @@ defmodule Pleroma.User do    def get_mascot(%{mascot: mascot}) when is_nil(mascot) do      # use instance-default -    config = Pleroma.Config.get([:assets, :mascots]) -    default_mascot = Pleroma.Config.get([:assets, :default_mascot]) +    config = Config.get([:assets, :mascots]) +    default_mascot = Config.get([:assets, :default_mascot])      mascot = Keyword.get(config, default_mascot)      %{ @@ -2057,7 +2057,7 @@ defmodule Pleroma.User do    def validate_fields(changeset, remote? \\ false) do      limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields -    limit = Pleroma.Config.get([:instance, limit_name], 0) +    limit = Config.get([:instance, limit_name], 0)      changeset      |> validate_length(:fields, max: limit) @@ -2071,8 +2071,8 @@ defmodule Pleroma.User do    end    defp valid_field?(%{"name" => name, "value" => value}) do -    name_limit = Pleroma.Config.get([:instance, :account_field_name_length], 255) -    value_limit = Pleroma.Config.get([:instance, :account_field_value_length], 255) +    name_limit = Config.get([:instance, :account_field_name_length], 255) +    value_limit = Config.get([:instance, :account_field_value_length], 255)      is_binary(name) && is_binary(value) && String.length(name) <= name_limit &&        String.length(value) <= value_limit @@ -2082,10 +2082,10 @@ defmodule Pleroma.User do    defp truncate_field(%{"name" => name, "value" => value}) do      {name, _chopped} = -      String.split_at(name, Pleroma.Config.get([:instance, :account_field_name_length], 255)) +      String.split_at(name, Config.get([:instance, :account_field_name_length], 255))      {value, _chopped} = -      String.split_at(value, Pleroma.Config.get([:instance, :account_field_value_length], 255)) +      String.split_at(value, Config.get([:instance, :account_field_value_length], 255))      %{"name" => name, "value" => value}    end @@ -2140,7 +2140,7 @@ defmodule Pleroma.User do    def add_pinnned_activity(user, %Pleroma.Activity{id: id}) do      if id not in user.pinned_activities do -      max_pinned_statuses = Pleroma.Config.get([:instance, :max_pinned_statuses], 0) +      max_pinned_statuses = Config.get([:instance, :max_pinned_statuses], 0)        params = %{pinned_activities: user.pinned_activities ++ [id]}        user diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 8abbef487..1c2908805 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -322,28 +322,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      end    end -  @spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) :: -          {:ok, Activity.t()} | {:error, any()} -  def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do -    with {:ok, result} <- -           Repo.transaction(fn -> do_follow(follower, followed, activity_id, local, opts) end) do -      result -    end -  end - -  defp do_follow(follower, followed, activity_id, local, opts) do -    skip_notify_and_stream = Keyword.get(opts, :skip_notify_and_stream, false) -    data = make_follow_data(follower, followed, activity_id) - -    with {:ok, activity} <- insert(data, local), -         _ <- skip_notify_and_stream || notify_and_stream(activity), -         :ok <- maybe_federate(activity) do -      {:ok, activity} -    else -      {:error, error} -> Repo.rollback(error) -    end -  end -    @spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::            {:ok, Activity.t()} | nil | {:error, any()}    def unfollow(follower, followed, activity_id \\ nil, local \\ true) do diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index cabc28de9..d5f3610ed 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -14,6 +14,19 @@ defmodule Pleroma.Web.ActivityPub.Builder do    require Pleroma.Constants +  @spec follow(User.t(), User.t()) :: {:ok, map(), keyword()} +  def follow(follower, followed) do +    data = %{ +      "id" => Utils.generate_activity_id(), +      "actor" => follower.ap_id, +      "type" => "Follow", +      "object" => followed.ap_id, +      "to" => [followed.ap_id] +    } + +    {:ok, data, []} +  end +    @spec emoji_react(User.t(), Object.t(), String.t()) :: {:ok, map(), keyword()}    def emoji_react(actor, object, emoji) do      with {:ok, data, meta} <- object_action(actor, object) do diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex index b0ccb63c8..a62914135 100644 --- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex @@ -98,7 +98,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do    @impl true    def describe do      mrf_object_age = -      Pleroma.Config.get(:mrf_object_age) +      Config.get(:mrf_object_age)        |> Enum.into(%{})      {:ok, %{mrf_object_age: mrf_object_age}} diff --git a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex index 3092f3272..4fd63106d 100644 --- a/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex +++ b/lib/pleroma/web/activity_pub/mrf/reject_non_public.ex @@ -47,5 +47,5 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do    @impl true    def describe, -    do: {:ok, %{mrf_rejectnonpublic: Pleroma.Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}} +    do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}  end diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 9cea6bcf9..70a2ca053 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -155,7 +155,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do      %{host: actor_host} = URI.parse(actor)      reject_deletes = -      Pleroma.Config.get([:mrf_simple, :reject_deletes]) +      Config.get([:mrf_simple, :reject_deletes])        |> MRF.subdomains_regex()      if MRF.subdomain_match?(reject_deletes, actor_host) do diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index bb6324460..df926829c 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do    alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator +  alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator @@ -25,6 +26,16 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do    @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}    def validate(object, meta) +  def validate(%{"type" => "Follow"} = object, meta) do +    with {:ok, object} <- +           object +           |> FollowValidator.cast_and_validate() +           |> Ecto.Changeset.apply_action(:insert) do +      object = stringify_keys(object) +      {:ok, object, meta} +    end +  end +    def validate(%{"type" => "Block"} = block_activity, meta) do      with {:ok, block_activity} <-             block_activity diff --git a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex new file mode 100644 index 000000000..ca2724616 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex @@ -0,0 +1,44 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do +  use Ecto.Schema + +  alias Pleroma.EctoType.ActivityPub.ObjectValidators + +  import Ecto.Changeset +  import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations + +  @primary_key false + +  embedded_schema do +    field(:id, ObjectValidators.ObjectID, primary_key: true) +    field(:type, :string) +    field(:actor, ObjectValidators.ObjectID) +    field(:to, ObjectValidators.Recipients, default: []) +    field(:cc, ObjectValidators.Recipients, default: []) +    field(:object, ObjectValidators.ObjectID) +    field(:state, :string, default: "pending") +  end + +  def cast_data(data) do +    %__MODULE__{} +    |> cast(data, __schema__(:fields)) +  end + +  def validate_data(cng) do +    cng +    |> validate_required([:id, :type, :actor, :to, :cc, :object]) +    |> validate_inclusion(:type, ["Follow"]) +    |> validate_inclusion(:state, ~w{pending reject accept}) +    |> validate_actor_presence() +    |> validate_actor_presence(field_name: :object) +  end + +  def cast_and_validate(data) do +    data +    |> cast_data +    |> validate_data +  end +end diff --git a/lib/pleroma/web/activity_pub/relay.ex b/lib/pleroma/web/activity_pub/relay.ex index 484178edd..b09764d2b 100644 --- a/lib/pleroma/web/activity_pub/relay.ex +++ b/lib/pleroma/web/activity_pub/relay.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Web.ActivityPub.Relay do    def follow(target_instance) do      with %User{} = local_user <- get_actor(),           {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance), -         {:ok, activity} <- ActivityPub.follow(local_user, target_user) do +         {:ok, _, _, activity} <- CommonAPI.follow(local_user, target_user) do        Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")        {:ok, activity}      else diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 61feeae4d..1d2c296a5 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do    alias Pleroma.Activity.Ir.Topics    alias Pleroma.Chat    alias Pleroma.Chat.MessageReference +  alias Pleroma.FollowingRelationship    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Repo @@ -21,6 +22,69 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do    def handle(object, meta \\ []) +  # Tasks this handle +  # - Follows if possible +  # - Sends a notification +  # - Generates accept or reject if appropriate +  def handle( +        %{ +          data: %{ +            "id" => follow_id, +            "type" => "Follow", +            "object" => followed_user, +            "actor" => following_user +          } +        } = object, +        meta +      ) do +    with %User{} = follower <- User.get_cached_by_ap_id(following_user), +         %User{} = followed <- User.get_cached_by_ap_id(followed_user), +         {_, {:ok, _}, _, _} <- +           {:following, User.follow(follower, followed, :follow_pending), follower, followed} do +      if followed.local && !followed.locked do +        Utils.update_follow_state_for_all(object, "accept") +        FollowingRelationship.update(follower, followed, :follow_accept) +        User.update_follower_count(followed) +        User.update_following_count(follower) + +        %{ +          to: [following_user], +          actor: followed, +          object: follow_id, +          local: true +        } +        |> ActivityPub.accept() +      end +    else +      {:following, {:error, _}, follower, followed} -> +        Utils.update_follow_state_for_all(object, "reject") +        FollowingRelationship.update(follower, followed, :follow_reject) + +        if followed.local do +          %{ +            to: [follower.ap_id], +            actor: followed, +            object: follow_id, +            local: true +          } +          |> ActivityPub.reject() +        end + +      _ -> +        nil +    end + +    {:ok, notifications} = Notification.create_notifications(object, do_send: false) + +    meta = +      meta +      |> add_notifications(notifications) + +    updated_object = Activity.get_by_ap_id(follow_id) + +    {:ok, updated_object, meta} +  end +    # Tasks this handles:    # - Unfollow and block    def handle( @@ -209,14 +273,20 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do      {:ok, object}    end -  def handle_undoing(%{data: %{"type" => "Like"}} = object) do -    with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]), -         {:ok, _} <- Utils.remove_like_from_object(object, liked_object), -         {:ok, _} <- Repo.delete(object) do -      :ok +  defp undo_like(nil, object), do: delete_object(object) + +  defp undo_like(%Object{} = liked_object, object) do +    with {:ok, _} <- Utils.remove_like_from_object(object, liked_object) do +      delete_object(object)      end    end +  def handle_undoing(%{data: %{"type" => "Like"}} = object) do +    object.data["object"] +    |> Object.get_by_ap_id() +    |> undo_like(object) +  end +    def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do      with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]),           {:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object), @@ -246,6 +316,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do    def handle_undoing(object), do: {:error, ["don't know how to handle", object]} +  @spec delete_object(Object.t()) :: :ok | {:error, Ecto.Changeset.t()} +  defp delete_object(object) do +    with {:ok, _} <- Repo.delete(object), do: :ok +  end +    defp send_notifications(meta) do      Keyword.get(meta, :notifications, [])      |> Enum.each(fn notification -> diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 117e930b3..884646ceb 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -530,66 +530,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    end    def handle_incoming( -        %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data, -        _options -      ) do -    with %User{local: true} = followed <- -           User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})), -         {:ok, %User{} = follower} <- -           User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})), -         {:ok, activity} <- -           ActivityPub.follow(follower, followed, id, false, skip_notify_and_stream: true) do -      with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]), -           {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked}, -           {_, false} <- {:user_locked, User.locked?(followed)}, -           {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)}, -           {_, {:ok, _}} <- -             {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")}, -           {:ok, _relationship} <- -             FollowingRelationship.update(follower, followed, :follow_accept) do -        ActivityPub.accept(%{ -          to: [follower.ap_id], -          actor: followed, -          object: data, -          local: true -        }) -      else -        {:user_blocked, true} -> -          {:ok, _} = Utils.update_follow_state_for_all(activity, "reject") -          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject) - -          ActivityPub.reject(%{ -            to: [follower.ap_id], -            actor: followed, -            object: data, -            local: true -          }) - -        {:follow, {:error, _}} -> -          {:ok, _} = Utils.update_follow_state_for_all(activity, "reject") -          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject) - -          ActivityPub.reject(%{ -            to: [follower.ap_id], -            actor: followed, -            object: data, -            local: true -          }) - -        {:user_locked, true} -> -          {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending) -          :noop -      end - -      ActivityPub.notify_and_stream(activity) -      {:ok, activity} -    else -      _e -> -        :error -    end -  end - -  def handle_incoming(          %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => id} = data,          _options        ) do @@ -696,7 +636,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do          %{"type" => type} = data,          _options        ) -      when type in ~w{Update Block} do +      when type in ~w{Update Block Follow} do      with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),           {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do        {:ok, activity} diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 9bde8fc0d..b8c527606 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -203,14 +203,23 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do        security: [%{"oAuth" => ["follow", "write:follows"]}],        description: "Follow the given account",        parameters: [ -        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}, -        Operation.parameter( -          :reblogs, -          :query, -          BooleanLike, -          "Receive this account's reblogs in home timeline? Defaults to true." -        ) +        %Reference{"$ref": "#/components/parameters/accountIdOrNickname"}        ], +      requestBody: +        request_body( +          "Parameters", +          %Schema{ +            type: :object, +            properties: %{ +              reblogs: %Schema{ +                type: :boolean, +                description: "Receive this account's reblogs in home timeline? Defaults to true.", +                default: true +              } +            } +          }, +          required: false +        ),        responses: %{          200 => Operation.response("Relationship", "application/json", AccountRelationship),          400 => Operation.response("Error", "application/json", ApiError), @@ -438,6 +447,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do      }    end +  # TODO: This is actually a token respone, but there's no oauth operation file yet.    defp create_response do      %Schema{        title: "AccountCreateResponse", @@ -446,14 +456,20 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do        properties: %{          token_type: %Schema{type: :string},          access_token: %Schema{type: :string}, -        scope: %Schema{type: :array, items: %Schema{type: :string}}, -        created_at: %Schema{type: :integer, format: :"date-time"} +        refresh_token: %Schema{type: :string}, +        scope: %Schema{type: :string}, +        created_at: %Schema{type: :integer, format: :"date-time"}, +        me: %Schema{type: :string}, +        expires_in: %Schema{type: :integer}        },        example: %{ +        "token_type" => "Bearer",          "access_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzk", +        "refresh_token" => "i9hAVVzGld86Pl5JtLtizKoXVvtTlSCJvwaugCxvZzz",          "created_at" => 1_585_918_714, -        "scope" => ["read", "write", "follow", "push"], -        "token_type" => "Bearer" +        "expires_in" => 600, +        "scope" => "read write follow push", +        "me" => "https://gensokyo.2hu/users/raymoo"        }      }    end diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 84f18f1b6..e6f163cb7 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -102,6 +102,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do              type: :object,              description:                "A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`" +          }, +          favicon: %Schema{ +            type: :string, +            format: :uri, +            nullable: true, +            description: "Favicon image of the user's instance"            }          }        }, diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index fd7149079..4d5b0decf 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -101,10 +101,14 @@ defmodule Pleroma.Web.CommonAPI do    def follow(follower, followed) do      timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout]) -    with {:ok, follower} <- User.maybe_direct_follow(follower, followed), -         {:ok, activity} <- ActivityPub.follow(follower, followed), +    with {:ok, follow_data, _} <- Builder.follow(follower, followed), +         {:ok, activity, _} <- Pipeline.common_pipeline(follow_data, local: true),           {:ok, follower, followed} <- User.wait_and_refresh(timeout, follower, followed) do -      {:ok, follower, followed, activity} +      if activity.data["state"] == "reject" do +        {:error, :rejected} +      else +        {:ok, follower, followed, activity} +      end      end    end diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 15594125f..9c38b73eb 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -143,7 +143,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do    def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data)        when is_list(options) do -    limits = Pleroma.Config.get([:instance, :poll_limits]) +    limits = Config.get([:instance, :poll_limits])      with :ok <- validate_poll_expiration(expires_in, limits),           :ok <- validate_poll_options_amount(options, limits), @@ -502,7 +502,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do    def make_report_content_html(nil), do: {:ok, {nil, [], []}}    def make_report_content_html(comment) do -    max_size = Pleroma.Config.get([:instance, :max_report_comment_size], 1000) +    max_size = Config.get([:instance, :max_report_comment_size], 1000)      if String.length(comment) <= max_size do        {:ok, format_input(comment, "text/plain")} @@ -564,7 +564,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do    end    def validate_character_limit(full_payload, _attachments) do -    limit = Pleroma.Config.get([:instance, :limit]) +    limit = Config.get([:instance, :limit])      length = String.length(full_payload)      if length <= limit do diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index d4532258c..743442855 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -27,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do    alias Pleroma.Web.MastodonAPI.MastodonAPI    alias Pleroma.Web.MastodonAPI.MastodonAPIController    alias Pleroma.Web.MastodonAPI.StatusView +  alias Pleroma.Web.OAuth.OAuthView    alias Pleroma.Web.OAuth.Token    alias Pleroma.Web.TwitterAPI.TwitterAPI @@ -101,12 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do           :ok <- TwitterAPI.validate_captcha(app, params),           {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),           {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do -      json(conn, %{ -        token_type: "Bearer", -        access_token: token.token, -        scope: app.scopes, -        created_at: Token.Utils.format_created_at(token) -      }) +      json(conn, OAuthView.render("token.json", %{user: user, token: token}))      else        {:error, error} -> json_response(conn, :bad_request, %{error: error})      end @@ -353,7 +349,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do      {:error, "Can not follow yourself"}    end -  def follow(%{assigns: %{user: follower, account: followed}} = conn, params) do +  def follow(%{body_params: params, assigns: %{user: follower, account: followed}} = conn, _) do      with {:ok, follower} <- MastodonAPI.follow(follower, followed, params) do        render(conn, "relationship.json", user: follower, target: followed)      else diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 3f4c53437..12be530c9 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -201,15 +201,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do    @doc "DELETE /api/v1/statuses/:id"    def delete(%{assigns: %{user: user}} = conn, %{id: id}) do      with %Activity{} = activity <- Activity.get_by_id_with_object(id), -         render <- -           try_render(conn, "show.json", -             activity: activity, -             for: user, -             with_direct_conversation_id: true, -             with_source: true -           ),           {:ok, %Activity{}} <- CommonAPI.delete(id, user) do -      render +      try_render(conn, "show.json", +        activity: activity, +        for: user, +        with_direct_conversation_id: true, +        with_source: true +      )      else        _e -> {:error, :not_found}      end diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 4bdd46d7e..ab7b1d6aa 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -88,21 +88,20 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do      )    end +  defp restrict_unauthenticated?(true = _local_only) do +    Pleroma.Config.get([:restrict_unauthenticated, :timelines, :local]) +  end + +  defp restrict_unauthenticated?(_) do +    Pleroma.Config.get([:restrict_unauthenticated, :timelines, :federated]) +  end +    # GET /api/v1/timelines/public    def public(%{assigns: %{user: user}} = conn, params) do      local_only = params[:local] -    cfg_key = -      if local_only do -        :local -      else -        :federated -      end - -    restrict? = Pleroma.Config.get([:restrict_unauthenticated, :timelines, cfg_key]) - -    if restrict? and is_nil(user) do -      render_error(conn, :unauthorized, "authorization required for timeline view") +    if is_nil(user) and restrict_unauthenticated?(local_only) do +      fail_on_bad_auth(conn)      else        activities =          params @@ -123,6 +122,10 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do      end    end +  defp fail_on_bad_auth(conn) do +    render_error(conn, :unauthorized, "authorization required for timeline view") +  end +    defp hashtag_fetching(params, user, local_only) do      tags =        [params[:tag], params[:any]] @@ -157,15 +160,20 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do    # GET /api/v1/timelines/tag/:tag    def hashtag(%{assigns: %{user: user}} = conn, params) do      local_only = params[:local] -    activities = hashtag_fetching(params, user, local_only) -    conn -    |> add_link_headers(activities, %{"local" => local_only}) -    |> render("index.json", -      activities: activities, -      for: user, -      as: :activity -    ) +    if is_nil(user) and restrict_unauthenticated?(local_only) do +      fail_on_bad_auth(conn) +    else +      activities = hashtag_fetching(params, user, local_only) + +      conn +      |> add_link_headers(activities, %{"local" => local_only}) +      |> render("index.json", +        activities: activities, +        for: user, +        as: :activity +      ) +    end    end    # GET /api/v1/timelines/list/:list_id diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index a6e64b4ab..2feba4778 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -204,6 +204,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do          %{}        end +    favicon = +      if Pleroma.Config.get([:instances_favicons, :enabled]) do +        user +        |> Map.get(:ap_id, "") +        |> URI.parse() +        |> URI.merge("/") +        |> Pleroma.Instances.Instance.get_or_update_favicon() +        |> MediaProxy.url() +      else +        nil +      end +      %{        id: to_string(user.id),        username: username_from_nickname(user.nickname), @@ -245,7 +257,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do          hide_favorites: user.hide_favorites,          relationship: relationship,          skip_thread_containment: user.skip_thread_containment, -        background_image: image_url(user.background) |> MediaProxy.url() +        background_image: image_url(user.background) |> MediaProxy.url(), +        favicon: favicon        }      }      |> maybe_put_role(user, opts[:for]) diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 077fabe47..6f35826da 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -106,7 +106,7 @@ defmodule Pleroma.Web.MediaProxy do    def build_url(sig_base64, url_base64, filename \\ nil) do      [ -      Pleroma.Config.get([:media_proxy, :base_url], Web.base_url()), +      Config.get([:media_proxy, :base_url], Web.base_url()),        "proxy",        sig_base64,        url_base64, diff --git a/lib/pleroma/web/oauth/mfa_controller.ex b/lib/pleroma/web/oauth/mfa_controller.ex index 53e19f82e..f102c93e7 100644 --- a/lib/pleroma/web/oauth/mfa_controller.ex +++ b/lib/pleroma/web/oauth/mfa_controller.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.OAuth.MFAController do    alias Pleroma.Web.Auth.TOTPAuthenticator    alias Pleroma.Web.OAuth.MFAView, as: View    alias Pleroma.Web.OAuth.OAuthController +  alias Pleroma.Web.OAuth.OAuthView    alias Pleroma.Web.OAuth.Token    plug(:fetch_session when action in [:show, :verify]) @@ -74,7 +75,7 @@ defmodule Pleroma.Web.OAuth.MFAController do           {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),           {:ok, _} <- validates_challenge(user, params),           {:ok, token} <- Token.exchange_token(app, auth) do -      json(conn, Token.Response.build(user, token)) +      json(conn, OAuthView.render("token.json", %{user: user, token: token}))      else        _error ->          conn diff --git a/lib/pleroma/web/oauth/mfa_view.ex b/lib/pleroma/web/oauth/mfa_view.ex index 41d5578dc..5d87db268 100644 --- a/lib/pleroma/web/oauth/mfa_view.ex +++ b/lib/pleroma/web/oauth/mfa_view.ex @@ -5,4 +5,13 @@  defmodule Pleroma.Web.OAuth.MFAView do    use Pleroma.Web, :view    import Phoenix.HTML.Form +  alias Pleroma.MFA + +  def render("mfa_response.json", %{token: token, user: user}) do +    %{ +      error: "mfa_required", +      mfa_token: token.token, +      supported_challenge_types: MFA.supported_methods(user) +    } +  end  end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index c557778ca..7683589cf 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -17,6 +17,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do    alias Pleroma.Web.OAuth.App    alias Pleroma.Web.OAuth.Authorization    alias Pleroma.Web.OAuth.MFAController +  alias Pleroma.Web.OAuth.MFAView +  alias Pleroma.Web.OAuth.OAuthView    alias Pleroma.Web.OAuth.Scopes    alias Pleroma.Web.OAuth.Token    alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken @@ -233,9 +235,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do      with {:ok, app} <- Token.Utils.fetch_app(conn),           {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),           {:ok, token} <- RefreshToken.grant(token) do -      response_attrs = %{created_at: Token.Utils.format_created_at(token)} - -      json(conn, Token.Response.build(user, token, response_attrs)) +      json(conn, OAuthView.render("token.json", %{user: user, token: token}))      else        _error -> render_invalid_credentials_error(conn)      end @@ -247,9 +247,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do           {:ok, auth} <- Authorization.get_by_token(app, fixed_token),           %User{} = user <- User.get_cached_by_id(auth.user_id),           {:ok, token} <- Token.exchange_token(app, auth) do -      response_attrs = %{created_at: Token.Utils.format_created_at(token)} - -      json(conn, Token.Response.build(user, token, response_attrs)) +      json(conn, OAuthView.render("token.json", %{user: user, token: token}))      else        error ->          handle_token_exchange_error(conn, error) @@ -267,7 +265,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do           {:ok, auth} <- Authorization.create_authorization(app, user, scopes),           {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},           {:ok, token} <- Token.exchange_token(app, auth) do -      json(conn, Token.Response.build(user, token)) +      json(conn, OAuthView.render("token.json", %{user: user, token: token}))      else        error ->          handle_token_exchange_error(conn, error) @@ -290,7 +288,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do      with {:ok, app} <- Token.Utils.fetch_app(conn),           {:ok, auth} <- Authorization.create_authorization(app, %User{}),           {:ok, token} <- Token.exchange_token(app, auth) do -      json(conn, Token.Response.build_for_client_credentials(token)) +      json(conn, OAuthView.render("token.json", %{token: token}))      else        _error ->          handle_token_exchange_error(conn, :invalid_credentails) @@ -548,7 +546,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do    defp build_and_response_mfa_token(user, auth) do      with {:ok, token} <- MFA.Token.create_token(user, auth) do -      Token.Response.build_for_mfa_token(user, token) +      MFAView.render("mfa_response.json", %{token: token, user: user})      end    end diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/oauth/oauth_view.ex index 94ddaf913..f55247ebd 100644 --- a/lib/pleroma/web/oauth/oauth_view.ex +++ b/lib/pleroma/web/oauth/oauth_view.ex @@ -5,4 +5,26 @@  defmodule Pleroma.Web.OAuth.OAuthView do    use Pleroma.Web, :view    import Phoenix.HTML.Form + +  alias Pleroma.Web.OAuth.Token.Utils + +  def render("token.json", %{token: token} = opts) do +    response = %{ +      token_type: "Bearer", +      access_token: token.token, +      refresh_token: token.refresh_token, +      expires_in: expires_in(), +      scope: Enum.join(token.scopes, " "), +      created_at: Utils.format_created_at(token) +    } + +    if user = opts[:user] do +      response +      |> Map.put(:me, user.ap_id) +    else +      response +    end +  end + +  defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)  end diff --git a/lib/pleroma/web/oauth/token/response.ex b/lib/pleroma/web/oauth/token/response.ex deleted file mode 100644 index 0e72c31e9..000000000 --- a/lib/pleroma/web/oauth/token/response.ex +++ /dev/null @@ -1,45 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.OAuth.Token.Response do -  @moduledoc false - -  alias Pleroma.MFA -  alias Pleroma.User -  alias Pleroma.Web.OAuth.Token.Utils - -  @doc false -  def build(%User{} = user, token, opts \\ %{}) do -    %{ -      token_type: "Bearer", -      access_token: token.token, -      refresh_token: token.refresh_token, -      expires_in: expires_in(), -      scope: Enum.join(token.scopes, " "), -      me: user.ap_id -    } -    |> Map.merge(opts) -  end - -  def build_for_client_credentials(token) do -    %{ -      token_type: "Bearer", -      access_token: token.token, -      refresh_token: token.refresh_token, -      created_at: Utils.format_created_at(token), -      expires_in: expires_in(), -      scope: Enum.join(token.scopes, " ") -    } -  end - -  def build_for_mfa_token(user, mfa_token) do -    %{ -      error: "mfa_required", -      mfa_token: mfa_token.token, -      supported_challenge_types: MFA.supported_methods(user) -    } -  end - -  defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600) -end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 8314e75b4..f02c4075c 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -83,7 +83,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    def frontend_configurations(conn, _params) do      config = -      Pleroma.Config.get(:frontend_configurations, %{}) +      Config.get(:frontend_configurations, %{})        |> Enum.into(%{})      json(conn, config)  | 
