diff options
Diffstat (limited to 'lib')
68 files changed, 1700 insertions, 843 deletions
| diff --git a/lib/healthcheck.ex b/lib/healthcheck.ex new file mode 100644 index 000000000..646fb3b9d --- /dev/null +++ b/lib/healthcheck.ex @@ -0,0 +1,60 @@ +defmodule Pleroma.Healthcheck do +  @moduledoc """ +  Module collects metrics about app and assign healthy status. +  """ +  alias Pleroma.Healthcheck +  alias Pleroma.Repo + +  defstruct pool_size: 0, +            active: 0, +            idle: 0, +            memory_used: 0, +            healthy: true + +  @type t :: %__MODULE__{ +          pool_size: non_neg_integer(), +          active: non_neg_integer(), +          idle: non_neg_integer(), +          memory_used: number(), +          healthy: boolean() +        } + +  @spec system_info() :: t() +  def system_info do +    %Healthcheck{ +      memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2) +    } +    |> assign_db_info() +    |> check_health() +  end + +  defp assign_db_info(healthcheck) do +    database = Application.get_env(:pleroma, 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] + +    db_info = +      Enum.reduce(result.rows, %{active: 0, idle: 0}, fn [state, cnt], states -> +        if state == "active" do +          Map.put(states, :active, states.active + cnt) +        else +          Map.put(states, :idle, states.idle + cnt) +        end +      end) +      |> Map.put(:pool_size, pool_size) + +    Map.merge(healthcheck, db_info) +  end + +  @spec check_health(Healthcheck.t()) :: Healthcheck.t() +  def check_health(%{pool_size: pool_size, active: active} = check) +      when active >= pool_size do +    %{check | healthy: false} +  end + +  def check_health(check), do: check +end diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex new file mode 100644 index 000000000..ab9a3a7ff --- /dev/null +++ b/lib/mix/tasks/pleroma/database.ex @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.Database do +  alias Mix.Tasks.Pleroma.Common +  require Logger +  use Mix.Task + +  @shortdoc "A collection of database related tasks" +  @moduledoc """ +   A collection of database related tasks + +   ## Replace embedded objects with their references + +   Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration. + +       mix pleroma.database remove_embedded_objects + +    Options: +    - `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references +  """ +  def run(["remove_embedded_objects" | args]) do +    {options, [], []} = +      OptionParser.parse( +        args, +        strict: [ +          vacuum: :boolean +        ] +      ) + +    Common.start_pleroma() +    Logger.info("Removing embedded objects") + +    Pleroma.Repo.query!( +      "update activities set data = jsonb_set(data, '{object}'::text[], data->'object'->'id') where data->'object'->>'id' is not null;", +      [], +      timeout: :infinity +    ) + +    if Keyword.get(options, :vacuum) do +      Logger.info("Runnning VACUUM FULL") + +      Pleroma.Repo.query!( +        "vacuum full;", +        [], +        timeout: :infinity +      ) +    end +  end +end diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex new file mode 100644 index 000000000..cced73226 --- /dev/null +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -0,0 +1,293 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.Emoji do +  use Mix.Task + +  @shortdoc "Manages emoji packs" +  @moduledoc """ +  Manages emoji packs + +  ## ls-packs + +      mix pleroma.emoji ls-packs [OPTION...] + +  Lists the emoji packs and metadata specified in the manifest. + +  ### Options + +  - `-m, --manifest PATH/URL` - path to a custom manifest, it can +    either be an URL starting with `http`, in that case the +    manifest will be fetched from that address, or a local path + +  ## get-packs + +      mix pleroma.emoji get-packs [OPTION...] PACKS + +  Fetches, verifies and installs the specified PACKS from the +  manifest into the `STATIC-DIR/emoji/PACK-NAME` + +  ### Options + +  - `-m, --manifest PATH/URL` - same as ls-packs + +  ## gen-pack + +      mix pleroma.emoji gen-pack PACK-URL + +  Creates a new manifest entry and a file list from the specified +  remote pack file. Currently, only .zip archives are recognized +  as remote pack files and packs are therefore assumed to be zip +  archives. This command is intended to run interactively and will +  first ask you some basic questions about the pack, then download +  the remote file and generate an SHA256 checksum for it, then +  generate an emoji file list for you. + +  The manifest entry will either be written to a newly created +  `index.json` file or appended to the existing one, *replacing* +  the old pack with the same name if it was in the file previously. + +  The file list will be written to the file specified previously, +  *replacing* that file. You _should_ check that the file list doesn't +  contain anything you don't need in the pack, that is, anything that is +  not an emoji (the whole pack is downloaded, but only emoji files +  are extracted). +  """ + +  @default_manifest Pleroma.Config.get!([:emoji, :default_manifest]) + +  def run(["ls-packs" | args]) do +    Application.ensure_all_started(:hackney) + +    {options, [], []} = parse_global_opts(args) + +    manifest = +      fetch_manifest(if options[:manifest], do: options[:manifest], else: @default_manifest) + +    Enum.each(manifest, fn {name, info} -> +      to_print = [ +        {"Name", name}, +        {"Homepage", info["homepage"]}, +        {"Description", info["description"]}, +        {"License", info["license"]}, +        {"Source", info["src"]} +      ] + +      for {param, value} <- to_print do +        IO.puts(IO.ANSI.format([:bright, param, :normal, ": ", value])) +      end + +      # A newline +      IO.puts("") +    end) +  end + +  def run(["get-packs" | args]) do +    Application.ensure_all_started(:hackney) + +    {options, pack_names, []} = parse_global_opts(args) + +    manifest_url = if options[:manifest], do: options[:manifest], else: @default_manifest + +    manifest = fetch_manifest(manifest_url) + +    for pack_name <- pack_names do +      if Map.has_key?(manifest, pack_name) do +        pack = manifest[pack_name] +        src_url = pack["src"] + +        IO.puts( +          IO.ANSI.format([ +            "Downloading ", +            :bright, +            pack_name, +            :normal, +            " from ", +            :underline, +            src_url +          ]) +        ) + +        binary_archive = Tesla.get!(src_url).body +        archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16() + +        sha_status_text = ["SHA256 of ", :bright, pack_name, :normal, " source file is ", :bright] + +        if archive_sha == String.upcase(pack["src_sha256"]) do +          IO.puts(IO.ANSI.format(sha_status_text ++ [:green, "OK"])) +        else +          IO.puts(IO.ANSI.format(sha_status_text ++ [:red, "BAD"])) + +          raise "Bad SHA256 for #{pack_name}" +        end + +        # The url specified in files should be in the same directory +        files_url = Path.join(Path.dirname(manifest_url), pack["files"]) + +        IO.puts( +          IO.ANSI.format([ +            "Fetching the file list for ", +            :bright, +            pack_name, +            :normal, +            " from ", +            :underline, +            files_url +          ]) +        ) + +        files = Tesla.get!(files_url).body |> Poison.decode!() + +        IO.puts(IO.ANSI.format(["Unpacking ", :bright, pack_name])) + +        pack_path = +          Path.join([ +            Pleroma.Config.get!([:instance, :static_dir]), +            "emoji", +            pack_name +          ]) + +        files_to_unzip = +          Enum.map( +            files, +            fn {_, f} -> to_charlist(f) end +          ) + +        {:ok, _} = +          :zip.unzip(binary_archive, +            cwd: pack_path, +            file_list: files_to_unzip +          ) + +        IO.puts(IO.ANSI.format(["Writing emoji.txt for ", :bright, pack_name])) + +        emoji_txt_str = +          Enum.map( +            files, +            fn {shortcode, path} -> +              emojo_path = Path.join("/emoji/#{pack_name}", path) +              "#{shortcode}, #{emojo_path}" +            end +          ) +          |> Enum.join("\n") + +        File.write!(Path.join(pack_path, "emoji.txt"), emoji_txt_str) +      else +        IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"])) +      end +    end +  end + +  def run(["gen-pack", src]) do +    Application.ensure_all_started(:hackney) + +    proposed_name = Path.basename(src) |> Path.rootname() +    name = String.trim(IO.gets("Pack name [#{proposed_name}]: ")) +    # If there's no name, use the default one +    name = if String.length(name) > 0, do: name, else: proposed_name + +    license = String.trim(IO.gets("License: ")) +    homepage = String.trim(IO.gets("Homepage: ")) +    description = String.trim(IO.gets("Description: ")) + +    proposed_files_name = "#{name}.json" +    files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: ")) +    files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name + +    default_exts = [".png", ".gif"] +    default_exts_str = Enum.join(default_exts, " ") + +    exts = +      String.trim( +        IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ") +      ) + +    exts = +      if String.length(exts) > 0 do +        String.split(exts, " ") +        |> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end) +      else +        default_exts +      end + +    IO.puts("Downloading the pack and generating SHA256") + +    binary_archive = Tesla.get!(src).body +    archive_sha = :crypto.hash(:sha256, binary_archive) |> Base.encode16() + +    IO.puts("SHA256 is #{archive_sha}") + +    pack_json = %{ +      name => %{ +        license: license, +        homepage: homepage, +        description: description, +        src: src, +        src_sha256: archive_sha, +        files: files_name +      } +    } + +    tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}") + +    {:ok, _} = +      :zip.unzip( +        binary_archive, +        cwd: tmp_pack_dir +      ) + +    emoji_map = Pleroma.Emoji.make_shortcode_to_file_map(tmp_pack_dir, exts) + +    File.write!(files_name, Poison.encode!(emoji_map, pretty: true)) + +    IO.puts(""" + +    #{files_name} has been created and contains the list of all found emojis in the pack. +    Please review the files in the remove those not needed. +    """) + +    if File.exists?("index.json") do +      existing_data = File.read!("index.json") |> Poison.decode!() + +      File.write!( +        "index.json", +        Poison.encode!( +          Map.merge( +            existing_data, +            pack_json +          ), +          pretty: true +        ) +      ) + +      IO.puts("index.json file has been update with the #{name} pack") +    else +      File.write!("index.json", Poison.encode!(pack_json, pretty: true)) + +      IO.puts("index.json has been created with the #{name} pack") +    end +  end + +  defp fetch_manifest(from) do +    Poison.decode!( +      if String.starts_with?(from, "http") do +        Tesla.get!(from).body +      else +        File.read!(from) +      end +    ) +  end + +  defp parse_global_opts(args) do +    OptionParser.parse( +      args, +      strict: [ +        manifest: :string +      ], +      aliases: [ +        m: :manifest +      ] +    ) +  end +end diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 8f8d86a11..6cee8d630 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -24,10 +24,12 @@ defmodule Mix.Tasks.Pleroma.Instance do    - `--domain DOMAIN` - the domain of your instance    - `--instance-name INSTANCE_NAME` - the name of your instance    - `--admin-email ADMIN_EMAIL` - the email address of the instance admin +  - `--notify-email NOTIFY_EMAIL` - email address for notifications    - `--dbhost HOSTNAME` - the hostname of the PostgreSQL database to use    - `--dbname DBNAME` - the name of the database to use    - `--dbuser DBUSER` - the user (aka role) to use for the database connection    - `--dbpass DBPASS` - the password to use for the database connection +  - `--indexable Y/N` - Allow/disallow indexing site by search engines    """    def run(["gen" | rest]) do @@ -41,10 +43,12 @@ defmodule Mix.Tasks.Pleroma.Instance do            domain: :string,            instance_name: :string,            admin_email: :string, +          notify_email: :string,            dbhost: :string,            dbname: :string,            dbuser: :string, -          dbpass: :string +          dbpass: :string, +          indexable: :string          ],          aliases: [            o: :output, @@ -61,7 +65,7 @@ defmodule Mix.Tasks.Pleroma.Instance do      will_overwrite = Enum.filter(paths, &File.exists?/1)      proceed? = Enum.empty?(will_overwrite) or Keyword.get(options, :force, false) -    unless not proceed? do +    if proceed? do        [domain, port | _] =          String.split(            Common.get_option( @@ -81,6 +85,14 @@ defmodule Mix.Tasks.Pleroma.Instance do        email = Common.get_option(options, :admin_email, "What is your admin email address?") +      notify_email = +        Common.get_option( +          options, +          :notify_email, +          "What email address do you want to use for sending email notifications?", +          email +        ) +        indexable =          Common.get_option(            options, @@ -122,6 +134,7 @@ defmodule Mix.Tasks.Pleroma.Instance do            domain: domain,            port: port,            email: email, +          notify_email: notify_email,            name: name,            dbhost: dbhost,            dbname: dbname, diff --git a/lib/mix/tasks/pleroma/sample_config.eex b/lib/mix/tasks/pleroma/sample_config.eex index 1c935c0d8..52bd57cb7 100644 --- a/lib/mix/tasks/pleroma/sample_config.eex +++ b/lib/mix/tasks/pleroma/sample_config.eex @@ -13,6 +13,7 @@ config :pleroma, Pleroma.Web.Endpoint,  config :pleroma, :instance,    name: "<%= name %>",    email: "<%= email %>", +  notify_email: "<%= notify_email %>",    limit: 5000,    registrations_open: true,    dedupe_media: false @@ -75,4 +76,3 @@ config :web_push_encryption, :vapid_details,  #  storage_url: "https://swift-endpoint.prodider.com/v1/AUTH_<tenant>/<container>",  #  object_url: "https://cdn-endpoint.provider.com/<container>"  # - diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 441168df2..b396ff0de 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -162,7 +162,7 @@ defmodule Mix.Tasks.Pleroma.User do    def run(["rm", nickname]) do      Common.start_pleroma() -    with %User{local: true} = user <- User.get_by_nickname(nickname) do +    with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do        User.delete(user)        Mix.shell().info("User #{nickname} deleted.")      else @@ -174,7 +174,7 @@ defmodule Mix.Tasks.Pleroma.User do    def run(["toggle_activated", nickname]) do      Common.start_pleroma() -    with %User{} = user <- User.get_by_nickname(nickname) do +    with %User{} = user <- User.get_cached_by_nickname(nickname) do        {:ok, user} = User.deactivate(user, !user.info.deactivated)        Mix.shell().info( @@ -189,7 +189,7 @@ defmodule Mix.Tasks.Pleroma.User do    def run(["reset_password", nickname]) do      Common.start_pleroma() -    with %User{local: true} = user <- User.get_by_nickname(nickname), +    with %User{local: true} = user <- User.get_cached_by_nickname(nickname),           {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do        Mix.shell().info("Generated password reset token for #{user.nickname}") @@ -211,14 +211,14 @@ defmodule Mix.Tasks.Pleroma.User do    def run(["unsubscribe", nickname]) do      Common.start_pleroma() -    with %User{} = user <- User.get_by_nickname(nickname) do +    with %User{} = user <- User.get_cached_by_nickname(nickname) do        Mix.shell().info("Deactivating #{user.nickname}")        User.deactivate(user)        {:ok, friends} = User.get_friends(user)        Enum.each(friends, fn friend -> -        user = User.get_by_id(user.id) +        user = User.get_cached_by_id(user.id)          Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")          User.unfollow(user, friend) @@ -226,7 +226,7 @@ defmodule Mix.Tasks.Pleroma.User do        :timer.sleep(500) -      user = User.get_by_id(user.id) +      user = User.get_cached_by_id(user.id)        if Enum.empty?(user.following) do          Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}") @@ -250,7 +250,7 @@ defmodule Mix.Tasks.Pleroma.User do          ]        ) -    with %User{local: true} = user <- User.get_by_nickname(nickname) do +    with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do        user =          case Keyword.get(options, :moderator) do            nil -> user @@ -277,7 +277,7 @@ defmodule Mix.Tasks.Pleroma.User do    def run(["tag", nickname | tags]) do      Common.start_pleroma() -    with %User{} = user <- User.get_by_nickname(nickname) do +    with %User{} = user <- User.get_cached_by_nickname(nickname) do        user = user |> User.tag(tags)        Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}") @@ -290,7 +290,7 @@ defmodule Mix.Tasks.Pleroma.User do    def run(["untag", nickname | tags]) do      Common.start_pleroma() -    with %User{} = user <- User.get_by_nickname(nickname) do +    with %User{} = user <- User.get_cached_by_nickname(nickname) do        user = user |> User.untag(tags)        Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}") @@ -379,7 +379,7 @@ defmodule Mix.Tasks.Pleroma.User do    def run(["delete_activities", nickname]) do      Common.start_pleroma() -    with %User{local: true} = user <- User.get_by_nickname(nickname) do +    with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do        User.delete_user_activities(user)        Mix.shell().info("User #{nickname} statuses deleted.")      else diff --git a/lib/pleroma/PasswordResetToken.ex b/lib/pleroma/PasswordResetToken.ex index 7afbc8751..f31ea5bc5 100644 --- a/lib/pleroma/PasswordResetToken.ex +++ b/lib/pleroma/PasswordResetToken.ex @@ -39,7 +39,7 @@ defmodule Pleroma.PasswordResetToken do    def reset_password(token, data) do      with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), -         %User{} = user <- User.get_by_id(token.user_id), +         %User{} = user <- User.get_cached_by_id(token.user_id),           {:ok, _user} <- User.reset_password(user, data),           {:ok, token} <- Repo.update(used_changeset(token)) do        {:ok, token} diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index ab8861b27..4a2ded518 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Activity do    alias Pleroma.Object    alias Pleroma.Repo +  import Ecto.Changeset    import Ecto.Query    @type t :: %__MODULE__{} @@ -79,6 +80,13 @@ defmodule Pleroma.Activity do      )    end +  def change(struct, params \\ %{}) do +    struct +    |> cast(params, [:data]) +    |> validate_required([:data]) +    |> unique_constraint(:ap_id, name: :activities_unique_apid_index) +  end +    def get_by_ap_id_with_object(ap_id) do      Repo.one(        from( @@ -196,21 +204,27 @@ defmodule Pleroma.Activity do    def create_by_object_ap_id_with_object(_), do: nil -  def get_create_by_object_ap_id_with_object(ap_id) do +  def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do      ap_id      |> create_by_object_ap_id_with_object()      |> Repo.one()    end -  def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"]) -  def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id) -  def normalize(_), do: nil +  def get_create_by_object_ap_id_with_object(_), do: nil -  def get_in_reply_to_activity(%Activity{data: %{"object" => %{"inReplyTo" => ap_id}}}) do -    get_create_by_object_ap_id(ap_id) +  defp get_in_reply_to_activity_from_object(%Object{data: %{"inReplyTo" => ap_id}}) do +    get_create_by_object_ap_id_with_object(ap_id)    end -  def get_in_reply_to_activity(_), do: nil +  defp get_in_reply_to_activity_from_object(_), do: nil + +  def get_in_reply_to_activity(%Activity{data: %{"object" => object}}) do +    get_in_reply_to_activity_from_object(Object.normalize(object)) +  end + +  def normalize(obj) when is_map(obj), do: get_by_ap_id_with_object(obj["id"]) +  def normalize(ap_id) when is_binary(ap_id), do: get_by_ap_id_with_object(ap_id) +  def normalize(_), do: nil    def delete_by_ap_id(id) when is_binary(id) do      by_object_ap_id(id) @@ -218,6 +232,7 @@ defmodule Pleroma.Activity do      |> Repo.delete_all()      |> elem(1)      |> Enum.find(fn +      %{data: %{"type" => "Create", "object" => ap_id}} when is_binary(ap_id) -> ap_id == id        %{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id        _ -> nil      end) @@ -245,50 +260,4 @@ defmodule Pleroma.Activity do      |> where([s], s.actor == ^actor)      |> Repo.all()    end - -  def increase_replies_count(id) do -    Activity -    |> where(id: ^id) -    |> update([a], -      set: [ -        data: -          fragment( -            """ -            jsonb_set(?, '{object, repliesCount}', -              (coalesce((?->'object'->>'repliesCount')::int, 0) + 1)::varchar::jsonb, true) -            """, -            a.data, -            a.data -          ) -      ] -    ) -    |> Repo.update_all([]) -    |> case do -      {1, [activity]} -> activity -      _ -> {:error, "Not found"} -    end -  end - -  def decrease_replies_count(id) do -    Activity -    |> where(id: ^id) -    |> update([a], -      set: [ -        data: -          fragment( -            """ -            jsonb_set(?, '{object, repliesCount}', -              (greatest(0, (?->'object'->>'repliesCount')::int - 1))::varchar::jsonb, true) -            """, -            a.data, -            a.data -          ) -      ] -    ) -    |> Repo.update_all([]) -    |> case do -      {1, [activity]} -> activity -      _ -> {:error, "Not found"} -    end -  end  end diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex new file mode 100644 index 000000000..7f8fd43b6 --- /dev/null +++ b/lib/pleroma/bookmark.ex @@ -0,0 +1,60 @@ +defmodule Pleroma.Bookmark do +  use Ecto.Schema + +  import Ecto.Changeset +  import Ecto.Query + +  alias Pleroma.Activity +  alias Pleroma.Bookmark +  alias Pleroma.FlakeId +  alias Pleroma.Repo +  alias Pleroma.User + +  @type t :: %__MODULE__{} + +  schema "bookmarks" do +    belongs_to(:user, User, type: FlakeId) +    belongs_to(:activity, Activity, type: FlakeId) + +    timestamps() +  end + +  @spec create(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()} +  def create(user_id, activity_id) do +    attrs = %{ +      user_id: user_id, +      activity_id: activity_id +    } + +    %Bookmark{} +    |> cast(attrs, [:user_id, :activity_id]) +    |> validate_required([:user_id, :activity_id]) +    |> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index) +    |> Repo.insert() +  end + +  @spec for_user_query(FlakeId.t()) :: Ecto.Query.t() +  def for_user_query(user_id) do +    Bookmark +    |> where(user_id: ^user_id) +    |> join(:inner, [b], activity in assoc(b, :activity)) +    |> preload([b, a], activity: a) +  end + +  def get(user_id, activity_id) do +    Bookmark +    |> where(user_id: ^user_id) +    |> where(activity_id: ^activity_id) +    |> Repo.one() +  end + +  @spec destroy(FlakeId.t(), FlakeId.t()) :: {:ok, Bookmark.t()} | {:error, Changeset.t()} +  def destroy(user_id, activity_id) do +    from(b in Bookmark, +      where: b.user_id == ^user_id, +      where: b.activity_id == ^activity_id +    ) +    |> Repo.one() +    |> Repo.delete() +  end +end diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index afefccec5..df0f72f96 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.AdminEmail do +defmodule Pleroma.Emails.AdminEmail do    @moduledoc "Admin emails"    import Swoosh.Email @@ -11,7 +11,10 @@ defmodule Pleroma.AdminEmail do    defp instance_config, do: Pleroma.Config.get(:instance)    defp instance_name, do: instance_config()[:name] -  defp instance_email, do: instance_config()[:email] + +  defp instance_notify_email do +    Keyword.get(instance_config(), :notify_email, instance_config()[:email]) +  end    defp user_url(user) do      Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, user.nickname) @@ -59,7 +62,7 @@ defmodule Pleroma.AdminEmail do      new()      |> to({to.name, to.email}) -    |> from({instance_name(), instance_email()}) +    |> from({instance_name(), instance_notify_email()})      |> reply_to({reporter.name, reporter.email})      |> subject("#{instance_name()} Report")      |> html_body(html_body) diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index b384e6fec..53f5a661c 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Mailer do +defmodule Pleroma.Emails.Mailer do    use Swoosh.Mailer, otp_app: :pleroma    def deliver_async(email, config \\ []) do diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index a3a09e96c..8502a0d0c 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -2,7 +2,7 @@  # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>  # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.UserEmail do +defmodule Pleroma.Emails.UserEmail do    @moduledoc "User emails"    import Swoosh.Email @@ -15,7 +15,8 @@ defmodule Pleroma.UserEmail do    defp instance_name, do: instance_config()[:name]    defp sender do -    {instance_name(), instance_config()[:email]} +    email = Keyword.get(instance_config(), :notify_email, instance_config()[:email]) +    {instance_name(), email}    end    defp recipient(email, nil), do: email diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex index 87c7f2cec..6390cce4c 100644 --- a/lib/pleroma/emoji.ex +++ b/lib/pleroma/emoji.ex @@ -6,7 +6,7 @@ defmodule Pleroma.Emoji do    @moduledoc """    The emojis are loaded from: -    * the built-in Finmojis (if enabled in configuration), +    * emoji packs in INSTANCE-DIR/emoji      * the files: `config/emoji.txt` and `config/custom_emoji.txt`      * glob paths, nested folder is used as tag name for grouping e.g. priv/static/emoji/custom/nested_folder @@ -14,6 +14,8 @@ defmodule Pleroma.Emoji do    """    use GenServer +  require Logger +    @type pattern :: Regex.t() | module() | String.t()    @type patterns :: pattern() | [pattern()]    @type group_patterns :: keyword(patterns()) @@ -79,95 +81,94 @@ defmodule Pleroma.Emoji do    end    defp load do -    finmoji_enabled = Keyword.get(Application.get_env(:pleroma, :instance), :finmoji_enabled) +    emoji_dir_path = +      Path.join( +        Pleroma.Config.get!([:instance, :static_dir]), +        "emoji" +      ) + +    case File.ls(emoji_dir_path) do +      {:error, :enoent} -> +        # The custom emoji directory doesn't exist, +        # don't do anything +        nil + +      {:error, e} -> +        # There was some other error +        Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}") + +      {:ok, packs} -> +        # Print the packs we've found +        Logger.info("Found emoji packs: #{Enum.join(packs, ", ")}") + +        emojis = +          Enum.flat_map( +            packs, +            fn pack -> load_pack(Path.join(emoji_dir_path, pack)) end +          ) + +        true = :ets.insert(@ets, emojis) +    end + +    # 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] || []      emojis = -      (load_finmoji(finmoji_enabled) ++ -         load_from_file("config/emoji.txt") ++ +      (load_from_file("config/emoji.txt") ++           load_from_file("config/custom_emoji.txt") ++           load_from_globs(shortcode_globs))        |> Enum.reject(fn value -> value == nil end)      true = :ets.insert(@ets, emojis) +      :ok    end -  @finmoji [ -    "a_trusted_friend", -    "alandislands", -    "association", -    "auroraborealis", -    "baby_in_a_box", -    "bear", -    "black_gold", -    "christmasparty", -    "crosscountryskiing", -    "cupofcoffee", -    "education", -    "fashionista_finns", -    "finnishlove", -    "flag", -    "forest", -    "four_seasons_of_bbq", -    "girlpower", -    "handshake", -    "happiness", -    "headbanger", -    "icebreaker", -    "iceman", -    "joulutorttu", -    "kaamos", -    "kalsarikannit_f", -    "kalsarikannit_m", -    "karjalanpiirakka", -    "kicksled", -    "kokko", -    "lavatanssit", -    "losthopes_f", -    "losthopes_m", -    "mattinykanen", -    "meanwhileinfinland", -    "moominmamma", -    "nordicfamily", -    "out_of_office", -    "peacemaker", -    "perkele", -    "pesapallo", -    "polarbear", -    "pusa_hispida_saimensis", -    "reindeer", -    "sami", -    "sauna_f", -    "sauna_m", -    "sauna_whisk", -    "sisu", -    "stuck", -    "suomimainittu", -    "superfood", -    "swan", -    "the_cap", -    "the_conductor", -    "the_king", -    "the_voice", -    "theoriginalsanta", -    "tomoffinland", -    "torillatavataan", -    "unbreakable", -    "waiting", -    "white_nights", -    "woollysocks" -  ] - -  defp load_finmoji(true) do -    Enum.map(@finmoji, fn finmoji -> -      file_name = "/finmoji/128px/#{finmoji}-128.png" -      group = match_extra(@groups, file_name) -      {finmoji, file_name, to_string(group)} -    end) +  defp load_pack(pack_dir) do +    pack_name = Path.basename(pack_dir) + +    emoji_txt = Path.join(pack_dir, "emoji.txt") + +    if File.exists?(emoji_txt) do +      load_from_file(emoji_txt) +    else +      Logger.info( +        "No emoji.txt found for pack \"#{pack_name}\", assuming all .png files are emoji" +      ) + +      make_shortcode_to_file_map(pack_dir, [".png"]) +      |> Enum.map(fn {shortcode, rel_file} -> +        filename = Path.join("/emoji/#{pack_name}", rel_file) + +        {shortcode, filename, [to_string(match_extra(@groups, filename))]} +      end) +    end +  end + +  def make_shortcode_to_file_map(pack_dir, exts) do +    find_all_emoji(pack_dir, exts) +    |> Enum.map(&Path.relative_to(&1, pack_dir)) +    |> Enum.map(fn f -> {f |> Path.basename() |> Path.rootname(), f} end) +    |> Enum.into(%{})    end -  defp load_finmoji(_), do: [] +  def find_all_emoji(dir, exts) do +    Enum.reduce( +      File.ls!(dir), +      [], +      fn f, acc -> +        filepath = Path.join(dir, f) + +        if File.dir?(filepath) do +          acc ++ find_all_emoji(filepath, exts) +        else +          acc ++ [filepath] +        end +      end +    ) +    |> Enum.filter(fn f -> Path.extname(f) in exts end) +  end    defp load_from_file(file) do      if File.exists?(file) do @@ -182,11 +183,11 @@ defmodule Pleroma.Emoji do      |> Stream.map(&String.trim/1)      |> Stream.map(fn line ->        case String.split(line, ~r/,\s*/) do -        [name, file, tags] -> -          {name, file, tags} -          [name, file] -> -          {name, file, to_string(match_extra(@groups, file))} +          {name, file, [to_string(match_extra(@groups, file))]} + +        [name, file | tags] -> +          {name, file, tags}          _ ->            nil @@ -209,7 +210,7 @@ defmodule Pleroma.Emoji do        tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path)))        shortcode = Path.basename(path, Path.extname(path))        external_path = Path.join("/", Path.relative_to(path, static_path)) -      {shortcode, external_path, to_string(tag)} +      {shortcode, external_path, [to_string(tag)]}      end)    end diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex index 8ea9dbd38..dab8910c1 100644 --- a/lib/pleroma/formatter.ex +++ b/lib/pleroma/formatter.ex @@ -9,20 +9,31 @@ defmodule Pleroma.Formatter do    alias Pleroma.Web.MediaProxy    @safe_mention_regex ~r/^(\s*(?<mentions>@.+?\s+)+)(?<rest>.*)/ +  @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui    @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ -  @link_regex ~r{((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+}ui -  # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength    @auto_linker_config hashtag: true,                        hashtag_handler: &Pleroma.Formatter.hashtag_handler/4,                        mention: true,                        mention_handler: &Pleroma.Formatter.mention_handler/4 +  def escape_mention_handler("@" <> nickname = mention, buffer, _, _) do +    case User.get_cached_by_nickname(nickname) do +      %User{} -> +        # escape markdown characters with `\\` +        # (we don't want something like @user__name to be parsed by markdown) +        String.replace(mention, @markdown_characters_regex, "\\\\\\1") + +      _ -> +        buffer +    end +  end +    def mention_handler("@" <> nickname, buffer, opts, acc) do      case User.get_cached_by_nickname(nickname) do        %User{id: id} = user ->          ap_id = get_ap_id(user) -        nickname_text = get_nickname_text(nickname, opts) |> maybe_escape(opts) +        nickname_text = get_nickname_text(nickname, opts)          link =            "<span class='h-card'><a data-user='#{id}' class='u-url mention' href='#{ap_id}'>@<span>#{ @@ -70,6 +81,25 @@ defmodule Pleroma.Formatter do      end    end +  @doc """ +  Escapes a special characters in mention names. +  """ +  def mentions_escape(text, options \\ []) do +    options = +      Keyword.merge(options, +        mention: true, +        url: false, +        mention_handler: &Pleroma.Formatter.escape_mention_handler/4 +      ) + +    if options[:safe_mention] && Regex.named_captures(@safe_mention_regex, text) do +      %{"mentions" => mentions, "rest" => rest} = Regex.named_captures(@safe_mention_regex, text) +      AutoLinker.link(mentions, options) <> AutoLinker.link(rest, options) +    else +      AutoLinker.link(text, options) +    end +  end +    def emojify(text) do      emojify(text, Emoji.get_all())    end @@ -140,10 +170,4 @@ defmodule Pleroma.Formatter do    defp get_nickname_text(nickname, %{mentions_format: :full}), do: User.full_nickname(nickname)    defp get_nickname_text(nickname, _), do: User.local_nickname(nickname) - -  defp maybe_escape(str, %{mentions_escape: true}) do -    String.replace(str, @markdown_characters_regex, "\\\\\\1") -  end - -  defp maybe_escape(str, _), do: str  end diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex index 6a56a6f67..1d2e0785c 100644 --- a/lib/pleroma/gopher/server.ex +++ b/lib/pleroma/gopher/server.ex @@ -38,6 +38,7 @@ end  defmodule Pleroma.Gopher.Server.ProtocolHandler do    alias Pleroma.Activity    alias Pleroma.HTML +  alias Pleroma.Object    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Visibility @@ -75,14 +76,14 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do      |> Enum.map(fn activity ->        user = User.get_cached_by_ap_id(activity.data["actor"]) -      object = activity.data["object"] +      object = Object.normalize(activity)        like_count = object["like_count"] || 0        announcement_count = object["announcement_count"] || 0        link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>          info("#{like_count} likes, #{announcement_count} repeats") <>          "i\tfake\t(NULL)\t0\r\n" <> -        info(HTML.strip_tags(String.replace(activity.data["object"]["content"], "<br>", "\r"))) +        info(HTML.strip_tags(String.replace(object["content"], "<br>", "\r")))      end)      |> Enum.join("i\tfake\t(NULL)\t0\r\n")    end diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex index 7f1dbe28c..cf6c0ee0a 100644 --- a/lib/pleroma/html.ex +++ b/lib/pleroma/html.ex @@ -32,7 +32,8 @@ defmodule Pleroma.HTML do      key = "#{key}#{generate_scrubber_signature(scrubbers)}|#{activity.id}"      Cachex.fetch!(:scrubber_cache, key, fn _key -> -      ensure_scrubbed_html(content, scrubbers, activity.data["object"]["fake"] || false) +      object = Pleroma.Object.normalize(activity) +      ensure_scrubbed_html(content, scrubbers, object.data["fake"] || false)      end)    end @@ -105,7 +106,14 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do    # links    Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes) -  Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"]) + +  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", @@ -114,12 +122,15 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do      "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_these_attributes("span", ["class"]) +  Meta.allow_tag_with_this_attribute_values("span", "class", ["h-card"]) +  Meta.allow_tag_with_these_attributes("span", [])    # allow inline images for custom emoji    @allow_inline_images Keyword.get(@markup, :allow_inline_images) @@ -154,7 +165,14 @@ defmodule Pleroma.HTML.Scrubber.Default do    Meta.strip_comments()    Meta.allow_tag_with_uri_attributes("a", ["href", "data-user", "data-tag"], @valid_schemes) -  Meta.allow_tag_with_these_attributes("a", ["name", "title", "class"]) + +  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", @@ -163,6 +181,8 @@ defmodule Pleroma.HTML.Scrubber.Default do      "noreferrer"    ]) +  Meta.allow_tag_with_these_attributes("a", ["name", "title"]) +    Meta.allow_tag_with_these_attributes("abbr", ["title"])    Meta.allow_tag_with_these_attributes("b", []) @@ -176,11 +196,13 @@ defmodule Pleroma.HTML.Scrubber.Default do    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("span", ["class"])    Meta.allow_tag_with_these_attributes("strong", [])    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 Keyword.get(@markup, :allow_inline_images)    if @allow_inline_images do diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex index 110be8355..a5b1cad68 100644 --- a/lib/pleroma/list.ex +++ b/lib/pleroma/list.ex @@ -80,7 +80,7 @@ defmodule Pleroma.List do    # Get lists to which the account belongs.    def get_lists_account_belongs(%User{} = owner, account_id) do -    user = User.get_by_id(account_id) +    user = User.get_cached_by_id(account_id)      query =        from( diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 15789907a..dd274cf6b 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -98,6 +98,14 @@ defmodule Pleroma.Notification do      |> Repo.delete_all()    end +  def destroy_multiple(%{id: user_id} = _user, ids) do +    from(n in Notification, +      where: n.id in ^ids, +      where: n.user_id == ^user_id +    ) +    |> Repo.delete_all() +  end +    def dismiss(%{id: user_id} = _user, id) do      notification = Repo.get(Notification, id) @@ -173,8 +181,7 @@ defmodule Pleroma.Notification do    def skip?(:muted, activity, user) do      actor = activity.data["actor"] -    User.mutes?(user, %{ap_id: actor}) or -      CommonAPI.thread_muted?(user, activity) +    User.mutes?(user, %{ap_id: actor}) or CommonAPI.thread_muted?(user, activity)    end    def skip?( @@ -189,7 +196,7 @@ defmodule Pleroma.Notification do    def skip?(:follows, activity, %{info: %{notification_settings: %{"follows" => false}}} = user) do      actor = activity.data["actor"] -    followed = User.get_by_ap_id(actor) +    followed = User.get_cached_by_ap_id(actor)      User.following?(user, followed)    end diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 013d62157..740d687a3 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Object do    alias Pleroma.Activity    alias Pleroma.Object +  alias Pleroma.Object.Fetcher    alias Pleroma.ObjectTombstone    alias Pleroma.Repo    alias Pleroma.User @@ -40,41 +41,44 @@ defmodule Pleroma.Object do      Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))    end +  def normalize(_, fetch_remote \\ true)    # If we pass an Activity to Object.normalize(), we can try to use the preloaded object.    # Use this whenever possible, especially when walking graphs in an O(N) loop! -  def normalize(%Activity{object: %Object{} = object}), do: object +  def normalize(%Object{} = object, _), do: object +  def normalize(%Activity{object: %Object{} = object}, _), do: object    # A hack for fake activities -  def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}) do +  def normalize(%Activity{data: %{"object" => %{"fake" => true} = data}}, _) do      %Object{id: "pleroma:fake_object_id", data: data}    end    # Catch and log Object.normalize() calls where the Activity's child object is not    # preloaded. -  def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}) do +  def normalize(%Activity{data: %{"object" => %{"id" => ap_id}}}, fetch_remote) do      Logger.debug(        "Object.normalize() called without preloaded object (#{ap_id}).  Consider preloading the object!"      )      Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") -    normalize(ap_id) +    normalize(ap_id, fetch_remote)    end -  def normalize(%Activity{data: %{"object" => ap_id}}) do +  def normalize(%Activity{data: %{"object" => ap_id}}, fetch_remote) do      Logger.debug(        "Object.normalize() called without preloaded object (#{ap_id}).  Consider preloading the object!"      )      Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") -    normalize(ap_id) +    normalize(ap_id, fetch_remote)    end    # Old way, try fetching the object through cache. -  def normalize(%{"id" => ap_id}), do: normalize(ap_id) -  def normalize(ap_id) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id) -  def normalize(_), do: nil +  def normalize(%{"id" => ap_id}, fetch_remote), do: normalize(ap_id, fetch_remote) +  def normalize(ap_id, false) when is_binary(ap_id), do: get_cached_by_ap_id(ap_id) +  def normalize(ap_id, true) when is_binary(ap_id), do: Fetcher.fetch_object_from_id!(ap_id) +  def normalize(_, _), do: nil    # Owned objects can only be mutated by their owner    def authorize_mutation(%Object{data: %{"actor" => actor}}, %User{ap_id: ap_id}), diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex new file mode 100644 index 000000000..25bd911fb --- /dev/null +++ b/lib/pleroma/object/containment.ex @@ -0,0 +1,61 @@ +defmodule Pleroma.Object.Containment do +  @moduledoc """ +  # Object Containment + +  This module contains some useful functions for containing objects to specific +  origins and determining those origins.  They previously lived in the +  ActivityPub `Transmogrifier` module. + +  Object containment is an important step in validating remote objects to prevent +  spoofing, therefore removal of object containment functions is NOT recommended. +  """ +  def get_actor(%{"actor" => actor}) when is_binary(actor) do +    actor +  end + +  def get_actor(%{"actor" => actor}) when is_list(actor) do +    if is_binary(Enum.at(actor, 0)) do +      Enum.at(actor, 0) +    else +      Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end) +      |> Map.get("id") +    end +  end + +  def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do +    id +  end + +  def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do +    get_actor(%{"actor" => actor}) +  end + +  @doc """ +  Checks that an imported AP object's actor matches the domain it came from. +  """ +  def contain_origin(_id, %{"actor" => nil}), do: :error + +  def contain_origin(id, %{"actor" => _actor} = params) do +    id_uri = URI.parse(id) +    actor_uri = URI.parse(get_actor(params)) + +    if id_uri.host == actor_uri.host do +      :ok +    else +      :error +    end +  end + +  def contain_origin_from_id(_id, %{"id" => nil}), do: :error + +  def contain_origin_from_id(id, %{"id" => other_id} = _params) do +    id_uri = URI.parse(id) +    other_uri = URI.parse(other_id) + +    if id_uri.host == other_uri.host do +      :ok +    else +      :error +    end +  end +end diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex new file mode 100644 index 000000000..8d4bcc95e --- /dev/null +++ b/lib/pleroma/object/fetcher.ex @@ -0,0 +1,75 @@ +defmodule Pleroma.Object.Fetcher do +  alias Pleroma.Object +  alias Pleroma.Object.Containment +  alias Pleroma.Web.ActivityPub.Transmogrifier +  alias Pleroma.Web.OStatus + +  require Logger + +  @httpoison Application.get_env(:pleroma, :httpoison) + +  # TODO: +  # This will create a Create activity, which we need internally at the moment. +  def fetch_object_from_id(id) do +    if object = Object.get_cached_by_ap_id(id) do +      {:ok, object} +    else +      Logger.info("Fetching #{id} via AP") + +      with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), +           nil <- Object.normalize(data, false), +           params <- %{ +             "type" => "Create", +             "to" => data["to"], +             "cc" => data["cc"], +             "actor" => data["actor"] || data["attributedTo"], +             "object" => data +           }, +           :ok <- Containment.contain_origin(id, params), +           {:ok, activity} <- Transmogrifier.handle_incoming(params) do +        {:ok, Object.normalize(activity, false)} +      else +        {:error, {:reject, nil}} -> +          {:reject, nil} + +        object = %Object{} -> +          {:ok, object} + +        _e -> +          Logger.info("Couldn't get object via AP, trying out OStatus fetching...") + +          case OStatus.fetch_activity_from_url(id) do +            {:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)} +            e -> e +          end +      end +    end +  end + +  def fetch_object_from_id!(id) do +    with {:ok, object} <- fetch_object_from_id(id) do +      object +    else +      _e -> +        nil +    end +  end + +  def fetch_and_contain_remote_object_from_id(id) do +    Logger.info("Fetching object #{id} via AP") + +    with true <- String.starts_with?(id, "http"), +         {:ok, %{body: body, status: code}} when code in 200..299 <- +           @httpoison.get( +             id, +             [{:Accept, "application/activity+json"}] +           ), +         {:ok, data} <- Jason.decode(body), +         :ok <- Containment.contain_origin_from_id(id, data) do +      {:ok, data} +    else +      e -> +        {:error, e} +    end +  end +end diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index 7c864deef..f435e5c9c 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -36,6 +36,12 @@ defmodule Pleroma.Pagination do        limit: :integer      } +    params = +      Enum.reduce(params, %{}, fn +        {key, _value}, acc when is_atom(key) -> Map.drop(acc, [key]) +        {key, value}, acc -> Map.put(acc, key, value) +      end) +      changeset = cast({%{}, param_types}, params, Map.keys(param_types))      changeset.changes    end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 6e2269aff..c5b1ddc5d 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.Bookmark    alias Pleroma.Formatter    alias Pleroma.Notification    alias Pleroma.Object @@ -53,8 +54,8 @@ defmodule Pleroma.User do      field(:search_rank, :float, virtual: true)      field(:search_type, :integer, virtual: true)      field(:tags, {:array, :string}, default: []) -    field(:bookmarks, {:array, :string}, default: [])      field(:last_refreshed_at, :naive_datetime_usec) +    has_many(:bookmarks, Bookmark)      has_many(:notifications, Notification)      has_many(:registrations, Registration)      embeds_one(:info, Pleroma.User.Info) @@ -269,6 +270,7 @@ defmodule Pleroma.User do    def register(%Ecto.Changeset{} = changeset) do      with {:ok, user} <- Repo.insert(changeset),           {:ok, user} <- autofollow_users(user), +         {:ok, user} <- set_cache(user),           {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),           {:ok, _} <- try_send_confirmation_email(user) do        {:ok, user} @@ -279,8 +281,10 @@ defmodule Pleroma.User do      if user.info.confirmation_pending &&           Pleroma.Config.get([:instance, :account_activation_required]) do        user -      |> Pleroma.UserEmail.account_confirmation_email() -      |> Pleroma.Mailer.deliver_async() +      |> Pleroma.Emails.UserEmail.account_confirmation_email() +      |> Pleroma.Emails.Mailer.deliver_async() + +      {:ok, :enqueued}      else        {:ok, :noop}      end @@ -451,10 +455,13 @@ defmodule Pleroma.User do      name = List.last(String.split(ap_id, "/"))      nickname = "#{name}@#{domain}" -    get_by_nickname(nickname) +    get_cached_by_nickname(nickname)    end -  def set_cache(user) do +  def set_cache({:ok, user}), do: set_cache(user) +  def set_cache({:error, err}), do: {:error, err} + +  def set_cache(%User{} = user) do      Cachex.put(:user_cache, "ap_id:#{user.ap_id}", user)      Cachex.put(:user_cache, "nickname:#{user.nickname}", user)      Cachex.put(:user_cache, "user_info:#{user.id}", user_info(user)) @@ -542,6 +549,7 @@ defmodule Pleroma.User do          with [_nick, _domain] <- String.split(nickname, "@"),               {:ok, user} <- fetch_by_nickname(nickname) do            if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do +            # TODO turn into job              {:ok, _} = Task.start(__MODULE__, :fetch_initial_posts, [user])            end @@ -1000,7 +1008,7 @@ defmodule Pleroma.User do    # helper to handle the block given only an actor's AP id    def block(blocker, %{ap_id: ap_id}) do -    block(blocker, User.get_by_ap_id(ap_id)) +    block(blocker, get_cached_by_ap_id(ap_id))    end    def unblock(blocker, %{ap_id: ap_id}) do @@ -1030,7 +1038,7 @@ defmodule Pleroma.User do    end    def subscribed_to?(user, %{ap_id: ap_id}) do -    with %User{} = target <- User.get_by_ap_id(ap_id) do +    with %User{} = target <- get_cached_by_ap_id(ap_id) do        Enum.member?(target.info.subscribers, user.ap_id)      end    end @@ -1205,7 +1213,7 @@ defmodule Pleroma.User do    end    def get_or_fetch_by_ap_id(ap_id) do -    user = get_by_ap_id(ap_id) +    user = get_cached_by_ap_id(ap_id)      if !is_nil(user) and !User.needs_update?(user) do        user @@ -1228,7 +1236,7 @@ defmodule Pleroma.User do    def get_or_create_instance_user do      relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay" -    if user = get_by_ap_id(relay_uri) do +    if user = get_cached_by_ap_id(relay_uri) do        user      else        changes = @@ -1275,13 +1283,11 @@ defmodule Pleroma.User do    defp blank?(n), do: n    def insert_or_update_user(data) do -    data = -      data -      |> Map.put(:name, blank?(data[:name]) || data[:nickname]) - -    cs = User.remote_user_creation(data) - -    Repo.insert(cs, on_conflict: :replace_all, conflict_target: :nickname) +    data +    |> Map.put(:name, blank?(data[:name]) || data[:nickname]) +    |> remote_user_creation() +    |> Repo.insert(on_conflict: :replace_all, conflict_target: :nickname) +    |> set_cache()    end    def ap_enabled?(%User{local: true}), do: true @@ -1297,8 +1303,8 @@ defmodule Pleroma.User do    # this is because we have synchronous follow APIs and need to simulate them    # with an async handshake    def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do -    with %User{} = a <- User.get_by_id(a.id), -         %User{} = b <- User.get_by_id(b.id) do +    with %User{} = a <- User.get_cached_by_id(a.id), +         %User{} = b <- User.get_cached_by_id(b.id) do        {:ok, a, b}      else        _e -> @@ -1308,8 +1314,8 @@ defmodule Pleroma.User do    def wait_and_refresh(timeout, %User{} = a, %User{} = b) do      with :ok <- :timer.sleep(timeout), -         %User{} = a <- User.get_by_id(a.id), -         %User{} = b <- User.get_by_id(b.id) do +         %User{} = a <- User.get_cached_by_id(a.id), +         %User{} = b <- User.get_cached_by_id(b.id) do        {:ok, a, b}      else        _e -> @@ -1348,7 +1354,7 @@ defmodule Pleroma.User do    end    def tag(nickname, tags) when is_binary(nickname), -    do: tag(User.get_by_nickname(nickname), tags) +    do: tag(get_by_nickname(nickname), tags)    def tag(%User{} = user, tags),      do: update_tags(user, Enum.uniq((user.tags || []) ++ normalize_tags(tags))) @@ -1360,7 +1366,7 @@ defmodule Pleroma.User do    end    def untag(nickname, tags) when is_binary(nickname), -    do: untag(User.get_by_nickname(nickname), tags) +    do: untag(get_by_nickname(nickname), tags)    def untag(%User{} = user, tags),      do: update_tags(user, (user.tags || []) -- normalize_tags(tags)) @@ -1374,22 +1380,6 @@ defmodule Pleroma.User do      updated_user    end -  def bookmark(%User{} = user, status_id) do -    bookmarks = Enum.uniq(user.bookmarks ++ [status_id]) -    update_bookmarks(user, bookmarks) -  end - -  def unbookmark(%User{} = user, status_id) do -    bookmarks = Enum.uniq(user.bookmarks -- [status_id]) -    update_bookmarks(user, bookmarks) -  end - -  def update_bookmarks(%User{} = user, bookmarks) do -    user -    |> change(%{bookmarks: bookmarks}) -    |> update_and_set_cache -  end -    defp normalize_tags(tags) do      [tags]      |> List.flatten() diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex index 5afa7988c..a3658d57f 100644 --- a/lib/pleroma/user/info.ex +++ b/lib/pleroma/user/info.ex @@ -38,6 +38,7 @@ defmodule Pleroma.User.Info do      field(:salmon, :string, default: nil)      field(:hide_followers, :boolean, default: false)      field(:hide_follows, :boolean, default: false) +    field(:hide_favorites, :boolean, default: true)      field(:pinned_activities, {:array, :string}, default: [])      field(:flavour, :string, default: nil) @@ -202,6 +203,7 @@ defmodule Pleroma.User.Info do        :banner,        :hide_follows,        :hide_followers, +      :hide_favorites,        :background,        :show_role      ]) @@ -225,14 +227,6 @@ defmodule Pleroma.User.Info do      cast(info, params, [:confirmation_pending, :confirmation_token])    end -  def mastodon_profile_update(info, params) do -    info -    |> cast(params, [ -      :locked, -      :banner -    ]) -  end -    def mastodon_settings_update(info, settings) do      params = %{settings: settings} diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index f217e7bac..604ffae7b 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -7,13 +7,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    alias Pleroma.Instances    alias Pleroma.Notification    alias Pleroma.Object +  alias Pleroma.Object.Fetcher +  alias Pleroma.Pagination    alias Pleroma.Repo    alias Pleroma.Upload    alias Pleroma.User    alias Pleroma.Web.ActivityPub.MRF    alias Pleroma.Web.ActivityPub.Transmogrifier    alias Pleroma.Web.Federator -  alias Pleroma.Web.OStatus    alias Pleroma.Web.WebFinger    import Ecto.Query @@ -90,12 +91,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    end    def increase_replies_count_if_reply(%{ -        "object" => -          %{"inReplyTo" => reply_ap_id, "inReplyToStatusId" => reply_status_id} = object, +        "object" => %{"inReplyTo" => reply_ap_id} = object,          "type" => "Create"        }) do      if is_public?(object) do -      Activity.increase_replies_count(reply_status_id)        Object.increase_replies_count(reply_ap_id)      end    end @@ -103,10 +102,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    def increase_replies_count_if_reply(_create_data), do: :noop    def decrease_replies_count_if_reply(%Object{ -        data: %{"inReplyTo" => reply_ap_id, "inReplyToStatusId" => reply_status_id} = object +        data: %{"inReplyTo" => reply_ap_id} = object        }) do      if is_public?(object) do -      Activity.decrease_replies_count(reply_status_id)        Object.decrease_replies_count(reply_ap_id)      end    end @@ -121,7 +119,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           {:ok, map} <- MRF.filter(map),           {recipients, _, _} = get_recipients(map),           {:fake, false, map, recipients} <- {:fake, fake, map, recipients}, -         {:ok, object} <- insert_full_object(map) do +         {:ok, map, object} <- insert_full_object(map) do        {:ok, activity} =          Repo.insert(%Activity{            data: map, @@ -170,6 +168,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      public = "https://www.w3.org/ns/activitystreams#Public"      if activity.data["type"] in ["Create", "Announce", "Delete"] do +      object = Object.normalize(activity)        Pleroma.Web.Streamer.stream("user", activity)        Pleroma.Web.Streamer.stream("list", activity) @@ -181,12 +180,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do          end          if activity.data["type"] in ["Create"] do -          activity.data["object"] +          object.data            |> Map.get("tag", [])            |> Enum.filter(fn tag -> is_bitstring(tag) end)            |> Enum.each(fn tag -> Pleroma.Web.Streamer.stream("hashtag:" <> tag, activity) end) -          if activity.data["object"]["attachment"] != [] do +          if object.data["attachment"] != [] do              Pleroma.Web.Streamer.stream("public:media", activity)              if activity.local do @@ -198,7 +197,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do          if !Enum.member?(activity.data["cc"] || [], public) &&               !Enum.member?(                 activity.data["to"], -               User.get_by_ap_id(activity.data["actor"]).follower_address +               User.get_cached_by_ap_id(activity.data["actor"]).follower_address               ),             do: Pleroma.Web.Streamer.stream("direct", activity)        end @@ -449,8 +448,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do           :ok <- maybe_federate(activity) do        Enum.each(User.all_superusers(), fn superuser ->          superuser -        |> Pleroma.AdminEmail.report(actor, account, statuses, content) -        |> Pleroma.Mailer.deliver_async() +        |> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content) +        |> Pleroma.Emails.Mailer.deliver_async()        end)        {:ok, activity} @@ -493,7 +492,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      q      |> restrict_unlisted() -    |> Repo.all() +    |> Pagination.fetch_paginated(opts)      |> Enum.reverse()    end @@ -572,37 +571,49 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp restrict_since(query, _), do: query +  defp restrict_tag_reject(_query, %{"tag_reject" => _tag_reject, "skip_preload" => true}) do +    raise "Can't use the child object without preloading!" +  end +    defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})         when is_list(tag_reject) and tag_reject != [] do      from( -      activity in query, -      where: fragment(~s(\(not \(? #> '{"object","tag"}'\) \\?| ?\)), activity.data, ^tag_reject) +      [_activity, object] in query, +      where: fragment("not (?)->'tag' \\?| (?)", object.data, ^tag_reject)      )    end    defp restrict_tag_reject(query, _), do: query +  defp restrict_tag_all(_query, %{"tag_all" => _tag_all, "skip_preload" => true}) do +    raise "Can't use the child object without preloading!" +  end +    defp restrict_tag_all(query, %{"tag_all" => tag_all})         when is_list(tag_all) and tag_all != [] do      from( -      activity in query, -      where: fragment(~s(\(? #> '{"object","tag"}'\) \\?& ?), activity.data, ^tag_all) +      [_activity, object] in query, +      where: fragment("(?)->'tag' \\?& (?)", object.data, ^tag_all)      )    end    defp restrict_tag_all(query, _), do: query +  defp restrict_tag(_query, %{"tag" => _tag, "skip_preload" => true}) do +    raise "Can't use the child object without preloading!" +  end +    defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do      from( -      activity in query, -      where: fragment(~s(\(? #> '{"object","tag"}'\) \\?| ?), activity.data, ^tag) +      [_activity, object] in query, +      where: fragment("(?)->'tag' \\?| (?)", object.data, ^tag)      )    end    defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do      from( -      activity in query, -      where: fragment(~s(? <@ (? #> '{"object","tag"}'\)), ^tag, activity.data) +      [_activity, object] in query, +      where: fragment("(?)->'tag' \\? (?)", object.data, ^tag)      )    end @@ -636,26 +647,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      )    end -  defp restrict_limit(query, %{"limit" => limit}) do -    from(activity in query, limit: ^limit) -  end - -  defp restrict_limit(query, _), do: query -    defp restrict_local(query, %{"local_only" => true}) do      from(activity in query, where: activity.local == true)    end    defp restrict_local(query, _), do: query -  defp restrict_max(query, %{"max_id" => ""}), do: query - -  defp restrict_max(query, %{"max_id" => max_id}) do -    from(activity in query, where: activity.id < ^max_id) -  end - -  defp restrict_max(query, _), do: query -    defp restrict_actor(query, %{"actor_id" => actor_id}) do      from(activity in query, where: activity.actor == ^actor_id)    end @@ -681,10 +678,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp restrict_favorited_by(query, _), do: query +  defp restrict_media(_query, %{"only_media" => _val, "skip_preload" => true}) do +    raise "Can't use the child object without preloading!" +  end +    defp restrict_media(query, %{"only_media" => val}) when val == "true" or val == "1" do      from( -      activity in query, -      where: fragment(~s(not (? #> '{"object","attachment"}' = ?\)), activity.data, ^[]) +      [_activity, object] in query, +      where: fragment("not (?)->'attachment' = (?)", object.data, ^[])      )    end @@ -726,7 +727,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      from(        activity in query,        where: fragment("not (? = ANY(?))", activity.actor, ^blocks), -      where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks), +      where: fragment("not (? && ?)", activity.recipients, ^blocks), +      where: +        fragment( +          "not (?->>'type' = 'Announce' and ?->'to' \\?| ?)", +          activity.data, +          activity.data, +          ^blocks +        ),        where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)      )    end @@ -776,12 +784,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    end    def fetch_activities_query(recipients, opts \\ %{}) do -    base_query = -      from( -        activity in Activity, -        limit: 20, -        order_by: [fragment("? desc nulls last", activity.id)] -      ) +    base_query = from(activity in Activity)      base_query      |> maybe_preload_objects(opts) @@ -791,8 +794,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      |> restrict_tag_all(opts)      |> restrict_since(opts)      |> restrict_local(opts) -    |> restrict_limit(opts) -    |> restrict_max(opts)      |> restrict_actor(opts)      |> restrict_type(opts)      |> restrict_favorited_by(opts) @@ -808,14 +809,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    def fetch_activities(recipients, opts \\ %{}) do      fetch_activities_query(recipients, opts) -    |> Repo.all() +    |> Pagination.fetch_paginated(opts)      |> Enum.reverse()    end    def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do      fetch_activities_query([], opts)      |> restrict_to_cc(recipients_to, recipients_cc) -    |> Repo.all() +    |> Pagination.fetch_paginated(opts)      |> Enum.reverse()    end @@ -880,7 +881,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    end    def fetch_and_prepare_user_from_ap_id(ap_id) do -    with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do +    with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do        user_data_from_user_object(data)      else        e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}") @@ -888,7 +889,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    end    def make_user_from_ap_id(ap_id) do -    if _user = User.get_by_ap_id(ap_id) do +    if _user = User.get_cached_by_ap_id(ap_id) do        Transmogrifier.upgrade_user_from_ap_id(ap_id)      else        with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do @@ -990,60 +991,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      end    end -  # TODO: -  # This will create a Create activity, which we need internally at the moment. -  def fetch_object_from_id(id) do -    if object = Object.get_cached_by_ap_id(id) do -      {:ok, object} -    else -      with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), -           nil <- Object.normalize(data), -           params <- %{ -             "type" => "Create", -             "to" => data["to"], -             "cc" => data["cc"], -             "actor" => data["actor"] || data["attributedTo"], -             "object" => data -           }, -           :ok <- Transmogrifier.contain_origin(id, params), -           {:ok, activity} <- Transmogrifier.handle_incoming(params) do -        {:ok, Object.normalize(activity)} -      else -        {:error, {:reject, nil}} -> -          {:reject, nil} - -        object = %Object{} -> -          {:ok, object} - -        _e -> -          Logger.info("Couldn't get object via AP, trying out OStatus fetching...") - -          case OStatus.fetch_activity_from_url(id) do -            {:ok, [activity | _]} -> {:ok, Object.normalize(activity)} -            e -> e -          end -      end -    end -  end - -  def fetch_and_contain_remote_object_from_id(id) do -    Logger.info("Fetching object #{id} via AP") - -    with true <- String.starts_with?(id, "http"), -         {:ok, %{body: body, status: code}} when code in 200..299 <- -           @httpoison.get( -             id, -             [{:Accept, "application/activity+json"}] -           ), -         {:ok, data} <- Jason.decode(body), -         :ok <- Transmogrifier.contain_origin_from_id(id, data) do -      {:ok, data} -    else -      e -> -        {:error, e} -    end -  end -    # filter out broken threads    def contain_broken_threads(%Activity{} = activity, %User{} = user) do      entire_thread_visible_for_user?(activity, user) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 7091d6927..0b80566bf 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    alias Pleroma.Activity    alias Pleroma.Object +  alias Pleroma.Object.Fetcher    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.ObjectView @@ -153,9 +154,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    end    def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do -    with %User{} = user <- User.get_cached_by_nickname(nickname), -         true <- Utils.recipient_in_message(user.ap_id, params), -         params <- Utils.maybe_splice_recipient(user.ap_id, params) do +    with %User{} = recipient <- User.get_cached_by_nickname(nickname), +         %User{} = actor <- User.get_or_fetch_by_ap_id(params["actor"]), +         true <- Utils.recipient_in_message(recipient, actor, params), +         params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do        Federator.incoming_ap_doc(params)        json(conn, "ok")      end @@ -172,7 +174,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do        "Signature missing or not from author, relayed Create message, fetching object from source"      ) -    ActivityPub.fetch_object_from_id(params["object"]["id"]) +    Fetcher.fetch_object_from_id(params["object"]["id"])      json(conn, "ok")    end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 49ea73204..b1e859d7c 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -8,8 +8,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    """    alias Pleroma.Activity    alias Pleroma.Object +  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 @@ -18,56 +20,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    require Logger -  def get_actor(%{"actor" => actor}) when is_binary(actor) do -    actor -  end - -  def get_actor(%{"actor" => actor}) when is_list(actor) do -    if is_binary(Enum.at(actor, 0)) do -      Enum.at(actor, 0) -    else -      Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end) -      |> Map.get("id") -    end -  end - -  def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do -    id -  end - -  def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do -    get_actor(%{"actor" => actor}) -  end - -  @doc """ -  Checks that an imported AP object's actor matches the domain it came from. -  """ -  def contain_origin(_id, %{"actor" => nil}), do: :error - -  def contain_origin(id, %{"actor" => _actor} = params) do -    id_uri = URI.parse(id) -    actor_uri = URI.parse(get_actor(params)) - -    if id_uri.host == actor_uri.host do -      :ok -    else -      :error -    end -  end - -  def contain_origin_from_id(_id, %{"id" => nil}), do: :error - -  def contain_origin_from_id(id, %{"id" => other_id} = _params) do -    id_uri = URI.parse(id) -    other_uri = URI.parse(other_id) - -    if id_uri.host == other_uri.host do -      :ok -    else -      :error -    end -  end -    @doc """    Modifies an incoming AP object (mastodon format) to our internal format.    """ @@ -188,7 +140,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def fix_actor(%{"attributedTo" => actor} = object) do      object -    |> Map.put("actor", get_actor(%{"actor" => actor})) +    |> Map.put("actor", Containment.get_actor(%{"actor" => actor}))    end    # Check for standardisation @@ -223,14 +175,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do            ""        end -    case fetch_obj_helper(in_reply_to_id) do +    case get_obj_helper(in_reply_to_id) do        {:ok, replied_object} -> -        with %Activity{} = activity <- +        with %Activity{} = _activity <-                 Activity.get_create_by_object_ap_id(replied_object.data["id"]) do            object            |> Map.put("inReplyTo", replied_object.data["id"])            |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) -          |> Map.put("inReplyToStatusId", activity.id)            |> Map.put("conversation", replied_object.data["context"] || object["conversation"])            |> Map.put("context", replied_object.data["context"] || object["conversation"])          else @@ -449,7 +400,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    # - emoji    def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)        when objtype in ["Article", "Note", "Video", "Page"] do -    actor = get_actor(data) +    actor = Containment.get_actor(data)      data =        Map.put(data, "actor", actor) @@ -487,27 +438,53 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),           %User{} = follower <- User.get_or_fetch_by_ap_id(follower),           {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do -      if not User.locked?(followed) do +      with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]), +           {:user_blocked, false} <- +             {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked}, +           {:user_locked, false} <- {:user_locked, User.locked?(followed)}, +           {:follow, {:ok, follower}} <- {:follow, User.follow(follower, followed)} do          ActivityPub.accept(%{            to: [follower.ap_id],            actor: followed,            object: data,            local: true          }) - -        User.follow(follower, followed) +      else +        {:user_blocked, true} -> +          {:ok, _} = Utils.update_follow_state(activity, "reject") + +          ActivityPub.reject(%{ +            to: [follower.ap_id], +            actor: followed, +            object: data, +            local: true +          }) + +        {:follow, {:error, _}} -> +          {:ok, _} = Utils.update_follow_state(activity, "reject") + +          ActivityPub.reject(%{ +            to: [follower.ap_id], +            actor: followed, +            object: data, +            local: true +          }) + +        {:user_locked, true} -> +          :noop        end        {:ok, activity}      else -      _e -> :error +      _e -> +        :error      end    end    def handle_incoming(          %{"type" => "Accept", "object" => follow_object, "actor" => _actor, "id" => _id} = data        ) do -    with actor <- get_actor(data), +    with actor <- Containment.get_actor(data),           %User{} = followed <- User.get_or_fetch_by_ap_id(actor),           {:ok, follow_activity} <- get_follow_activity(follow_object, followed),           {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"), @@ -533,7 +510,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def handle_incoming(          %{"type" => "Reject", "object" => follow_object, "actor" => _actor, "id" => _id} = data        ) do -    with actor <- get_actor(data), +    with actor <- Containment.get_actor(data),           %User{} = followed <- User.get_or_fetch_by_ap_id(actor),           {:ok, follow_activity} <- get_follow_activity(follow_object, followed),           {:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"), @@ -557,9 +534,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def handle_incoming(          %{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data        ) do -    with actor <- get_actor(data), +    with actor <- Containment.get_actor(data),           %User{} = actor <- User.get_or_fetch_by_ap_id(actor), -         {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), +         {:ok, object} <- get_obj_helper(object_id),           {:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do        {:ok, activity}      else @@ -570,9 +547,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def handle_incoming(          %{"type" => "Announce", "object" => object_id, "actor" => _actor, "id" => id} = data        ) do -    with actor <- get_actor(data), +    with actor <- Containment.get_actor(data),           %User{} = actor <- User.get_or_fetch_by_ap_id(actor), -         {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), +         {:ok, object} <- get_obj_helper(object_id),           public <- Visibility.is_public?(data),           {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do        {:ok, activity} @@ -586,7 +563,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do            data        )        when object_type in ["Person", "Application", "Service", "Organization"] do -    with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do +    with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do        {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)        banner = new_user_data[:info]["banner"] @@ -625,10 +602,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do        ) do      object_id = Utils.get_ap_id(object_id) -    with actor <- get_actor(data), +    with actor <- Containment.get_actor(data),           %User{} = actor <- User.get_or_fetch_by_ap_id(actor), -         {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), -         :ok <- contain_origin(actor.ap_id, object.data), +         {:ok, object} <- get_obj_helper(object_id), +         :ok <- Containment.contain_origin(actor.ap_id, object.data),           {:ok, activity} <- ActivityPub.delete(object, false) do        {:ok, activity}      else @@ -644,9 +621,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do            "id" => id          } = data        ) do -    with actor <- get_actor(data), +    with actor <- Containment.get_actor(data),           %User{} = actor <- User.get_or_fetch_by_ap_id(actor), -         {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), +         {:ok, object} <- get_obj_helper(object_id),           {:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do        {:ok, activity}      else @@ -714,9 +691,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do            "id" => id          } = data        ) do -    with actor <- get_actor(data), +    with actor <- Containment.get_actor(data),           %User{} = actor <- User.get_or_fetch_by_ap_id(actor), -         {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), +         {:ok, object} <- get_obj_helper(object_id),           {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do        {:ok, activity}      else @@ -726,9 +703,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def handle_incoming(_), do: :error -  def fetch_obj_helper(id) when is_bitstring(id), do: ActivityPub.fetch_object_from_id(id) -  def fetch_obj_helper(obj) when is_map(obj), do: ActivityPub.fetch_object_from_id(obj["id"]) -    def get_obj_helper(id) do      if object = Object.normalize(id), do: {:ok, object}, else: nil    end @@ -765,9 +739,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    #  internal -> Mastodon    #  """ -  def prepare_outgoing(%{"type" => "Create", "object" => object} = data) do +  def prepare_outgoing(%{"type" => "Create", "object" => object_id} = data) do      object = -      object +      Object.normalize(object_id).data        |> prepare_object      data = @@ -828,7 +802,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    def maybe_fix_object_url(data) do      if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do -      case fetch_obj_helper(data["object"]) do +      case get_obj_helper(data["object"]) do          {:ok, relative_object} ->            if relative_object.data["external_url"] do              _data = @@ -1016,7 +990,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do    end    def upgrade_user_from_ap_id(ap_id) do -    with %User{local: false} = user <- User.get_by_ap_id(ap_id), +    with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),           {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),           already_ap <- User.ap_enabled?(user),           {:ok, user} <- user |> User.upgrade_changeset(data) |> User.update_and_set_cache() do diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 0b53f71c3..581b9d1ab 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -52,7 +52,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do    defp recipient_in_collection(ap_id, coll) when is_list(coll), do: ap_id in coll    defp recipient_in_collection(_, _), do: false -  def recipient_in_message(ap_id, params) do +  def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params) do      cond do        recipient_in_collection(ap_id, params["to"]) ->          true @@ -71,6 +71,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do        !params["to"] && !params["cc"] && !params["bto"] && !params["bcc"] ->          true +      # if the message is sent from somebody the user is following, then assume it +      # is addressed to the recipient +      User.following?(recipient, actor) -> +        true +        true ->          false      end @@ -229,14 +234,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do    @doc """    Inserts a full object if it is contained in an activity.    """ -  def insert_full_object(%{"object" => %{"type" => type} = object_data}) +  def insert_full_object(%{"object" => %{"type" => type} = object_data} = map)        when is_map(object_data) and type in @supported_object_types do      with {:ok, object} <- Object.create(object_data) do -      {:ok, object} +      map = +        map +        |> Map.put("object", object.data["id"]) + +      {:ok, map, object}      end    end -  def insert_full_object(_), do: {:ok, nil} +  def insert_full_object(map), do: {:ok, map, nil}    def update_object_in_activities(%{data: %{"id" => id}} = object) do      # TODO diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index db52fe933..6dee61dd6 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -41,16 +41,21 @@ defmodule Pleroma.Web.ActivityPub.Visibility do    # guard    def entire_thread_visible_for_user?(nil, _user), do: false -  # child +  # 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{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail, +        %Activity{} = tail, +        # %Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail,          user -      ) -      when is_binary(parent_id) do -    parent = Activity.get_in_reply_to_activity(tail) -    visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, 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    end - -  # root -  def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user)  end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index 70a5b5c5d..711f233a6 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    action_fallback(:errors)    def user_delete(conn, %{"nickname" => nickname}) do -    User.get_by_nickname(nickname) +    User.get_cached_by_nickname(nickname)      |> User.delete()      conn @@ -27,8 +27,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    end    def user_follow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do -    with %User{} = follower <- User.get_by_nickname(follower_nick), -         %User{} = followed <- User.get_by_nickname(followed_nick) do +    with %User{} = follower <- User.get_cached_by_nickname(follower_nick), +         %User{} = followed <- User.get_cached_by_nickname(followed_nick) do        User.follow(follower, followed)      end @@ -37,8 +37,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    end    def user_unfollow(conn, %{"follower" => follower_nick, "followed" => followed_nick}) do -    with %User{} = follower <- User.get_by_nickname(follower_nick), -         %User{} = followed <- User.get_by_nickname(followed_nick) do +    with %User{} = follower <- User.get_cached_by_nickname(follower_nick), +         %User{} = followed <- User.get_cached_by_nickname(followed_nick) do        User.unfollow(follower, followed)      end @@ -67,7 +67,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    end    def user_show(conn, %{"nickname" => nickname}) do -    with %User{} = user <- User.get_by_nickname(nickname) do +    with %User{} = user <- User.get_cached_by_nickname(nickname) do        conn        |> json(AccountView.render("show.json", %{user: user}))      else @@ -76,7 +76,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    end    def user_toggle_activation(conn, %{"nickname" => nickname}) do -    user = User.get_by_nickname(nickname) +    user = User.get_cached_by_nickname(nickname)      {:ok, updated_user} = User.deactivate(user, !user.info.deactivated) @@ -131,7 +131,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})        when permission_group in ["moderator", "admin"] do -    user = User.get_by_nickname(nickname) +    user = User.get_cached_by_nickname(nickname)      info =        %{} @@ -156,7 +156,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    end    def right_get(conn, %{"nickname" => nickname}) do -    user = User.get_by_nickname(nickname) +    user = User.get_cached_by_nickname(nickname)      conn      |> json(%{ @@ -178,7 +178,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do        |> put_status(403)        |> json(%{error: "You can't revoke your own admin status."})      else -      user = User.get_by_nickname(nickname) +      user = User.get_cached_by_nickname(nickname)        info =          %{} @@ -204,7 +204,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    def set_activation_status(conn, %{"nickname" => nickname, "status" => status}) do      with {:ok, status} <- Ecto.Type.cast(:boolean, status), -         %User{} = user <- User.get_by_nickname(nickname), +         %User{} = user <- User.get_cached_by_nickname(nickname),           {:ok, _} <- User.deactivate(user, !status),           do: json_response(conn, :no_content, "")    end @@ -238,8 +238,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do               !Pleroma.Config.get([:instance, :registrations_open]),           {:ok, invite_token} <- UserInviteToken.create_invite(),           email <- -           Pleroma.UserEmail.user_invitation_email(user, invite_token, email, params["name"]), -         {:ok, _} <- Pleroma.Mailer.deliver(email) do +           Pleroma.Emails.UserEmail.user_invitation_email( +             user, +             invite_token, +             email, +             params["name"] +           ), +         {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do        json_response(conn, :no_content, "")      end    end @@ -272,7 +277,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    @doc "Get a password reset token (base64 string) for given nickname"    def get_password_reset(conn, %{"nickname" => nickname}) do -    (%User{local: true} = user) = User.get_by_nickname(nickname) +    (%User{local: true} = user) = User.get_cached_by_nickname(nickname)      {:ok, token} = Pleroma.PasswordResetToken.create_token(user)      conn diff --git a/lib/pleroma/web/auth/authenticator.ex b/lib/pleroma/web/auth/authenticator.ex index 89d88af32..b02f595dc 100644 --- a/lib/pleroma/web/auth/authenticator.ex +++ b/lib/pleroma/web/auth/authenticator.ex @@ -13,21 +13,21 @@ defmodule Pleroma.Web.Auth.Authenticator do      )    end -  @callback get_user(Plug.Conn.t(), Map.t()) :: {:ok, User.t()} | {:error, any()} -  def get_user(plug, params), do: implementation().get_user(plug, params) +  @callback get_user(Plug.Conn.t()) :: {:ok, User.t()} | {:error, any()} +  def get_user(plug), do: implementation().get_user(plug) -  @callback create_from_registration(Plug.Conn.t(), Map.t(), Registration.t()) :: +  @callback create_from_registration(Plug.Conn.t(), Registration.t()) ::                {:ok, User.t()} | {:error, any()} -  def create_from_registration(plug, params, registration), -    do: implementation().create_from_registration(plug, params, registration) +  def create_from_registration(plug, registration), +    do: implementation().create_from_registration(plug, registration) -  @callback get_registration(Plug.Conn.t(), Map.t()) :: +  @callback get_registration(Plug.Conn.t()) ::                {:ok, Registration.t()} | {:error, any()} -  def get_registration(plug, params), -    do: implementation().get_registration(plug, params) +  def get_registration(plug), do: implementation().get_registration(plug)    @callback handle_error(Plug.Conn.t(), any()) :: any() -  def handle_error(plug, error), do: implementation().handle_error(plug, error) +  def handle_error(plug, error), +    do: implementation().handle_error(plug, error)    @callback auth_template() :: String.t() | nil    def auth_template do diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex index 8b6d5a77f..363c99597 100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -13,14 +13,16 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do    @connection_timeout 10_000    @search_timeout 10_000 -  defdelegate get_registration(conn, params), to: @base +  defdelegate get_registration(conn), to: @base +  defdelegate create_from_registration(conn, registration), to: @base +  defdelegate handle_error(conn, error), to: @base +  defdelegate auth_template, to: @base +  defdelegate oauth_consumer_template, to: @base -  defdelegate create_from_registration(conn, params, registration), to: @base - -  def get_user(%Plug.Conn{} = conn, params) do +  def get_user(%Plug.Conn{} = conn) do      if Pleroma.Config.get([:ldap, :enabled]) do        {name, password} = -        case params do +        case conn.params do            %{"authorization" => %{"name" => name, "password" => password}} ->              {name, password} @@ -34,25 +36,17 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do          {:error, {:ldap_connection_error, _}} ->            # When LDAP is unavailable, try default authenticator -          @base.get_user(conn, params) +          @base.get_user(conn)          error ->            error        end      else        # Fall back to default authenticator -      @base.get_user(conn, params) +      @base.get_user(conn)      end    end -  def handle_error(%Plug.Conn{} = _conn, error) do -    error -  end - -  def auth_template, do: nil - -  def oauth_consumer_template, do: nil -    defp ldap_user(name, password) do      ldap = Pleroma.Config.get(:ldap, [])      host = Keyword.get(ldap, :host, "localhost") diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex index c826adb4c..d647f1e05 100644 --- a/lib/pleroma/web/auth/pleroma_authenticator.ex +++ b/lib/pleroma/web/auth/pleroma_authenticator.ex @@ -10,9 +10,9 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do    @behaviour Pleroma.Web.Auth.Authenticator -  def get_user(%Plug.Conn{} = _conn, params) do +  def get_user(%Plug.Conn{} = conn) do      {name, password} = -      case params do +      case conn.params do          %{"authorization" => %{"name" => name, "password" => password}} ->            {name, password} @@ -29,10 +29,9 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do      end    end -  def get_registration( -        %Plug.Conn{assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth}}, -        _params -      ) do +  def get_registration(%Plug.Conn{ +        assigns: %{ueberauth_auth: %{provider: provider, uid: uid} = auth} +      }) do      registration = Registration.get_by_provider_uid(provider, uid)      if registration do @@ -40,7 +39,8 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do      else        info = auth.info -      Registration.changeset(%Registration{}, %{ +      %Registration{} +      |> Registration.changeset(%{          provider: to_string(provider),          uid: to_string(uid),          info: %{ @@ -54,13 +54,16 @@ defmodule Pleroma.Web.Auth.PleromaAuthenticator do      end    end -  def get_registration(%Plug.Conn{} = _conn, _params), do: {:error, :missing_credentials} +  def get_registration(%Plug.Conn{} = _conn), do: {:error, :missing_credentials} -  def create_from_registration(_conn, params, registration) do -    nickname = value([params["nickname"], Registration.nickname(registration)]) -    email = value([params["email"], Registration.email(registration)]) -    name = value([params["name"], Registration.name(registration)]) || nickname -    bio = value([params["bio"], Registration.description(registration)]) +  def create_from_registration( +        %Plug.Conn{params: %{"authorization" => registration_attrs}}, +        registration +      ) do +    nickname = value([registration_attrs["nickname"], Registration.nickname(registration)]) +    email = value([registration_attrs["email"], Registration.email(registration)]) +    name = value([registration_attrs["name"], Registration.name(registration)]) || nickname +    bio = value([registration_attrs["bio"], Registration.description(registration)])      random_password = :crypto.strong_rand_bytes(64) |> Base.encode64() diff --git a/lib/pleroma/web/channels/user_socket.ex b/lib/pleroma/web/channels/user_socket.ex index 6503979a1..8e2759e3b 100644 --- a/lib/pleroma/web/channels/user_socket.ex +++ b/lib/pleroma/web/channels/user_socket.ex @@ -24,7 +24,7 @@ defmodule Pleroma.Web.UserSocket do    def connect(%{"token" => token}, socket) do      with true <- Pleroma.Config.get([:chat, :enabled]),           {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84_600), -         %User{} = user <- Pleroma.User.get_by_id(user_id) do +         %User{} = user <- Pleroma.User.get_cached_by_id(user_id) do        {:ok, assign(socket, :user_name, user.nickname)}      else        _e -> :error diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 74babdf14..ecd183110 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -4,6 +4,7 @@  defmodule Pleroma.Web.CommonAPI do    alias Pleroma.Activity +  alias Pleroma.Bookmark    alias Pleroma.Formatter    alias Pleroma.Object    alias Pleroma.ThreadMute @@ -125,7 +126,10 @@ defmodule Pleroma.Web.CommonAPI do          "public"        in_reply_to -> -        Pleroma.Web.MastodonAPI.StatusView.get_visibility(in_reply_to.data["object"]) +        # XXX: these heuristics should be moved out of MastodonAPI. +        with %Object{} = object <- Object.normalize(in_reply_to) do +          Pleroma.Web.MastodonAPI.StatusView.get_visibility(object) +        end      end    end @@ -214,8 +218,10 @@ defmodule Pleroma.Web.CommonAPI do      with %Activity{             actor: ^user_ap_id,             data: %{ -             "type" => "Create", -             "object" => %{ +             "type" => "Create" +           }, +           object: %Object{ +             data: %{                 "to" => object_to,                 "type" => "Note"               } @@ -277,9 +283,18 @@ defmodule Pleroma.Web.CommonAPI do      end    end +  def bookmarked?(user, activity) do +    with %Bookmark{} <- Bookmark.get(user.id, activity.id) do +      true +    else +      _ -> +        false +    end +  end +    def report(user, data) do      with {:account_id, %{"account_id" => account_id}} <- {:account_id, data}, -         {:account, %User{} = account} <- {:account, User.get_by_id(account_id)}, +         {:account, %User{} = account} <- {:account, User.get_cached_by_id(account_id)},           {:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]),           {:ok, statuses} <- get_report_statuses(account, data),           {:ok, activity} <- diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 7b9f0ea06..1dfe50b40 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -183,6 +183,18 @@ defmodule Pleroma.Web.CommonAPI.Utils do    end    @doc """ +  Formatting text as BBCode. +  """ +  def format_input(text, "text/bbcode", options) do +    text +    |> String.replace(~r/\r/, "") +    |> Formatter.html_escape("text/plain") +    |> BBCode.to_html() +    |> (fn {:ok, html} -> html end).() +    |> Formatter.linkify(options) +  end + +  @doc """    Formatting text to html.    """    def format_input(text, "text/html", options) do @@ -195,11 +207,10 @@ defmodule Pleroma.Web.CommonAPI.Utils do    Formatting text to markdown.    """    def format_input(text, "text/markdown", options) do -    options = Keyword.put(options, :mentions_escape, true) -      text +    |> Formatter.mentions_escape(options) +    |> Earmark.as_html!()      |> Formatter.linkify(options) -    |> (fn {text, mentions, tags} -> {Earmark.as_html!(text), mentions, tags} end).()      |> Formatter.html_escape("text/html")    end @@ -209,7 +220,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do          context,          content_html,          attachments, -        inReplyTo, +        in_reply_to,          tags,          cw \\ nil,          cc \\ [] @@ -226,10 +237,11 @@ defmodule Pleroma.Web.CommonAPI.Utils do        "tag" => tags |> Enum.map(fn {_, tag} -> tag end) |> Enum.uniq()      } -    if inReplyTo do +    if in_reply_to do +      in_reply_to_object = Object.normalize(in_reply_to) +        object -      |> Map.put("inReplyTo", inReplyTo.data["object"]["id"]) -      |> Map.put("inReplyToStatusId", inReplyTo.id) +      |> Map.put("inReplyTo", in_reply_to_object.data["id"])      else        object      end @@ -284,7 +296,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do    end    def confirm_current_password(user, password) do -    with %User{local: true} = db_user <- User.get_by_id(user.id), +    with %User{local: true} = db_user <- User.get_cached_by_id(user.id),           true <- Pbkdf2.checkpw(password, db_user.password_hash) do        {:ok, db_user}      else diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 1633477c3..7f939991d 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -58,14 +58,9 @@ defmodule Pleroma.Web.Endpoint do        do: "__Host-pleroma_key",        else: "pleroma_key" -  same_site = -    if Pleroma.Config.oauth_consumer_enabled?() do -      # Note: "SameSite=Strict" prevents sign in with external OAuth provider -      #   (there would be no cookies during callback request from OAuth provider) -      "SameSite=Lax" -    else -      "SameSite=Strict" -    end +  extra = +    Pleroma.Config.get([__MODULE__, :extra_cookie_attrs]) +    |> Enum.join(";")    # The session will be stored in the cookie and signed,    # this means its contents can be read but not tampered with. @@ -77,7 +72,7 @@ defmodule Pleroma.Web.Endpoint do      signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},      http_only: true,      secure: secure_cookies, -    extra: same_site +    extra: extra    )    # Note: the plug and its configuration is compile-time this can't be upstreamed yet diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex index c47328e13..29e178ba9 100644 --- a/lib/pleroma/web/federator/federator.ex +++ b/lib/pleroma/web/federator/federator.ex @@ -4,6 +4,7 @@  defmodule Pleroma.Web.Federator do    alias Pleroma.Activity +  alias Pleroma.Object.Containment    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Relay @@ -136,7 +137,7 @@ defmodule Pleroma.Web.Federator do      # actor shouldn't be acting on objects outside their own AP server.      with {:ok, _user} <- ap_enabled_actor(params["actor"]),           nil <- Activity.normalize(params["id"]), -         :ok <- Transmogrifier.contain_origin_from_id(params["actor"], params), +         :ok <- Containment.contain_origin_from_id(params["actor"], params),           {:ok, activity} <- Transmogrifier.handle_incoming(params) do        {:ok, activity}      else @@ -185,7 +186,7 @@ defmodule Pleroma.Web.Federator do    end    def ap_enabled_actor(id) do -    user = User.get_by_ap_id(id) +    user = User.get_cached_by_ap_id(id)      if User.ap_enabled?(user) do        {:ok, user} diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 382f07e6b..3a3ec7c2a 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -7,6 +7,31 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do    alias Pleroma.Pagination    alias Pleroma.ScheduledActivity    alias Pleroma.User +  alias Pleroma.Web.CommonAPI + +  def follow(follower, followed, params \\ %{}) do +    options = cast_params(params) +    reblogs = options[:reblogs] + +    result = +      if not User.following?(follower, followed) do +        CommonAPI.follow(follower, followed) +      else +        {:ok, follower, followed, nil} +      end + +    with {:ok, follower, followed, _} <- result do +      reblogs +      |> case do +        false -> CommonAPI.hide_reblogs(follower, followed) +        _ -> CommonAPI.show_reblogs(follower, followed) +      end +      |> case do +        {:ok, follower} -> {:ok, follower} +        _ -> {:ok, follower} +      end +    end +  end    def get_followers(user, params \\ %{}) do      user @@ -37,7 +62,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do    defp cast_params(params) do      param_types = %{ -      exclude_types: {:array, :string} +      exclude_types: {:array, :string}, +      reblogs: :boolean      }      changeset = cast({%{}, param_types}, params, Map.keys(param_types)) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index ed082abdf..811a45c79 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -4,13 +4,15 @@  defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    use Pleroma.Web, :controller -    alias Ecto.Changeset    alias Pleroma.Activity +  alias Pleroma.Bookmark    alias Pleroma.Config    alias Pleroma.Filter    alias Pleroma.Notification    alias Pleroma.Object +  alias Pleroma.Object.Fetcher +  alias Pleroma.Pagination    alias Pleroma.Repo    alias Pleroma.ScheduledActivity    alias Pleroma.Stats @@ -34,7 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    alias Pleroma.Web.OAuth.Authorization    alias Pleroma.Web.OAuth.Token -  import Pleroma.Web.ControllerHelper, only: [oauth_scopes: 2] +  alias Pleroma.Web.ControllerHelper    import Ecto.Query    require Logger @@ -45,7 +47,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    action_fallback(:errors)    def create_app(conn, params) do -    scopes = oauth_scopes(params, ["read"]) +    scopes = ControllerHelper.oauth_scopes(params, ["read"])      app_attrs =        params @@ -95,8 +97,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do        end)      info_params = -      %{} -      |> add_if_present(params, "locked", :locked, fn value -> {:ok, value == "true"} end) +      [:no_rich_text, :locked, :hide_followers, :hide_follows, :hide_favorites, :show_role] +      |> Enum.reduce(%{}, fn key, acc -> +        add_if_present(acc, params, to_string(key), key, fn value -> +          {:ok, ControllerHelper.truthy_param?(value)} +        end) +      end) +      |> add_if_present(params, "default_scope", :default_scope)        |> add_if_present(params, "header", :banner, fn value ->          with %Plug.Upload{} <- value,               {:ok, object} <- ActivityPub.upload(value, type: :banner) do @@ -106,7 +113,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do          end        end) -    info_cng = User.Info.mastodon_profile_update(user.info, info_params) +    info_cng = User.Info.profile_update(user.info, info_params)      with changeset <- User.update_changeset(user, user_params),           changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng), @@ -189,7 +196,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do          "static_url" => url,          "visible_in_picker" => true,          "url" => url, -        "tags" => String.split(tags, ",") +        "tags" => tags        }      end)    end @@ -202,15 +209,29 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    defp add_link_headers(conn, method, activities, param \\ nil, params \\ %{}) do      params =        conn.params -      |> Map.drop(["since_id", "max_id"]) +      |> Map.drop(["since_id", "max_id", "min_id"])        |> Map.merge(params)      last = List.last(activities) -    first = List.first(activities)      if last do -      min = last.id -      max = first.id +      max_id = last.id + +      limit = +        params +        |> Map.get("limit", "20") +        |> String.to_integer() + +      min_id = +        if length(activities) <= limit do +          activities +          |> List.first() +          |> Map.get(:id) +        else +          activities +          |> Enum.at(limit * -1) +          |> Map.get(:id) +        end        {next_url, prev_url} =          if param do @@ -219,13 +240,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do                Pleroma.Web.Endpoint,                method,                param, -              Map.merge(params, %{max_id: min}) +              Map.merge(params, %{max_id: max_id})              ),              mastodon_api_url(                Pleroma.Web.Endpoint,                method,                param, -              Map.merge(params, %{since_id: max}) +              Map.merge(params, %{min_id: min_id})              )            }          else @@ -233,12 +254,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do              mastodon_api_url(                Pleroma.Web.Endpoint,                method, -              Map.merge(params, %{max_id: min}) +              Map.merge(params, %{max_id: max_id})              ),              mastodon_api_url(                Pleroma.Web.Endpoint,                method, -              Map.merge(params, %{since_id: max}) +              Map.merge(params, %{min_id: min_id})              )            }          end @@ -264,6 +285,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do        |> ActivityPub.contain_timeline(user)        |> Enum.reverse() +    user = Repo.preload(user, bookmarks: :activity) +      conn      |> add_link_headers(:home_timeline, activities)      |> put_view(StatusView) @@ -282,6 +305,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do        |> ActivityPub.fetch_public_activities()        |> Enum.reverse() +    user = Repo.preload(user, bookmarks: :activity) +      conn      |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})      |> put_view(StatusView) @@ -289,7 +314,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do -    with %User{} = user <- User.get_by_id(params["id"]) do +    with %User{} = user <- User.get_cached_by_id(params["id"]), +         reading_user <- Repo.preload(reading_user, :bookmarks) do        activities = ActivityPub.fetch_user_activities(user, reading_user, params)        conn @@ -314,7 +340,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      activities =        [user.ap_id]        |> ActivityPub.fetch_activities_query(params) -      |> Repo.all() +      |> Pagination.fetch_paginated(params) + +    user = Repo.preload(user, bookmarks: :activity)      conn      |> add_link_headers(:dm_timeline, activities) @@ -323,8 +351,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Activity{} = activity <- Activity.get_by_id(id), +    with %Activity{} = activity <- Activity.get_by_id_with_object(id),           true <- Visibility.visible_for_user?(activity, user) do +      user = Repo.preload(user, bookmarks: :activity) +        conn        |> put_view(StatusView)        |> try_render("status.json", %{activity: activity, for: user}) @@ -472,7 +502,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do -    with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user) do +    with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user), +         %Activity{} = announce <- Activity.normalize(announce.data) do +      user = Repo.preload(user, bookmarks: :activity) +        conn        |> put_view(StatusView)        |> try_render("status.json", %{activity: announce, for: user, as: :activity}) @@ -481,7 +514,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do      with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), -         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do +         %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do +      user = Repo.preload(user, bookmarks: :activity) +        conn        |> put_view(StatusView)        |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -528,10 +563,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Activity{} = activity <- Activity.get_by_id(id), -         %User{} = user <- User.get_by_nickname(user.nickname), +    with %Activity{} = activity <- Activity.get_by_id_with_object(id), +         %User{} = user <- User.get_cached_by_nickname(user.nickname),           true <- Visibility.visible_for_user?(activity, user), -         {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do +         {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do +      user = Repo.preload(user, bookmarks: :activity) +        conn        |> put_view(StatusView)        |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -539,10 +576,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Activity{} = activity <- Activity.get_by_id(id), -         %User{} = user <- User.get_by_nickname(user.nickname), +    with %Activity{} = activity <- Activity.get_by_id_with_object(id), +         %User{} = user <- User.get_cached_by_nickname(user.nickname),           true <- Visibility.visible_for_user?(activity, user), -         {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do +         {:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do +      user = Repo.preload(user, bookmarks: :activity) +        conn        |> put_view(StatusView)        |> try_render("status.json", %{activity: activity, for: user, as: :activity}) @@ -612,6 +651,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      end    end +  def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do +    Notification.destroy_multiple(user, ids) +    json(conn, %{}) +  end +    def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do      id = List.wrap(id)      q = from(u in User, where: u.id in ^id) @@ -661,7 +705,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def favourited_by(conn, %{"id" => id}) do -    with %Activity{data: %{"object" => %{"likes" => likes}}} <- Activity.get_by_id(id) do +    with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id), +         %Object{data: %{"likes" => likes}} <- Object.normalize(object) do        q = from(u in User, where: u.ap_id in ^likes)        users = Repo.all(q) @@ -674,7 +719,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def reblogged_by(conn, %{"id" => id}) do -    with %Activity{data: %{"object" => %{"announcements" => announces}}} <- Activity.get_by_id(id) do +    with %Activity{data: %{"object" => object}} <- Repo.get(Activity, id), +         %Object{data: %{"announcements" => announces}} <- Object.normalize(object) do        q = from(u in User, where: u.ap_id in ^announces)        users = Repo.all(q) @@ -725,7 +771,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def followers(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do -    with %User{} = user <- User.get_by_id(id), +    with %User{} = user <- User.get_cached_by_id(id),           followers <- MastodonAPI.get_followers(user, params) do        followers =          cond do @@ -742,7 +788,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def following(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do -    with %User{} = user <- User.get_by_id(id), +    with %User{} = user <- User.get_cached_by_id(id),           followers <- MastodonAPI.get_friends(user, params) do        followers =          cond do @@ -767,7 +813,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do -    with %User{} = follower <- User.get_by_id(id), +    with %User{} = follower <- User.get_cached_by_id(id),           {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do        conn        |> put_view(AccountView) @@ -781,7 +827,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do -    with %User{} = follower <- User.get_by_id(id), +    with %User{} = follower <- User.get_cached_by_id(id),           {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do        conn        |> put_view(AccountView) @@ -795,25 +841,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do -    with %User{} = followed <- User.get_by_id(id), -         false <- User.following?(follower, followed), -         {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do +    with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)}, +         {_, true} <- {:followed, follower.id != followed.id}, +         {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do        conn        |> put_view(AccountView)        |> render("relationship.json", %{user: follower, target: followed})      else -      true -> -        followed = User.get_cached_by_id(id) - -        {:ok, follower} = -          case conn.params["reblogs"] do -            true -> CommonAPI.show_reblogs(follower, followed) -            false -> CommonAPI.hide_reblogs(follower, followed) -          end - -        conn -        |> put_view(AccountView) -        |> render("relationship.json", %{user: follower, target: followed}) +      {:followed, _} -> +        {:error, :not_found}        {:error, message} ->          conn @@ -823,12 +859,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def follow(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do -    with %User{} = followed <- User.get_by_nickname(uri), +    with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)}, +         {_, true} <- {:followed, follower.id != followed.id},           {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do        conn        |> put_view(AccountView)        |> render("account.json", %{user: followed, for: follower})      else +      {:followed, _} -> +        {:error, :not_found} +        {:error, message} ->          conn          |> put_resp_content_type("application/json") @@ -837,16 +877,23 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def unfollow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do -    with %User{} = followed <- User.get_by_id(id), +    with {_, %User{} = followed} <- {:followed, User.get_cached_by_id(id)}, +         {_, true} <- {:followed, follower.id != followed.id},           {:ok, follower} <- CommonAPI.unfollow(follower, followed) do        conn        |> put_view(AccountView)        |> render("relationship.json", %{user: follower, target: followed}) +    else +      {:followed, _} -> +        {:error, :not_found} + +      error -> +        error      end    end    def mute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do -    with %User{} = muted <- User.get_by_id(id), +    with %User{} = muted <- User.get_cached_by_id(id),           {:ok, muter} <- User.mute(muter, muted) do        conn        |> put_view(AccountView) @@ -860,7 +907,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def unmute(%{assigns: %{user: muter}} = conn, %{"id" => id}) do -    with %User{} = muted <- User.get_by_id(id), +    with %User{} = muted <- User.get_cached_by_id(id),           {:ok, muter} <- User.unmute(muter, muted) do        conn        |> put_view(AccountView) @@ -881,7 +928,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def block(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do -    with %User{} = blocked <- User.get_by_id(id), +    with %User{} = blocked <- User.get_cached_by_id(id),           {:ok, blocker} <- User.block(blocker, blocked),           {:ok, _activity} <- ActivityPub.block(blocker, blocked) do        conn @@ -896,7 +943,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do -    with %User{} = blocked <- User.get_by_id(id), +    with %User{} = blocked <- User.get_cached_by_id(id),           {:ok, blocker} <- User.unblock(blocker, blocked),           {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do        conn @@ -962,7 +1009,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    def status_search(user, query) do      fetched =        if Regex.match?(~r/https?:/, query) do -        with {:ok, object} <- ActivityPub.fetch_object_from_id(query), +        with {:ok, object} <- Fetcher.fetch_object_from_id(query),               %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),               true <- Visibility.visible_for_user?(activity, user) do            [activity] @@ -973,13 +1020,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      q =        from( -        a in Activity, +        [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', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", -            a.data, +            "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", +            o.data,              ^query            ),          limit: 20, @@ -1055,21 +1102,65 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do        ActivityPub.fetch_activities([], params)        |> Enum.reverse() +    user = Repo.preload(user, bookmarks: :activity) +      conn      |> add_link_headers(:favourites, activities)      |> put_view(StatusView)      |> render("index.json", %{activities: activities, for: user, as: :activity})    end -  def bookmarks(%{assigns: %{user: user}} = conn, _) do -    user = User.get_by_id(user.id) +  def user_favourites(%{assigns: %{user: for_user}} = conn, %{"id" => id} = params) do +    with %User{} = user <- User.get_by_id(id), +         false <- user.info.hide_favorites do +      params = +        params +        |> Map.put("type", "Create") +        |> Map.put("favorited_by", user.ap_id) +        |> Map.put("blocking_user", for_user) + +      recipients = +        if for_user do +          ["https://www.w3.org/ns/activitystreams#Public"] ++ +            [for_user.ap_id | for_user.following] +        else +          ["https://www.w3.org/ns/activitystreams#Public"] +        end + +      activities = +        recipients +        |> ActivityPub.fetch_activities(params) +        |> Enum.reverse() + +      conn +      |> add_link_headers(:favourites, activities) +      |> put_view(StatusView) +      |> render("index.json", %{activities: activities, for: for_user, as: :activity}) +    else +      nil -> +        {:error, :not_found} + +      true -> +        conn +        |> put_status(403) +        |> json(%{error: "Can't get favorites"}) +    end +  end + +  def bookmarks(%{assigns: %{user: user}} = conn, params) do +    user = User.get_cached_by_id(user.id) +    user = Repo.preload(user, bookmarks: :activity) + +    bookmarks = +      Bookmark.for_user_query(user.id) +      |> Pagination.fetch_paginated(params)      activities = -      user.bookmarks -      |> Enum.map(fn id -> Activity.get_create_by_object_ap_id(id) end) -      |> Enum.reverse() +      bookmarks +      |> Enum.map(fn b -> b.activity end)      conn +    |> add_link_headers(:bookmarks, bookmarks)      |> put_view(StatusView)      |> render("index.json", %{activities: activities, for: user, as: :activity})    end @@ -1119,7 +1210,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      accounts      |> Enum.each(fn account_id ->        with %Pleroma.List{} = list <- Pleroma.List.get(id, user), -           %User{} = followed <- User.get_by_id(account_id) do +           %User{} = followed <- User.get_cached_by_id(account_id) do          Pleroma.List.follow(list, followed)        end      end) @@ -1131,7 +1222,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_by_id(account_id) do +           %User{} = followed <- Pleroma.User.get_cached_by_id(account_id) do          Pleroma.List.unfollow(list, followed)        end      end) @@ -1175,6 +1266,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do          |> ActivityPub.fetch_activities_bounded(following, params)          |> Enum.reverse() +      user = Repo.preload(user, bookmarks: :activity) +        conn        |> put_view(StatusView)        |> render("index.json", %{activities: activities, for: user, as: :activity}) @@ -1424,7 +1517,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    def relationship_noop(%{assigns: %{user: user}} = conn, %{"id" => id}) do      Logger.debug("Unimplemented, returning unmodified relationship") -    with %User{} = target <- User.get_by_id(id) do +    with %User{} = target <- User.get_cached_by_id(id) do        conn        |> put_view(AccountView)        |> render("relationship.json", %{user: user, target: target}) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index af56c4149..779b9a382 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -68,7 +68,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do    defp do_render("account.json", %{user: user} = opts) do      image = User.avatar_url(user) |> MediaProxy.url()      header = User.banner_url(user) |> MediaProxy.url() -    user_info = User.user_info(user) +    user_info = User.get_cached_user_info(user)      bot = (user.info.source_data["type"] || "Person") in ["Application", "Service"]      emojis = @@ -113,21 +113,23 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do        bot: bot,        source: %{          note: "", -        privacy: user_info.default_scope, -        sensitive: false +        sensitive: false, +        pleroma: %{}        },        # Pleroma extension -      pleroma: -        %{ -          confirmation_pending: user_info.confirmation_pending, -          tags: user.tags, -          is_moderator: user.info.is_moderator, -          is_admin: user.info.is_admin, -          relationship: relationship -        } -        |> with_notification_settings(user, opts[:for]) +      pleroma: %{ +        confirmation_pending: user_info.confirmation_pending, +        tags: user.tags, +        hide_followers: user.info.hide_followers, +        hide_follows: user.info.hide_follows, +        hide_favorites: user.info.hide_favorites, +        relationship: relationship +      }      } +    |> maybe_put_role(user, opts[:for]) +    |> maybe_put_settings(user, opts[:for], user_info) +    |> maybe_put_notification_settings(user, opts[:for])    end    defp username_from_nickname(string) when is_binary(string) do @@ -136,9 +138,37 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do    defp username_from_nickname(_), do: nil -  defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do -    Map.put(data, :notification_settings, user.info.notification_settings) +  defp maybe_put_settings( +         data, +         %User{id: user_id} = user, +         %User{id: user_id}, +         user_info +       ) do +    data +    |> Kernel.put_in([:source, :privacy], user_info.default_scope) +    |> Kernel.put_in([:source, :pleroma, :show_role], user.info.show_role) +    |> Kernel.put_in([:source, :pleroma, :no_rich_text], user.info.no_rich_text) +  end + +  defp maybe_put_settings(data, _, _, _), do: data + +  defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do +    data +    |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin) +    |> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator) +  end + +  defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do +    data +    |> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin) +    |> Kernel.put_in([:pleroma, :is_moderator], user.info.is_moderator) +  end + +  defp maybe_put_role(data, _, _), do: data + +  defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do +    Kernel.put_in(data, [:pleroma, :notification_settings], user.info.notification_settings)    end -  defp with_notification_settings(data, _, _), do: data +  defp maybe_put_notification_settings(data, _, _), do: data  end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index d4a8e4fff..62d064d71 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    alias Pleroma.Activity    alias Pleroma.HTML +  alias Pleroma.Object    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.CommonAPI @@ -19,8 +20,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    defp get_replied_to_activities(activities) do      activities      |> Enum.map(fn -      %{data: %{"type" => "Create", "object" => %{"inReplyTo" => in_reply_to}}} -> -        in_reply_to != "" && in_reply_to +      %{data: %{"type" => "Create", "object" => object}} -> +        object = Object.normalize(object) +        object.data["inReplyTo"] != "" && object.data["inReplyTo"]        _ ->          nil @@ -29,7 +31,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      |> Activity.create_by_object_ap_id()      |> Repo.all()      |> Enum.reduce(%{}, fn activity, acc -> -      Map.put(acc, activity.data["object"]["id"], activity) +      object = Object.normalize(activity) +      Map.put(acc, object.data["id"], activity)      end)    end @@ -54,6 +57,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    defp get_context_id(_), do: nil +  defp reblogged?(activity, user) do +    object = Object.normalize(activity) || %{} +    present?(user && user.ap_id in (object.data["announcements"] || [])) +  end +    def render("index.json", opts) do      replied_to_activities = get_replied_to_activities(opts.activities) @@ -72,8 +80,13 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      user = get_user(activity.data["actor"])      created_at = Utils.to_masto_date(activity.data["published"]) -    reblogged = Activity.get_create_by_object_ap_id(object) -    reblogged = render("status.json", Map.put(opts, :activity, reblogged)) +    reblogged_activity = Activity.get_create_by_object_ap_id(object) +    reblogged = render("status.json", Map.put(opts, :activity, reblogged_activity)) + +    activity_object = Object.normalize(activity) +    favorited = opts[:for] && opts[:for].ap_id in (activity_object.data["likes"] || []) + +    bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], reblogged_activity)      mentions =        activity.recipients @@ -94,9 +107,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        reblogs_count: 0,        replies_count: 0,        favourites_count: 0, -      reblogged: false, -      favourited: false, -      bookmarked: false, +      reblogged: reblogged?(reblogged_activity, opts[:for]), +      favourited: present?(favorited), +      bookmarked: present?(bookmarked),        muted: false,        pinned: pinned?(activity, user),        sensitive: false, @@ -117,14 +130,16 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      }    end -  def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do +  def render("status.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do +    object = Object.normalize(activity) +      user = get_user(activity.data["actor"]) -    like_count = object["like_count"] || 0 -    announcement_count = object["announcement_count"] || 0 +    like_count = object.data["like_count"] || 0 +    announcement_count = object.data["announcement_count"] || 0 -    tags = object["tag"] || [] -    sensitive = object["sensitive"] || Enum.member?(tags, "nsfw") +    tags = object.data["tag"] || [] +    sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")      mentions =        activity.recipients @@ -132,16 +147,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        |> Enum.filter(& &1)        |> Enum.map(fn user -> AccountView.render("mention.json", %{user: user}) end) -    repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || []) -    favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || []) -    bookmarked = opts[:for] && object["id"] in opts[:for].bookmarks +    favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) + +    bookmarked = opts[:for] && CommonAPI.bookmarked?(opts[:for], activity) -    attachment_data = object["attachment"] || [] +    attachment_data = object.data["attachment"] || []      attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment) -    created_at = Utils.to_masto_date(object["published"]) +    created_at = Utils.to_masto_date(object.data["published"])      reply_to = get_reply_to(activity, opts) +      reply_to_user = reply_to && get_user(reply_to.data["actor"])      content = @@ -163,7 +179,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do          "mastoapi:content"        ) -    summary = object["summary"] || "" +    summary = object.data["summary"] || ""      summary_html =        summary @@ -186,12 +202,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        if user.local do          Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity)        else -        object["external_url"] || object["id"] +        object.data["external_url"] || object.data["id"]        end      %{        id: to_string(activity.id), -      uri: object["id"], +      uri: object.data["id"],        url: url,        account: AccountView.render("account.json", %{user: user}),        in_reply_to_id: reply_to && to_string(reply_to.id), @@ -201,9 +217,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        content: content_html,        created_at: created_at,        reblogs_count: announcement_count, -      replies_count: object["repliesCount"] || 0, +      replies_count: object.data["repliesCount"] || 0,        favourites_count: like_count, -      reblogged: present?(repeated), +      reblogged: reblogged?(activity, opts[:for]),        favourited: present?(favorited),        bookmarked: present?(bookmarked),        muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user), @@ -219,10 +235,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do          website: nil        },        language: nil, -      emojis: build_emojis(activity.data["object"]["emoji"]), +      emojis: build_emojis(object.data["emoji"]),        pleroma: %{          local: activity.local,          conversation_id: get_context_id(activity), +        in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,          content: %{"text/plain" => content_plaintext},          spoiler_text: %{"text/plain" => summary_plaintext}        } @@ -301,13 +318,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    end    def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do -    _id = activity.data["object"]["inReplyTo"] -    replied_to_activities[activity.data["object"]["inReplyTo"]] +    object = Object.normalize(activity) + +    with nil <- replied_to_activities[object.data["inReplyTo"]] do +      # If user didn't participate in the thread +      Activity.get_in_reply_to_activity(activity) +    end    end -  def get_reply_to(%{data: %{"object" => object}}, _) do -    if object["inReplyTo"] && object["inReplyTo"] != "" do -      Activity.get_create_by_object_ap_id(object["inReplyTo"]) +  def get_reply_to(%{data: %{"object" => _object}} = activity, _) do +    object = Object.normalize(activity) + +    if object.data["inReplyTo"] && object.data["inReplyTo"] != "" do +      Activity.get_create_by_object_ap_id(object.data["inReplyTo"])      else        nil      end @@ -315,8 +338,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do    def get_visibility(object) do      public = "https://www.w3.org/ns/activitystreams#Public" -    to = object["to"] || [] -    cc = object["cc"] || [] +    to = object.data["to"] || [] +    cc = object.data["cc"] || []      cond do        public in to -> @@ -337,25 +360,25 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      end    end -  def render_content(%{"type" => "Video"} = object) do -    with name when not is_nil(name) and name != "" <- object["name"] do -      "<p><a href=\"#{object["id"]}\">#{name}</a></p>#{object["content"]}" +  def render_content(%{data: %{"type" => "Video"}} = object) 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 -      _ -> object["content"] || "" +      _ -> object.data["content"] || ""      end    end -  def render_content(%{"type" => object_type} = object) +  def render_content(%{data: %{"type" => object_type}} = object)        when object_type in ["Article", "Page"] do -    with summary when not is_nil(summary) and summary != "" <- object["name"], -         url when is_bitstring(url) <- object["url"] do -      "<p><a href=\"#{url}\">#{summary}</a></p>#{object["content"]}" +    with summary when not is_nil(summary) and summary != "" <- object.data["name"], +         url when is_bitstring(url) <- object.data["url"] do +      "<p><a href=\"#{url}\">#{summary}</a></p>#{object.data["content"]}"      else -      _ -> object["content"] || "" +      _ -> object.data["content"] || ""      end    end -  def render_content(object), do: object["content"] || "" +  def render_content(object), do: object.data["content"] || ""    @doc """    Builds a dictionary tags. diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex index 1b3721e2b..abfa26754 100644 --- a/lib/pleroma/web/mastodon_api/websocket_handler.ex +++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex @@ -90,7 +90,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do    # Authenticated streams.    defp allow_request(stream, {"access_token", access_token}) when stream in @streams do      with %Token{user_id: user_id} <- Repo.get_by(Token, token: access_token), -         user = %User{} <- User.get_by_id(user_id) do +         user = %User{} <- User.get_cached_by_id(user_id) do        {:ok, user}      else        _ -> {:error, 403} diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy/media_proxy.ex index 3bd2affe9..5762e767b 100644 --- a/lib/pleroma/web/media_proxy/media_proxy.ex +++ b/lib/pleroma/web/media_proxy/media_proxy.ex @@ -13,32 +13,44 @@ defmodule Pleroma.Web.MediaProxy do    def url(url) do      config = Application.get_env(:pleroma, :media_proxy, []) +    domain = URI.parse(url).host -    if !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) do -      url -    else -      secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] - -      # Must preserve `%2F` for compatibility with S3 -      # https://git.pleroma.social/pleroma/pleroma/issues/580 -      replacement = get_replacement(url, ":2F:") - -      # The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice. -      base64 = +    cond do +      !Keyword.get(config, :enabled, false) or String.starts_with?(url, Pleroma.Web.base_url()) ->          url -        |> String.replace("%2F", replacement) -        |> URI.decode() -        |> URI.encode() -        |> String.replace(replacement, "%2F") -        |> Base.url_encode64(@base64_opts) -      sig = :crypto.hmac(:sha, secret, base64) -      sig64 = sig |> Base.url_encode64(@base64_opts) +      Enum.any?(Pleroma.Config.get([:media_proxy, :whitelist]), fn pattern -> +        String.equivalent?(domain, pattern) +      end) -> +        url -      build_url(sig64, base64, filename(url)) +      true -> +        encode_url(url)      end    end +  def encode_url(url) do +    secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] + +    # Must preserve `%2F` for compatibility with S3 +    # https://git.pleroma.social/pleroma/pleroma/issues/580 +    replacement = get_replacement(url, ":2F:") + +    # The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice. +    base64 = +      url +      |> String.replace("%2F", replacement) +      |> URI.decode() +      |> URI.encode() +      |> String.replace(replacement, "%2F") +      |> Base.url_encode64(@base64_opts) + +    sig = :crypto.hmac(:sha, secret, base64) +    sig64 = sig |> Base.url_encode64(@base64_opts) + +    build_url(sig64, base64, filename(url)) +  end +    def decode_url(sig, url) do      secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base]      sig = Base.url_decode64!(sig, @base64_opts) diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/rel_me.ex new file mode 100644 index 000000000..03af899c4 --- /dev/null +++ b/lib/pleroma/web/metadata/rel_me.ex @@ -0,0 +1,13 @@ +defmodule Pleroma.Web.Metadata.Providers.RelMe do +  alias Pleroma.Web.Metadata.Providers.Provider +  @behaviour Provider + +  @impl Provider +  def build_tags(%{user: user}) do +    (Floki.attribute(user.bio, "link[rel~=me]", "href") ++ +       Floki.attribute(user.bio, "a[rel~=me]", "href")) +    |> Enum.map(fn link -> +      {:link, [rel: "me", href: link], []} +    end) +  end +end diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/oauth/fallback_controller.ex index afaa00242..e3984f009 100644 --- a/lib/pleroma/web/oauth/fallback_controller.ex +++ b/lib/pleroma/web/oauth/fallback_controller.ex @@ -24,6 +24,6 @@ defmodule Pleroma.Web.OAuth.FallbackController do      conn      |> put_status(:unauthorized)      |> put_flash(:error, "Invalid Username/Password") -    |> OAuthController.authorize(conn.params["authorization"]) +    |> OAuthController.authorize(conn.params)    end  end diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index bee7084ad..688eaca11 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -23,6 +23,12 @@ defmodule Pleroma.Web.OAuth.OAuthController do    action_fallback(Pleroma.Web.OAuth.FallbackController) +  # Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg +  def authorize(conn, %{"authorization" => _} = params) do +    {auth_attrs, params} = Map.pop(params, "authorization") +    authorize(conn, Map.merge(params, auth_attrs)) +  end +    def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do      if ControllerHelper.truthy_param?(params["force_login"]) do        do_authorize(conn, params) @@ -49,6 +55,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do      available_scopes = (app && app.scopes) || []      scopes = oauth_scopes(params, nil) || available_scopes +    # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template      render(conn, Authenticator.auth_template(), %{        response_type: params["response_type"],        client_id: params["client_id"], @@ -62,18 +69,20 @@ defmodule Pleroma.Web.OAuth.OAuthController do    def create_authorization(          conn, -        %{"authorization" => auth_params} = params, +        %{"authorization" => _} = params,          opts \\ []        ) do      with {:ok, auth} <- do_create_authorization(conn, params, opts[:user]) do -      after_create_authorization(conn, auth, auth_params) +      after_create_authorization(conn, auth, params)      else        error -> -        handle_create_authorization_error(conn, error, auth_params) +        handle_create_authorization_error(conn, error, params)      end    end -  def after_create_authorization(conn, auth, %{"redirect_uri" => redirect_uri} = auth_params) do +  def after_create_authorization(conn, auth, %{ +        "authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs +      }) do      redirect_uri = redirect_uri(conn, redirect_uri)      if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do @@ -86,8 +95,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do        url_params = %{:code => auth.token}        url_params = -        if auth_params["state"] do -          Map.put(url_params, :state, auth_params["state"]) +        if auth_attrs["state"] do +          Map.put(url_params, :state, auth_attrs["state"])          else            url_params          end @@ -98,26 +107,34 @@ defmodule Pleroma.Web.OAuth.OAuthController do      end    end -  defp handle_create_authorization_error(conn, {scopes_issue, _}, auth_params) +  defp handle_create_authorization_error( +         conn, +         {scopes_issue, _}, +         %{"authorization" => _} = params +       )         when scopes_issue in [:unsupported_scopes, :missing_scopes] do      # Per https://github.com/tootsuite/mastodon/blob/      #   51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39      conn      |> put_flash(:error, "This action is outside the authorized scopes")      |> put_status(:unauthorized) -    |> authorize(auth_params) +    |> authorize(params)    end -  defp handle_create_authorization_error(conn, {:auth_active, false}, auth_params) do +  defp handle_create_authorization_error( +         conn, +         {:auth_active, false}, +         %{"authorization" => _} = params +       ) do      # Per https://github.com/tootsuite/mastodon/blob/      #   51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76      conn      |> put_flash(:error, "Your login is missing a confirmed e-mail address")      |> put_status(:forbidden) -    |> authorize(auth_params) +    |> authorize(params)    end -  defp handle_create_authorization_error(conn, error, _auth_params) do +  defp handle_create_authorization_error(conn, error, %{"authorization" => _}) do      Authenticator.handle_error(conn, error)    end @@ -126,7 +143,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do           fixed_token = fix_padding(params["code"]),           %Authorization{} = auth <-             Repo.get_by(Authorization, token: fixed_token, app_id: app.id), -         %User{} = user <- User.get_by_id(auth.user_id), +         %User{} = user <- User.get_cached_by_id(auth.user_id),           {:ok, token} <- Token.exchange_token(app, auth),           {:ok, inserted_at} <- DateTime.from_naive(token.inserted_at, "Etc/UTC") do        response = %{ @@ -151,7 +168,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do          conn,          %{"grant_type" => "password"} = params        ) do -    with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn, params)}, +    with {_, {:ok, %User{} = user}} <- {:get_user, Authenticator.get_user(conn)},           %App{} = app <- get_app_from_request(conn, params),           {:auth_active, true} <- {:auth_active, User.auth_active?(user)},           {:user_active, true} <- {:user_active, !user.info.deactivated}, @@ -214,19 +231,19 @@ defmodule Pleroma.Web.OAuth.OAuthController do    end    @doc "Prepares OAuth request to provider for Ueberauth" -  def prepare_request(conn, %{"provider" => provider} = params) do +  def prepare_request(conn, %{"provider" => provider, "authorization" => auth_attrs}) do      scope = -      oauth_scopes(params, []) +      oauth_scopes(auth_attrs, [])        |> Enum.join(" ")      state = -      params +      auth_attrs        |> Map.delete("scopes")        |> Map.put("scope", scope)        |> Poison.encode!()      params = -      params +      auth_attrs        |> Map.drop(~w(scope scopes client_id redirect_uri))        |> Map.put("state", state) @@ -260,26 +277,26 @@ defmodule Pleroma.Web.OAuth.OAuthController do    def callback(conn, params) do      params = callback_params(params) -    with {:ok, registration} <- Authenticator.get_registration(conn, params) do +    with {:ok, registration} <- Authenticator.get_registration(conn) do        user = Repo.preload(registration, :user).user -      auth_params = Map.take(params, ~w(client_id redirect_uri scope scopes state)) +      auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))        if user do          create_authorization(            conn, -          %{"authorization" => auth_params}, +          %{"authorization" => auth_attrs},            user: user          )        else          registration_params = -          Map.merge(auth_params, %{ +          Map.merge(auth_attrs, %{              "nickname" => Registration.nickname(registration),              "email" => Registration.email(registration)            })          conn          |> put_session(:registration_id, registration.id) -        |> registration_details(registration_params) +        |> registration_details(%{"authorization" => registration_params})        end      else        _ -> @@ -293,53 +310,44 @@ defmodule Pleroma.Web.OAuth.OAuthController do      Map.merge(params, Poison.decode!(state))    end -  def registration_details(conn, params) do +  def registration_details(conn, %{"authorization" => auth_attrs}) do      render(conn, "register.html", %{ -      client_id: params["client_id"], -      redirect_uri: params["redirect_uri"], -      state: params["state"], -      scopes: oauth_scopes(params, []), -      nickname: params["nickname"], -      email: params["email"] +      client_id: auth_attrs["client_id"], +      redirect_uri: auth_attrs["redirect_uri"], +      state: auth_attrs["state"], +      scopes: oauth_scopes(auth_attrs, []), +      nickname: auth_attrs["nickname"], +      email: auth_attrs["email"]      })    end -  def register(conn, %{"op" => "connect"} = params) do -    authorization_params = Map.put(params, "name", params["auth_name"]) -    create_authorization_params = %{"authorization" => authorization_params} - +  def register(conn, %{"authorization" => _, "op" => "connect"} = params) do      with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),           %Registration{} = registration <- Repo.get(Registration, registration_id),           {_, {:ok, auth}} <- -           {:create_authorization, do_create_authorization(conn, create_authorization_params)}, +           {:create_authorization, do_create_authorization(conn, params)},           %User{} = user <- Repo.preload(auth, :user).user,           {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do        conn        |> put_session_registration_id(nil) -      |> after_create_authorization(auth, authorization_params) +      |> after_create_authorization(auth, params)      else        {:create_authorization, error} -> -        {:register, handle_create_authorization_error(conn, error, create_authorization_params)} +        {:register, handle_create_authorization_error(conn, error, params)}        _ ->          {:register, :generic_error}      end    end -  def register(conn, %{"op" => "register"} = params) do +  def register(conn, %{"authorization" => _, "op" => "register"} = params) do      with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),           %Registration{} = registration <- Repo.get(Registration, registration_id), -         {:ok, user} <- Authenticator.create_from_registration(conn, params, registration) do +         {:ok, user} <- Authenticator.create_from_registration(conn, registration) do        conn        |> put_session_registration_id(nil)        |> create_authorization( -        %{ -          "authorization" => %{ -            "client_id" => params["client_id"], -            "redirect_uri" => params["redirect_uri"], -            "scopes" => oauth_scopes(params, nil) -          } -        }, +        params,          user: user        )      else @@ -374,15 +382,15 @@ defmodule Pleroma.Web.OAuth.OAuthController do               %{                 "client_id" => client_id,                 "redirect_uri" => redirect_uri -             } = auth_params -         } = params, +             } = auth_attrs +         },           user \\ nil         ) do      with {_, {:ok, %User{} = user}} <- -           {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn, params)}, +           {: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), -         scopes <- oauth_scopes(auth_params, []), +         scopes <- oauth_scopes(auth_attrs, []),           {:unsupported_scopes, []} <- {:unsupported_scopes, scopes -- app.scopes},           # Note: `scope` param is intentionally not optional in this context           {:missing_scopes, false} <- {:missing_scopes, scopes == []}, diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex index 2b5ad9b94..399140003 100644 --- a/lib/pleroma/web/oauth/token.ex +++ b/lib/pleroma/web/oauth/token.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Web.OAuth.Token do    def exchange_token(app, auth) do      with {:ok, auth} <- Authorization.use_token(auth),           true <- auth.app_id == app.id do -      create_token(app, User.get_by_id(auth.user_id), auth.scopes) +      create_token(app, User.get_cached_by_id(auth.user_id), auth.scopes)      end    end diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex index 1a1b74bb0..166691a09 100644 --- a/lib/pleroma/web/ostatus/activity_representer.ex +++ b/lib/pleroma/web/ostatus/activity_representer.ex @@ -54,23 +54,16 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do      end)    end -  defp get_links(%{local: true, data: data}) do +  defp get_links(%{local: true}, %{"id" => object_id}) do      h = fn str -> [to_charlist(str)] end      [ -      {:link, [type: ['application/atom+xml'], href: h.(data["object"]["id"]), rel: 'self'], []}, -      {:link, [type: ['text/html'], href: h.(data["object"]["id"]), rel: 'alternate'], []} +      {:link, [type: ['application/atom+xml'], href: h.(object_id), rel: 'self'], []}, +      {:link, [type: ['text/html'], href: h.(object_id), rel: 'alternate'], []}      ]    end -  defp get_links(%{ -         local: false, -         data: %{ -           "object" => %{ -             "external_url" => external_url -           } -         } -       }) do +  defp get_links(%{local: false}, %{"external_url" => external_url}) do      h = fn str -> [to_charlist(str)] end      [ @@ -78,7 +71,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do      ]    end -  defp get_links(_activity), do: [] +  defp get_links(_activity, _object_data), do: []    defp get_emoji_links(emojis) do      Enum.map(emojis, fn {emoji, file} -> @@ -88,14 +81,16 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do    def to_simple_form(activity, user, with_author \\ false) -  def to_simple_form(%{data: %{"object" => %{"type" => "Note"}}} = activity, user, with_author) do +  def to_simple_form(%{data: %{"type" => "Create"}} = activity, user, with_author) do      h = fn str -> [to_charlist(str)] end -    updated_at = activity.data["object"]["published"] -    inserted_at = activity.data["object"]["published"] +    object = Object.normalize(activity) + +    updated_at = object.data["published"] +    inserted_at = object.data["published"]      attachments = -      Enum.map(activity.data["object"]["attachment"] || [], fn attachment -> +      Enum.map(object.data["attachment"] || [], fn attachment ->          url = hd(attachment["url"])          {:link, @@ -108,7 +103,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do      mentions = activity.recipients |> get_mentions      categories = -      (activity.data["object"]["tag"] || []) +      (object.data["tag"] || [])        |> Enum.map(fn tag ->          if is_binary(tag) do            {:category, [term: to_charlist(tag)], []} @@ -118,11 +113,11 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do        end)        |> Enum.filter(& &1) -    emoji_links = get_emoji_links(activity.data["object"]["emoji"] || %{}) +    emoji_links = get_emoji_links(object.data["emoji"] || %{})      summary = -      if activity.data["object"]["summary"] do -        [{:summary, [], h.(activity.data["object"]["summary"])}] +      if object.data["summary"] do +        [{:summary, [], h.(object.data["summary"])}]        else          []        end @@ -131,10 +126,9 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do        {:"activity:object-type", ['http://activitystrea.ms/schema/1.0/note']},        {:"activity:verb", ['http://activitystrea.ms/schema/1.0/post']},        # For notes, federate the object id. -      {:id, h.(activity.data["object"]["id"])}, +      {:id, h.(object.data["id"])},        {:title, ['New note by #{user.nickname}']}, -      {:content, [type: 'html'], -       h.(activity.data["object"]["content"] |> String.replace(~r/[\n\r]/, ""))}, +      {:content, [type: 'html'], h.(object.data["content"] |> String.replace(~r/[\n\r]/, ""))},        {:published, h.(inserted_at)},        {:updated, h.(updated_at)},        {:"ostatus:conversation", [ref: h.(activity.data["context"])], @@ -142,7 +136,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do        {:link, [ref: h.(activity.data["context"]), rel: 'ostatus:conversation'], []}      ] ++        summary ++ -      get_links(activity) ++ +      get_links(activity, object.data) ++        categories ++ attachments ++ in_reply_to ++ author ++ mentions ++ emoji_links    end diff --git a/lib/pleroma/web/ostatus/handlers/note_handler.ex b/lib/pleroma/web/ostatus/handlers/note_handler.ex index db995ec77..ec6e5cfaf 100644 --- a/lib/pleroma/web/ostatus/handlers/note_handler.ex +++ b/lib/pleroma/web/ostatus/handlers/note_handler.ex @@ -113,8 +113,9 @@ defmodule Pleroma.Web.OStatus.NoteHandler do           cw <- OStatus.get_cw(entry),           in_reply_to <- XML.string_from_xpath("//thr:in-reply-to[1]/@ref", entry),           in_reply_to_activity <- fetch_replied_to_activity(entry, in_reply_to), -         in_reply_to <- -           (in_reply_to_activity && in_reply_to_activity.data["object"]["id"]) || in_reply_to, +         in_reply_to_object <- +           (in_reply_to_activity && Object.normalize(in_reply_to_activity)) || nil, +         in_reply_to <- (in_reply_to_object && in_reply_to_object.data["id"]) || in_reply_to,           attachments <- OStatus.get_attachments(entry),           context <- get_context(entry, in_reply_to),           tags <- OStatus.get_tags(entry), diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex index 9a34d7ad5..4744c6d83 100644 --- a/lib/pleroma/web/ostatus/ostatus.ex +++ b/lib/pleroma/web/ostatus/ostatus.ex @@ -294,7 +294,7 @@ defmodule Pleroma.Web.OStatus do        }        with false <- update, -           %User{} = user <- User.get_by_ap_id(data.ap_id) do +           %User{} = user <- User.get_cached_by_ap_id(data.ap_id) do          {:ok, user}        else          _e -> User.insert_or_update_user(data) diff --git a/lib/pleroma/web/push/impl.ex b/lib/pleroma/web/push/impl.ex index 2233480c5..35d3ff07c 100644 --- a/lib/pleroma/web/push/impl.ex +++ b/lib/pleroma/web/push/impl.ex @@ -21,8 +21,10 @@ defmodule Pleroma.Web.Push.Impl do    @doc "Performs sending notifications for user subscriptions"    @spec perform(Notification.t()) :: list(any) | :error    def perform( -        %{activity: %{data: %{"type" => activity_type}, id: activity_id}, user_id: user_id} = -          notif +        %{ +          activity: %{data: %{"type" => activity_type}, id: activity_id} = activity, +          user_id: user_id +        } = notif        )        when activity_type in @types do      actor = User.get_cached_by_ap_id(notif.activity.data["actor"]) @@ -30,13 +32,14 @@ defmodule Pleroma.Web.Push.Impl do      type = Activity.mastodon_notification_type(notif.activity)      gcm_api_key = Application.get_env(:web_push_encryption, :gcm_api_key)      avatar_url = User.avatar_url(actor) +    object = Object.normalize(activity)      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), +        body: format_body(notif, actor, object),          notification_id: notif.id,          notification_type: type,          icon: avatar_url, @@ -95,25 +98,25 @@ defmodule Pleroma.Web.Push.Impl do    end    def format_body( -        %{activity: %{data: %{"type" => "Create", "object" => %{"content" => content}}}}, -        actor +        %{activity: %{data: %{"type" => "Create"}}}, +        actor, +        %{data: %{"content" => content}}        ) do      "@#{actor.nickname}: #{Utils.scrub_html_and_truncate(content, 80)}"    end    def format_body( -        %{activity: %{data: %{"type" => "Announce", "object" => activity_id}}}, -        actor +        %{activity: %{data: %{"type" => "Announce"}}}, +        actor, +        %{data: %{"content" => content}}        ) do -    %Activity{data: %{"object" => %{"id" => object_id}}} = Activity.get_by_ap_id(activity_id) -    %Object{data: %{"content" => content}} = Object.get_by_ap_id(object_id) -      "@#{actor.nickname} repeated: #{Utils.scrub_html_and_truncate(content, 80)}"    end    def format_body(          %{activity: %{data: %{"type" => type}}}, -        actor +        actor, +        _object        )        when type in ["Follow", "Like"] do      case type do diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex index eaca41132..26eb614a6 100644 --- a/lib/pleroma/web/rel_me.ex +++ b/lib/pleroma/web/rel_me.ex @@ -6,7 +6,8 @@ defmodule Pleroma.Web.RelMe do    @hackney_options [      pool: :media,      recv_timeout: 2_000, -    max_body: 2_000_000 +    max_body: 2_000_000, +    with_body: true    ]    if Mix.env() == :test do diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex index 4bd271d8e..62e8fa610 100644 --- a/lib/pleroma/web/rich_media/parser.ex +++ b/lib/pleroma/web/rich_media/parser.ex @@ -12,7 +12,8 @@ defmodule Pleroma.Web.RichMedia.Parser do    @hackney_options [      pool: :media,      recv_timeout: 2_000, -    max_body: 2_000_000 +    max_body: 2_000_000, +    with_body: true    ]    def parse(nil), do: {:error, "No URL provided"} diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 172f337db..ff4f08af5 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -135,6 +135,7 @@ defmodule Pleroma.Web.Router do      post("/password_reset", UtilController, :password_reset)      get("/emoji", UtilController, :emoji)      get("/captcha", UtilController, :captcha) +    get("/healthcheck", UtilController, :healthcheck)    end    scope "/api/pleroma", Pleroma.Web do @@ -242,7 +243,6 @@ defmodule Pleroma.Web.Router do        get("/accounts/verify_credentials", MastodonAPIController, :verify_credentials)        get("/accounts/relationships", MastodonAPIController, :relationships) -      get("/accounts/search", MastodonAPIController, :account_search)        get("/accounts/:id/lists", MastodonAPIController, :account_lists)        get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array) @@ -261,6 +261,7 @@ defmodule Pleroma.Web.Router do        post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)        get("/notifications", MastodonAPIController, :notifications)        get("/notifications/:id", MastodonAPIController, :get_notification) +      delete("/notifications/destroy_multiple", MastodonAPIController, :destroy_multiple)        get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)        get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status) @@ -376,6 +377,8 @@ defmodule Pleroma.Web.Router do      get("/trends", MastodonAPIController, :empty_array) +    get("/accounts/search", MastodonAPIController, :account_search) +      scope [] do        pipe_through(:oauth_read_or_unauthenticated) @@ -392,6 +395,8 @@ defmodule Pleroma.Web.Router do        get("/accounts/:id", MastodonAPIController, :user)        get("/search", MastodonAPIController, :search) + +      get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)      end    end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index a82109f92..72eaf2084 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -81,7 +81,7 @@ defmodule Pleroma.Web.Streamer do          _ ->            Pleroma.List.get_lists_from_activity(item)            |> Enum.filter(fn list -> -            owner = User.get_by_id(list.user_id) +            owner = User.get_cached_by_id(list.user_id)              Visibility.visible_for_user?(item, owner)            end) diff --git a/lib/pleroma/web/templates/layout/app.html.eex b/lib/pleroma/web/templates/layout/app.html.eex index 7d2d609d1..a26b2c3c4 100644 --- a/lib/pleroma/web/templates/layout/app.html.eex +++ b/lib/pleroma/web/templates/layout/app.html.eex @@ -208,6 +208,17 @@            content: "";          }        } +      .form-row { +        display: flex; +      } +      .form-row > label { +        text-align: left; +        line-height: 47px; +        flex: 1; +      } +      .form-row > input { +        flex: 2; +      }      </style>    </head>    <body> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex index 4b8fb5dae..e6cfe108b 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex @@ -5,7 +5,7 @@      <%= for scope <- @available_scopes do %>        <%# Note: using hidden input with `unchecked_value` in order to distinguish user's empty selection from `scope` param being omitted %>        <div class="scope"> -        <%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: assigns[:scope_param] || "scope[]" %> +        <%= checkbox @form, :"scope_#{scope}", value: scope in @scopes && scope, checked_value: scope, unchecked_value: "", name: "authorization[scope][]" %>          <%= label @form, :"scope_#{scope}", String.capitalize(scope) %>        </div>      <% end %> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex index 85f62ca64..4bcda7300 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex @@ -1,6 +1,6 @@  <h2>Sign in with external provider</h2> -<%= form_for @conn, o_auth_path(@conn, :prepare_request), [method: "get"], fn f -> %> +<%= form_for @conn, o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %>    <%= render @view_module, "_scopes.html", Map.put(assigns, :form, f) %>    <%= hidden_input f, :client_id, value: @client_id %> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex index 126390391..facedc8db 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex @@ -8,8 +8,7 @@  <h2>Registration Details</h2>  <p>If you'd like to register a new account, please provide the details below.</p> - -<%= form_for @conn, o_auth_path(@conn, :register), [], fn f -> %> +<%= form_for @conn, o_auth_path(@conn, :register), [as: "authorization"], fn f -> %>  <div class="input">    <%= label f, :nickname, "Nickname" %> @@ -25,8 +24,8 @@  <p>Alternatively, sign in to connect to existing account.</p>  <div class="input"> -  <%= label f, :auth_name, "Name or email" %> -  <%= text_input f, :auth_name %> +  <%= label f, :name, "Name or email" %> +  <%= text_input f, :name %>  </div>  <div class="input">    <%= label f, :password, "Password" %> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index 45f2b5cc0..8b69c3033 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -50,6 +50,8 @@    </div>  <% end %> +<%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %> +  <%= hidden_input f, :client_id, value: @client_id %>  <%= hidden_input f, :response_type, value: @response_type %>  <%= hidden_input f, :redirect_uri, value: @redirect_uri %> diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex b/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex index 3c7960998..a3facf017 100644 --- a/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex +++ b/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex @@ -1,12 +1,13 @@  <h2>Password Reset for <%= @user.nickname %></h2>  <%= form_for @conn, util_path(@conn, :password_reset), [as: "data"], fn f -> %> -<%= label f, :password, "Password" %> -<%= password_input f, :password %> -<br> - -<%= label f, :password_confirmation, "Confirmation" %> -<%= password_input f, :password_confirmation %> -<br> -<%= hidden_input f, :token, value: @token.token %> -<%= submit "Reset" %> +  <div class="form-row"> +    <%= label f, :password, "Password" %> +    <%= password_input f, :password %> +  </div> +  <div class="form-row"> +    <%= label f, :password_confirmation, "Confirmation" %> +    <%= password_input f, :password_confirmation %> +  </div> +  <%= hidden_input f, :token, value: @token.token %> +  <%= submit "Reset" %>  <% end %> diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index d066d35f5..1122e6c5d 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    def show_password_reset(conn, %{"token" => token}) do      with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), -         %User{} = user <- User.get_by_id(token.user_id) do +         %User{} = user <- User.get_cached_by_id(token.user_id) do        render(conn, "password_reset.html", %{          token: token,          user: user @@ -75,7 +75,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    def remote_follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do      if is_status?(acct) do -      {:ok, object} = ActivityPub.fetch_object_from_id(acct) +      {:ok, object} = Pleroma.Object.Fetcher.fetch_object_from_id(acct)        %Activity{id: activity_id} = Activity.get_create_by_object_ap_id(object.data["id"])        redirect(conn, to: "/notice/#{activity_id}")      else @@ -101,7 +101,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    end    defp is_status?(acct) do -    case ActivityPub.fetch_and_contain_remote_object_from_id(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"] ->          true @@ -113,13 +113,13 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    def do_remote_follow(conn, %{          "authorization" => %{"name" => username, "password" => password, "id" => id}        }) do -    followee = User.get_by_id(id) +    followee = User.get_cached_by_id(id)      avatar = User.avatar_url(followee)      name = followee.nickname      with %User{} = user <- User.get_cached_by_nickname(username),           true <- Pbkdf2.checkpw(password, user.password_hash), -         %User{} = _followed <- User.get_by_id(id), +         %User{} = _followed <- User.get_cached_by_id(id),           {:ok, follower} <- User.follow(user, followee),           {:ok, _activity} <- ActivityPub.follow(follower, followee) do        conn @@ -141,7 +141,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    end    def do_remote_follow(%{assigns: %{user: user}} = conn, %{"user" => %{"id" => id}}) do -    with %User{} = followee <- User.get_by_id(id), +    with %User{} = followee <- User.get_cached_by_id(id),           {:ok, follower} <- User.follow(user, followee),           {:ok, _activity} <- ActivityPub.follow(follower, followee) do        conn @@ -286,7 +286,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do      emoji =        Emoji.get_all()        |> Enum.map(fn {short_code, path, tags} -> -        {short_code, %{image_url: path, tags: String.split(tags, ",")}} +        {short_code, %{image_url: path, tags: tags}}        end)        |> Enum.into(%{}) @@ -304,7 +304,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    end    def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do -    with followed_identifiers <- String.split(list), +    with lines <- String.split(list, "\n"), +         followed_identifiers <- +           Enum.map(lines, fn line -> +             String.split(line, ",") |> List.first() +           end) +           |> List.delete("Account address"),           {:ok, _} = Task.start(fn -> User.follow_import(follower, followed_identifiers) end) do        json(conn, "job started")      end @@ -358,4 +363,22 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do    def captcha(conn, _params) do      json(conn, Pleroma.Captcha.new())    end + +  def healthcheck(conn, _params) do +    info = +      if Pleroma.Config.get([:instance, :healthcheck]) do +        Pleroma.Healthcheck.system_info() +      else +        %{} +      end + +    conn = +      if info[:healthy] do +        conn +      else +        Plug.Conn.put_status(conn, :service_unavailable) +      end + +    json(conn, info) +  end  end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 9e9a46cf1..adeac6f3c 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -4,10 +4,10 @@  defmodule Pleroma.Web.TwitterAPI.TwitterAPI do    alias Pleroma.Activity -  alias Pleroma.Mailer +  alias Pleroma.Emails.Mailer +  alias Pleroma.Emails.UserEmail    alias Pleroma.Repo    alias Pleroma.User -  alias Pleroma.UserEmail    alias Pleroma.UserInviteToken    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.CommonAPI @@ -240,7 +240,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do          end        %{"screen_name" => nickname} -> -        case User.get_by_nickname(nickname) do +        case User.get_cached_by_nickname(nickname) do            nil -> {:error, "No user with such screen_name"}            target -> {:ok, target}          end @@ -266,6 +266,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do    defp parse_int(_, default), do: default +  # TODO: unify the search query with MastoAPI one and do only pagination here    def search(_user, %{"q" => query} = params) do      limit = parse_int(params["rpp"], 20)      page = parse_int(params["page"], 1) @@ -273,13 +274,13 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do      q =        from( -        a in Activity, +        [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', ?->'object'->>'content') @@ plainto_tsquery('english', ?)", -            a.data, +            "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", +            o.data,              ^query            ),          limit: ^limit, diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index a7ec9949c..79ed9dad2 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -434,7 +434,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do    end    def confirm_email(conn, %{"user_id" => uid, "token" => token}) do -    with %User{} = user <- User.get_by_id(uid), +    with %User{} = user <- User.get_cached_by_id(uid),           true <- user.local,           true <- user.info.confirmation_pending,           true <- user.info.confirmation_token == token, @@ -587,7 +587,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do    def approve_friend_request(conn, %{"user_id" => uid} = _params) do      with followed <- conn.assigns[:user], -         %User{} = follower <- User.get_by_id(uid), +         %User{} = follower <- User.get_cached_by_id(uid),           {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do        conn        |> put_view(UserView) @@ -599,7 +599,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do    def deny_friend_request(conn, %{"user_id" => uid} = _params) do      with followed <- conn.assigns[:user], -         %User{} = follower <- User.get_by_id(uid), +         %User{} = follower <- User.get_cached_by_id(uid),           {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do        conn        |> put_view(UserView) @@ -632,7 +632,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do    defp build_info_cng(user, params) do      info_params = -      ["no_rich_text", "locked", "hide_followers", "hide_follows", "show_role"] +      ["no_rich_text", "locked", "hide_followers", "hide_follows", "hide_favorites", "show_role"]        |> Enum.reduce(%{}, fn key, res ->          if value = params[key] do            Map.put(res, key, value == "true") diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex index 433322eb8..c64152da8 100644 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ b/lib/pleroma/web/twitter_api/views/activity_view.ex @@ -224,15 +224,17 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do    def render(          "activity.json", -        %{activity: %{data: %{"type" => "Create", "object" => object}} = activity} = opts +        %{activity: %{data: %{"type" => "Create", "object" => object_id}} = activity} = opts        ) do      user = get_user(activity.data["actor"], opts) -    created_at = object["published"] |> Utils.date_to_asctime() -    like_count = object["like_count"] || 0 -    announcement_count = object["announcement_count"] || 0 -    favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || []) -    repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || []) +    object = Object.normalize(object_id) + +    created_at = object.data["published"] |> Utils.date_to_asctime() +    like_count = object.data["like_count"] || 0 +    announcement_count = object.data["announcement_count"] || 0 +    favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) +    repeated = opts[:for] && opts[:for].ap_id in (object.data["announcements"] || [])      pinned = activity.id in user.info.pinned_activities      attentions = @@ -245,12 +247,12 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do      conversation_id = get_context_id(activity, opts) -    tags = activity.data["object"]["tag"] || [] -    possibly_sensitive = activity.data["object"]["sensitive"] || Enum.member?(tags, "nsfw") +    tags = object.data["tag"] || [] +    possibly_sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")      tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags -    {summary, content} = render_content(object) +    {summary, content} = render_content(object.data)      html =        content @@ -259,7 +261,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do          activity,          "twitterapi:content"        ) -      |> Formatter.emojify(object["emoji"]) +      |> Formatter.emojify(object.data["emoji"])      text =        if content do @@ -284,33 +286,33 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do      %{        "id" => activity.id, -      "uri" => activity.data["object"]["id"], +      "uri" => object.data["id"],        "user" => UserView.render("show.json", %{user: user, for: opts[:for]}),        "statusnet_html" => html,        "text" => text,        "is_local" => activity.local,        "is_post_verb" => true,        "created_at" => created_at, -      "in_reply_to_status_id" => object["inReplyToStatusId"], +      "in_reply_to_status_id" => reply_parent && reply_parent.id,        "in_reply_to_screen_name" => reply_user && reply_user.nickname,        "in_reply_to_profileurl" => User.profile_url(reply_user),        "in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id,        "in_reply_to_user_id" => reply_user && reply_user.id,        "statusnet_conversation_id" => conversation_id, -      "attachments" => (object["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts), +      "attachments" => (object.data["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts),        "attentions" => attentions,        "fave_num" => like_count,        "repeat_num" => announcement_count,        "favorited" => !!favorited,        "repeated" => !!repeated,        "pinned" => pinned, -      "external_url" => object["external_url"] || object["id"], +      "external_url" => object.data["external_url"] || object.data["id"],        "tags" => tags,        "activity_type" => "post",        "possibly_sensitive" => possibly_sensitive,        "visibility" => StatusView.get_visibility(object),        "summary" => summary, -      "summary_html" => summary |> Formatter.emojify(object["emoji"]), +      "summary_html" => summary |> Formatter.emojify(object.data["emoji"]),        "card" => card,        "muted" => CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user)      } diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index 0791ed760..ea015b8f0 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -74,58 +74,49 @@ defmodule Pleroma.Web.TwitterAPI.UserView do        |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end)        |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end) -    data = %{ -      "created_at" => user.inserted_at |> Utils.format_naive_asctime(), -      "description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")), -      "description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)), -      "favourites_count" => 0, -      "followers_count" => user_info[:follower_count], -      "following" => following, -      "follows_you" => follows_you, -      "statusnet_blocking" => statusnet_blocking, -      "friends_count" => user_info[:following_count], -      "id" => user.id, -      "name" => user.name || user.nickname, -      "name_html" => -        if(user.name, -          do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji), -          else: user.nickname -        ), -      "profile_image_url" => image, -      "profile_image_url_https" => image, -      "profile_image_url_profile_size" => image, -      "profile_image_url_original" => image, -      "rights" => %{ -        "delete_others_notice" => !!user.info.is_moderator, -        "admin" => !!user.info.is_admin -      }, -      "screen_name" => user.nickname, -      "statuses_count" => user_info[:note_count], -      "statusnet_profile_url" => user.ap_id, -      "cover_photo" => User.banner_url(user) |> MediaProxy.url(), -      "background_image" => image_url(user.info.background) |> MediaProxy.url(), -      "is_local" => user.local, -      "locked" => user.info.locked, -      "default_scope" => user.info.default_scope, -      "no_rich_text" => user.info.no_rich_text, -      "hide_followers" => user.info.hide_followers, -      "hide_follows" => user.info.hide_follows, -      "fields" => fields, - -      # Pleroma extension -      "pleroma" => -        %{ -          "confirmation_pending" => user_info.confirmation_pending, -          "tags" => user.tags -        } -        |> maybe_with_activation_status(user, for_user) -    } -      data = -      if(user.info.is_admin || user.info.is_moderator, -        do: maybe_with_role(data, user, for_user), -        else: data -      ) +      %{ +        "created_at" => user.inserted_at |> Utils.format_naive_asctime(), +        "description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")), +        "description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)), +        "favourites_count" => 0, +        "followers_count" => user_info[:follower_count], +        "following" => following, +        "follows_you" => follows_you, +        "statusnet_blocking" => statusnet_blocking, +        "friends_count" => user_info[:following_count], +        "id" => user.id, +        "name" => user.name || user.nickname, +        "name_html" => +          if(user.name, +            do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji), +            else: user.nickname +          ), +        "profile_image_url" => image, +        "profile_image_url_https" => image, +        "profile_image_url_profile_size" => image, +        "profile_image_url_original" => image, +        "screen_name" => user.nickname, +        "statuses_count" => user_info[:note_count], +        "statusnet_profile_url" => user.ap_id, +        "cover_photo" => User.banner_url(user) |> MediaProxy.url(), +        "background_image" => image_url(user.info.background) |> MediaProxy.url(), +        "is_local" => user.local, +        "locked" => user.info.locked, +        "hide_followers" => user.info.hide_followers, +        "hide_follows" => user.info.hide_follows, +        "fields" => fields, + +        # Pleroma extension +        "pleroma" => +          %{ +            "confirmation_pending" => user_info.confirmation_pending, +            "tags" => user.tags +          } +          |> maybe_with_activation_status(user, for_user) +      } +      |> maybe_with_user_settings(user, for_user) +      |> maybe_with_role(user, for_user)      if assigns[:token] do        Map.put(data, "token", token_string(assigns[:token])) @@ -141,15 +132,35 @@ defmodule Pleroma.Web.TwitterAPI.UserView do    defp maybe_with_activation_status(data, _, _), do: data    defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do -    Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role}) +    Map.merge(data, %{ +      "role" => role(user), +      "show_role" => user.info.show_role, +      "rights" => %{ +        "delete_others_notice" => !!user.info.is_moderator, +        "admin" => !!user.info.is_admin +      } +    })    end    defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do -    Map.merge(data, %{"role" => role(user)}) +    Map.merge(data, %{ +      "role" => role(user), +      "rights" => %{ +        "delete_others_notice" => !!user.info.is_moderator, +        "admin" => !!user.info.is_admin +      } +    })    end    defp maybe_with_role(data, _, _), do: data +  defp maybe_with_user_settings(data, %User{info: info, id: id} = _user, %User{id: id}) do +    data +    |> Kernel.put_in(["default_scope"], info.default_scope) +    |> Kernel.put_in(["no_rich_text"], info.no_rich_text) +  end + +  defp maybe_with_user_settings(data, _, _), do: data    defp role(%User{info: %{:is_admin => true}}), do: "admin"    defp role(%User{info: %{:is_moderator => true}}), do: "moderator"    defp role(_), do: "member" diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex index 32c3455f5..a3b0bf999 100644 --- a/lib/pleroma/web/web_finger/web_finger.ex +++ b/lib/pleroma/web/web_finger/web_finger.ex @@ -37,7 +37,7 @@ defmodule Pleroma.Web.WebFinger do      regex = ~r/(acct:)?(?<username>\w+)@#{host}/      with %{"username" => username} <- Regex.named_captures(regex, resource), -         %User{} = user <- User.get_by_nickname(username) do +         %User{} = user <- User.get_cached_by_nickname(username) do        {:ok, represent_user(user, fmt)}      else        _e -> | 
