diff options
Diffstat (limited to 'lib')
48 files changed, 842 insertions, 459 deletions
| diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 0e21408b2..590c7a914 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -52,7 +52,9 @@ defmodule Mix.Tasks.Pleroma.Config do        |> Enum.each(fn config ->          IO.write(            file, -          "config :#{config.group}, #{config.key}, #{inspect(Config.from_binary(config.value))}\r\n\r\n" +          "config :#{config.group}, #{config.key}, #{ +            inspect(Config.from_binary(config.value), limit: :infinity) +          }\r\n\r\n"          )          if delete? do diff --git a/lib/mix/tasks/pleroma/notification_settings.ex b/lib/mix/tasks/pleroma/notification_settings.ex new file mode 100644 index 000000000..7d65f0587 --- /dev/null +++ b/lib/mix/tasks/pleroma/notification_settings.ex @@ -0,0 +1,83 @@ +defmodule Mix.Tasks.Pleroma.NotificationSettings do +  @shortdoc "Enable&Disable privacy option for push notifications" +  @moduledoc """ +  Example: + +  > mix pleroma.notification_settings --privacy-option=false --nickname-users="parallel588"  # set false only for parallel588 user +  > mix pleroma.notification_settings --privacy-option=true # set true for all users + +  """ + +  use Mix.Task +  import Mix.Pleroma +  import Ecto.Query + +  def run(args) do +    start_pleroma() + +    {options, _, _} = +      OptionParser.parse( +        args, +        strict: [ +          privacy_option: :boolean, +          email_users: :string, +          nickname_users: :string +        ] +      ) + +    privacy_option = Keyword.get(options, :privacy_option) + +    if not is_nil(privacy_option) do +      privacy_option +      |> build_query(options) +      |> Pleroma.Repo.update_all([]) +    end + +    shell_info("Done") +  end + +  defp build_query(privacy_option, options) do +    query = +      from(u in Pleroma.User, +        update: [ +          set: [ +            notification_settings: +              fragment( +                "jsonb_set(notification_settings, '{privacy_option}', ?)", +                ^privacy_option +              ) +          ] +        ] +      ) + +    user_emails = +      options +      |> Keyword.get(:email_users, "") +      |> String.split(",") +      |> Enum.map(&String.trim(&1)) +      |> Enum.reject(&(&1 == "")) + +    query = +      if length(user_emails) > 0 do +        where(query, [u], u.email in ^user_emails) +      else +        query +      end + +    user_nicknames = +      options +      |> Keyword.get(:nickname_users, "") +      |> String.split(",") +      |> Enum.map(&String.trim(&1)) +      |> Enum.reject(&(&1 == "")) + +    query = +      if length(user_nicknames) > 0 do +        where(query, [u], u.nickname in ^user_nicknames) +      else +        query +      end + +    query +  end +end diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index bc8eacda8..85c9e4954 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -8,7 +8,6 @@ defmodule Mix.Tasks.Pleroma.User do    alias Ecto.Changeset    alias Pleroma.User    alias Pleroma.UserInviteToken -  alias Pleroma.Web.OAuth    @shortdoc "Manages Pleroma users"    @moduledoc File.read!("docs/administration/CLI_tasks/user.md") @@ -354,8 +353,7 @@ defmodule Mix.Tasks.Pleroma.User do      start_pleroma()      with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do -      OAuth.Token.delete_user_tokens(user) -      OAuth.Authorization.delete_user_authorizations(user) +      User.global_sign_out(user)        shell_info("#{nickname} signed out from all apps.")      else @@ -373,9 +371,9 @@ defmodule Mix.Tasks.Pleroma.User do        users        |> Enum.each(fn user ->          shell_info( -          "#{user.nickname} moderator: #{user.info.is_moderator}, admin: #{user.info.is_admin}, locked: #{ -            user.info.locked -          }, deactivated: #{user.info.deactivated}" +          "#{user.nickname} moderator: #{user.is_moderator}, admin: #{user.is_admin}, locked: #{ +            user.locked +          }, deactivated: #{user.deactivated}"          )        end)      end) @@ -393,10 +391,7 @@ defmodule Mix.Tasks.Pleroma.User do    end    defp set_admin(user, value) do -    {:ok, user} = -      user -      |> Changeset.change(%{is_admin: value}) -      |> User.update_and_set_cache() +    {:ok, user} = User.admin_api_update(user, %{is_admin: value})      shell_info("Admin status of #{user.nickname}: #{user.is_admin}")      user diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 480b261cf..510d3273c 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -12,6 +12,7 @@ defmodule Pleroma.Activity do    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Repo +  alias Pleroma.ReportNote    alias Pleroma.ThreadMute    alias Pleroma.User @@ -48,6 +49,8 @@ defmodule Pleroma.Activity do      has_one(:user_actor, User, on_delete: :nothing, foreign_key: :id)      # This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark      has_one(:bookmark, Bookmark) +    # This is a fake relation, do not use outside of with_preloaded_report_notes +    has_many(:report_notes, ReportNote)      has_many(:notifications, Notification, on_delete: :delete_all)      # Attention: this is a fake relation, don't try to preload it blindly and expect it to work! @@ -114,6 +117,16 @@ defmodule Pleroma.Activity do    def with_preloaded_bookmark(query, _), do: query +  def with_preloaded_report_notes(query) do +    from([a] in query, +      left_join: r in ReportNote, +      on: a.id == r.activity_id, +      preload: [report_notes: r] +    ) +  end + +  def with_preloaded_report_notes(query, _), do: query +    def with_set_thread_muted_field(query, %User{} = user) do      from([a] in query,        left_join: tm in ThreadMute, diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex index f847ac238..d30a5a6a5 100644 --- a/lib/pleroma/activity/search.ex +++ b/lib/pleroma/activity/search.ex @@ -86,7 +86,7 @@ defmodule Pleroma.Activity.Search do           {:ok, object} <- Fetcher.fetch_object_from_id(search_query),           %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),           true <- Visibility.visible_for_user?(activity, user) do -      activities ++ [activity] +      [activity | activities]      else        _ -> activities      end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 57462740c..2ae052069 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -5,6 +5,7 @@  defmodule Pleroma.Application do    import Cachex.Spec    use Application +  require Logger    @name Mix.Project.config()[:name]    @version Mix.Project.config()[:version] @@ -30,8 +31,10 @@ defmodule Pleroma.Application do    # See http://elixir-lang.org/docs/stable/elixir/Application.html    # for more information on OTP Applications    def start(_type, _args) do +    Pleroma.HTML.compile_scrubbers()      Pleroma.Config.DeprecationWarnings.warn()      setup_instrumenters() +    load_custom_modules()      # Define workers and child supervisors to be supervised      children = @@ -67,6 +70,28 @@ defmodule Pleroma.Application do      Supervisor.start_link(children, opts)    end +  def load_custom_modules do +    dir = Pleroma.Config.get([:modules, :runtime_dir]) + +    if dir && File.exists?(dir) do +      dir +      |> Pleroma.Utils.compile_dir() +      |> case do +        {:error, _errors, _warnings} -> +          raise "Invalid custom modules" + +        {:ok, modules, _warnings} -> +          if @env != :test do +            Enum.each(modules, fn mod -> +              Logger.info("Custom module loaded: #{inspect(mod)}") +            end) +          end + +          :ok +      end +    end +  end +    defp setup_instrumenters do      require Prometheus.Registry diff --git a/lib/pleroma/captcha/native.ex b/lib/pleroma/captcha/native.ex new file mode 100644 index 000000000..5306fe1aa --- /dev/null +++ b/lib/pleroma/captcha/native.ex @@ -0,0 +1,35 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Captcha.Native do +  import Pleroma.Web.Gettext +  alias Pleroma.Captcha.Service +  @behaviour Service + +  @impl Service +  def new do +    case Captcha.get() do +      {:timeout} -> +        %{error: dgettext("errors", "Captcha timeout")} + +      {:ok, answer_data, img_binary} -> +        %{ +          type: :native, +          token: token(), +          url: "data:image/png;base64," <> Base.encode64(img_binary), +          answer_data: answer_data +        } +    end +  end + +  @impl Service +  def validate(_token, captcha, captcha) when not is_nil(captcha), do: :ok +  def validate(_token, _captcha, _answer), do: {:error, dgettext("errors", "Invalid CAPTCHA")} + +  defp token do +    10 +    |> :crypto.strong_rand_bytes() +    |> Base.url_encode64(padding: false) +  end +end diff --git a/lib/pleroma/config.ex b/lib/pleroma/config.ex index fcc039710..bad6d505c 100644 --- a/lib/pleroma/config.ex +++ b/lib/pleroma/config.ex @@ -65,4 +65,16 @@ defmodule Pleroma.Config do    def oauth_consumer_strategies, do: get([:auth, :oauth_consumer_strategies], [])    def oauth_consumer_enabled?, do: oauth_consumer_strategies() != [] + +  def enforce_oauth_admin_scope_usage?, do: !!get([:auth, :enforce_oauth_admin_scope_usage]) + +  def oauth_admin_scopes(scopes) when is_list(scopes) do +    Enum.flat_map( +      scopes, +      fn scope -> +        ["admin:#{scope}"] ++ +          if enforce_oauth_admin_scope_usage?(), do: [], else: [scope] +      end +    ) +  end  end diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 71c53ce0e..11513106e 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -3,6 +3,23 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.HTML do +  # Scrubbers are compiled on boot so they can be configured in OTP releases +  #  @on_load :compile_scrubbers + +  def compile_scrubbers do +    dir = Path.join(:code.priv_dir(:pleroma), "scrubbers") + +    dir +    |> Pleroma.Utils.compile_dir() +    |> case do +      {:error, _errors, _warnings} -> +        raise "Compiling scrubbers failed" + +      {:ok, _modules, _warnings} -> +        :ok +    end +  end +    defp get_scrubbers(scrubber) when is_atom(scrubber), do: [scrubber]    defp get_scrubbers(scrubbers) when is_list(scrubbers), do: scrubbers    defp get_scrubbers(_), do: [Pleroma.HTML.Scrubber.Default] @@ -99,216 +116,3 @@ defmodule Pleroma.HTML do      end)    end  end - -defmodule Pleroma.HTML.Scrubber.TwitterText do -  @moduledoc """ -  An HTML scrubbing policy which limits to twitter-style text.  Only -  paragraphs, breaks and links are allowed through the filter. -  """ - -  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], []) - -  require FastSanitize.Sanitizer.Meta -  alias FastSanitize.Sanitizer.Meta - -  Meta.strip_comments() - -  # links -  Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes) - -  Meta.allow_tag_with_this_attribute_values(:a, "class", [ -    "hashtag", -    "u-url", -    "mention", -    "u-url mention", -    "mention u-url" -  ]) - -  Meta.allow_tag_with_this_attribute_values(:a, "rel", [ -    "tag", -    "nofollow", -    "noopener", -    "noreferrer" -  ]) - -  Meta.allow_tag_with_these_attributes(:a, ["name", "title"]) - -  # paragraphs and linebreaks -  Meta.allow_tag_with_these_attributes(:br, []) -  Meta.allow_tag_with_these_attributes(:p, []) - -  # microformats -  Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"]) -  Meta.allow_tag_with_these_attributes(:span, []) - -  # allow inline images for custom emoji -  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"]) - -    Meta.allow_tag_with_these_attributes(:img, [ -      "width", -      "height", -      "class", -      "title", -      "alt" -    ]) -  end - -  Meta.strip_everything_not_covered() -end - -defmodule Pleroma.HTML.Scrubber.Default do -  @doc "The default HTML scrubbing policy: no " - -  require FastSanitize.Sanitizer.Meta -  alias FastSanitize.Sanitizer.Meta - -  # credo:disable-for-previous-line -  # No idea how to fix this one… - -  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], []) - -  Meta.strip_comments() - -  Meta.allow_tag_with_uri_attributes(:a, ["href", "data-user", "data-tag"], @valid_schemes) - -  Meta.allow_tag_with_this_attribute_values(:a, "class", [ -    "hashtag", -    "u-url", -    "mention", -    "u-url mention", -    "mention u-url" -  ]) - -  Meta.allow_tag_with_this_attribute_values(:a, "rel", [ -    "tag", -    "nofollow", -    "noopener", -    "noreferrer", -    "ugc" -  ]) - -  Meta.allow_tag_with_these_attributes(:a, ["name", "title"]) - -  Meta.allow_tag_with_these_attributes(:abbr, ["title"]) - -  Meta.allow_tag_with_these_attributes(:b, []) -  Meta.allow_tag_with_these_attributes(:blockquote, []) -  Meta.allow_tag_with_these_attributes(:br, []) -  Meta.allow_tag_with_these_attributes(:code, []) -  Meta.allow_tag_with_these_attributes(:del, []) -  Meta.allow_tag_with_these_attributes(:em, []) -  Meta.allow_tag_with_these_attributes(:i, []) -  Meta.allow_tag_with_these_attributes(:li, []) -  Meta.allow_tag_with_these_attributes(:ol, []) -  Meta.allow_tag_with_these_attributes(:p, []) -  Meta.allow_tag_with_these_attributes(:pre, []) -  Meta.allow_tag_with_these_attributes(:strong, []) -  Meta.allow_tag_with_these_attributes(:sub, []) -  Meta.allow_tag_with_these_attributes(:sup, []) -  Meta.allow_tag_with_these_attributes(:u, []) -  Meta.allow_tag_with_these_attributes(:ul, []) - -  Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card"]) -  Meta.allow_tag_with_these_attributes(:span, []) - -  @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. -    Meta.allow_tag_with_uri_attributes(:img, ["src"], ["http", "https"]) - -    Meta.allow_tag_with_these_attributes(:img, [ -      "width", -      "height", -      "class", -      "title", -      "alt" -    ]) -  end - -  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, []) -    Meta.allow_tag_with_these_attributes(:th, []) -    Meta.allow_tag_with_these_attributes(:thead, []) -    Meta.allow_tag_with_these_attributes(:tr, []) -  end - -  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, []) -    Meta.allow_tag_with_these_attributes(:h4, []) -    Meta.allow_tag_with_these_attributes(:h5, []) -  end - -  if Pleroma.Config.get([:markup, :allow_fonts]) do -    Meta.allow_tag_with_these_attributes(:font, ["face"]) -  end - -  Meta.strip_everything_not_covered() -end - -defmodule Pleroma.HTML.Transform.MediaProxy do -  @moduledoc "Transforms inline image URIs to use MediaProxy." - -  alias Pleroma.Web.MediaProxy - -  def before_scrub(html), do: html - -  def scrub_attribute(:img, {"src", "http" <> target}) do -    media_url = -      ("http" <> target) -      |> MediaProxy.url() - -    {"src", media_url} -  end - -  def scrub_attribute(_tag, attribute), do: attribute - -  def scrub({:img, attributes, children}) do -    attributes = -      attributes -      |> Enum.map(fn attr -> scrub_attribute(:img, attr) end) -      |> Enum.reject(&is_nil(&1)) - -    {:img, attributes, children} -  end - -  def scrub({:comment, _text, _children}), do: "" - -  def scrub({tag, attributes, children}), do: {tag, attributes, children} -  def scrub({_tag, children}), do: children -  def scrub(text), do: text -end - -defmodule Pleroma.HTML.Scrubber.LinksOnly do -  @moduledoc """ -  An HTML scrubbing policy which limits to links only. -  """ - -  @valid_schemes Pleroma.Config.get([:uri_schemes, :valid_schemes], []) - -  require FastSanitize.Sanitizer.Meta -  alias FastSanitize.Sanitizer.Meta - -  Meta.strip_comments() - -  # links -  Meta.allow_tag_with_uri_attributes(:a, ["href"], @valid_schemes) - -  Meta.allow_tag_with_this_attribute_values(:a, "rel", [ -    "tag", -    "nofollow", -    "noopener", -    "noreferrer", -    "me", -    "ugc" -  ]) - -  Meta.allow_tag_with_these_attributes(:a, ["name", "title"]) -  Meta.strip_everything_not_covered() -end diff --git a/lib/pleroma/moderation_log.ex b/lib/pleroma/moderation_log.ex index 706f089dc..c81477f48 100644 --- a/lib/pleroma/moderation_log.ex +++ b/lib/pleroma/moderation_log.ex @@ -128,17 +128,35 @@ defmodule Pleroma.ModerationLog do            {:ok, ModerationLog} | {:error, any}    def insert_log(%{          actor: %User{} = actor, -        action: "report_response", +        action: "report_note",          subject: %Activity{} = subject,          text: text        }) do      %ModerationLog{        data: %{          "actor" => user_to_map(actor), -        "action" => "report_response", +        "action" => "report_note",          "subject" => report_to_map(subject), -        "text" => text, -        "message" => "" +        "text" => text +      } +    } +    |> insert_log_entry_with_message() +  end + +  @spec insert_log(%{actor: User, subject: Activity, action: String.t(), text: String.t()}) :: +          {:ok, ModerationLog} | {:error, any} +  def insert_log(%{ +        actor: %User{} = actor, +        action: "report_note_delete", +        subject: %Activity{} = subject, +        text: text +      }) do +    %ModerationLog{ +      data: %{ +        "actor" => user_to_map(actor), +        "action" => "report_note_delete", +        "subject" => report_to_map(subject), +        "text" => text        }      }      |> insert_log_entry_with_message() @@ -556,12 +574,24 @@ defmodule Pleroma.ModerationLog do    def get_log_entry_message(%ModerationLog{          data: %{            "actor" => %{"nickname" => actor_nickname}, -          "action" => "report_response", +          "action" => "report_note", +          "subject" => %{"id" => subject_id, "type" => "report"}, +          "text" => text +        } +      }) do +    "@#{actor_nickname} added note '#{text}' to report ##{subject_id}" +  end + +  @spec get_log_entry_message(ModerationLog) :: String.t() +  def get_log_entry_message(%ModerationLog{ +        data: %{ +          "actor" => %{"nickname" => actor_nickname}, +          "action" => "report_note_delete",            "subject" => %{"id" => subject_id, "type" => "report"},            "text" => text          }        }) do -    "@#{actor_nickname} responded with '#{text}' to report ##{subject_id}" +    "@#{actor_nickname} deleted note '#{text}' from report ##{subject_id}"    end    @spec get_log_entry_message(ModerationLog) :: String.t() diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 71423ce5e..8f3e46af9 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -121,10 +121,28 @@ defmodule Pleroma.Notification do         when is_list(visibility) do      if Enum.all?(visibility, &(&1 in @valid_visibilities)) do        query +      |> join(:left, [n, a], mutated_activity in Pleroma.Activity, +        on: +          fragment("?->>'context'", a.data) == +            fragment("?->>'context'", mutated_activity.data) and +            fragment("(?->>'type' = 'Like' or ?->>'type' = 'Announce')", a.data, a.data) and +            fragment("?->>'type'", mutated_activity.data) == "Create", +        as: :mutated_activity +      )        |> where( -        [n, a], +        [n, a, mutated_activity: mutated_activity],          not fragment( -          "activity_visibility(?, ?, ?) = ANY (?)", +          """ +          CASE WHEN (?->>'type') = 'Like' or (?->>'type') = 'Announce' +            THEN (activity_visibility(?, ?, ?) = ANY (?)) +            ELSE (activity_visibility(?, ?, ?) = ANY (?)) END +          """, +          a.data, +          a.data, +          mutated_activity.actor, +          mutated_activity.recipients, +          mutated_activity.data, +          ^visibility,            a.actor,            a.recipients,            a.data, @@ -139,17 +157,7 @@ defmodule Pleroma.Notification do    defp exclude_visibility(query, %{exclude_visibilities: visibility})         when visibility in @valid_visibilities do -    query -    |> where( -      [n, a], -      not fragment( -        "activity_visibility(?, ?, ?) = (?)", -        a.actor, -        a.recipients, -        a.data, -        ^visibility -      ) -    ) +    exclude_visibility(query, [visibility])    end    defp exclude_visibility(query, %{exclude_visibilities: visibility}) @@ -347,7 +355,7 @@ defmodule Pleroma.Notification do    def skip?(          :followers,          activity, -        %{notification_settings: %{"followers" => false}} = user +        %{notification_settings: %{followers: false}} = user        ) do      actor = activity.data["actor"]      follower = User.get_cached_by_ap_id(actor) @@ -357,14 +365,14 @@ defmodule Pleroma.Notification do    def skip?(          :non_followers,          activity, -        %{notification_settings: %{"non_followers" => false}} = user +        %{notification_settings: %{non_followers: false}} = user        ) do      actor = activity.data["actor"]      follower = User.get_cached_by_ap_id(actor)      !User.following?(follower, user)    end -  def skip?(:follows, activity, %{notification_settings: %{"follows" => false}} = user) do +  def skip?(:follows, activity, %{notification_settings: %{follows: false}} = user) do      actor = activity.data["actor"]      followed = User.get_cached_by_ap_id(actor)      User.following?(user, followed) @@ -373,7 +381,7 @@ defmodule Pleroma.Notification do    def skip?(          :non_follows,          activity, -        %{notification_settings: %{"non_follows" => false}} = user +        %{notification_settings: %{non_follows: false}} = user        ) do      actor = activity.data["actor"]      followed = User.get_cached_by_ap_id(actor) diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index ff0e59241..eb37b95a6 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -23,6 +23,23 @@ defmodule Pleroma.Object do      timestamps()    end +  def with_joined_activity(query, activity_type \\ "Create", join_type \\ :inner) do +    object_position = Map.get(query.aliases, :object, 0) + +    join(query, join_type, [{object, object_position}], a in Activity, +      on: +        fragment( +          "COALESCE(?->'object'->>'id', ?->>'object') = (? ->> 'id') AND (?->>'type' = ?) ", +          a.data, +          a.data, +          object.data, +          a.data, +          ^activity_type +        ), +      as: :object_activity +    ) +  end +    def create(data) do      Object.change(%Object{}, %{data: data})      |> Repo.insert() diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 4d71c91a8..a1bde90f1 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -154,7 +154,7 @@ defmodule Pleroma.Object.Fetcher do    end    def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do -    Logger.info("Fetching object #{id} via AP") +    Logger.debug("Fetching object #{id} via AP")      date = Pleroma.Signature.signed_date() diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index 9d279fba7..4535ca7c5 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -13,60 +13,66 @@ defmodule Pleroma.Pagination do    alias Pleroma.Repo    @default_limit 20 +  @page_keys ["max_id", "min_id", "limit", "since_id", "order"] -  def fetch_paginated(query, params, type \\ :keyset) +  def page_keys, do: @page_keys -  def fetch_paginated(query, %{"total" => true} = params, :keyset) do +  def fetch_paginated(query, params, type \\ :keyset, table_binding \\ nil) + +  def fetch_paginated(query, %{"total" => true} = params, :keyset, table_binding) do      total = Repo.aggregate(query, :count, :id)      %{        total: total, -      items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset) +      items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset, table_binding)      }    end -  def fetch_paginated(query, params, :keyset) do +  def fetch_paginated(query, params, :keyset, table_binding) do      options = cast_params(params)      query -    |> paginate(options, :keyset) +    |> paginate(options, :keyset, table_binding)      |> Repo.all()      |> enforce_order(options)    end -  def fetch_paginated(query, %{"total" => true} = params, :offset) do -    total = Repo.aggregate(query, :count, :id) +  def fetch_paginated(query, %{"total" => true} = params, :offset, table_binding) do +    total = +      query +      |> Ecto.Query.exclude(:left_join) +      |> Repo.aggregate(:count, :id)      %{        total: total, -      items: fetch_paginated(query, Map.drop(params, ["total"]), :offset) +      items: fetch_paginated(query, Map.drop(params, ["total"]), :offset, table_binding)      }    end -  def fetch_paginated(query, params, :offset) do +  def fetch_paginated(query, params, :offset, table_binding) do      options = cast_params(params)      query -    |> paginate(options, :offset) +    |> paginate(options, :offset, table_binding)      |> Repo.all()    end -  def paginate(query, options, method \\ :keyset) +  def paginate(query, options, method \\ :keyset, table_binding \\ nil) -  def paginate(query, options, :keyset) do +  def paginate(query, options, :keyset, table_binding) do      query -    |> restrict(:min_id, options) -    |> restrict(:since_id, options) -    |> restrict(:max_id, options) -    |> restrict(:order, options) -    |> restrict(:limit, options) +    |> restrict(:min_id, options, table_binding) +    |> restrict(:since_id, options, table_binding) +    |> restrict(:max_id, options, table_binding) +    |> restrict(:order, options, table_binding) +    |> restrict(:limit, options, table_binding)    end -  def paginate(query, options, :offset) do +  def paginate(query, options, :offset, table_binding) do      query -    |> restrict(:order, options) -    |> restrict(:offset, options) -    |> restrict(:limit, options) +    |> restrict(:order, options, table_binding) +    |> restrict(:offset, options, table_binding) +    |> restrict(:limit, options, table_binding)    end    defp cast_params(params) do @@ -75,7 +81,8 @@ defmodule Pleroma.Pagination do        since_id: :string,        max_id: :string,        offset: :integer, -      limit: :integer +      limit: :integer, +      skip_order: :boolean      }      params = @@ -88,38 +95,48 @@ defmodule Pleroma.Pagination do      changeset.changes    end -  defp restrict(query, :min_id, %{min_id: min_id}) do -    where(query, [q], q.id > ^min_id) +  defp restrict(query, :min_id, %{min_id: min_id}, table_binding) do +    where(query, [{q, table_position(query, table_binding)}], q.id > ^min_id)    end -  defp restrict(query, :since_id, %{since_id: since_id}) do -    where(query, [q], q.id > ^since_id) +  defp restrict(query, :since_id, %{since_id: since_id}, table_binding) do +    where(query, [{q, table_position(query, table_binding)}], q.id > ^since_id)    end -  defp restrict(query, :max_id, %{max_id: max_id}) do -    where(query, [q], q.id < ^max_id) +  defp restrict(query, :max_id, %{max_id: max_id}, table_binding) do +    where(query, [{q, table_position(query, table_binding)}], q.id < ^max_id)    end -  defp restrict(query, :order, %{min_id: _}) do -    order_by(query, [u], fragment("? asc nulls last", u.id)) +  defp restrict(query, :order, %{skip_order: true}, _), do: query + +  defp restrict(query, :order, %{min_id: _}, table_binding) do +    order_by( +      query, +      [{u, table_position(query, table_binding)}], +      fragment("? asc nulls last", u.id) +    )    end -  defp restrict(query, :order, _options) do -    order_by(query, [u], fragment("? desc nulls last", u.id)) +  defp restrict(query, :order, _options, table_binding) do +    order_by( +      query, +      [{u, table_position(query, table_binding)}], +      fragment("? desc nulls last", u.id) +    )    end -  defp restrict(query, :offset, %{offset: offset}) do +  defp restrict(query, :offset, %{offset: offset}, _table_binding) do      offset(query, ^offset)    end -  defp restrict(query, :limit, options) do +  defp restrict(query, :limit, options, _table_binding) do      limit = Map.get(options, :limit, @default_limit)      query      |> limit(^limit)    end -  defp restrict(query, _, _), do: query +  defp restrict(query, _, _, _), do: query    defp enforce_order(result, %{min_id: _}) do      result @@ -127,4 +144,10 @@ defmodule Pleroma.Pagination do    end    defp enforce_order(result, _), do: result + +  defp table_position(%Ecto.Query{} = query, binding_name) do +    Map.get(query.aliases, binding_name, 0) +  end + +  defp table_position(_, _), do: 0  end diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex index a3278dbef..174a8389c 100644 --- a/lib/pleroma/plugs/oauth_scopes_plug.ex +++ b/lib/pleroma/plugs/oauth_scopes_plug.ex @@ -6,6 +6,7 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do    import Plug.Conn    import Pleroma.Web.Gettext +  alias Pleroma.Config    alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug    @behaviour Plug @@ -15,6 +16,8 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do    def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do      op = options[:op] || :|      token = assigns[:token] + +    scopes = transform_scopes(scopes, options)      matched_scopes = token && filter_descendants(scopes, token.scopes)      cond do @@ -60,6 +63,15 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do      )    end +  @doc "Transforms scopes by applying supported options (e.g. :admin)" +  def transform_scopes(scopes, options) do +    if options[:admin] do +      Config.oauth_admin_scopes(scopes) +    else +      scopes +    end +  end +    defp maybe_perform_instance_privacy_check(%Plug.Conn{} = conn, options) do      if options[:skip_instance_privacy_check] do        conn diff --git a/lib/pleroma/plugs/parsers_plug.ex b/lib/pleroma/plugs/parsers_plug.ex new file mode 100644 index 000000000..2e493ce0e --- /dev/null +++ b/lib/pleroma/plugs/parsers_plug.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.Parsers do +  @moduledoc "Initializes Plug.Parsers with upload limit set at boot time" + +  @behaviour Plug + +  def init(_opts) do +    Plug.Parsers.init( +      parsers: [:urlencoded, :multipart, :json], +      pass: ["*/*"], +      json_decoder: Jason, +      length: Pleroma.Config.get([:instance, :upload_limit]), +      body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []} +    ) +  end + +  defdelegate call(conn, opts), to: Plug.Parsers +end diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex index ee808f31f..582fb1f92 100644 --- a/lib/pleroma/plugs/user_is_admin_plug.ex +++ b/lib/pleroma/plugs/user_is_admin_plug.ex @@ -5,19 +5,38 @@  defmodule Pleroma.Plugs.UserIsAdminPlug do    import Pleroma.Web.TranslationHelpers    import Plug.Conn +    alias Pleroma.User +  alias Pleroma.Web.OAuth    def init(options) do      options    end -  def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do -    conn +  def call(%{assigns: %{user: %User{is_admin: true}} = assigns} = conn, _) do +    token = assigns[:token] + +    cond do +      not Pleroma.Config.enforce_oauth_admin_scope_usage?() -> +        conn + +      token && OAuth.Scopes.contains_admin_scopes?(token.scopes) -> +        # Note: checking for _any_ admin scope presence, not necessarily fitting requested action. +        #   Thus, controller must explicitly invoke OAuthScopesPlug to verify scope requirements. +        conn + +      true -> +        fail(conn) +    end    end    def call(conn, _) do +    fail(conn) +  end + +  defp fail(conn) do      conn -    |> render_error(:forbidden, "User is not admin.") -    |> halt +    |> render_error(:forbidden, "User is not an admin or OAuth admin scope is not granted.") +    |> halt()    end  end diff --git a/lib/pleroma/report_note.ex b/lib/pleroma/report_note.ex new file mode 100644 index 000000000..0db86d1a1 --- /dev/null +++ b/lib/pleroma/report_note.ex @@ -0,0 +1,48 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.ReportNote do +  use Ecto.Schema + +  import Ecto.Changeset +  import Ecto.Query + +  alias Pleroma.Activity +  alias Pleroma.Repo +  alias Pleroma.ReportNote +  alias Pleroma.User + +  @type t :: %__MODULE__{} + +  schema "report_notes" do +    field(:content, :string) +    belongs_to(:user, User, type: FlakeId.Ecto.CompatType) +    belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) + +    timestamps() +  end + +  @spec create(FlakeId.Ecto.CompatType.t(), FlakeId.Ecto.CompatType.t(), String.t()) :: +          {:ok, ReportNote.t()} | {:error, Changeset.t()} +  def create(user_id, activity_id, content) do +    attrs = %{ +      user_id: user_id, +      activity_id: activity_id, +      content: content +    } + +    %ReportNote{} +    |> cast(attrs, [:user_id, :activity_id, :content]) +    |> validate_required([:user_id, :activity_id, :content]) +    |> Repo.insert() +  end + +  @spec destroy(FlakeId.Ecto.CompatType.t()) :: +          {:ok, ReportNote.t()} | {:error, Changeset.t()} +  def destroy(id) do +    from(r in ReportNote, where: r.id == ^id) +    |> Repo.one() +    |> Repo.delete() +  end +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index b7f50e5ac..706aee2ff 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -127,15 +127,13 @@ defmodule Pleroma.User do      field(:invisible, :boolean, default: false)      field(:allow_following_move, :boolean, default: true)      field(:skip_thread_containment, :boolean, default: false) +    field(:actor_type, :string, default: "Person")      field(:also_known_as, {:array, :string}, default: []) -    field(:notification_settings, :map, -      default: %{ -        "followers" => true, -        "follows" => true, -        "non_follows" => true, -        "non_followers" => true -      } +    embeds_one( +      :notification_settings, +      Pleroma.User.NotificationSetting, +      on_replace: :update      )      has_many(:notifications, Notification) @@ -349,6 +347,7 @@ defmodule Pleroma.User do            :following_count,            :discoverable,            :invisible, +          :actor_type,            :also_known_as          ]        ) @@ -399,6 +398,7 @@ defmodule Pleroma.User do          :raw_fields,          :pleroma_settings_store,          :discoverable, +        :actor_type,          :also_known_as        ]      ) @@ -441,6 +441,7 @@ defmodule Pleroma.User do          :discoverable,          :hide_followers_count,          :hide_follows_count, +        :actor_type,          :also_known_as        ]      ) @@ -861,6 +862,13 @@ defmodule Pleroma.User do      |> Repo.all()    end +  def get_friends_ap_ids(user) do +    user +    |> get_friends_query(nil) +    |> select([u], u.ap_id) +    |> Repo.all() +  end +    def get_friends_ids(user, page \\ nil) do      user      |> get_friends_query(page) @@ -1135,7 +1143,8 @@ defmodule Pleroma.User do    def blocks?(nil, _), do: false    def blocks?(%User{} = user, %User{} = target) do -    blocks_user?(user, target) || blocks_domain?(user, target) +    blocks_user?(user, target) || +      (!User.following?(user, target) && blocks_domain?(user, target))    end    def blocks_user?(%User{} = user, %User{} = target) do @@ -1221,20 +1230,9 @@ defmodule Pleroma.User do    end    def update_notification_settings(%User{} = user, settings) do -    settings = -      settings -      |> Enum.map(fn {k, v} -> {k, v in [true, "true", "True", "1"]} end) -      |> Map.new() - -    notification_settings = -      user.notification_settings -      |> Map.merge(settings) -      |> Map.take(["followers", "follows", "non_follows", "non_followers"]) - -    params = %{notification_settings: notification_settings} -      user -    |> cast(params, [:notification_settings]) +    |> cast(%{notification_settings: settings}, []) +    |> cast_embed(:notification_settings)      |> validate_required([:notification_settings])      |> update_and_set_cache()    end @@ -1849,13 +1847,28 @@ defmodule Pleroma.User do    end    def admin_api_update(user, params) do -    user -    |> cast(params, [ -      :is_moderator, -      :is_admin, -      :show_role -    ]) -    |> update_and_set_cache() +    changeset = +      cast(user, params, [ +        :is_moderator, +        :is_admin, +        :show_role +      ]) + +    with {:ok, updated_user} <- update_and_set_cache(changeset) do +      if user.is_admin && !updated_user.is_admin do +        # Tokens & authorizations containing any admin scopes must be revoked (revoking all). +        # This is an extra safety measure (tokens' admin scopes won't be accepted for non-admins). +        global_sign_out(user) +      end + +      {:ok, updated_user} +    end +  end + +  @doc "Signs user out of all applications" +  def global_sign_out(user) do +    OAuth.Authorization.delete_user_authorizations(user) +    OAuth.Token.delete_user_tokens(user)    end    def mascot_update(user, url) do diff --git a/lib/pleroma/user/notification_setting.ex b/lib/pleroma/user/notification_setting.ex new file mode 100644 index 000000000..f0899613e --- /dev/null +++ b/lib/pleroma/user/notification_setting.ex @@ -0,0 +1,40 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.User.NotificationSetting do +  use Ecto.Schema +  import Ecto.Changeset + +  @derive Jason.Encoder +  @primary_key false + +  embedded_schema do +    field(:followers, :boolean, default: true) +    field(:follows, :boolean, default: true) +    field(:non_follows, :boolean, default: true) +    field(:non_followers, :boolean, default: true) +    field(:privacy_option, :boolean, default: false) +  end + +  def changeset(schema, params) do +    schema +    |> cast(prepare_attrs(params), [ +      :followers, +      :follows, +      :non_follows, +      :non_followers, +      :privacy_option +    ]) +  end + +  defp prepare_attrs(params) do +    Enum.reduce(params, %{}, fn +      {k, v}, acc when is_binary(v) -> +        Map.put(acc, k, String.downcase(v)) + +      {k, v}, acc -> +        Map.put(acc, k, v) +    end) +  end +end diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex new file mode 100644 index 000000000..8d36a0001 --- /dev/null +++ b/lib/pleroma/utils.ex @@ -0,0 +1,12 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Utils do +  def compile_dir(dir) when is_binary(dir) do +    dir +    |> File.ls!() +    |> Enum.map(&Path.join(dir, &1)) +    |> Kernel.ParallelCompiler.compile() +  end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 1e2cc2e2b..60c9e7e64 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -950,6 +950,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      blocked_ap_ids = opts["blocked_users_ap_ids"] || User.blocked_users_ap_ids(user)      domain_blocks = user.domain_blocks || [] +    following_ap_ids = User.get_friends_ap_ids(user) +      query =        if has_named_binding?(query, :object), do: query, else: Activity.with_joined_object(query) @@ -964,8 +966,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do            activity.data,            ^blocked_ap_ids          ), -      where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks), -      where: fragment("not (split_part(?->>'actor', '/', 3) = ANY(?))", o.data, ^domain_blocks) +      where: +        fragment( +          "(not (split_part(?, '/', 3) = ANY(?))) or ? = ANY(?)", +          activity.actor, +          ^domain_blocks, +          activity.actor, +          ^following_ap_ids +        ), +      where: +        fragment( +          "(not (split_part(?->>'actor', '/', 3) = ANY(?))) or (?->>'actor') = ANY(?)", +          o.data, +          ^domain_blocks, +          o.data, +          ^following_ap_ids +        )      )    end @@ -1052,6 +1068,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      |> Activity.with_preloaded_bookmark(opts["user"])    end +  defp maybe_preload_report_notes(query, %{"preload_report_notes" => true}) do +    query +    |> Activity.with_preloaded_report_notes() +  end + +  defp maybe_preload_report_notes(query, _), do: query +    defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query    defp maybe_set_thread_muted_field(query, opts) do @@ -1105,6 +1128,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      Activity      |> maybe_preload_objects(opts)      |> maybe_preload_bookmarks(opts) +    |> maybe_preload_report_notes(opts)      |> maybe_set_thread_muted_field(opts)      |> maybe_order(opts)      |> restrict_recipients(recipients, opts["user"]) @@ -1141,6 +1165,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      |> maybe_update_cc(list_memberships, opts["user"])    end +  @doc """ +  Fetch favorites activities of user with order by sort adds to favorites +  """ +  @spec fetch_favourites(User.t(), map(), atom()) :: list(Activity.t()) +  def fetch_favourites(user, params \\ %{}, pagination \\ :keyset) do +    user.ap_id +    |> Activity.Queries.by_actor() +    |> Activity.Queries.by_type("Like") +    |> Activity.with_joined_object() +    |> Object.with_joined_activity() +    |> select([_like, object, activity], %{activity | object: object}) +    |> order_by([like, _, _], desc: like.id) +    |> Pagination.fetch_paginated( +      Map.merge(params, %{"skip_order" => true}), +      pagination, +      :object_activity +    ) +  end +    defp maybe_update_cc(activities, list_memberships, %User{ap_id: user_ap_id})         when is_list(list_memberships) and length(list_memberships) > 0 do      Enum.map(activities, fn @@ -1217,6 +1260,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      data = Transmogrifier.maybe_fix_user_object(data)      discoverable = data["discoverable"] || false      invisible = data["invisible"] || false +    actor_type = data["type"] || "Person"      user_data = %{        ap_id: data["id"], @@ -1232,6 +1276,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do        follower_address: data["followers"],        following_address: data["following"],        bio: data["summary"], +      actor_type: actor_type,        also_known_as: Map.get(data, "alsoKnownAs", [])      } @@ -1253,28 +1298,26 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    def fetch_follow_information_for_user(user) do      with {:ok, following_data} <-             Fetcher.fetch_and_contain_remote_object_from_id(user.following_address), -         following_count when is_integer(following_count) <- following_data["totalItems"],           {:ok, hide_follows} <- collection_private(following_data),           {:ok, followers_data} <-             Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address), -         followers_count when is_integer(followers_count) <- followers_data["totalItems"],           {:ok, hide_followers} <- collection_private(followers_data) do        {:ok,         %{           hide_follows: hide_follows, -         follower_count: followers_count, -         following_count: following_count, +         follower_count: normalize_counter(followers_data["totalItems"]), +         following_count: normalize_counter(following_data["totalItems"]),           hide_followers: hide_followers         }}      else -      {:error, _} = e -> -        e - -      e -> -        {:error, e} +      {:error, _} = e -> e +      e -> {:error, e}      end    end +  defp normalize_counter(counter) when is_integer(counter), do: counter +  defp normalize_counter(_), do: 0 +    defp maybe_update_follow_information(data) do      with {:enabled, true} <-             {:enabled, Pleroma.Config.get([:instance, :external_user_synchronization])}, @@ -1294,24 +1337,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      end    end +  defp collection_private(%{"first" => %{"type" => type}}) +       when type in ["CollectionPage", "OrderedCollectionPage"], +       do: {:ok, false} +    defp collection_private(%{"first" => first}) do -    if is_map(first) and -         first["type"] in ["CollectionPage", "OrderedCollectionPage"] do +    with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <- +           Fetcher.fetch_and_contain_remote_object_from_id(first) do        {:ok, false}      else -      with {:ok, %{"type" => type}} when type in ["CollectionPage", "OrderedCollectionPage"] <- -             Fetcher.fetch_and_contain_remote_object_from_id(first) do -        {:ok, false} -      else -        {:error, {:ok, %{status: code}}} when code in [401, 403] -> -          {:ok, true} - -        {:error, _} = e -> -          e - -        e -> -          {:error, e} -      end +      {:error, {:ok, %{status: code}}} when code in [401, 403] -> {:ok, true} +      {:error, _} = e -> e +      e -> {:error, e}      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 dec5da0d3..5059e3984 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -257,7 +257,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    # only accept relayed Creates    def inbox(conn, %{"type" => "Create"} = params) do -    Logger.info( +    Logger.debug(        "Signature missing or not from author, relayed Create message, fetching object from source"      ) @@ -270,11 +270,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do      headers = Enum.into(conn.req_headers, %{})      if String.contains?(headers["signature"], params["actor"]) do -      Logger.info( +      Logger.debug(          "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"        ) -      Logger.info(inspect(conn.req_headers)) +      Logger.debug(inspect(conn.req_headers))      end      json(conn, dgettext("errors", "error")) diff --git a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex index f7831bc3e..4a5709974 100644 --- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do    @impl true    def filter(object) do -    Logger.info("REJECTING #{inspect(object)}") +    Logger.debug("REJECTING #{inspect(object)}")      {:reject, object}    end diff --git a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex index 26b8539fe..df774b0f7 100644 --- a/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/mediaproxy_warming_policy.ex @@ -18,7 +18,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do    ]    def perform(:prefetch, url) do -    Logger.info("Prefetching #{inspect(url)}") +    Logger.debug("Prefetching #{inspect(url)}")      url      |> MediaProxy.url() diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 4ea37fc7b..db072bad2 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do    alias Pleroma.HTTP    alias Pleroma.Instances    alias Pleroma.Object +  alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.ActivityPub.Relay    alias Pleroma.Web.ActivityPub.Transmogrifier @@ -47,7 +48,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do    * `id`: the ActivityStreams URI of the message    """    def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do -    Logger.info("Federating #{id} to #{inbox}") +    Logger.debug("Federating #{id} to #{inbox}")      %{host: host, path: path} = URI.parse(inbox)      digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) @@ -188,31 +189,35 @@ defmodule Pleroma.Web.ActivityPub.Publisher do      recipients = recipients(actor, activity) -    recipients -    |> Enum.filter(&User.ap_enabled?/1) -    |> Enum.map(fn %{source_data: data} -> data["inbox"] end) -    |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) -    |> Instances.filter_reachable() -    |> Enum.each(fn {inbox, unreachable_since} -> -      %User{ap_id: ap_id} = -        Enum.find(recipients, fn %{source_data: data} -> data["inbox"] == inbox end) - -      # Get all the recipients on the same host and add them to cc. Otherwise, a remote -      # instance would only accept a first message for the first recipient and ignore the rest. -      cc = get_cc_ap_ids(ap_id, recipients) - -      json = -        data -        |> Map.put("cc", cc) -        |> Jason.encode!() - -      Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{ -        inbox: inbox, -        json: json, -        actor_id: actor.id, -        id: activity.data["id"], -        unreachable_since: unreachable_since -      }) +    inboxes = +      recipients +      |> Enum.filter(&User.ap_enabled?/1) +      |> Enum.map(fn %{source_data: data} -> data["inbox"] end) +      |> Enum.filter(fn inbox -> should_federate?(inbox, public) end) +      |> Instances.filter_reachable() + +    Repo.checkout(fn -> +      Enum.each(inboxes, fn {inbox, unreachable_since} -> +        %User{ap_id: ap_id} = +          Enum.find(recipients, fn %{source_data: data} -> data["inbox"] == inbox end) + +        # Get all the recipients on the same host and add them to cc. Otherwise, a remote +        # instance would only accept a first message for the first recipient and ignore the rest. +        cc = get_cc_ap_ids(ap_id, recipients) + +        json = +          data +          |> Map.put("cc", cc) +          |> Jason.encode!() + +        Pleroma.Web.Federator.Publisher.enqueue_one(__MODULE__, %{ +          inbox: inbox, +          json: json, +          actor_id: actor.id, +          id: activity.data["id"], +          unreachable_since: unreachable_since +        }) +      end)      end)    end @@ -223,7 +228,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do      public = is_public?(activity)      if public && Config.get([:instance, :allow_relay]) do -      Logger.info(fn -> "Relaying #{activity.data["id"]} out" end) +      Logger.debug(fn -> "Relaying #{activity.data["id"]} out" end)        Relay.publish(activity)      end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index ecba27bef..3fa789d53 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -397,7 +397,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do          %{"type" => "Create", "object" => %{"type" => objtype} = object} = data,          options        ) -      when objtype in ["Article", "Note", "Video", "Page", "Question", "Answer"] do +      when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer"] do      actor = Containment.get_actor(data)      data = diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 2ca805c09..db7084246 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -22,7 +22,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do    require Logger    require Pleroma.Constants -  @supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer", "Audio"] +  @supported_object_types [ +    "Article", +    "Note", +    "Event", +    "Video", +    "Page", +    "Question", +    "Answer", +    "Audio" +  ]    @strip_status_report_states ~w(closed resolved)    @supported_report_states ~w(open closed resolved)    @valid_visibilities ~w(public unlisted private direct) @@ -787,6 +796,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do        params        |> Map.put("type", "Flag")        |> Map.put("skip_preload", true) +      |> Map.put("preload_report_notes", true)        |> Map.put("total", true)        |> Map.put("limit", page_size)        |> Map.put("offset", (page - 1) * page_size) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index cf08045c9..350c4391d 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -91,7 +91,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do      %{        "id" => user.ap_id, -      "type" => "Person", +      "type" => user.actor_type,        "following" => "#{user.ap_id}/following",        "followers" => "#{user.ap_id}/followers",        "inbox" => "#{user.ap_id}/inbox", @@ -201,7 +201,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do      %{        "id" => "#{user.ap_id}/followers",        "type" => "OrderedCollection", -      "totalItems" => total,        "first" =>          if showing_items do            collection(followers, "#{user.ap_id}/followers", 1, showing_items, total) @@ -209,6 +208,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do            "#{user.ap_id}/followers?page=1"          end      } +    |> maybe_put_total_items(showing_count, total)      |> Map.merge(Utils.make_json_ld_header())    end @@ -251,6 +251,12 @@ defmodule Pleroma.Web.ActivityPub.UserView do      |> Map.merge(Utils.make_json_ld_header())    end +  defp maybe_put_total_items(map, false, _total), do: map + +  defp maybe_put_total_items(map, true, total) do +    Map.put(map, "totalItems", total) +  end +    def collection(collection, iri, page, show_items \\ true, total \\ nil) do      offset = (page - 1) * 10      items = Enum.slice(collection, offset, 10) diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index b003d1f35..c8abeff06 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    alias Pleroma.Activity    alias Pleroma.ModerationLog    alias Pleroma.Plugs.OAuthScopesPlug +  alias Pleroma.ReportNote    alias Pleroma.User    alias Pleroma.UserInviteToken    alias Pleroma.Web.ActivityPub.ActivityPub @@ -30,13 +31,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    plug(      OAuthScopesPlug, -    %{scopes: ["read:accounts"]} +    %{scopes: ["read:accounts"], admin: true}      when action in [:list_users, :user_show, :right_get, :invites]    )    plug(      OAuthScopesPlug, -    %{scopes: ["write:accounts"]} +    %{scopes: ["write:accounts"], admin: true}      when action in [             :get_invite_token,             :revoke_invite, @@ -58,35 +59,37 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    plug(      OAuthScopesPlug, -    %{scopes: ["read:reports"]} when action in [:list_reports, :report_show] +    %{scopes: ["read:reports"], admin: true} +    when action in [:list_reports, :report_show]    )    plug(      OAuthScopesPlug, -    %{scopes: ["write:reports"]} +    %{scopes: ["write:reports"], admin: true}      when action in [:report_update_state, :report_respond]    )    plug(      OAuthScopesPlug, -    %{scopes: ["read:statuses"]} when action == :list_user_statuses +    %{scopes: ["read:statuses"], admin: true} +    when action == :list_user_statuses    )    plug(      OAuthScopesPlug, -    %{scopes: ["write:statuses"]} +    %{scopes: ["write:statuses"], admin: true}      when action in [:status_update, :status_delete]    )    plug(      OAuthScopesPlug, -    %{scopes: ["read"]} +    %{scopes: ["read"], admin: true}      when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]    )    plug(      OAuthScopesPlug, -    %{scopes: ["write"]} +    %{scopes: ["write"], admin: true}      when action in [:relay_follow, :relay_unfollow, :config_update]    ) @@ -238,7 +241,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do        })      conn -    |> put_view(StatusView) +    |> put_view(Pleroma.Web.AdminAPI.StatusView)      |> render("index.json", %{activities: activities, as: :activity})    end @@ -641,9 +644,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    def list_reports(conn, params) do      {page, page_size} = page_params(params) +    reports = Utils.get_reports(params, page, page_size) +      conn      |> put_view(ReportView) -    |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)}) +    |> render("index.json", %{reports: reports})    end    def list_grouped_reports(conn, _params) do @@ -687,32 +692,39 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do      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") +  def report_notes_create(%{assigns: %{user: user}} = conn, %{ +        "id" => report_id, +        "content" => content +      }) do +    with {:ok, _} <- ReportNote.create(user.id, report_id, content) do +      ModerationLog.insert_log(%{ +        action: "report_note", +        actor: user, +        subject: Activity.get_by_id(report_id), +        text: content +      }) -      {:ok, activity} = CommonAPI.post(user, params) +      json_response(conn, :no_content, "") +    else +      _ -> json_response(conn, :bad_request, "") +    end +  end +  def report_notes_delete(%{assigns: %{user: user}} = conn, %{ +        "id" => note_id, +        "report_id" => report_id +      }) do +    with {:ok, note} <- ReportNote.destroy(note_id) do        ModerationLog.insert_log(%{ -        action: "report_response", +        action: "report_note_delete",          actor: user, -        subject: activity, -        text: params["status"] +        subject: Activity.get_by_id(report_id), +        text: note.content        }) -      conn -      |> put_view(StatusView) -      |> render("show.json", %{activity: activity}) +      json_response(conn, :no_content, "")      else -      true -> -        {:param_cast, nil} - -      nil -> -        {:error, :not_found} +      _ -> json_response(conn, :bad_request, "")      end    end diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index 13602efd9..4880d2992 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -39,7 +39,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do        content: content,        created_at: created_at,        statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}), -      state: report.data["state"] +      state: report.data["state"], +      notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})      }    end @@ -69,6 +70,28 @@ defmodule Pleroma.Web.AdminAPI.ReportView do      }    end +  def render("index_notes.json", %{notes: notes}) when is_list(notes) do +    Enum.map(notes, &render(__MODULE__, "show_note.json", &1)) +  end + +  def render("index_notes.json", _), do: [] + +  def render("show_note.json", %{ +        id: id, +        content: content, +        user_id: user_id, +        inserted_at: inserted_at +      }) do +    user = User.get_by_id(user_id) + +    %{ +      id: id, +      content: content, +      user: merge_account_views(user), +      created_at: Utils.to_masto_date(inserted_at) +    } +  end +    defp merge_account_views(%User{} = user) do      Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user})      |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})) diff --git a/lib/pleroma/web/admin_api/views/status_view.ex b/lib/pleroma/web/admin_api/views/status_view.ex new file mode 100644 index 000000000..6f2b2b09c --- /dev/null +++ b/lib/pleroma/web/admin_api/views/status_view.ex @@ -0,0 +1,42 @@ +# 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.StatusView do +  use Pleroma.Web, :view + +  require Pleroma.Constants + +  alias Pleroma.User + +  def render("index.json", opts) do +    render_many(opts.activities, __MODULE__, "show.json", opts) +  end + +  def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do +    user = get_user(activity.data["actor"]) + +    Pleroma.Web.MastodonAPI.StatusView.render("show.json", opts) +    |> Map.merge(%{account: merge_account_views(user)}) +  end + +  defp merge_account_views(%User{} = user) do +    Pleroma.Web.MastodonAPI.AccountView.render("show.json", %{user: user}) +    |> Map.merge(Pleroma.Web.AdminAPI.AccountView.render("show.json", %{user: user})) +  end + +  defp merge_account_views(_), do: %{} + +  defp get_user(ap_id) do +    cond do +      user = User.get_cached_by_ap_id(ap_id) -> +        user + +      user = User.get_by_guessed_nickname(ap_id) -> +        user + +      true -> +        User.error_user(ap_id) +    end +  end +end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 49735b5c2..d32c38a05 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -59,16 +59,9 @@ defmodule Pleroma.Web.Endpoint do    plug(Pleroma.Plugs.TrailingFormatPlug)    plug(Plug.RequestId) -  plug(Plug.Logger) +  plug(Plug.Logger, log: :debug) -  plug( -    Plug.Parsers, -    parsers: [:urlencoded, :multipart, :json], -    pass: ["*/*"], -    json_decoder: Jason, -    length: Pleroma.Config.get([:instance, :upload_limit]), -    body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []} -  ) +  plug(Pleroma.Plugs.Parsers)    plug(Plug.MethodOverride)    plug(Plug.Head) diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index e8a56ebd7..f506a7d24 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -58,7 +58,7 @@ defmodule Pleroma.Web.Federator do    end    def perform(:incoming_ap_doc, params) do -    Logger.info("Handling incoming AP activity") +    Logger.debug("Handling incoming AP activity")      params = Utils.normalize_params(params) @@ -71,13 +71,13 @@ defmodule Pleroma.Web.Federator do        {:ok, activity}      else        %Activity{} -> -        Logger.info("Already had #{params["id"]}") +        Logger.debug("Already had #{params["id"]}")          :error        _e ->          # Just drop those for now -        Logger.info("Unhandled activity") -        Logger.info(Jason.encode!(params, pretty: true)) +        Logger.debug("Unhandled activity") +        Logger.debug(Jason.encode!(params, pretty: true))          :error      end    end diff --git a/lib/pleroma/web/federator/publisher.ex b/lib/pleroma/web/federator/publisher.ex index fb9b26649..1d045c644 100644 --- a/lib/pleroma/web/federator/publisher.ex +++ b/lib/pleroma/web/federator/publisher.ex @@ -47,7 +47,7 @@ defmodule Pleroma.Web.Federator.Publisher do      Config.get([:instance, :federation_publisher_modules])      |> Enum.each(fn module ->        if module.is_representable?(activity) do -        Logger.info("Publishing #{activity.data["id"]} using #{inspect(module)}") +        Logger.debug("Publishing #{activity.data["id"]} using #{inspect(module)}")          module.publish(user, activity)        end      end) diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index d19029cb5..38d14256f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -188,6 +188,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do          {:ok, Map.merge(user.pleroma_settings_store, value)}        end)        |> add_if_present(params, "default_scope", :default_scope) +      |> add_if_present(params, "actor_type", :actor_type)      emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "") diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 74b223cf4..1149fb469 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -346,15 +346,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do    @doc "GET /api/v1/favourites"    def favourites(%{assigns: %{user: user}} = conn, params) do -    params = -      params -      |> Map.put("type", "Create") -      |> Map.put("favorited_by", user.ap_id) -      |> Map.put("blocking_user", user) -      activities = -      ActivityPub.fetch_activities([], params) -      |> Enum.reverse() +      ActivityPub.fetch_favourites( +        user, +        Map.take(params, Pleroma.Pagination.page_keys()) +      )      conn      |> add_link_headers(activities) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 546cc0ed5..a5420f480 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -86,7 +86,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do          0        end -    bot = (user.source_data["type"] || "Person") in ["Application", "Service"] +    bot = user.actor_type in ["Application", "Service"]      emojis =        (user.source_data["tag"] || []) @@ -137,7 +137,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do          sensitive: false,          fields: user.raw_fields,          pleroma: %{ -          discoverable: user.discoverable +          discoverable: user.discoverable, +          actor_type: user.actor_type          }        }, diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index a0257dfa6..e9590224b 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -421,7 +421,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      end    end -  def render_content(%{data: %{"type" => "Video"}} = object) do +  def render_content(%{data: %{"type" => object_type}} = object) +      when object_type in ["Video", "Event"] do      with name when not is_nil(name) and name != "" <- object.data["name"] do        "<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"      else diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/twitter_card.ex index d6a6049b3..67419a666 100644 --- a/lib/pleroma/web/metadata/twitter_card.ex +++ b/lib/pleroma/web/metadata/twitter_card.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do        if attachments == [] or Metadata.activity_nsfw?(object) do          [            image_tag(user), -          {:meta, [property: "twitter:card", content: "summary_large_image"], []} +          {:meta, [property: "twitter:card", content: "summary"], []}          ]        else          attachments diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 2aee8cab2..87acdec97 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -222,7 +222,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do           {:user_active, true} <- {:user_active, !user.deactivated},           {:password_reset_pending, false} <-             {:password_reset_pending, user.password_reset_pending}, -         {:ok, scopes} <- validate_scopes(app, params), +         {:ok, scopes} <- validate_scopes(app, params, user),           {:ok, auth} <- Authorization.create_authorization(app, user, scopes),           {:ok, token} <- Token.exchange_token(app, auth) do        json(conn, Token.Response.build(user, token)) @@ -471,7 +471,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do             {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},           %App{} = app <- Repo.get_by(App, client_id: client_id),           true <- redirect_uri in String.split(app.redirect_uris), -         {:ok, scopes} <- validate_scopes(app, auth_attrs), +         {:ok, scopes} <- validate_scopes(app, auth_attrs, user),           {:auth_active, true} <- {:auth_active, User.auth_active?(user)} do        Authorization.create_authorization(app, user, scopes)      end @@ -487,12 +487,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do    defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),      do: put_session(conn, :registration_id, registration_id) -  @spec validate_scopes(App.t(), map()) :: +  @spec validate_scopes(App.t(), map(), User.t()) ::            {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} -  defp validate_scopes(app, params) do +  defp validate_scopes(%App{} = app, params, %User{} = user) do      params      |> Scopes.fetch_scopes(app.scopes) -    |> Scopes.validate(app.scopes) +    |> Scopes.validate(app.scopes, user)    end    def default_redirect_uri(%App{} = app) do diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex index 48bd14407..00da225b9 100644 --- a/lib/pleroma/web/oauth/scopes.ex +++ b/lib/pleroma/web/oauth/scopes.ex @@ -7,6 +7,9 @@ defmodule Pleroma.Web.OAuth.Scopes do    Functions for dealing with scopes.    """ +  alias Pleroma.Plugs.OAuthScopesPlug +  alias Pleroma.User +    @doc """    Fetch scopes from request params. @@ -53,15 +56,38 @@ defmodule Pleroma.Web.OAuth.Scopes do    @doc """    Validates scopes.    """ -  @spec validate(list() | nil, list()) :: +  @spec validate(list() | nil, list(), User.t()) ::            {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} -  def validate([], _app_scopes), do: {:error, :missing_scopes} -  def validate(nil, _app_scopes), do: {:error, :missing_scopes} +  def validate(blank_scopes, _app_scopes, _user) when blank_scopes in [nil, []], +    do: {:error, :missing_scopes} -  def validate(scopes, app_scopes) do -    case Pleroma.Plugs.OAuthScopesPlug.filter_descendants(scopes, app_scopes) do +  def validate(scopes, app_scopes, %User{} = user) do +    with {:ok, _} <- ensure_scopes_support(scopes, app_scopes), +         {:ok, scopes} <- authorize_admin_scopes(scopes, app_scopes, user) do +      {:ok, scopes} +    end +  end + +  defp ensure_scopes_support(scopes, app_scopes) do +    case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do        ^scopes -> {:ok, scopes}        _ -> {:error, :unsupported_scopes}      end    end + +  defp authorize_admin_scopes(scopes, app_scopes, %User{} = user) do +    if user.is_admin || !contains_admin_scopes?(scopes) || !contains_admin_scopes?(app_scopes) do +      {:ok, scopes} +    else +      # Gracefully dropping admin scopes from requested scopes if user isn't an admin (not raising) +      scopes = scopes -- OAuthScopesPlug.filter_descendants(scopes, ["admin"]) +      validate(scopes, app_scopes, user) +    end +  end + +  def contains_admin_scopes?(scopes) do +    scopes +    |> OAuthScopesPlug.filter_descendants(["admin"]) +    |> Enum.any?() +  end  end diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex index f639f9c6f..3c9c580d5 100644 --- a/lib/pleroma/web/oauth/token/clean_worker.ex +++ b/lib/pleroma/web/oauth/token/clean_worker.ex @@ -11,11 +11,6 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do    @ten_seconds 10_000    @one_day 86_400_000 -  @interval Pleroma.Config.get( -              [:oauth2, :clean_expired_tokens_interval], -              @one_day -            ) -    alias Pleroma.Web.OAuth.Token    alias Pleroma.Workers.BackgroundWorker @@ -29,8 +24,9 @@ defmodule Pleroma.Web.OAuth.Token.CleanWorker do    @doc false    def handle_info(:perform, state) do      BackgroundWorker.enqueue("clean_expired_tokens", %{}) +    interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day) -    Process.send_after(self(), :perform, @interval) +    Process.send_after(self(), :perform, interval)      {:noreply, state}    end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index a474d41d4..69dfa92e3 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do    plug(      OAuthScopesPlug, -    %{scopes: ["write"]} +    %{scopes: ["write"], admin: true}      when action in [             :create,             :delete, diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index a6a924d02..34ec1d8d9 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -22,8 +22,8 @@ defmodule Pleroma.Web.Push.Impl do    @spec perform(Notification.t()) :: list(any) | :error    def perform(          %{ -          activity: %{data: %{"type" => activity_type}, id: activity_id} = activity, -          user_id: user_id +          activity: %{data: %{"type" => activity_type}} = activity, +          user: %User{id: user_id}          } = notif        )        when activity_type in @types do @@ -39,18 +39,17 @@ defmodule Pleroma.Web.Push.Impl do      for subscription <- fetch_subsriptions(user_id),          get_in(subscription.data, ["alerts", type]) do        %{ -        title: format_title(notif),          access_token: subscription.token.token, -        body: format_body(notif, actor, object),          notification_id: notif.id,          notification_type: type,          icon: avatar_url,          preferred_locale: "en",          pleroma: %{ -          activity_id: activity_id, +          activity_id: notif.activity.id,            direct_conversation_id: direct_conversation_id          }        } +      |> Map.merge(build_content(notif, actor, object))        |> Jason.encode!()        |> push_message(build_sub(subscription), gcm_api_key, subscription)      end @@ -100,6 +99,24 @@ defmodule Pleroma.Web.Push.Impl do      }    end +  def build_content( +        %{ +          activity: %{data: %{"directMessage" => true}}, +          user: %{notification_settings: %{privacy_option: true}} +        }, +        actor, +        _ +      ) do +    %{title: "New Direct Message", body: "@#{actor.nickname}"} +  end + +  def build_content(notif, actor, object) do +    %{ +      title: format_title(notif), +      body: format_body(notif, actor, object) +    } +  end +    def format_body(          %{activity: %{data: %{"type" => "Create"}}},          actor, diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index e6c4f6f14..f6c128283 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -187,7 +187,8 @@ defmodule Pleroma.Web.Router do      get("/grouped_reports", AdminAPIController, :list_grouped_reports)      get("/reports/:id", AdminAPIController, :report_show)      patch("/reports", AdminAPIController, :reports_update) -    post("/reports/:id/respond", AdminAPIController, :report_respond) +    post("/reports/:id/notes", AdminAPIController, :report_notes_create) +    delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete)      put("/statuses/:id", AdminAPIController, :status_update)      delete("/statuses/:id", AdminAPIController, :status_delete) @@ -528,7 +529,10 @@ defmodule Pleroma.Web.Router do      get("/users/:nickname/feed", Feed.FeedController, :feed)      get("/users/:nickname", Feed.FeedController, :feed_redirect) +  end +  scope "/", Pleroma.Web do +    pipe_through(:browser)      get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)    end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 2305bb413..799dd17ae 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -104,7 +104,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    defp is_status?(acct) do      case Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(acct) do -      {:ok, %{"type" => type}} when type in ["Article", "Note", "Video", "Page", "Question"] -> +      {:ok, %{"type" => type}} +      when type in ["Article", "Event", "Note", "Video", "Page", "Question"] ->          true        _ -> diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex index 61b451e3e..a978c4013 100644 --- a/lib/pleroma/workers/web_pusher_worker.ex +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -13,7 +13,7 @@ defmodule Pleroma.Workers.WebPusherWorker do      notification =        Notification        |> Repo.get(notification_id) -      |> Repo.preload([:activity]) +      |> Repo.preload([:activity, :user])      Pleroma.Web.Push.Impl.perform(notification)    end | 
