diff options
| author | Sachin Joshi <satchin.joshi@gmail.com> | 2019-06-01 11:42:37 +0545 | 
|---|---|---|
| committer | Sachin Joshi <satchin.joshi@gmail.com> | 2019-06-01 11:42:37 +0545 | 
| commit | ad5263c647aea65dbeb4c329825671895e0a8863 (patch) | |
| tree | 6217011a3fd2ea943498ce296de07cefe1206b87 /lib | |
| parent | 5534d4c67675901ab272ee47355ad43dfae99033 (diff) | |
| parent | f1890d2cacfa09dd22b06a8d041c04dbeba9e138 (diff) | |
| download | pleroma-ad5263c647aea65dbeb4c329825671895e0a8863.tar.gz pleroma-ad5263c647aea65dbeb4c329825671895e0a8863.zip | |
Merge remote-tracking branch 'upstream/develop' into admin-create-users
Diffstat (limited to 'lib')
65 files changed, 1163 insertions, 400 deletions
| diff --git a/lib/healthcheck.ex b/lib/healthcheck.ex index 646fb3b9d..32aafc210 100644 --- a/lib/healthcheck.ex +++ b/lib/healthcheck.ex @@ -29,13 +29,13 @@ defmodule Pleroma.Healthcheck do    end    defp assign_db_info(healthcheck) do -    database = Application.get_env(:pleroma, Repo)[:database] +    database = Pleroma.Config.get([Repo, :database])      query =        "select state, count(pid) from pg_stat_activity where datname = '#{database}' group by state;"      result = Repo.query!(query) -    pool_size = Application.get_env(:pleroma, Repo)[:pool_size] +    pool_size = Pleroma.Config.get([Repo, :pool_size])      db_info =        Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states -> diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index ab9a3a7ff..4d480ac3f 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -4,6 +4,10 @@  defmodule Mix.Tasks.Pleroma.Database do    alias Mix.Tasks.Pleroma.Common +  alias Pleroma.Conversation +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.User    require Logger    use Mix.Task @@ -19,6 +23,18 @@ defmodule Mix.Tasks.Pleroma.Database do      Options:      - `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references + +  ## Prune old objects from the database + +      mix pleroma.database prune_objects + +  ## Create a conversation for all existing DMs. Can be safely re-run. + +      mix pleroma.database bump_all_conversations + +  ## Remove duplicated items from following and update followers count for all users + +      mix pleroma.database update_users_following_followers_counts    """    def run(["remove_embedded_objects" | args]) do      {options, [], []} = @@ -32,7 +48,7 @@ defmodule Mix.Tasks.Pleroma.Database do      Common.start_pleroma()      Logger.info("Removing embedded objects") -    Pleroma.Repo.query!( +    Repo.query!(        "update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;",        [],        timeout: :infinity @@ -41,7 +57,62 @@ defmodule Mix.Tasks.Pleroma.Database do      if Keyword.get(options, :vacuum) do        Logger.info("Runnning VACUUM FULL") -      Pleroma.Repo.query!( +      Repo.query!( +        "vacuum full;", +        [], +        timeout: :infinity +      ) +    end +  end + +  def run(["bump_all_conversations"]) do +    Common.start_pleroma() +    Conversation.bump_for_all_activities() +  end + +  def run(["update_users_following_followers_counts"]) do +    Common.start_pleroma() + +    users = Repo.all(User) +    Enum.each(users, &User.remove_duplicated_following/1) +    Enum.each(users, &User.update_follower_count/1) +  end + +  def run(["prune_objects" | args]) do +    import Ecto.Query + +    {options, [], []} = +      OptionParser.parse( +        args, +        strict: [ +          vacuum: :boolean +        ] +      ) + +    Common.start_pleroma() + +    deadline = Pleroma.Config.get([:instance, :remote_post_retention_days]) + +    Logger.info("Pruning objects older than #{deadline} days") + +    time_deadline = +      NaiveDateTime.utc_now() +      |> NaiveDateTime.add(-(deadline * 86_400)) + +    public = "https://www.w3.org/ns/activitystreams#Public" + +    from(o in Object, +      where: fragment("?->'to' \\? ? OR ?->'cc' \\? ?", o.data, ^public, o.data, ^public), +      where: o.inserted_at < ^time_deadline, +      where: +        fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host()) +    ) +    |> Repo.delete_all(timeout: :infinity) + +    if Keyword.get(options, :vacuum) do +      Logger.info("Runnning VACUUM FULL") + +      Repo.query!(          "vacuum full;",          [],          timeout: :infinity diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index d130ff8c9..25fc40ea7 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -77,6 +77,10 @@ defmodule Mix.Tasks.Pleroma.User do    ## Delete tags from a user.        mix pleroma.user untag NICKNAME TAGS + +  ## Toggle confirmation of the user's account. + +      mix pleroma.user toggle_confirmed NICKNAME    """    def run(["new", nickname, email | rest]) do      {options, [], []} = @@ -388,6 +392,21 @@ defmodule Mix.Tasks.Pleroma.User do      end    end +  def run(["toggle_confirmed", nickname]) do +    Common.start_pleroma() + +    with %User{} = user <- User.get_cached_by_nickname(nickname) do +      {:ok, user} = User.toggle_confirmation(user) + +      message = if user.info.confirmation_pending, do: "needs", else: "doesn't need" + +      Mix.shell().info("#{nickname} #{message} confirmation.") +    else +      _ -> +        Mix.shell().error("No local user #{nickname}") +    end +  end +    defp set_moderator(user, value) do      info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value}) diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 4a0919478..99589590c 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Activity do    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Repo +  alias Pleroma.ThreadMute    alias Pleroma.User    import Ecto.Changeset @@ -37,6 +38,7 @@ defmodule Pleroma.Activity do      field(:local, :boolean, default: true)      field(:actor, :string)      field(:recipients, {:array, :string}, default: []) +    field(:thread_muted?, :boolean, virtual: true)      # This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark      has_one(:bookmark, Bookmark)      has_many(:notifications, Notification, on_delete: :delete_all) @@ -60,21 +62,24 @@ defmodule Pleroma.Activity do      timestamps()    end -  def with_preloaded_object(query) do -    query -    |> join( -      :inner, -      [activity], -      o in Object, +  def with_joined_object(query) do +    join(query, :inner, [activity], o in Object,        on:          fragment(            "(?->>'id') = COALESCE(?->'object'->>'id', ?->>'object')",            o.data,            activity.data,            activity.data -        ) +        ), +      as: :object      ) -    |> preload([activity, object], object: object) +  end + +  def with_preloaded_object(query) do +    query +    |> has_named_binding?(:object) +    |> if(do: query, else: with_joined_object(query)) +    |> preload([activity, object: object], object: object)    end    def with_preloaded_bookmark(query, %User{} = user) do @@ -87,6 +92,16 @@ defmodule Pleroma.Activity do    def with_preloaded_bookmark(query, _), do: query +  def with_set_thread_muted_field(query, %User{} = user) do +    from([a] in query, +      left_join: tm in ThreadMute, +      on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data), +      select: %Activity{a | thread_muted?: not is_nil(tm.id)} +    ) +  end + +  def with_set_thread_muted_field(query, _), do: query +    def get_by_ap_id(ap_id) do      Repo.one(        from( @@ -108,7 +123,7 @@ defmodule Pleroma.Activity do    def change(struct, params \\ %{}) do      struct -    |> cast(params, [:data]) +    |> cast(params, [:data, :recipients])      |> validate_required([:data])      |> unique_constraint(:ap_id, name: :activities_unique_apid_index)    end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index eeb415084..76df3945e 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -110,6 +110,7 @@ defmodule Pleroma.Application do          hackney_pool_children() ++          [            worker(Pleroma.Web.Federator.RetryQueue, []), +          worker(Pleroma.Web.OAuth.Token.CleanWorker, []),            worker(Pleroma.Stats, []),            worker(Task, [&Pleroma.Web.Push.init/0], restart: :temporary, id: :web_push_init),            worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary, id: :federator_init) @@ -131,19 +132,22 @@ defmodule Pleroma.Application do    defp setup_instrumenters do      require Prometheus.Registry -    :ok = -      :telemetry.attach( -        "prometheus-ecto", -        [:pleroma, :repo, :query], -        &Pleroma.Repo.Instrumenter.handle_event/4, -        %{} -      ) +    if Application.get_env(:prometheus, Pleroma.Repo.Instrumenter) do +      :ok = +        :telemetry.attach( +          "prometheus-ecto", +          [:pleroma, :repo, :query], +          &Pleroma.Repo.Instrumenter.handle_event/4, +          %{} +        ) + +      Pleroma.Repo.Instrumenter.setup() +    end      Prometheus.Registry.register_collector(:prometheus_process_collector)      Pleroma.Web.Endpoint.MetricsExporter.setup()      Pleroma.Web.Endpoint.PipelineInstrumenter.setup()      Pleroma.Web.Endpoint.Instrumenter.setup() -    Pleroma.Repo.Instrumenter.setup()    end    def enabled_hackney_pools do diff --git a/lib/pleroma/bbs/handler.ex b/lib/pleroma/bbs/handler.ex index 106fe5d18..f34be961f 100644 --- a/lib/pleroma/bbs/handler.ex +++ b/lib/pleroma/bbs/handler.ex @@ -95,7 +95,6 @@ defmodule Pleroma.BBS.Handler do      activities =        [user.ap_id | user.following]        |> ActivityPub.fetch_activities(params) -      |> ActivityPub.contain_timeline(user)      Enum.each(activities, fn activity ->        puts_activity(activity) diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex index 0db195988..238c1acf2 100644 --- a/lib/pleroma/conversation.ex +++ b/lib/pleroma/conversation.ex @@ -45,7 +45,7 @@ defmodule Pleroma.Conversation do    2. Create a participation for all the people involved who don't have one already    3. Bump all relevant participations to 'unread'    """ -  def create_or_bump_for(activity) do +  def create_or_bump_for(activity, opts \\ []) do      with true <- Pleroma.Web.ActivityPub.Visibility.is_direct?(activity),           "Create" <- activity.data["type"],           object <- Pleroma.Object.normalize(activity), @@ -58,7 +58,7 @@ defmodule Pleroma.Conversation do        participations =          Enum.map(users, fn user ->            {:ok, participation} = -            Participation.create_for_user_and_conversation(user, conversation) +            Participation.create_for_user_and_conversation(user, conversation, opts)            participation          end) @@ -72,4 +72,21 @@ defmodule Pleroma.Conversation do        e -> {:error, e}      end    end + +  @doc """ +  This is only meant to be run by a mix task. It creates conversations/participations for all direct messages in the database. +  """ +  def bump_for_all_activities do +    stream = +      Pleroma.Web.ActivityPub.ActivityPub.fetch_direct_messages_query() +      |> Repo.stream() + +    Repo.transaction( +      fn -> +        stream +        |> Enum.each(fn a -> create_or_bump_for(a, read: true) end) +      end, +      timeout: :infinity +    ) +  end  end diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex index 61021fb18..2a11f9069 100644 --- a/lib/pleroma/conversation/participation.ex +++ b/lib/pleroma/conversation/participation.ex @@ -22,15 +22,17 @@ defmodule Pleroma.Conversation.Participation do    def creation_cng(struct, params) do      struct -    |> cast(params, [:user_id, :conversation_id]) +    |> cast(params, [:user_id, :conversation_id, :read])      |> validate_required([:user_id, :conversation_id])    end -  def create_for_user_and_conversation(user, conversation) do +  def create_for_user_and_conversation(user, conversation, opts \\ []) do +    read = !!opts[:read] +      %__MODULE__{} -    |> creation_cng(%{user_id: user.id, conversation_id: conversation.id}) +    |> creation_cng(%{user_id: user.id, conversation_id: conversation.id, read: read})      |> Repo.insert( -      on_conflict: [set: [read: false, updated_at: NaiveDateTime.utc_now()]], +      on_conflict: [set: [read: read, updated_at: NaiveDateTime.utc_now()]],        returning: true,        conflict_target: [:user_id, :conversation_id]      ) diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index df0f72f96..d0e254362 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Emails.AdminEmail do        end      statuses_html = -      if length(statuses) > 0 do +      if is_list(statuses) && length(statuses) > 0 do          statuses_list_html =            statuses            |> Enum.map(fn diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 6390cce4c..7d12eff7f 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Emoji do    @ets __MODULE__.Ets    @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}] -  @groups Application.get_env(:pleroma, :emoji)[:groups] +  @groups Pleroma.Config.get([:emoji, :groups])    @doc false    def start_link do @@ -112,7 +112,7 @@ defmodule Pleroma.Emoji do      # Compat thing for old custom emoji handling & default emoji,      # it should run even if there are no emoji packs -    shortcode_globs = Application.get_env(:pleroma, :emoji)[:shortcode_globs] || [] +    shortcode_globs = Pleroma.Config.get([:emoji, :shortcode_globs], [])      emojis =        (load_from_file("config/emoji.txt") ++ diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex index 79efc29f0..90457dadf 100644 --- a/lib/pleroma/filter.ex +++ b/lib/pleroma/filter.ex @@ -38,7 +38,8 @@ defmodule Pleroma.Filter do      query =        from(          f in Pleroma.Filter, -        where: f.user_id == ^user_id +        where: f.user_id == ^user_id, +        order_by: [desc: :id]        )      Repo.all(query) diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 3d7c36d21..607843a5b 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -8,7 +8,7 @@ defmodule Pleroma.Formatter do    alias Pleroma.User    alias Pleroma.Web.MediaProxy -  @safe_mention_regex ~r/^(\s*(?<mentions>@.+?\s+)+)(?<rest>.*)/ +  @safe_mention_regex ~r/^(\s*(?<mentions>(@.+?\s+){1,})+)(?<rest>.*)/s    @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui    @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index d1da746de..e5e78ee4f 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -104,7 +104,6 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do    paragraphs, breaks and links are allowed through the filter.    """ -  @markup Application.get_env(:pleroma, :markup)    @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])    require HtmlSanitizeEx.Scrubber.Meta @@ -142,9 +141,7 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do    Meta.allow_tag_with_these_attributes("span", [])    # allow inline images for custom emoji -  @allow_inline_images Keyword.get(@markup, :allow_inline_images) - -  if @allow_inline_images do +  if Pleroma.Config.get([:markup, :allow_inline_images]) do      # restrict img tags to http/https only, because of MediaProxy.      Meta.allow_tag_with_uri_attributes("img", ["src"], ["http", "https"]) @@ -168,7 +165,6 @@ defmodule Pleroma.HTML.Scrubber.Default do    # credo:disable-for-previous-line    # No idea how to fix this one… -  @markup Application.get_env(:pleroma, :markup)    @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], [])    Meta.remove_cdata_sections_before_scrub() @@ -213,7 +209,7 @@ defmodule Pleroma.HTML.Scrubber.Default do    Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"])    Meta.allow_tag_with_these_attributes("span", []) -  @allow_inline_images Keyword.get(@markup, :allow_inline_images) +  @allow_inline_images Pleroma.Config.get([:markup, :allow_inline_images])    if @allow_inline_images do      # restrict img tags to http/https only, because of MediaProxy. @@ -228,9 +224,7 @@ defmodule Pleroma.HTML.Scrubber.Default do      ])    end -  @allow_tables Keyword.get(@markup, :allow_tables) - -  if @allow_tables do +  if Pleroma.Config.get([:markup, :allow_tables]) do      Meta.allow_tag_with_these_attributes("table", [])      Meta.allow_tag_with_these_attributes("tbody", [])      Meta.allow_tag_with_these_attributes("td", []) @@ -239,9 +233,7 @@ defmodule Pleroma.HTML.Scrubber.Default do      Meta.allow_tag_with_these_attributes("tr", [])    end -  @allow_headings Keyword.get(@markup, :allow_headings) - -  if @allow_headings do +  if Pleroma.Config.get([:markup, :allow_headings]) do      Meta.allow_tag_with_these_attributes("h1", [])      Meta.allow_tag_with_these_attributes("h2", [])      Meta.allow_tag_with_these_attributes("h3", []) @@ -249,9 +241,7 @@ defmodule Pleroma.HTML.Scrubber.Default do      Meta.allow_tag_with_these_attributes("h5", [])    end -  @allow_fonts Keyword.get(@markup, :allow_fonts) - -  if @allow_fonts do +  if Pleroma.Config.get([:markup, :allow_fonts]) do      Meta.allow_tag_with_these_attributes("font", ["face"])    end diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex index c0173465a..c216cdcb1 100644 --- a/lib/pleroma/http/connection.ex +++ b/lib/pleroma/http/connection.ex @@ -8,7 +8,7 @@ defmodule Pleroma.HTTP.Connection do    """    @hackney_options [ -    connect_timeout: 2_000, +    connect_timeout: 10_000,      recv_timeout: 20_000,      follow_redirect: true,      pool: :federation @@ -32,9 +32,11 @@ defmodule Pleroma.HTTP.Connection do    defp hackney_options(opts) do      options = Keyword.get(opts, :adapter, [])      adapter_options = Pleroma.Config.get([:http, :adapter], []) +    proxy_url = Pleroma.Config.get([:http, :proxy_url], nil)      @hackney_options      |> Keyword.merge(adapter_options)      |> Keyword.merge(options) +    |> Keyword.merge(proxy: proxy_url)    end  end diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex index c5f720bc9..c96ee7353 100644 --- a/lib/pleroma/http/http.ex +++ b/lib/pleroma/http/http.ex @@ -65,12 +65,9 @@ defmodule Pleroma.HTTP do    end    def process_request_options(options) do -    config = Application.get_env(:pleroma, :http, []) -    proxy = Keyword.get(config, :proxy_url, nil) - -    case proxy do +    case Pleroma.Config.get([:http, :proxy_url]) do        nil -> options -      _ -> options ++ [proxy: proxy] +      proxy -> options ++ [proxy: proxy]      end    end diff --git a/lib/pleroma/http/request_builder.ex b/lib/pleroma/http/request_builder.ex index 5f2cff2c0..e23457999 100644 --- a/lib/pleroma/http/request_builder.ex +++ b/lib/pleroma/http/request_builder.ex @@ -45,8 +45,15 @@ defmodule Pleroma.HTTP.RequestBuilder do    Add headers to the request    """    @spec headers(map(), list(tuple)) :: map() -  def headers(request, h) do -    Map.put_new(request, :headers, h) +  def headers(request, header_list) do +    header_list = +      if Pleroma.Config.get([:http, :send_user_agent]) do +        header_list ++ [{"User-Agent", Pleroma.Application.user_agent()}] +      else +        header_list +      end + +    Map.put_new(request, :headers, header_list)    end    @doc """ diff --git a/lib/pleroma/keys.ex b/lib/pleroma/keys.ex new file mode 100644 index 000000000..b7bc7a4da --- /dev/null +++ b/lib/pleroma/keys.ex @@ -0,0 +1,44 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Keys do +  # Native generation of RSA keys is only available since OTP 20+ and in default build conditions +  # We try at compile time to generate natively an RSA key otherwise we fallback on the old way. +  try do +    _ = :public_key.generate_key({:rsa, 2048, 65_537}) + +    def generate_rsa_pem do +      key = :public_key.generate_key({:rsa, 2048, 65_537}) +      entry = :public_key.pem_entry_encode(:RSAPrivateKey, key) +      pem = :public_key.pem_encode([entry]) |> String.trim_trailing() +      {:ok, pem} +    end +  rescue +    _ -> +      def generate_rsa_pem do +        port = Port.open({:spawn, "openssl genrsa"}, [:binary]) + +        {:ok, pem} = +          receive do +            {^port, {:data, pem}} -> {:ok, pem} +          end + +        Port.close(port) + +        if Regex.match?(~r/RSA PRIVATE KEY/, pem) do +          {:ok, pem} +        else +          :error +        end +      end +  end + +  def keys_from_pem(pem) do +    [private_key_code] = :public_key.pem_decode(pem) +    private_key = :public_key.pem_entry_decode(private_key_code) +    {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key +    public_key = {:RSAPublicKey, modulus, exponent} +    {:ok, private_key, public_key} +  end +end diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 740d687a3..cc6fc9c5d 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -130,6 +130,13 @@ defmodule Pleroma.Object do      end    end +  def prune(%Object{data: %{"id" => id}} = object) do +    with {:ok, object} <- Repo.delete(object), +         {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do +      {:ok, object} +    end +  end +    def set_cache(%Object{data: %{"id" => ap_id}} = object) do      Cachex.put(:object_cache, "object:#{ap_id}", object)      {:ok, object} diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 8d4bcc95e..ca980c629 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -1,4 +1,5 @@  defmodule Pleroma.Object.Fetcher do +  alias Pleroma.HTTP    alias Pleroma.Object    alias Pleroma.Object.Containment    alias Pleroma.Web.ActivityPub.Transmogrifier @@ -6,7 +7,18 @@ defmodule Pleroma.Object.Fetcher do    require Logger -  @httpoison Application.get_env(:pleroma, :httpoison) +  defp reinject_object(data) do +    Logger.debug("Reinjecting object #{data["id"]}") + +    with data <- Transmogrifier.fix_object(data), +         {:ok, object} <- Object.create(data) do +      {:ok, object} +    else +      e -> +        Logger.error("Error while processing object: #{inspect(e)}") +        {:error, e} +    end +  end    # TODO:    # This will create a Create activity, which we need internally at the moment. @@ -26,12 +38,17 @@ defmodule Pleroma.Object.Fetcher do               "object" => data             },             :ok <- Containment.contain_origin(id, params), -           {:ok, activity} <- Transmogrifier.handle_incoming(params) do -        {:ok, Object.normalize(activity, false)} +           {:ok, activity} <- Transmogrifier.handle_incoming(params), +           {:object, _data, %Object{} = object} <- +             {:object, data, Object.normalize(activity, false)} do +        {:ok, object}        else          {:error, {:reject, nil}} ->            {:reject, nil} +        {:object, data, nil} -> +          reinject_object(data) +          object = %Object{} ->            {:ok, object} @@ -60,7 +77,7 @@ defmodule Pleroma.Object.Fetcher do      with true <- String.starts_with?(id, "http"),           {:ok, %{body: body, status: code}} when code in 200..299 <- -           @httpoison.get( +           HTTP.get(               id,               [{:Accept, "application/activity+json"}]             ), diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex index effc154bf..4dc4e9279 100644 --- a/lib/pleroma/plugs/federating_plug.ex +++ b/lib/pleroma/plugs/federating_plug.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Web.FederatingPlug do    end    def call(conn, _opts) do -    if Keyword.get(Application.get_env(:pleroma, :instance), :federating) do +    if Pleroma.Config.get([:instance, :federating]) do        conn      else        conn diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex index a476f1d49..485ddfbc7 100644 --- a/lib/pleroma/plugs/http_security_plug.ex +++ b/lib/pleroma/plugs/http_security_plug.ex @@ -20,8 +20,9 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do    defp headers do      referrer_policy = Config.get([:http_security, :referrer_policy]) +    report_uri = Config.get([:http_security, :report_uri]) -    [ +    headers = [        {"x-xss-protection", "1; mode=block"},        {"x-permitted-cross-domain-policies", "none"},        {"x-frame-options", "DENY"}, @@ -30,12 +31,27 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do        {"x-download-options", "noopen"},        {"content-security-policy", csp_string() <> ";"}      ] + +    if report_uri do +      report_group = %{ +        "group" => "csp-endpoint", +        "max-age" => 10_886_400, +        "endpoints" => [ +          %{"url" => report_uri} +        ] +      } + +      headers ++ [{"reply-to", Jason.encode!(report_group)}] +    else +      headers +    end    end    defp csp_string do      scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]      static_url = Pleroma.Web.Endpoint.static_url()      websocket_url = Pleroma.Web.Endpoint.websocket_url() +    report_uri = Config.get([:http_security, :report_uri])      connect_src = "connect-src 'self' #{static_url} #{websocket_url}" @@ -53,7 +69,7 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do          "script-src 'self'"        end -    [ +    main_part = [        "default-src 'none'",        "base-uri 'self'",        "frame-ancestors 'none'", @@ -63,11 +79,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do        "font-src 'self'",        "manifest-src 'self'",        connect_src, -      script_src, -      if scheme == "https" do -        "upgrade-insecure-requests" -      end +      script_src      ] + +    report = if report_uri, do: ["report-uri #{report_uri}; report-to csp-endpoint"], else: [] + +    insecure = if scheme == "https", do: ["upgrade-insecure-requests"], else: [] + +    (main_part ++ report ++ insecure)      |> Enum.join("; ")    end diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index a3f177fec..983e156f5 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -3,6 +3,8 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.ReverseProxy do +  alias Pleroma.HTTP +    @keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++                        ~w(if-unmodified-since if-none-match if-range range)    @resp_cache_headers ~w(etag date last-modified cache-control) @@ -59,8 +61,7 @@ defmodule Pleroma.ReverseProxy do    * `http`: options for [hackney](https://github.com/benoitc/hackney).    """ -  @hackney Application.get_env(:pleroma, :hackney, :hackney) -  @httpoison Application.get_env(:pleroma, :httpoison, HTTPoison) +  @hackney Pleroma.Config.get(:hackney, :hackney)    @default_hackney_options [] @@ -97,7 +98,7 @@ defmodule Pleroma.ReverseProxy do      hackney_opts =        @default_hackney_options        |> Keyword.merge(Keyword.get(opts, :http, [])) -      |> @httpoison.process_request_options() +      |> HTTP.process_request_options()      req_headers = build_req_headers(conn.req_headers, opts) diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index b7ecf00a0..1a4d54c62 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -5,11 +5,10 @@  defmodule Pleroma.Signature do    @behaviour HTTPSignatures.Adapter +  alias Pleroma.Keys    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Utils -  alias Pleroma.Web.Salmon -  alias Pleroma.Web.WebFinger    def fetch_public_key(conn) do      with actor_id <- Utils.get_ap_id(conn.params["actor"]), @@ -33,8 +32,8 @@ defmodule Pleroma.Signature do    end    def sign(%User{} = user, headers) do -    with {:ok, %{info: %{keys: keys}}} <- WebFinger.ensure_keys_present(user), -         {:ok, private_key, _} <- Salmon.keys_from_pem(keys) do +    with {:ok, %{info: %{keys: keys}}} <- User.ensure_keys_present(user), +         {:ok, private_key, _} <- Keys.keys_from_pem(keys) do        HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)      end    end diff --git a/lib/pleroma/uploaders/mdii.ex b/lib/pleroma/uploaders/mdii.ex index 190ed9f3a..237544337 100644 --- a/lib/pleroma/uploaders/mdii.ex +++ b/lib/pleroma/uploaders/mdii.ex @@ -4,11 +4,10 @@  defmodule Pleroma.Uploaders.MDII do    alias Pleroma.Config +  alias Pleroma.HTTP    @behaviour Pleroma.Uploaders.Uploader -  @httpoison Application.get_env(:pleroma, :httpoison) -    # MDII-hosted images are never passed through the MediaPlug; only local media.    # Delegate to Pleroma.Uploaders.Local    def get_file(file) do @@ -25,7 +24,7 @@ defmodule Pleroma.Uploaders.MDII do      query = "#{cgi}?#{extension}"      with {:ok, %{status: 200, body: body}} <- -           @httpoison.post(query, file_data, [], adapter: [pool: :default]) do +           HTTP.post(query, file_data, [], adapter: [pool: :default]) do        remote_file_name = String.split(body) |> List.first()        public_url = "#{files}/#{remote_file_name}.#{extension}"        {:ok, {:url, public_url}} diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 722e8ff6b..6abcb7288 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -10,6 +10,7 @@ defmodule Pleroma.User do    alias Comeonin.Pbkdf2    alias Pleroma.Activity +  alias Pleroma.Keys    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Registration @@ -55,7 +56,7 @@ defmodule Pleroma.User do      field(:last_refreshed_at, :naive_datetime_usec)      has_many(:notifications, Notification)      has_many(:registrations, Registration) -    embeds_one(:info, Pleroma.User.Info) +    embeds_one(:info, User.Info)      timestamps()    end @@ -166,7 +167,7 @@ defmodule Pleroma.User do    def update_changeset(struct, params \\ %{}) do      struct -    |> cast(params, [:bio, :name, :avatar]) +    |> cast(params, [:bio, :name, :avatar, :following])      |> unique_constraint(:nickname)      |> validate_format(:nickname, local_nickname_regex())      |> validate_length(:bio, max: 5000) @@ -233,7 +234,7 @@ defmodule Pleroma.User do        |> validate_confirmation(:password)        |> unique_constraint(:email)        |> unique_constraint(:nickname) -      |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames])) +      |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))        |> validate_format(:nickname, local_nickname_regex())        |> validate_format(:email, @email_regex)        |> validate_length(:bio, max: 1000) @@ -284,7 +285,7 @@ defmodule Pleroma.User do    def post_register_action(%User{} = user) do      with {:ok, user} <- autofollow_users(user),           {:ok, user} <- set_cache(user), -         {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user), +         {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),           {:ok, _} <- try_send_confirmation_email(user) do        {:ok, user}      end @@ -371,9 +372,7 @@ defmodule Pleroma.User do    end    def follow(%User{} = follower, %User{info: info} = followed) do -    user_config = Application.get_env(:pleroma, :user) -    deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked) - +    deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])      ap_followers = followed.follower_address      cond do @@ -715,6 +714,18 @@ defmodule Pleroma.User do      end    end +  def remove_duplicated_following(%User{following: following} = user) do +    uniq_following = Enum.uniq(following) + +    if length(following) == length(uniq_following) do +      {:ok, user} +    else +      user +      |> update_changeset(%{following: uniq_following}) +      |> update_and_set_cache() +    end +  end +    @spec get_users_from_set([String.t()], boolean()) :: [User.t()]    def get_users_from_set(ap_ids, local_only \\ true) do      criteria = %{ap_id: ap_ids, deactivated: false} @@ -753,7 +764,7 @@ defmodule Pleroma.User do      from(s in subquery(boost_search_rank_query(distinct_query, for_user)),        order_by: [desc: s.search_rank], -      limit: 20 +      limit: 40      )    end @@ -1138,7 +1149,6 @@ defmodule Pleroma.User do      stream =        ap_id        |> Activity.query_by_actor() -      |> Activity.with_preloaded_object()        |> Repo.stream()      Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity) @@ -1384,4 +1394,57 @@ defmodule Pleroma.User do    def showing_reblogs?(%User{} = user, %User{} = target) do      target.ap_id not in user.info.muted_reblogs    end + +  @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()} +  def toggle_confirmation(%User{} = user) do +    need_confirmation? = !user.info.confirmation_pending + +    info_changeset = +      User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?) + +    user +    |> change() +    |> put_embed(:info, info_changeset) +    |> update_and_set_cache() +  end + +  def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do +    mascot +  end + +  def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do +    # use instance-default +    config = Pleroma.Config.get([:assets, :mascots]) +    default_mascot = Pleroma.Config.get([:assets, :default_mascot]) +    mascot = Keyword.get(config, default_mascot) + +    %{ +      "id" => "default-mascot", +      "url" => mascot[:url], +      "preview_url" => mascot[:url], +      "pleroma" => %{ +        "mime_type" => mascot[:mime_type] +      } +    } +  end + +  def ensure_keys_present(user) do +    info = user.info + +    if info.keys do +      {:ok, user} +    else +      {:ok, pem} = Keys.generate_rsa_pem() + +      info_cng = +        info +        |> User.Info.set_keys(pem) + +      cng = +        Ecto.Changeset.change(user) +        |> Ecto.Changeset.put_embed(:info, info_cng) + +      update_and_set_cache(cng) +    end +  end  end diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 5a50ee639..6397e2737 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -43,6 +43,7 @@ defmodule Pleroma.User.Info do      field(:hide_favorites, :boolean, default: true)      field(:pinned_activities, {:array, :string}, default: [])      field(:flavour, :string, default: nil) +    field(:mascot, :map, default: nil)      field(:emoji, {:array, :map}, default: [])      field(:notification_settings, :map, @@ -212,7 +213,7 @@ defmodule Pleroma.User.Info do      ])    end -  @spec confirmation_changeset(Info.t(), keyword()) :: Ecto.Changerset.t() +  @spec confirmation_changeset(Info.t(), keyword()) :: Changeset.t()    def confirmation_changeset(info, opts) do      need_confirmation? = Keyword.get(opts, :need_confirmation) @@ -248,6 +249,14 @@ defmodule Pleroma.User.Info do      |> validate_required([:flavour])    end +  def mascot_update(info, url) do +    params = %{mascot: url} + +    info +    |> cast(params, [:mascot]) +    |> validate_required([:mascot]) +  end +    def set_source_data(info, source_data) do      params = %{source_data: source_data} diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 233fee4fa..8add62406 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -399,16 +399,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    end    def block(blocker, blocked, activity_id \\ nil, local \\ true) do -    ap_config = Application.get_env(:pleroma, :activitypub) -    unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked) -    outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks) +    outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks]) +    unfollow_blocked = Pleroma.Config.get([:activitypub, :unfollow_blocked]) -    with true <- unfollow_blocked do +    if unfollow_blocked do        follow_activity = fetch_latest_follow(blocker, blocked) - -      if follow_activity do -        unfollow(blocker, blocked, nil, local) -      end +      if follow_activity, do: unfollow(blocker, blocked, nil, local)      end      with true <- outgoing_blocks, @@ -540,8 +536,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do              )          ) -      Ecto.Adapters.SQL.to_sql(:all, Repo, query) -        query      else        Logger.error("Could not restrict visibility to #{visibility}") @@ -557,8 +551,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do            fragment("activity_visibility(?, ?, ?) = ?", a.actor, a.recipients, a.data, ^visibility)        ) -    Ecto.Adapters.SQL.to_sql(:all, Repo, query) -      query    end @@ -569,6 +561,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp restrict_visibility(query, _visibility), do: query +  defp restrict_thread_visibility(query, %{"user" => %User{ap_id: ap_id}}) do +    query = +      from( +        a in query, +        where: fragment("thread_visibility(?, (?)->>'id') = true", ^ap_id, a.data) +      ) + +    query +  end + +  defp restrict_thread_visibility(query, _), do: query +    def fetch_user_activities(user, reading_user, params \\ %{}) do      params =        params @@ -645,20 +649,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp restrict_tag(query, _), do: query -  defp restrict_to_cc(query, recipients_to, recipients_cc) do -    from( -      activity in query, -      where: -        fragment( -          "(?->'to' \\?| ?) or (?->'cc' \\?| ?)", -          activity.data, -          ^recipients_to, -          activity.data, -          ^recipients_cc -        ) -    ) -  end -    defp restrict_recipients(query, [], _user), do: query    defp restrict_recipients(query, recipients, nil) do @@ -695,6 +685,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp restrict_type(query, _), do: query +  defp restrict_state(query, %{"state" => state}) do +    from(activity in query, where: fragment("?->>'state' = ?", activity.data, ^state)) +  end + +  defp restrict_state(query, _), do: query +    defp restrict_favorited_by(query, %{"favorited_by" => ap_id}) do      from(        activity in query, @@ -750,8 +746,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      blocks = info.blocks || []      domain_blocks = info.domain_blocks || [] +    query = +      if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query) +      from( -      activity in query, +      [activity, object: o] in query,        where: fragment("not (? = ANY(?))", activity.actor, ^blocks),        where: fragment("not (? && ?)", activity.recipients, ^blocks),        where: @@ -761,7 +760,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do            activity.data,            ^blocks          ), -      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks) +      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks), +      where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks)      )    end @@ -816,6 +816,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      |> Activity.with_preloaded_bookmark(opts["user"])    end +  defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query + +  defp maybe_set_thread_muted_field(query, opts) do +    query +    |> Activity.with_set_thread_muted_field(opts["user"]) +  end +    defp maybe_order(query, %{order: :desc}) do      query      |> order_by(desc: :id) @@ -834,6 +841,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      base_query      |> maybe_preload_objects(opts)      |> maybe_preload_bookmarks(opts) +    |> maybe_set_thread_muted_field(opts)      |> maybe_order(opts)      |> restrict_recipients(recipients, opts["user"])      |> restrict_tag(opts) @@ -843,11 +851,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      |> restrict_local(opts)      |> restrict_actor(opts)      |> restrict_type(opts) +    |> restrict_state(opts)      |> restrict_favorited_by(opts)      |> restrict_blocked(opts)      |> restrict_muted(opts)      |> restrict_media(opts)      |> restrict_visibility(opts) +    |> restrict_thread_visibility(opts)      |> restrict_replies(opts)      |> restrict_reblogs(opts)      |> restrict_pinned(opts) @@ -861,9 +871,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      |> Enum.reverse()    end -  def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do +  def fetch_activities_bounded_query(query, recipients, recipients_with_public) do +    from(activity in query, +      where: +        fragment("? && ?", activity.recipients, ^recipients) or +          (fragment("? && ?", activity.recipients, ^recipients_with_public) and +             "https://www.w3.org/ns/activitystreams#Public" in activity.recipients) +    ) +  end + +  def fetch_activities_bounded(recipients, recipients_with_public, opts \\ %{}) do      fetch_activities_query([], opts) -    |> restrict_to_cc(recipients_to, recipients_cc) +    |> fetch_activities_bounded_query(recipients, recipients_with_public)      |> Pagination.fetch_paginated(opts)      |> Enum.reverse()    end @@ -881,7 +900,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      end    end -  def user_data_from_user_object(data) do +  defp object_to_user_data(data) do      avatar =        data["icon"]["url"] &&          %{ @@ -928,9 +947,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      {:ok, user_data}    end +  def user_data_from_user_object(data) do +    with {:ok, data} <- MRF.filter(data), +         {:ok, data} <- object_to_user_data(data) do +      {:ok, data} +    else +      e -> {:error, e} +    end +  end +    def fetch_and_prepare_user_from_ap_id(ap_id) do -    with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do -      user_data_from_user_object(data) +    with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id), +         {:ok, data} <- user_data_from_user_object(data) do +      {:ok, data}      else        e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")      end @@ -966,11 +995,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      contain_broken_threads(activity, user)    end -  # do post-processing on a timeline -  def contain_timeline(timeline, user) do -    timeline -    |> Enum.filter(fn activity -> -      contain_activity(activity, user) -    end) +  def fetch_direct_messages_query do +    Activity +    |> restrict_type(%{"type" => "Create"}) +    |> restrict_visibility(%{visibility: "direct"}) +    |> order_by([activity], asc: activity.id)    end  end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index c967ab7a9..0182bda46 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    plug(:relay_active? when action in [:relay])    def relay_active?(conn, _) do -    if Keyword.get(Application.get_env(:pleroma, :instance), :allow_relay) do +    if Pleroma.Config.get([:instance, :allow_relay]) do        conn      else        conn @@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    def user(conn, %{"nickname" => nickname}) do      with %User{} = user <- User.get_cached_by_nickname(nickname), -         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do +         {:ok, user} <- User.ensure_keys_present(user) do        conn        |> put_resp_header("content-type", "application/activity+json")        |> json(UserView.render("user.json", %{user: user})) @@ -106,7 +106,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    def following(conn, %{"nickname" => nickname, "page" => page}) do      with %User{} = user <- User.get_cached_by_nickname(nickname), -         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do +         {:ok, user} <- User.ensure_keys_present(user) do        {page, _} = Integer.parse(page)        conn @@ -117,7 +117,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    def following(conn, %{"nickname" => nickname}) do      with %User{} = user <- User.get_cached_by_nickname(nickname), -         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do +         {:ok, user} <- User.ensure_keys_present(user) do        conn        |> put_resp_header("content-type", "application/activity+json")        |> json(UserView.render("following.json", %{user: user})) @@ -126,7 +126,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    def followers(conn, %{"nickname" => nickname, "page" => page}) do      with %User{} = user <- User.get_cached_by_nickname(nickname), -         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do +         {:ok, user} <- User.ensure_keys_present(user) do        {page, _} = Integer.parse(page)        conn @@ -137,7 +137,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    def followers(conn, %{"nickname" => nickname}) do      with %User{} = user <- User.get_cached_by_nickname(nickname), -         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do +         {:ok, user} <- User.ensure_keys_present(user) do        conn        |> put_resp_header("content-type", "application/activity+json")        |> json(UserView.render("followers.json", %{user: user})) @@ -146,7 +146,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    def outbox(conn, %{"nickname" => nickname} = params) do      with %User{} = user <- User.get_cached_by_nickname(nickname), -         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do +         {:ok, user} <- User.ensure_keys_present(user) do        conn        |> put_resp_header("content-type", "application/activity+json")        |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]})) @@ -195,7 +195,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    def relay(conn, _params) do      with %User{} = user <- Relay.get_actor(), -         {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do +         {:ok, user} <- User.ensure_keys_present(user) do        conn        |> put_resp_header("content-type", "application/activity+json")        |> json(UserView.render("user.json", %{user: user})) diff --git a/lib/pleroma/web/activity_pub/mrf.ex b/lib/pleroma/web/activity_pub/mrf.ex index 1aaa20050..3bf7955f3 100644 --- a/lib/pleroma/web/activity_pub/mrf.ex +++ b/lib/pleroma/web/activity_pub/mrf.ex @@ -17,9 +17,7 @@ defmodule Pleroma.Web.ActivityPub.MRF do    end    def get_policies do -    Application.get_env(:pleroma, :instance, []) -    |> Keyword.get(:rewrite_policy, []) -    |> get_policies() +    Pleroma.Config.get([:instance, :rewrite_policy], []) |> get_policies()    end    defp get_policies(policy) when is_atom(policy), do: [policy] diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index 2f105700b..433d23c5f 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -48,14 +48,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do           %{host: actor_host} = _actor_info,           %{             "type" => "Create", -           "object" => %{"attachment" => child_attachment} = child_object +           "object" => child_object           } = object -       ) -       when length(child_attachment) > 0 do +       ) do      object =        if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do          tags = (child_object["tag"] || []) ++ ["nsfw"] -        child_object = Map.put(child_object, "tags", tags) +        child_object = Map.put(child_object, "tag", tags)          child_object = Map.put(child_object, "sensitive", true)          Map.put(object, "object", child_object)        else @@ -75,8 +74,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do                 actor_host               ),             user <- User.get_cached_by_ap_id(object["actor"]), -           true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"], -           true <- user.follower_address in object["cc"] do +           true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do          to =            List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++              [user.follower_address] @@ -95,18 +93,63 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do      {:ok, object}    end +  defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do +    if actor_host in Pleroma.Config.get([:mrf_simple, :report_removal]) do +      {:reject, nil} +    else +      {:ok, object} +    end +  end + +  defp check_report_removal(_actor_info, object), do: {:ok, object} + +  defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do +    if actor_host in Pleroma.Config.get([:mrf_simple, :avatar_removal]) do +      {:ok, Map.delete(object, "icon")} +    else +      {:ok, object} +    end +  end + +  defp check_avatar_removal(_actor_info, object), do: {:ok, object} + +  defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do +    if actor_host in Pleroma.Config.get([:mrf_simple, :banner_removal]) do +      {:ok, Map.delete(object, "image")} +    else +      {:ok, object} +    end +  end + +  defp check_banner_removal(_actor_info, object), do: {:ok, object} +    @impl true -  def filter(object) do -    actor_info = URI.parse(object["actor"]) +  def filter(%{"actor" => actor} = object) do +    actor_info = URI.parse(actor)      with {:ok, object} <- check_accept(actor_info, object),           {:ok, object} <- check_reject(actor_info, object),           {:ok, object} <- check_media_removal(actor_info, object),           {:ok, object} <- check_media_nsfw(actor_info, object), -         {:ok, object} <- check_ftl_removal(actor_info, object) do +         {:ok, object} <- check_ftl_removal(actor_info, object), +         {:ok, object} <- check_report_removal(actor_info, object) do +      {:ok, object} +    else +      _e -> {:reject, nil} +    end +  end + +  def filter(%{"id" => actor, "type" => obj_type} = object) +      when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do +    actor_info = URI.parse(actor) + +    with {:ok, object} <- check_avatar_removal(actor_info, object), +         {:ok, object} <- check_banner_removal(actor_info, object) do        {:ok, object}      else        _e -> {:reject, nil}      end    end + +  def filter(object), do: {:ok, object}  end diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex index b52be30e7..6683b8d8e 100644 --- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do      object =        object -      |> Map.put("tags", tags) +      |> Map.put("tag", tags)        |> Map.put("sensitive", true)      message = Map.put(message, "object", object) diff --git a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex b/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex index f5078d818..47663414a 100644 --- a/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex +++ b/lib/pleroma/web/activity_pub/mrf/user_allowlist.ex @@ -19,10 +19,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do    end    @impl true -  def filter(object) do -    actor_info = URI.parse(object["actor"]) +  def filter(%{"actor" => actor} = object) do +    actor_info = URI.parse(actor)      allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], [])      filter_by_list(object, allow_list)    end + +  def filter(object), do: {:ok, object}  end diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 11dba87de..8f1399ce6 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -5,6 +5,7 @@  defmodule Pleroma.Web.ActivityPub.Publisher do    alias Pleroma.Activity    alias Pleroma.Config +  alias Pleroma.HTTP    alias Pleroma.Instances    alias Pleroma.User    alias Pleroma.Web.ActivityPub.Relay @@ -16,8 +17,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do    require Logger -  @httpoison Application.get_env(:pleroma, :httpoison) -    @moduledoc """    ActivityPub outgoing federation module.    """ @@ -63,7 +62,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do      with {:ok, %{status: code}} when code in 200..299 <-             result = -             @httpoison.post( +             HTTP.post(                 inbox,                 json,                 [ diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 508f3532f..d8fa2728d 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    alias Pleroma.Object.Containment    alias Pleroma.Repo    alias Pleroma.User -  alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.ActivityPub.Visibility @@ -94,7 +93,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do        object        |> Utils.determine_explicit_mentions() -    explicit_mentions = explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public"] +    follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address + +    explicit_mentions = +      explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public", follower_collection]      object      |> fix_explicit_addressing(explicit_mentions) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 236d1b4ac..ca8a0844b 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -20,6 +20,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do    require Logger    @supported_object_types ["Article", "Note", "Video", "Page"] +  @supported_report_states ~w(open closed resolved) +  @valid_visibilities ~w(public unlisted private direct)    # Some implementations send the actor URI as the actor field, others send the entire actor object,    # so figure out what the actor's URI is based on what we have. @@ -670,7 +672,8 @@ defmodule Pleroma.Web.ActivityPub.Utils do        "actor" => params.actor.ap_id,        "content" => params.content,        "object" => object, -      "context" => params.context +      "context" => params.context, +      "state" => "open"      }      |> Map.merge(additional)    end @@ -713,4 +716,77 @@ defmodule Pleroma.Web.ActivityPub.Utils do        end      end    end + +  #### Report-related helpers + +  def update_report_state(%Activity{} = activity, state) when state in @supported_report_states do +    with new_data <- Map.put(activity.data, "state", state), +         changeset <- Changeset.change(activity, data: new_data), +         {:ok, activity} <- Repo.update(changeset) do +      {:ok, activity} +    end +  end + +  def update_report_state(_, _), do: {:error, "Unsupported state"} + +  def update_activity_visibility(activity, visibility) when visibility in @valid_visibilities do +    [to, cc, recipients] = +      activity +      |> get_updated_targets(visibility) +      |> Enum.map(&Enum.uniq/1) + +    object_data = +      activity.object.data +      |> Map.put("to", to) +      |> Map.put("cc", cc) + +    {:ok, object} = +      activity.object +      |> Object.change(%{data: object_data}) +      |> Object.update_and_set_cache() + +    activity_data = +      activity.data +      |> Map.put("to", to) +      |> Map.put("cc", cc) + +    activity +    |> Map.put(:object, object) +    |> Activity.change(%{data: activity_data, recipients: recipients}) +    |> Repo.update() +  end + +  def update_activity_visibility(_, _), do: {:error, "Unsupported visibility"} + +  defp get_updated_targets( +         %Activity{data: %{"to" => to} = data, recipients: recipients}, +         visibility +       ) do +    cc = Map.get(data, "cc", []) +    follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address +    public = "https://www.w3.org/ns/activitystreams#Public" + +    case visibility do +      "public" -> +        to = [public | List.delete(to, follower_address)] +        cc = [follower_address | List.delete(cc, public)] +        recipients = [public | recipients] +        [to, cc, recipients] + +      "private" -> +        to = [follower_address | List.delete(to, public)] +        cc = List.delete(cc, public) +        recipients = List.delete(recipients, public) +        [to, cc, recipients] + +      "unlisted" -> +        to = [follower_address | List.delete(to, public)] +        cc = [public | List.delete(cc, follower_address)] +        recipients = recipients ++ [follower_address, public] +        [to, cc, recipients] + +      _ -> +        [to, cc, recipients] +    end +  end  end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 1254fdf6c..327e0e05b 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -5,6 +5,7 @@  defmodule Pleroma.Web.ActivityPub.UserView do    use Pleroma.Web, :view +  alias Pleroma.Keys    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub @@ -12,8 +13,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.Endpoint    alias Pleroma.Web.Router.Helpers -  alias Pleroma.Web.Salmon -  alias Pleroma.Web.WebFinger    import Ecto.Query @@ -34,8 +33,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do    # the instance itself is not a Person, but instead an Application    def render("user.json", %{user: %{nickname: nil} = user}) do -    {:ok, user} = WebFinger.ensure_keys_present(user) -    {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys) +    {:ok, user} = User.ensure_keys_present(user) +    {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)      public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)      public_key = :public_key.pem_encode([public_key]) @@ -62,8 +61,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do    end    def render("user.json", %{user: user}) do -    {:ok, user} = WebFinger.ensure_keys_present(user) -    {:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys) +    {:ok, user} = User.ensure_keys_present(user) +    {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)      public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)      public_key = :public_key.pem_encode([public_key]) diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index b38ee0442..93b50ee47 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -1,6 +1,7 @@  defmodule Pleroma.Web.ActivityPub.Visibility do    alias Pleroma.Activity    alias Pleroma.Object +  alias Pleroma.Repo    alias Pleroma.User    def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false @@ -13,11 +14,12 @@ defmodule Pleroma.Web.ActivityPub.Visibility do    end    def is_private?(activity) do -    unless is_public?(activity) do -      follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address -      Enum.any?(activity.data["to"], &(&1 == follower_address)) +    with false <- is_public?(activity), +         %User{follower_address: follower_address} <- +           User.get_cached_by_ap_id(activity.data["actor"]) do +      follower_address in activity.data["to"]      else -      false +      _ -> false      end    end @@ -38,25 +40,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do      visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y))    end -  # guard -  def entire_thread_visible_for_user?(nil, _user), do: false +  def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do +    {:ok, %{rows: [[result]]}} = +      Ecto.Adapters.SQL.query(Repo, "SELECT thread_visibility($1, $2)", [ +        user.ap_id, +        activity.data["id"] +      ]) -  # XXX: Probably even more inefficient than the previous implementation intended to be a placeholder untill https://git.pleroma.social/pleroma/pleroma/merge_requests/971 is in develop -  # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength - -  def entire_thread_visible_for_user?( -        %Activity{} = tail, -        # %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail, -        user -      ) do -    case Object.normalize(tail) do -      %{data: %{"inReplyTo" => parent_id}} when is_binary(parent_id) -> -        parent = Activity.get_in_reply_to_activity(tail) -        visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user) - -      _ -> -        visible_for_user?(tail, user) -    end +    result    end    def get_visibility(object) do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 60fd4e571..479fd5829 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -4,11 +4,16 @@  defmodule Pleroma.Web.AdminAPI.AdminAPIController do    use Pleroma.Web, :controller +  alias Pleroma.Activity    alias Pleroma.User    alias Pleroma.UserInviteToken +  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Relay    alias Pleroma.Web.AdminAPI.AccountView +  alias Pleroma.Web.AdminAPI.ReportView    alias Pleroma.Web.AdminAPI.Search +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.MastodonAPI.StatusView    import Pleroma.Web.ControllerHelper, only: [json_response: 3] @@ -315,12 +320,88 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      |> json(token.token)    end +  def list_reports(conn, params) do +    params = +      params +      |> Map.put("type", "Flag") +      |> Map.put("skip_preload", true) + +    reports = +      [] +      |> ActivityPub.fetch_activities(params) +      |> Enum.reverse() + +    conn +    |> put_view(ReportView) +    |> render("index.json", %{reports: reports}) +  end + +  def report_show(conn, %{"id" => id}) do +    with %Activity{} = report <- Activity.get_by_id(id) do +      conn +      |> put_view(ReportView) +      |> render("show.json", %{report: report}) +    else +      _ -> {:error, :not_found} +    end +  end + +  def report_update_state(conn, %{"id" => id, "state" => state}) do +    with {:ok, report} <- CommonAPI.update_report_state(id, state) do +      conn +      |> put_view(ReportView) +      |> render("show.json", %{report: report}) +    end +  end + +  def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do +    with false <- is_nil(params["status"]), +         %Activity{} <- Activity.get_by_id(id) do +      params = +        params +        |> Map.put("in_reply_to_status_id", id) +        |> Map.put("visibility", "direct") + +      {:ok, activity} = CommonAPI.post(user, params) + +      conn +      |> put_view(StatusView) +      |> render("status.json", %{activity: activity}) +    else +      true -> +        {:param_cast, nil} + +      nil -> +        {:error, :not_found} +    end +  end + +  def status_update(conn, %{"id" => id} = params) do +    with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do +      conn +      |> put_view(StatusView) +      |> render("status.json", %{activity: activity}) +    end +  end + +  def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do +    with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do +      json(conn, %{}) +    end +  end +    def errors(conn, {:error, :not_found}) do      conn      |> put_status(404)      |> json("Not found")    end +  def errors(conn, {:error, reason}) do +    conn +    |> put_status(400) +    |> json(reason) +  end +    def errors(conn, {:param_cast, _}) do      conn      |> put_status(400) diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex new file mode 100644 index 000000000..47a73dc7e --- /dev/null +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ReportView do +  use Pleroma.Web, :view +  alias Pleroma.Activity +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI.Utils +  alias Pleroma.Web.MastodonAPI.AccountView +  alias Pleroma.Web.MastodonAPI.StatusView + +  def render("index.json", %{reports: reports}) do +    %{ +      reports: render_many(reports, __MODULE__, "show.json", as: :report) +    } +  end + +  def render("show.json", %{report: report}) do +    user = User.get_cached_by_ap_id(report.data["actor"]) +    created_at = Utils.to_masto_date(report.data["published"]) + +    [account_ap_id | status_ap_ids] = report.data["object"] +    account = User.get_cached_by_ap_id(account_ap_id) + +    statuses = +      Enum.map(status_ap_ids, fn ap_id -> +        Activity.get_by_ap_id_with_object(ap_id) +      end) + +    %{ +      id: report.id, +      account: AccountView.render("account.json", %{user: account}), +      actor: AccountView.render("account.json", %{user: user}), +      content: report.data["content"], +      created_at: created_at, +      statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}), +      state: report.data["state"] +    } +  end +end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 29c4c1014..5a312d673 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -71,6 +71,9 @@ defmodule Pleroma.Web.CommonAPI do           {:ok, _} <- unpin(activity_id, user),           {:ok, delete} <- ActivityPub.delete(object) do        {:ok, delete} +    else +      _ -> +        {:error, "Could not delete"}      end    end @@ -154,6 +157,7 @@ defmodule Pleroma.Web.CommonAPI do           {to, cc} <- to_for_user_and_mentions(user, mentions, in_reply_to, visibility),           context <- make_context(in_reply_to),           cw <- data["spoiler_text"] || "", +         sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),           full_payload <- String.trim(status <> cw),           length when length in 1..limit <- String.length(full_payload),           object <- @@ -166,7 +170,8 @@ defmodule Pleroma.Web.CommonAPI do               in_reply_to,               tags,               cw, -             cc +             cc, +             sensitive             ),           object <-             Map.put( @@ -197,7 +202,7 @@ defmodule Pleroma.Web.CommonAPI do      user =        with emoji <- emoji_from_profile(user),             source_data <- (user.info.source_data || %{}) |> Map.put("tag", emoji), -           info_cng <- Pleroma.User.Info.set_source_data(user.info, source_data), +           info_cng <- User.Info.set_source_data(user.info, source_data),             change <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),             {:ok, user} <- User.update_and_set_cache(change) do          user @@ -230,7 +235,7 @@ defmodule Pleroma.Web.CommonAPI do           } = activity <- get_by_id_or_ap_id(id_or_ap_id),           true <- Enum.member?(object_to, "https://www.w3.org/ns/activitystreams#Public"),           %{valid?: true} = info_changeset <- -           Pleroma.User.Info.add_pinnned_activity(user.info, activity), +           User.Info.add_pinnned_activity(user.info, activity),           changeset <-             Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),           {:ok, _user} <- User.update_and_set_cache(changeset) do @@ -247,7 +252,7 @@ defmodule Pleroma.Web.CommonAPI do    def unpin(id_or_ap_id, user) do      with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),           %{valid?: true} = info_changeset <- -           Pleroma.User.Info.remove_pinnned_activity(user.info, activity), +           User.Info.remove_pinnned_activity(user.info, activity),           changeset <-             Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),           {:ok, _user} <- User.update_and_set_cache(changeset) do @@ -315,6 +320,60 @@ defmodule Pleroma.Web.CommonAPI do      end    end +  def update_report_state(activity_id, state) do +    with %Activity{} = activity <- Activity.get_by_id(activity_id), +         {:ok, activity} <- Utils.update_report_state(activity, state) do +      {:ok, activity} +    else +      nil -> +        {:error, :not_found} + +      {:error, reason} -> +        {:error, reason} + +      _ -> +        {:error, "Could not update state"} +    end +  end + +  def update_activity_scope(activity_id, opts \\ %{}) do +    with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), +         {:ok, activity} <- toggle_sensitive(activity, opts), +         {:ok, activity} <- set_visibility(activity, opts) do +      {:ok, activity} +    else +      nil -> +        {:error, :not_found} + +      {:error, reason} -> +        {:error, reason} +    end +  end + +  defp toggle_sensitive(activity, %{"sensitive" => sensitive}) when sensitive in ~w(true false) do +    toggle_sensitive(activity, %{"sensitive" => String.to_existing_atom(sensitive)}) +  end + +  defp toggle_sensitive(%Activity{object: object} = activity, %{"sensitive" => sensitive}) +       when is_boolean(sensitive) do +    new_data = Map.put(object.data, "sensitive", sensitive) + +    {:ok, object} = +      object +      |> Object.change(%{data: new_data}) +      |> Object.update_and_set_cache() + +    {:ok, Map.put(activity, :object, object)} +  end + +  defp toggle_sensitive(activity, _), do: {:ok, activity} + +  defp set_visibility(activity, %{"visibility" => visibility}) do +    Utils.update_activity_visibility(activity, visibility) +  end + +  defp set_visibility(activity, _), do: {:ok, activity} +    def hide_reblogs(user, muted) do      ap_id = muted.ap_id diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 1dfe50b40..d93c0d46e 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -223,7 +223,8 @@ defmodule Pleroma.Web.CommonAPI.Utils do          in_reply_to,          tags,          cw \\ nil, -        cc \\ [] +        cc \\ [], +        sensitive \\ false        ) do      object = %{        "type" => "Note", @@ -231,19 +232,18 @@ defmodule Pleroma.Web.CommonAPI.Utils do        "cc" => cc,        "content" => content_html,        "summary" => cw, +      "sensitive" => !Enum.member?(["false", "False", "0", false], sensitive),        "context" => context,        "attachment" => attachments,        "actor" => actor,        "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()      } -    if in_reply_to do -      in_reply_to_object = Object.normalize(in_reply_to) - -      object -      |> Map.put("inReplyTo", in_reply_to_object.data["id"]) +    with false <- is_nil(in_reply_to), +         %Object{} = in_reply_to_object <- Object.normalize(in_reply_to) do +      Map.put(object, "inReplyTo", in_reply_to_object.data["id"])      else -      object +      _ -> object      end    end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 9ef30e885..bd76e4295 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -16,17 +16,32 @@ defmodule Pleroma.Web.Endpoint do    plug(Pleroma.Plugs.UploadedMedia) +  @static_cache_control "public, no-cache" +    # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files    # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well -  plug(Pleroma.Plugs.InstanceStatic, at: "/") +  # Cache-control headers are duplicated in case we turn off etags in the future +  plug(Pleroma.Plugs.InstanceStatic, +    at: "/", +    gzip: true, +    cache_control_for_etags: @static_cache_control, +    headers: %{ +      "cache-control" => @static_cache_control +    } +  )    plug(      Plug.Static,      at: "/",      from: :pleroma,      only: -      ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc) +      ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc),      # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength +    gzip: true, +    cache_control_for_etags: @static_cache_control, +    headers: %{ +      "cache-control" => @static_cache_control +    }    )    plug(Plug.Static.IndexHtml, at: "/pleroma/admin/") @@ -51,7 +66,7 @@ defmodule Pleroma.Web.Endpoint do      parsers: [:urlencoded, :multipart, :json],      pass: ["*/*"],      json_decoder: Jason, -    length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit), +    length: Pleroma.Config.get([:instance, :upload_limit]),      body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}    ) diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index 169fdf4dc..f4c9fe284 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -11,14 +11,11 @@ defmodule Pleroma.Web.Federator do    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.Federator.Publisher    alias Pleroma.Web.Federator.RetryQueue -  alias Pleroma.Web.WebFinger +  alias Pleroma.Web.OStatus    alias Pleroma.Web.Websub    require Logger -  @websub Application.get_env(:pleroma, :websub) -  @ostatus Application.get_env(:pleroma, :ostatus) -    def init do      # 1 minute      Process.sleep(1000 * 60) @@ -77,9 +74,8 @@ defmodule Pleroma.Web.Federator do    def perform(:publish, activity) do      Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) -    with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do -      {:ok, actor} = WebFinger.ensure_keys_present(actor) - +    with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]), +         {:ok, actor} <- User.ensure_keys_present(actor) do        Publisher.publish(actor, activity)      end    end @@ -89,12 +85,12 @@ defmodule Pleroma.Web.Federator do        "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})"      end) -    @websub.verify(websub) +    Websub.verify(websub)    end    def perform(:incoming_doc, doc) do      Logger.info("Got document, trying to parse") -    @ostatus.handle_incoming(doc) +    OStatus.handle_incoming(doc)    end    def perform(:incoming_ap_doc, params) do diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex index 916bcdcba..70f870244 100644 --- a/lib/pleroma/web/federator/publisher.ex +++ b/lib/pleroma/web/federator/publisher.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Web.Federator.Publisher do    """    @spec enqueue_one(module(), Map.t()) :: :ok    def enqueue_one(module, %{} = params), -    do: PleromaJobQueue.enqueue(:federation_outgoing, __MODULE__, [:publish_one, module, params]) +    do: PleromaJobQueue.enqueue(:federator_outgoing, __MODULE__, [:publish_one, module, params])    @spec perform(atom(), module(), any()) :: {:ok, any()} | {:error, any()}    def perform(:publish_one, module, params) do @@ -52,9 +52,9 @@ defmodule Pleroma.Web.Federator.Publisher do    @doc """    Relays an activity to all specified peers.    """ -  @callback publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok | {:error, any()} +  @callback publish(User.t(), Activity.t()) :: :ok | {:error, any()} -  @spec publish(Pleroma.User.t(), Pleroma.Activity.t()) :: :ok +  @spec publish(User.t(), Activity.t()) :: :ok    def publish(%User{} = user, %Activity{} = activity) do      Config.get([:instance, :federation_publisher_modules])      |> Enum.each(fn module -> @@ -70,9 +70,9 @@ defmodule Pleroma.Web.Federator.Publisher do    @doc """    Gathers links used by an outgoing federation module for WebFinger output.    """ -  @callback gather_webfinger_links(Pleroma.User.t()) :: list() +  @callback gather_webfinger_links(User.t()) :: list() -  @spec gather_webfinger_links(Pleroma.User.t()) :: list() +  @spec gather_webfinger_links(User.t()) :: list()    def gather_webfinger_links(%User{} = user) do      Config.get([:instance, :federation_publisher_modules])      |> Enum.reduce([], fn module, links -> diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 87e597074..2110027c3 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    alias Pleroma.Conversation.Participation    alias Pleroma.Filter    alias Pleroma.Formatter +  alias Pleroma.HTTP    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Object.Fetcher @@ -55,7 +56,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      when action in [:account_register]    ) -  @httpoison Application.get_env(:pleroma, :httpoison)    @local_mastodon_name "Mastodon-Local"    action_fallback(:errors) @@ -303,7 +303,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      activities =        [user.ap_id | user.following]        |> ActivityPub.fetch_activities(params) -      |> ActivityPub.contain_timeline(user)        |> Enum.reverse()      conn @@ -708,6 +707,41 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end +  def set_mascot(%{assigns: %{user: user}} = conn, %{"file" => file}) do +    with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)), +         %{} = attachment_data <- Map.put(object.data, "id", object.id), +         %{type: type} = rendered <- +           StatusView.render("attachment.json", %{attachment: attachment_data}) do +      # Reject if not an image +      if type == "image" do +        # Sure! +        # Save to the user's info +        info_changeset = User.Info.mascot_update(user.info, rendered) + +        user_changeset = +          user +          |> Ecto.Changeset.change() +          |> Ecto.Changeset.put_embed(:info, info_changeset) + +        {:ok, _user} = User.update_and_set_cache(user_changeset) + +        conn +        |> json(rendered) +      else +        conn +        |> put_resp_content_type("application/json") +        |> send_resp(415, Jason.encode!(%{"error" => "mascots can only be images"})) +      end +    end +  end + +  def get_mascot(%{assigns: %{user: user}} = conn, _params) do +    mascot = User.get_mascot(user) + +    conn +    |> json(mascot) +  end +    def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do      with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id),           %Object{data: %{"likes" => likes}} <- Object.normalize(object) do @@ -1010,6 +1044,30 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end +  def status_search_query_with_gin(q, query) do +    from([a, o] in q, +      where: +        fragment( +          "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", +          o.data, +          ^query +        ), +      order_by: [desc: :id] +    ) +  end + +  def status_search_query_with_rum(q, query) do +    from([a, o] in q, +      where: +        fragment( +          "? @@ plainto_tsquery('english', ?)", +          o.fts_content, +          ^query +        ), +      order_by: [fragment("? <=> now()::date", o.inserted_at)] +    ) +  end +    def status_search(user, query) do      fetched =        if Regex.match?(~r/https?:/, query) do @@ -1023,20 +1081,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do        end || []      q = -      from( -        [a, o] in Activity.with_preloaded_object(Activity), +      from([a, o] in Activity.with_preloaded_object(Activity),          where: fragment("?->>'type' = 'Create'", a.data),          where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, -        where: -          fragment( -            "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", -            o.data, -            ^query -          ), -        limit: 20, -        order_by: [desc: :id] +        limit: 40        ) +    q = +      if Pleroma.Config.get([:database, :rum_enabled]) do +        status_search_query_with_rum(q, query) +      else +        status_search_query_with_gin(q, query) +      end +      Repo.all(q) ++ fetched    end @@ -1223,7 +1280,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      accounts      |> Enum.each(fn account_id ->        with %Pleroma.List{} = list <- Pleroma.List.get(id, user), -           %User{} = followed <- Pleroma.User.get_cached_by_id(account_id) do +           %User{} = followed <- User.get_cached_by_id(account_id) do          Pleroma.List.unfollow(list, followed)        end      end) @@ -1307,7 +1364,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do              display_sensitive_media: false,              reduce_motion: false,              max_toot_chars: limit, -            mascot: "/images/pleroma-fox-tan-smol.png" +            mascot: User.get_mascot(user)["url"]            },            rights: %{              delete_others_notice: present?(user.info.is_moderator), @@ -1634,7 +1691,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do          |> String.replace("{{user}}", user)        with {:ok, %{status: 200, body: body}} <- -             @httpoison.get( +             HTTP.get(                 url,                 [],                 adapter: [ diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 779b9a382..b82d3319b 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -40,7 +40,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do      follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)      requested = -      if follow_activity do +      if follow_activity && !User.following?(target, user) do          follow_activity.data["state"] == "pending"        else          false @@ -112,7 +112,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do        fields: fields,        bot: bot,        source: %{ -        note: "", +        note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),          sensitive: false,          pleroma: %{}        }, diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index c93d915e5..84ab20a1c 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -157,6 +157,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil +    thread_muted? = +      case activity.thread_muted? do +        thread_muted? when is_boolean(thread_muted?) -> thread_muted? +        nil -> CommonAPI.thread_muted?(user, activity) +      end +      attachment_data = object.data["attachment"] || []      attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment) @@ -228,7 +234,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        reblogged: reblogged?(activity, opts[:for]),        favourited: present?(favorited),        bookmarked: present?(bookmarked), -      muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user), +      muted: thread_muted? || User.mutes?(opts[:for], user),        pinned: pinned?(activity, user),        sensitive: sensitive,        spoiler_text: summary_html, @@ -284,8 +290,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        provider_url: page_url_data.scheme <> "://" <> page_url_data.host,        url: page_url,        image: image_url |> MediaProxy.url(), -      title: rich_media[:title], -      description: rich_media[:description], +      title: rich_media[:title] || "", +      description: rich_media[:description] || "",        pleroma: %{          opengraph: rich_media        } diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 5762e767b..cee6d8481 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -12,25 +12,27 @@ defmodule Pleroma.Web.MediaProxy do    def url("/" <> _ = url), do: url    def url(url) do -    config = Application.get_env(:pleroma, :media_proxy, []) -    domain = URI.parse(url).host +    if !enabled?() or local?(url) or whitelisted?(url) do +      url +    else +      encode_url(url) +    end +  end -    cond do -      !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) -> -        url +  defp enabled?, do: Pleroma.Config.get([:media_proxy, :enabled], false) -      Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern -> -        String.equivalent?(domain, pattern) -      end) -> -        url +  defp local?(url), do: String.starts_with?(url, Pleroma.Web.base_url()) -      true -> -        encode_url(url) -    end +  defp whitelisted?(url) do +    %{host: domain} = URI.parse(url) + +    Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern -> +      String.equivalent?(domain, pattern) +    end)    end    def encode_url(url) do -    secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] +    secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])      # Must preserve `%2F` for compatibility with S3      # https://git.pleroma.social/pleroma/pleroma/issues/580 @@ -52,7 +54,7 @@ defmodule Pleroma.Web.MediaProxy do    end    def decode_url(sig, url) do -    secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] +    secret = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])      sig = Base.url_decode64!(sig, @base64_opts)      local_sig = :crypto.hmac(:sha, secret, url) diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex new file mode 100644 index 000000000..489d5d3a5 --- /dev/null +++ b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MongooseIM.MongooseIMController do +  use Pleroma.Web, :controller +  alias Comeonin.Pbkdf2 +  alias Pleroma.Repo +  alias Pleroma.User + +  def user_exists(conn, %{"user" => username}) do +    with %User{} <- Repo.get_by(User, nickname: username, local: true) do +      conn +      |> json(true) +    else +      _ -> +        conn +        |> put_status(:not_found) +        |> json(false) +    end +  end + +  def check_password(conn, %{"user" => username, "pass" => password}) do +    with %User{password_hash: password_hash} <- +           Repo.get_by(User, nickname: username, local: true), +         true <- Pbkdf2.checkpw(password, password_hash) do +      conn +      |> json(true) +    else +      false -> +        conn +        |> put_status(403) +        |> json(false) + +      _ -> +        conn +        |> put_status(:not_found) +        |> json(false) +    end +  end +end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 3bf2a0fbc..59f3d4e11 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -12,8 +12,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do    alias Pleroma.Web.ActivityPub.MRF    alias Pleroma.Web.Federator.Publisher -  plug(Pleroma.Web.FederatingPlug) -    def schemas(conn, _params) do      response = %{        links: [ @@ -34,20 +32,15 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do    # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field    # under software.    def raw_nodeinfo do -    instance = Application.get_env(:pleroma, :instance) -    media_proxy = Application.get_env(:pleroma, :media_proxy) -    suggestions = Application.get_env(:pleroma, :suggestions) -    chat = Application.get_env(:pleroma, :chat) -    gopher = Application.get_env(:pleroma, :gopher)      stats = Stats.get_stats()      mrf_simple = -      Application.get_env(:pleroma, :mrf_simple) +      Config.get(:mrf_simple)        |> Enum.into(%{})      # This horror is needed to convert regex sigils to strings      mrf_keyword = -      Application.get_env(:pleroma, :mrf_keyword, []) +      Config.get(:mrf_keyword, [])        |> Enum.map(fn {key, value} ->          {key,           Enum.map(value, fn @@ -76,14 +69,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do        MRF.get_policies()        |> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end) -    quarantined = Keyword.get(instance, :quarantined_instances) - -    quarantined = -      if is_list(quarantined) do -        quarantined -      else -        [] -      end +    quarantined = Config.get([:instance, :quarantined_instances], [])      staff_accounts =        User.all_superusers() @@ -94,7 +80,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do        |> Enum.into(%{}, fn {k, v} -> {k, length(v)} end)      federation_response = -      if Keyword.get(instance, :mrf_transparency) do +      if Config.get([:instance, :mrf_transparency]) do          %{            mrf_policies: mrf_policies,            mrf_simple: mrf_simple, @@ -111,22 +97,22 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do          "pleroma_api",          "mastodon_api",          "mastodon_api_streaming", -        if Keyword.get(media_proxy, :enabled) do +        if Config.get([:media_proxy, :enabled]) do            "media_proxy"          end, -        if Keyword.get(gopher, :enabled) do +        if Config.get([:gopher, :enabled]) do            "gopher"          end, -        if Keyword.get(chat, :enabled) do +        if Config.get([:chat, :enabled]) do            "chat"          end, -        if Keyword.get(suggestions, :enabled) do +        if Config.get([:suggestions, :enabled]) do            "suggestions"          end, -        if Keyword.get(instance, :allow_relay) do +        if Config.get([:instance, :allow_relay]) do            "relay"          end, -        if Keyword.get(instance, :safe_dm_mentions) do +        if Config.get([:instance, :safe_dm_mentions]) do            "safe_dm_mentions"          end        ] @@ -143,7 +129,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do          inbound: [],          outbound: []        }, -      openRegistrations: Keyword.get(instance, :registrations_open), +      openRegistrations: Config.get([:instance, :registrations_open]),        usage: %{          users: %{            total: stats.user_count || 0 @@ -151,29 +137,29 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do          localPosts: stats.status_count || 0        },        metadata: %{ -        nodeName: Keyword.get(instance, :name), -        nodeDescription: Keyword.get(instance, :description), -        private: !Keyword.get(instance, :public, true), +        nodeName: Config.get([:instance, :name]), +        nodeDescription: Config.get([:instance, :description]), +        private: !Config.get([:instance, :public], true),          suggestions: %{ -          enabled: Keyword.get(suggestions, :enabled, false), -          thirdPartyEngine: Keyword.get(suggestions, :third_party_engine, ""), -          timeout: Keyword.get(suggestions, :timeout, 5000), -          limit: Keyword.get(suggestions, :limit, 23), -          web: Keyword.get(suggestions, :web, "") +          enabled: Config.get([:suggestions, :enabled], false), +          thirdPartyEngine: Config.get([:suggestions, :third_party_engine], ""), +          timeout: Config.get([:suggestions, :timeout], 5000), +          limit: Config.get([:suggestions, :limit], 23), +          web: Config.get([:suggestions, :web], "")          },          staffAccounts: staff_accounts,          federation: federation_response, -        postFormats: Keyword.get(instance, :allowed_post_formats), +        postFormats: Config.get([:instance, :allowed_post_formats]),          uploadLimits: %{ -          general: Keyword.get(instance, :upload_limit), -          avatar: Keyword.get(instance, :avatar_upload_limit), -          banner: Keyword.get(instance, :banner_upload_limit), -          background: Keyword.get(instance, :background_upload_limit) +          general: Config.get([:instance, :upload_limit]), +          avatar: Config.get([:instance, :avatar_upload_limit]), +          banner: Config.get([:instance, :banner_upload_limit]), +          background: Config.get([:instance, :background_upload_limit])          }, -        accountActivationRequired: Keyword.get(instance, :account_activation_required, false), -        invitesEnabled: Keyword.get(instance, :invites_enabled, false), +        accountActivationRequired: Config.get([:instance, :account_activation_required], false), +        invitesEnabled: Config.get([:instance, :invites_enabled], false),          features: features, -        restrictedNicknames: Pleroma.Config.get([Pleroma.User, :restricted_nicknames]) +        restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames])        }      }    end diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex index b47688de1..18973413e 100644 --- a/lib/pleroma/web/oauth/authorization.ex +++ b/lib/pleroma/web/oauth/authorization.ex @@ -20,7 +20,7 @@ defmodule Pleroma.Web.OAuth.Authorization do      field(:scopes, {:array, :string}, default: [])      field(:valid_until, :naive_datetime_usec)      field(:used, :boolean, default: false) -    belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId) +    belongs_to(:user, User, type: Pleroma.FlakeId)      belongs_to(:app, App)      timestamps() diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index ef047d565..f412f7eb2 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -5,7 +5,6 @@  defmodule Pleroma.Web.OAuth.Token do    use Ecto.Schema -  import Ecto.Query    import Ecto.Changeset    alias Pleroma.Repo @@ -13,6 +12,7 @@ defmodule Pleroma.Web.OAuth.Token do    alias Pleroma.Web.OAuth.App    alias Pleroma.Web.OAuth.Authorization    alias Pleroma.Web.OAuth.Token +  alias Pleroma.Web.OAuth.Token.Query    @expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)    @type t :: %__MODULE__{} @@ -22,7 +22,7 @@ defmodule Pleroma.Web.OAuth.Token do      field(:refresh_token, :string)      field(:scopes, {:array, :string}, default: [])      field(:valid_until, :naive_datetime_usec) -    belongs_to(:user, Pleroma.User, type: Pleroma.FlakeId) +    belongs_to(:user, User, type: Pleroma.FlakeId)      belongs_to(:app, App)      timestamps() @@ -31,17 +31,17 @@ defmodule Pleroma.Web.OAuth.Token do    @doc "Gets token for app by access token"    @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}    def get_by_token(%App{id: app_id} = _app, token) do -    from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token) +    Query.get_by_app(app_id) +    |> Query.get_by_token(token)      |> Repo.find_resource()    end    @doc "Gets token for app by refresh token"    @spec get_by_refresh_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}    def get_by_refresh_token(%App{id: app_id} = _app, token) do -    from(t in __MODULE__, -      where: t.app_id == ^app_id and t.refresh_token == ^token, -      preload: [:user] -    ) +    Query.get_by_app(app_id) +    |> Query.get_by_refresh_token(token) +    |> Query.preload([:user])      |> Repo.find_resource()    end @@ -97,29 +97,25 @@ defmodule Pleroma.Web.OAuth.Token do    end    def delete_user_tokens(%User{id: user_id}) do -    from( -      t in Token, -      where: t.user_id == ^user_id -    ) +    Query.get_by_user(user_id)      |> Repo.delete_all()    end    def delete_user_token(%User{id: user_id}, token_id) do -    from( -      t in Token, -      where: t.user_id == ^user_id, -      where: t.id == ^token_id -    ) +    Query.get_by_user(user_id) +    |> Query.get_by_id(token_id) +    |> Repo.delete_all() +  end + +  def delete_expired_tokens do +    Query.get_expired_tokens()      |> Repo.delete_all()    end    def get_user_tokens(%User{id: user_id}) do -    from( -      t in Token, -      where: t.user_id == ^user_id -    ) +    Query.get_by_user(user_id) +    |> Query.preload([:app])      |> Repo.all() -    |> Repo.preload(:app)    end    def is_expired?(%__MODULE__{valid_until: valid_until}) do diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex new file mode 100644 index 000000000..dca852449 --- /dev/null +++ b/lib/pleroma/web/oauth/token/clean_worker.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Token.CleanWorker do +  @moduledoc """ +  The module represents functions to clean an expired oauth tokens. +  """ + +  # 10 seconds +  @start_interval 10_000 +  @interval Pleroma.Config.get( +              # 24 hours +              [:oauth2, :clean_expired_tokens_interval], +              86_400_000 +            ) +  @queue :background + +  alias Pleroma.Web.OAuth.Token + +  def start_link, do: GenServer.start_link(__MODULE__, nil) + +  def init(_) do +    if Pleroma.Config.get([:oauth2, :clean_expired_tokens], false) do +      Process.send_after(self(), :perform, @start_interval) +      {:ok, nil} +    else +      :ignore +    end +  end + +  @doc false +  def handle_info(:perform, state) do +    Process.send_after(self(), :perform, @interval) +    PleromaJobQueue.enqueue(@queue, __MODULE__, [:clean]) +    {:noreply, state} +  end + +  # Job Worker Callbacks +  def perform(:clean), do: Token.delete_expired_tokens() +end diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/oauth/token/query.ex new file mode 100644 index 000000000..d92e1f071 --- /dev/null +++ b/lib/pleroma/web/oauth/token/query.ex @@ -0,0 +1,55 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.Token.Query do +  @moduledoc """ +  Contains queries for OAuth Token. +  """ + +  import Ecto.Query, only: [from: 2] + +  @type query :: Ecto.Queryable.t() | Token.t() + +  alias Pleroma.Web.OAuth.Token + +  @spec get_by_refresh_token(query, String.t()) :: query +  def get_by_refresh_token(query \\ Token, refresh_token) do +    from(q in query, where: q.refresh_token == ^refresh_token) +  end + +  @spec get_by_token(query, String.t()) :: query +  def get_by_token(query \\ Token, token) do +    from(q in query, where: q.token == ^token) +  end + +  @spec get_by_app(query, String.t()) :: query +  def get_by_app(query \\ Token, app_id) do +    from(q in query, where: q.app_id == ^app_id) +  end + +  @spec get_by_id(query, String.t()) :: query +  def get_by_id(query \\ Token, id) do +    from(q in query, where: q.id == ^id) +  end + +  @spec get_expired_tokens(query, DateTime.t() | nil) :: query +  def get_expired_tokens(query \\ Token, date \\ nil) do +    expired_date = date || Timex.now() +    from(q in query, where: fragment("?", q.valid_until) < ^expired_date) +  end + +  @spec get_by_user(query, String.t()) :: query +  def get_by_user(query \\ Token, user_id) do +    from(q in query, where: q.user_id == ^user_id) +  end + +  @spec preload(query, any) :: query +  def preload(query \\ Token, assoc_preload \\ []) + +  def preload(query, assoc_preload) when is_list(assoc_preload) do +    from(q in query, preload: ^assoc_preload) +  end + +  def preload(query, _assoc_preload), do: query +end diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index 61515b31e..6ed089d84 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -3,13 +3,12 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.OStatus do -  @httpoison Application.get_env(:pleroma, :httpoison) -    import Ecto.Query    import Pleroma.Web.XML    require Logger    alias Pleroma.Activity +  alias Pleroma.HTTP    alias Pleroma.Object    alias Pleroma.Repo    alias Pleroma.User @@ -363,7 +362,7 @@ defmodule Pleroma.Web.OStatus do    def fetch_activity_from_atom_url(url) do      with true <- String.starts_with?(url, "http"),           {:ok, %{body: body, status: code}} when code in 200..299 <- -           @httpoison.get( +           HTTP.get(               url,               [{:Accept, "application/atom+xml"}]             ) do @@ -380,7 +379,7 @@ defmodule Pleroma.Web.OStatus do      Logger.debug("Trying to fetch #{url}")      with true <- String.starts_with?(url, "http"), -         {:ok, %{body: body}} <- @httpoison.get(url, []), +         {:ok, %{body: body}} <- HTTP.get(url, []),           {:ok, atom_url} <- get_atom_url(body) do        fetch_activity_from_atom_url(atom_url)      else diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index 0162a5be9..9bc8f2559 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -24,6 +24,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do    def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do      with true <- Pleroma.Config.get([:rich_media, :enabled]),           %Object{} = object <- Object.normalize(activity), +         false <- object.data["sensitive"] || false,           {:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]),           :ok <- validate_page_url(page_url),           {:ok, rich_media} <- Parser.parse(page_url) do diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 62e8fa610..e4595800c 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -37,7 +37,10 @@ defmodule Pleroma.Web.RichMedia.Parser do      try do        {:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: @hackney_options) -      html |> maybe_parse() |> clean_parsed_data() |> check_parsed_data() +      html +      |> maybe_parse() +      |> clean_parsed_data() +      |> check_parsed_data()      rescue        e ->          {:error, "Parsing error: #{inspect(e)}"} diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index bbc2fda9b..eb3ee03f3 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -194,6 +194,14 @@ defmodule Pleroma.Web.Router do      get("/users", AdminAPIController, :list_users)      get("/users/:nickname", AdminAPIController, :user_show) + +    get("/reports", AdminAPIController, :list_reports) +    get("/reports/:id", AdminAPIController, :report_show) +    put("/reports/:id", AdminAPIController, :report_update_state) +    post("/reports/:id/respond", AdminAPIController, :report_respond) + +    put("/statuses/:id", AdminAPIController, :status_update) +    delete("/statuses/:id", AdminAPIController, :status_delete)    end    scope "/", Pleroma.Web.TwitterAPI do @@ -344,6 +352,9 @@ defmodule Pleroma.Web.Router do        post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour) +      get("/pleroma/mascot", MastodonAPIController, :get_mascot) +      put("/pleroma/mascot", MastodonAPIController, :set_mascot) +        post("/reports", MastodonAPIController, :reports)      end @@ -696,9 +707,15 @@ defmodule Pleroma.Web.Router do      end    end +  scope "/", Pleroma.Web.MongooseIM do +    get("/user_exists", MongooseIMController, :user_exists) +    get("/check_password", MongooseIMController, :check_password) +  end +    scope "/", Fallback do      get("/registration/:token", RedirectController, :registration_page)      get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) +    get("/api*path", RedirectController, :api_not_implemented)      get("/*path", RedirectController, :redirector)      options("/*path", RedirectController, :empty) @@ -710,6 +727,12 @@ defmodule Fallback.RedirectController do    alias Pleroma.User    alias Pleroma.Web.Metadata +  def api_not_implemented(conn, _params) do +    conn +    |> put_status(404) +    |> json(%{error: "Not implemented"}) +  end +    def redirector(conn, _params, code \\ 200) do      conn      |> put_resp_content_type("text/html") diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex index 42709ab47..9e91a5a40 100644 --- a/lib/pleroma/web/salmon/salmon.ex +++ b/lib/pleroma/web/salmon/salmon.ex @@ -5,12 +5,12 @@  defmodule Pleroma.Web.Salmon do    @behaviour Pleroma.Web.Federator.Publisher -  @httpoison Application.get_env(:pleroma, :httpoison) -    use Bitwise    alias Pleroma.Activity +  alias Pleroma.HTTP    alias Pleroma.Instances +  alias Pleroma.Keys    alias Pleroma.User    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.Federator.Publisher @@ -89,45 +89,6 @@ defmodule Pleroma.Web.Salmon do      "RSA.#{modulus_enc}.#{exponent_enc}"    end -  # Native generation of RSA keys is only available since OTP 20+ and in default build conditions -  # We try at compile time to generate natively an RSA key otherwise we fallback on the old way. -  try do -    _ = :public_key.generate_key({:rsa, 2048, 65_537}) - -    def generate_rsa_pem do -      key = :public_key.generate_key({:rsa, 2048, 65_537}) -      entry = :public_key.pem_entry_encode(:RSAPrivateKey, key) -      pem = :public_key.pem_encode([entry]) |> String.trim_trailing() -      {:ok, pem} -    end -  rescue -    _ -> -      def generate_rsa_pem do -        port = Port.open({:spawn, "openssl genrsa"}, [:binary]) - -        {:ok, pem} = -          receive do -            {^port, {:data, pem}} -> {:ok, pem} -          end - -        Port.close(port) - -        if Regex.match?(~r/RSA PRIVATE KEY/, pem) do -          {:ok, pem} -        else -          :error -        end -      end -  end - -  def keys_from_pem(pem) do -    [private_key_code] = :public_key.pem_decode(pem) -    private_key = :public_key.pem_entry_decode(private_key_code) -    {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key -    public_key = {:RSAPublicKey, modulus, exponent} -    {:ok, private_key, public_key} -  end -    def encode(private_key, doc) do      type = "application/atom+xml"      encoding = "base64url" @@ -176,7 +137,7 @@ defmodule Pleroma.Web.Salmon do    def publish_one(%{recipient: url, feed: feed} = params) when is_binary(url) do      with {:ok, %{status: code}} when code in 200..299 <- -           @httpoison.post( +           HTTP.post(               url,               feed,               [{"Content-Type", "application/magic-envelope+xml"}] @@ -227,7 +188,7 @@ defmodule Pleroma.Web.Salmon do          |> :xmerl.export_simple(:xmerl_xml)          |> to_string -      {:ok, private, _} = keys_from_pem(keys) +      {:ok, private, _} = Keys.keys_from_pem(keys)        {:ok, feed} = encode(private, feed)        remote_users = remote_users(activity) @@ -253,7 +214,7 @@ defmodule Pleroma.Web.Salmon do    def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)    def gather_webfinger_links(%User{} = user) do -    {:ok, _private, public} = keys_from_pem(user.info.keys) +    {:ok, _private, public} = Keys.keys_from_pem(user.info.keys)      magic_key = encode_key(public)      [ diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 3389c91cc..85ec4d76c 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -4,7 +4,7 @@      <meta charset="utf-8" />      <meta name="viewport" content="width=device-width,initial-scale=1,minimal-ui" />      <title> -    <%= Application.get_env(:pleroma, :instance)[:name] %> +    <%= Pleroma.Config.get([:instance, :name]) %>      </title>      <style>        body { @@ -194,7 +194,7 @@    </head>    <body>      <div class="container"> -      <h1><%= Application.get_env(:pleroma, :instance)[:name] %></h1> +      <h1><%= Pleroma.Config.get([:instance, :name]) %></h1>        <%= render @view_module, @view_template, assigns %>      </div>    </body> diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex index 5659c7828..ac63811d1 100644 --- a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex +++ b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex @@ -4,7 +4,7 @@  <meta charset='utf-8'>  <meta content='width=device-width, initial-scale=1' name='viewport'>  <title> -<%= Application.get_env(:pleroma, :instance)[:name] %> +<%= Pleroma.Config.get([:instance, :name]) %>  </title>  <link rel="icon" type="image/png" href="/favicon.png"/>  <script crossorigin='anonymous' src="/packs/locales.js"></script> diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 3c5a70be9..1b6b33e69 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -101,9 +101,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do        |> Map.put("blocking_user", user)        |> Map.put("user", user) -    activities = -      ActivityPub.fetch_activities([user.ap_id | user.following], params) -      |> ActivityPub.contain_timeline(user) +    activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)      conn      |> put_view(ActivityView) @@ -730,7 +728,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do    def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn    def only_if_public_instance(conn, _) do -    if Keyword.get(Application.get_env(:pleroma, :instance), :public) do +    if Pleroma.Config.get([:instance, :public]) do        conn      else        conn diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index 44bcafe0e..e84af84dc 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -284,6 +284,12 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do          Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)        ) +    thread_muted? = +      case activity.thread_muted? do +        thread_muted? when is_boolean(thread_muted?) -> thread_muted? +        nil -> CommonAPI.thread_muted?(user, activity) +      end +      %{        "id" => activity.id,        "uri" => object.data["id"], @@ -314,7 +320,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do        "summary" => summary,        "summary_html" => summary |> Formatter.emojify(object.data["emoji"]),        "card" => card, -      "muted" => CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user) +      "muted" => thread_muted? || User.mutes?(opts[:for], user)      }    end diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 3a3b98a10..3fca72de8 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -3,12 +3,10 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.WebFinger do -  @httpoison Application.get_env(:pleroma, :httpoison) - +  alias Pleroma.HTTP    alias Pleroma.User    alias Pleroma.Web    alias Pleroma.Web.Federator.Publisher -  alias Pleroma.Web.Salmon    alias Pleroma.Web.XML    alias Pleroma.XmlBuilder    require Jason @@ -61,7 +59,7 @@ defmodule Pleroma.Web.WebFinger do    end    def represent_user(user, "JSON") do -    {:ok, user} = ensure_keys_present(user) +    {:ok, user} = User.ensure_keys_present(user)      %{        "subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}", @@ -71,7 +69,7 @@ defmodule Pleroma.Web.WebFinger do    end    def represent_user(user, "XML") do -    {:ok, user} = ensure_keys_present(user) +    {:ok, user} = User.ensure_keys_present(user)      links =        gather_links(user) @@ -88,27 +86,6 @@ defmodule Pleroma.Web.WebFinger do      |> XmlBuilder.to_doc()    end -  # This seems a better fit in Salmon -  def ensure_keys_present(user) do -    info = user.info - -    if info.keys do -      {:ok, user} -    else -      {:ok, pem} = Salmon.generate_rsa_pem() - -      info_cng = -        info -        |> Pleroma.User.Info.set_keys(pem) - -      cng = -        Ecto.Changeset.change(user) -        |> Ecto.Changeset.put_embed(:info, info_cng) - -      User.update_and_set_cache(cng) -    end -  end -    defp get_magic_key(magic_key) do      "data:application/magic-public-key," <> magic_key = magic_key      {:ok, magic_key} @@ -198,11 +175,11 @@ defmodule Pleroma.Web.WebFinger do    def find_lrdd_template(domain) do      with {:ok, %{status: status, body: body}} when status in 200..299 <- -           @httpoison.get("http://#{domain}/.well-known/host-meta", []) do +           HTTP.get("http://#{domain}/.well-known/host-meta", []) do        get_template_from_xml(body)      else        _ -> -        with {:ok, %{body: body}} <- @httpoison.get("https://#{domain}/.well-known/host-meta", []) do +        with {:ok, %{body: body}} <- HTTP.get("https://#{domain}/.well-known/host-meta", []) do            get_template_from_xml(body)          else            e -> {:error, "Can't find LRDD template: #{inspect(e)}"} @@ -231,7 +208,7 @@ defmodule Pleroma.Web.WebFinger do        end      with response <- -           @httpoison.get( +           HTTP.get(               address,               Accept: "application/xrd+xml,application/jrd+json"             ), diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex index 7ad0414ab..b61f388b8 100644 --- a/lib/pleroma/web/websub/websub.ex +++ b/lib/pleroma/web/websub/websub.ex @@ -5,6 +5,7 @@  defmodule Pleroma.Web.Websub do    alias Ecto.Changeset    alias Pleroma.Activity +  alias Pleroma.HTTP    alias Pleroma.Instances    alias Pleroma.Repo    alias Pleroma.User @@ -24,9 +25,7 @@ defmodule Pleroma.Web.Websub do    @behaviour Pleroma.Web.Federator.Publisher -  @httpoison Application.get_env(:pleroma, :httpoison) - -  def verify(subscription, getter \\ &@httpoison.get/3) do +  def verify(subscription, getter \\ &HTTP.get/3) do      challenge = Base.encode16(:crypto.strong_rand_bytes(8))      lease_seconds = NaiveDateTime.diff(subscription.valid_until, subscription.updated_at)      lease_seconds = lease_seconds |> to_string @@ -207,7 +206,7 @@ defmodule Pleroma.Web.Websub do      requester.(subscription)    end -  def gather_feed_data(topic, getter \\ &@httpoison.get/1) do +  def gather_feed_data(topic, getter \\ &HTTP.get/1) do      with {:ok, response} <- getter.(topic),           status when status in 200..299 <- response.status,           body <- response.body, @@ -236,7 +235,7 @@ defmodule Pleroma.Web.Websub do      end    end -  def request_subscription(websub, poster \\ &@httpoison.post/3, timeout \\ 10_000) do +  def request_subscription(websub, poster \\ &HTTP.post/3, timeout \\ 10_000) do      data = [        "hub.mode": "subscribe",        "hub.topic": websub.topic, @@ -294,7 +293,7 @@ defmodule Pleroma.Web.Websub do      Logger.info(fn -> "Pushing #{topic} to #{callback}" end)      with {:ok, %{status: code}} when code in 200..299 <- -           @httpoison.post( +           HTTP.post(               callback,               xml,               [ | 
