aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorRoman Chvanikov <chvanikoff@pm.me>2019-06-29 00:52:50 +0300
committerRoman Chvanikov <chvanikoff@pm.me>2019-06-29 00:52:50 +0300
commit657277ffc0d3d25be4376ed629057a2d2cefb2e1 (patch)
tree3fdd4ca236669df9b2afc5bfcd0e5d6002e23666 /lib
parentc0fa0001476a8a45878a0c75125627164497eddf (diff)
parentc6668c2e7b9908e479527914ca7eb2c838aaab06 (diff)
downloadpleroma-657277ffc0d3d25be4376ed629057a2d2cefb2e1.tar.gz
Resolve conflicts
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/pleroma.ex67
-rw-r--r--lib/mix/tasks/pleroma/benchmark.ex (renamed from lib/mix/tasks/benchmark.ex)6
-rw-r--r--lib/mix/tasks/pleroma/common.ex28
-rw-r--r--lib/mix/tasks/pleroma/config.ex79
-rw-r--r--lib/mix/tasks/pleroma/database.ex10
-rw-r--r--lib/mix/tasks/pleroma/digest.ex3
-rw-r--r--lib/mix/tasks/pleroma/ecto/ecto.ex49
-rw-r--r--lib/mix/tasks/pleroma/ecto/migrate.ex63
-rw-r--r--lib/mix/tasks/pleroma/ecto/rollback.ex67
-rw-r--r--lib/mix/tasks/pleroma/emoji.ex8
-rw-r--r--lib/mix/tasks/pleroma/instance.ex113
-rw-r--r--lib/mix/tasks/pleroma/relay.ex10
-rw-r--r--lib/mix/tasks/pleroma/robots_txt.eex2
-rw-r--r--lib/mix/tasks/pleroma/sample_config.eex80
-rw-r--r--lib/mix/tasks/pleroma/sample_psql.eex7
-rw-r--r--lib/mix/tasks/pleroma/uploads.ex16
-rw-r--r--lib/mix/tasks/pleroma/user.ex117
-rw-r--r--lib/pleroma/activity/search.ex20
-rw-r--r--lib/pleroma/application.ex6
-rw-r--r--lib/pleroma/config/transfer_task.ex55
-rw-r--r--lib/pleroma/conversation/participation.ex4
-rw-r--r--lib/pleroma/emails/user_email.ex9
-rw-r--r--lib/pleroma/emoji.ex39
-rw-r--r--lib/pleroma/helpers/uri_helper.ex27
-rw-r--r--lib/pleroma/html.ex2
-rw-r--r--lib/pleroma/instances.ex2
-rw-r--r--lib/pleroma/notification.ex17
-rw-r--r--lib/pleroma/object/containment.ex4
-rw-r--r--lib/pleroma/object/fetcher.ex3
-rw-r--r--lib/pleroma/password_reset_token.ex (renamed from lib/pleroma/PasswordResetToken.ex)1
-rw-r--r--lib/pleroma/plugs/http_security_plug.ex4
-rw-r--r--lib/pleroma/plugs/idempotency_plug.ex84
-rw-r--r--lib/pleroma/plugs/rate_limit_plug.ex36
-rw-r--r--lib/pleroma/plugs/rate_limiter.ex94
-rw-r--r--lib/pleroma/plugs/uploaded_media.ex2
-rw-r--r--lib/pleroma/release_tasks.ex66
-rw-r--r--lib/pleroma/repo_streamer.ex34
-rw-r--r--lib/pleroma/reverse_proxy.ex6
-rw-r--r--lib/pleroma/user.ex99
-rw-r--r--lib/pleroma/user/search.ex97
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex19
-rw-r--r--lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex48
-rw-r--r--lib/pleroma/web/activity_pub/publisher.ex2
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex2
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex18
-rw-r--r--lib/pleroma/web/admin_api/admin_api_controller.ex37
-rw-r--r--lib/pleroma/web/admin_api/config.ex160
-rw-r--r--lib/pleroma/web/admin_api/views/config_view.ex17
-rw-r--r--lib/pleroma/web/admin_api/views/report_view.ex10
-rw-r--r--lib/pleroma/web/common_api/common_api.ex4
-rw-r--r--lib/pleroma/web/common_api/utils.ex14
-rw-r--r--lib/pleroma/web/controller_helper.ex18
-rw-r--r--lib/pleroma/web/endpoint.ex2
-rw-r--r--lib/pleroma/web/federator/retry_queue.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex132
-rw-r--r--lib/pleroma/web/mastodon_api/search_controller.ex79
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex20
-rw-r--r--lib/pleroma/web/mastodon_api/websocket_handler.ex1
-rw-r--r--lib/pleroma/web/oauth/authorization.ex12
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex158
-rw-r--r--lib/pleroma/web/oauth/token.ex5
-rw-r--r--lib/pleroma/web/oauth/token/response.ex8
-rw-r--r--lib/pleroma/web/rel_me.ex2
-rw-r--r--lib/pleroma/web/rich_media/helpers.ex44
-rw-r--r--lib/pleroma/web/rich_media/parser.ex2
-rw-r--r--lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex35
-rw-r--r--lib/pleroma/web/router.ex24
-rw-r--r--lib/pleroma/web/salmon/salmon.ex2
-rw-r--r--lib/pleroma/web/streamer.ex45
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex (renamed from lib/pleroma/web/templates/o_auth/o_auth/results.html.eex)0
-rw-r--r--lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex2
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex (renamed from lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex)0
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/reset.html.eex (renamed from lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex)2
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex (renamed from lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex)0
-rw-r--r--lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex (renamed from lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex)0
-rw-r--r--lib/pleroma/web/twitter_api/controllers/password_controller.ex37
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex22
-rw-r--r--lib/pleroma/web/twitter_api/views/password_view.ex8
-rw-r--r--lib/pleroma/web/views/error_view.ex2
79 files changed, 1782 insertions, 653 deletions
diff --git a/lib/mix/pleroma.ex b/lib/mix/pleroma.ex
new file mode 100644
index 000000000..1b758ea33
--- /dev/null
+++ b/lib/mix/pleroma.ex
@@ -0,0 +1,67 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Pleroma do
+ @doc "Common functions to be reused in mix tasks"
+ def start_pleroma do
+ Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
+ {:ok, _} = Application.ensure_all_started(:pleroma)
+ end
+
+ def load_pleroma do
+ Application.load(:pleroma)
+ end
+
+ def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do
+ Keyword.get(options, opt) || shell_prompt(prompt, defval, defname)
+ end
+
+ def shell_prompt(prompt, defval \\ nil, defname \\ nil) do
+ prompt_message = "#{prompt} [#{defname || defval}] "
+
+ input =
+ if mix_shell?(),
+ do: Mix.shell().prompt(prompt_message),
+ else: :io.get_line(prompt_message)
+
+ case input do
+ "\n" ->
+ case defval do
+ nil ->
+ shell_prompt(prompt, defval, defname)
+
+ defval ->
+ defval
+ end
+
+ input ->
+ String.trim(input)
+ end
+ end
+
+ def shell_yes?(message) do
+ if mix_shell?(),
+ do: Mix.shell().yes?("Continue?"),
+ else: shell_prompt(message, "Continue?") in ~w(Yn Y y)
+ end
+
+ def shell_info(message) do
+ if mix_shell?(),
+ do: Mix.shell().info(message),
+ else: IO.puts(message)
+ end
+
+ def shell_error(message) do
+ if mix_shell?(),
+ do: Mix.shell().error(message),
+ else: IO.puts(:stderr, message)
+ end
+
+ @doc "Performs a safe check whether `Mix.shell/0` is available (does not raise if Mix is not loaded)"
+ def mix_shell?, do: :erlang.function_exported(Mix, :shell, 0)
+
+ def escape_sh_path(path) do
+ ~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')
+ end
+end
diff --git a/lib/mix/tasks/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex
index e4b1a638a..d43db7b35 100644
--- a/lib/mix/tasks/benchmark.ex
+++ b/lib/mix/tasks/pleroma/benchmark.ex
@@ -1,9 +1,9 @@
defmodule Mix.Tasks.Pleroma.Benchmark do
+ import Mix.Pleroma
use Mix.Task
- alias Mix.Tasks.Pleroma.Common
def run(["search"]) do
- Common.start_pleroma()
+ start_pleroma()
Benchee.run(%{
"search" => fn ->
@@ -13,7 +13,7 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
end
def run(["tag"]) do
- Common.start_pleroma()
+ start_pleroma()
Benchee.run(%{
"tag" => fn ->
diff --git a/lib/mix/tasks/pleroma/common.ex b/lib/mix/tasks/pleroma/common.ex
deleted file mode 100644
index 48c0c1346..000000000
--- a/lib/mix/tasks/pleroma/common.ex
+++ /dev/null
@@ -1,28 +0,0 @@
-# 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.Common do
- @doc "Common functions to be reused in mix tasks"
- def start_pleroma do
- Mix.Task.run("app.start")
- end
-
- def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do
- Keyword.get(options, opt) ||
- case Mix.shell().prompt("#{prompt} [#{defname || defval}]") do
- "\n" ->
- case defval do
- nil -> get_option(options, opt, prompt, defval)
- defval -> defval
- end
-
- opt ->
- opt |> String.trim()
- end
- end
-
- def escape_sh_path(path) do
- ~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')
- end
-end
diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex
new file mode 100644
index 000000000..faa605d9b
--- /dev/null
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -0,0 +1,79 @@
+defmodule Mix.Tasks.Pleroma.Config do
+ use Mix.Task
+ import Mix.Pleroma
+ alias Pleroma.Repo
+ alias Pleroma.Web.AdminAPI.Config
+ @shortdoc "Manages the location of the config"
+ @moduledoc """
+ Manages the location of the config.
+
+ ## Transfers config from file to DB.
+
+ mix pleroma.config migrate_to_db
+
+ ## Transfers config from DB to file.
+
+ mix pleroma.config migrate_from_db ENV
+ """
+
+ def run(["migrate_to_db"]) do
+ start_pleroma()
+
+ if Pleroma.Config.get([:instance, :dynamic_configuration]) do
+ Application.get_all_env(:pleroma)
+ |> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end)
+ |> Enum.each(fn {k, v} ->
+ key = to_string(k) |> String.replace("Elixir.", "")
+ {:ok, _} = Config.update_or_create(%{group: "pleroma", key: key, value: v})
+ Mix.shell().info("#{key} is migrated.")
+ end)
+
+ Mix.shell().info("Settings migrated.")
+ else
+ Mix.shell().info(
+ "Migration is not allowed by config. You can change this behavior in instance settings."
+ )
+ end
+ end
+
+ def run(["migrate_from_db", env, delete?]) do
+ start_pleroma()
+
+ delete? = if delete? == "true", do: true, else: false
+
+ if Pleroma.Config.get([:instance, :dynamic_configuration]) do
+ config_path = "config/#{env}.exported_from_db.secret.exs"
+
+ {:ok, file} = File.open(config_path, [:write])
+ IO.write(file, "use Mix.Config\r\n")
+
+ Repo.all(Config)
+ |> Enum.each(fn config ->
+ mark =
+ if String.starts_with?(config.key, "Pleroma.") or
+ String.starts_with?(config.key, "Ueberauth"),
+ do: ",",
+ else: ":"
+
+ IO.write(
+ file,
+ "config :#{config.group}, #{config.key}#{mark} #{
+ inspect(Config.from_binary(config.value))
+ }\r\n"
+ )
+
+ if delete? do
+ {:ok, _} = Repo.delete(config)
+ Mix.shell().info("#{config.key} deleted from DB.")
+ end
+ end)
+
+ File.close(file)
+ System.cmd("mix", ["format", config_path])
+ else
+ Mix.shell().info(
+ "Migration is not allowed by config. You can change this behavior in instance settings."
+ )
+ end
+ end
+end
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 4d480ac3f..e91fb31d1 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -3,12 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.Database do
- alias Mix.Tasks.Pleroma.Common
alias Pleroma.Conversation
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
require Logger
+ import Mix.Pleroma
use Mix.Task
@shortdoc "A collection of database related tasks"
@@ -45,7 +45,7 @@ defmodule Mix.Tasks.Pleroma.Database do
]
)
- Common.start_pleroma()
+ start_pleroma()
Logger.info("Removing embedded objects")
Repo.query!(
@@ -66,12 +66,12 @@ defmodule Mix.Tasks.Pleroma.Database do
end
def run(["bump_all_conversations"]) do
- Common.start_pleroma()
+ start_pleroma()
Conversation.bump_for_all_activities()
end
def run(["update_users_following_followers_counts"]) do
- Common.start_pleroma()
+ start_pleroma()
users = Repo.all(User)
Enum.each(users, &User.remove_duplicated_following/1)
@@ -89,7 +89,7 @@ defmodule Mix.Tasks.Pleroma.Database do
]
)
- Common.start_pleroma()
+ start_pleroma()
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days])
diff --git a/lib/mix/tasks/pleroma/digest.ex b/lib/mix/tasks/pleroma/digest.ex
index 7ac3df5c7..19c4ce71e 100644
--- a/lib/mix/tasks/pleroma/digest.ex
+++ b/lib/mix/tasks/pleroma/digest.ex
@@ -1,6 +1,5 @@
defmodule Mix.Tasks.Pleroma.Digest do
use Mix.Task
- alias Mix.Tasks.Pleroma.Common
@shortdoc "Manages digest emails"
@moduledoc """
@@ -14,7 +13,7 @@ defmodule Mix.Tasks.Pleroma.Digest do
Example: ``mix pleroma.digest test donaldtheduck 2019-05-20``
"""
def run(["test", nickname | opts]) do
- Common.start_pleroma()
+ Mix.Pleroma.start_pleroma()
user = Pleroma.User.get_by_nickname(nickname)
diff --git a/lib/mix/tasks/pleroma/ecto/ecto.ex b/lib/mix/tasks/pleroma/ecto/ecto.ex
new file mode 100644
index 000000000..324f57fdd
--- /dev/null
+++ b/lib/mix/tasks/pleroma/ecto/ecto.ex
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-onl
+defmodule Mix.Tasks.Pleroma.Ecto do
+ @doc """
+ Ensures the given repository's migrations path exists on the file system.
+ """
+ @spec ensure_migrations_path(Ecto.Repo.t(), Keyword.t()) :: String.t()
+ def ensure_migrations_path(repo, opts) do
+ path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations")
+
+ path =
+ case Path.type(path) do
+ :relative ->
+ Path.join(Application.app_dir(:pleroma), path)
+
+ :absolute ->
+ path
+ end
+
+ if not File.dir?(path) do
+ raise_missing_migrations(Path.relative_to_cwd(path), repo)
+ end
+
+ path
+ end
+
+ @doc """
+ Returns the private repository path relative to the source.
+ """
+ def source_repo_priv(repo) do
+ config = repo.config()
+ priv = config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}"
+ Path.join(Application.app_dir(:pleroma), priv)
+ end
+
+ defp raise_missing_migrations(path, repo) do
+ raise("""
+ Could not find migrations directory #{inspect(path)}
+ for repo #{inspect(repo)}.
+ This may be because you are in a new project and the
+ migration directory has not been created yet. Creating an
+ empty directory at the path above will fix this error.
+ If you expected existing migrations to be found, please
+ make sure your repository has been properly configured
+ and the configured path exists.
+ """)
+ end
+end
diff --git a/lib/mix/tasks/pleroma/ecto/migrate.ex b/lib/mix/tasks/pleroma/ecto/migrate.ex
new file mode 100644
index 000000000..855c977f6
--- /dev/null
+++ b/lib/mix/tasks/pleroma/ecto/migrate.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-onl
+
+defmodule Mix.Tasks.Pleroma.Ecto.Migrate do
+ use Mix.Task
+ import Mix.Pleroma
+ require Logger
+
+ @shortdoc "Wrapper on `ecto.migrate` task."
+
+ @aliases [
+ n: :step,
+ v: :to
+ ]
+
+ @switches [
+ all: :boolean,
+ step: :integer,
+ to: :integer,
+ quiet: :boolean,
+ log_sql: :boolean,
+ strict_version_order: :boolean,
+ migrations_path: :string
+ ]
+
+ @moduledoc """
+ Changes `Logger` level to `:info` before start migration.
+ Changes level back when migration ends.
+
+ ## Start migration
+
+ mix pleroma.ecto.migrate [OPTIONS]
+
+ Options:
+ - see https://hexdocs.pm/ecto/2.0.0/Mix.Tasks.Ecto.Migrate.html
+ """
+
+ @impl true
+ def run(args \\ []) do
+ load_pleroma()
+ {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
+
+ opts =
+ if opts[:to] || opts[:step] || opts[:all],
+ do: opts,
+ else: Keyword.put(opts, :all, true)
+
+ opts =
+ if opts[:quiet],
+ do: Keyword.merge(opts, log: false, log_sql: false),
+ else: opts
+
+ path = Mix.Tasks.Pleroma.Ecto.ensure_migrations_path(Pleroma.Repo, opts)
+
+ level = Logger.level()
+ Logger.configure(level: :info)
+
+ {:ok, _, _} = Ecto.Migrator.with_repo(Pleroma.Repo, &Ecto.Migrator.run(&1, path, :up, opts))
+
+ Logger.configure(level: level)
+ end
+end
diff --git a/lib/mix/tasks/pleroma/ecto/rollback.ex b/lib/mix/tasks/pleroma/ecto/rollback.ex
new file mode 100644
index 000000000..2ffb0901c
--- /dev/null
+++ b/lib/mix/tasks/pleroma/ecto/rollback.ex
@@ -0,0 +1,67 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-onl
+
+defmodule Mix.Tasks.Pleroma.Ecto.Rollback do
+ use Mix.Task
+ import Mix.Pleroma
+ require Logger
+ @shortdoc "Wrapper on `ecto.rollback` task"
+
+ @aliases [
+ n: :step,
+ v: :to
+ ]
+
+ @switches [
+ all: :boolean,
+ step: :integer,
+ to: :integer,
+ start: :boolean,
+ quiet: :boolean,
+ log_sql: :boolean,
+ migrations_path: :string
+ ]
+
+ @moduledoc """
+ Changes `Logger` level to `:info` before start rollback.
+ Changes level back when rollback ends.
+
+ ## Start rollback
+
+ mix pleroma.ecto.rollback
+
+ Options:
+ - see https://hexdocs.pm/ecto/2.0.0/Mix.Tasks.Ecto.Rollback.html
+ """
+
+ @impl true
+ def run(args \\ []) do
+ load_pleroma()
+ {opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
+
+ opts =
+ if opts[:to] || opts[:step] || opts[:all],
+ do: opts,
+ else: Keyword.put(opts, :step, 1)
+
+ opts =
+ if opts[:quiet],
+ do: Keyword.merge(opts, log: false, log_sql: false),
+ else: opts
+
+ path = Mix.Tasks.Pleroma.Ecto.ensure_migrations_path(Pleroma.Repo, opts)
+
+ level = Logger.level()
+ Logger.configure(level: :info)
+
+ if Pleroma.Config.get(:env) == :test do
+ Logger.info("Rollback succesfully")
+ else
+ {:ok, _, _} =
+ Ecto.Migrator.with_repo(Pleroma.Repo, &Ecto.Migrator.run(&1, path, :down, opts))
+ end
+
+ Logger.configure(level: level)
+ end
+end
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
index d2ddf450a..c2225af7d 100644
--- a/lib/mix/tasks/pleroma/emoji.ex
+++ b/lib/mix/tasks/pleroma/emoji.ex
@@ -55,15 +55,13 @@ defmodule Mix.Tasks.Pleroma.Emoji do
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)
+ fetch_manifest(if options[:manifest], do: options[:manifest], else: default_manifest())
Enum.each(manifest, fn {name, info} ->
to_print = [
@@ -88,7 +86,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
{options, pack_names, []} = parse_global_opts(args)
- manifest_url = if options[:manifest], do: options[:manifest], else: @default_manifest
+ manifest_url = if options[:manifest], do: options[:manifest], else: default_manifest()
manifest = fetch_manifest(manifest_url)
@@ -298,4 +296,6 @@ defmodule Mix.Tasks.Pleroma.Emoji do
Tesla.client(middleware)
end
+
+ defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest])
end
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index d276df93a..0231b76cd 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -4,7 +4,7 @@
defmodule Mix.Tasks.Pleroma.Instance do
use Mix.Task
- alias Mix.Tasks.Pleroma.Common
+ import Mix.Pleroma
@shortdoc "Manages Pleroma instance"
@moduledoc """
@@ -29,7 +29,11 @@ defmodule Mix.Tasks.Pleroma.Instance do
- `--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
+ - `--rum Y/N` - Whether to enable RUM indexes
- `--indexable Y/N` - Allow/disallow indexing site by search engines
+ - `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
+ - `--uploads-dir` - the directory uploads go in when using a local uploader
+ - `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
"""
def run(["gen" | rest]) do
@@ -48,7 +52,11 @@ defmodule Mix.Tasks.Pleroma.Instance do
dbname: :string,
dbuser: :string,
dbpass: :string,
- indexable: :string
+ rum: :string,
+ indexable: :string,
+ db_configurable: :string,
+ uploads_dir: :string,
+ static_dir: :string
],
aliases: [
o: :output,
@@ -68,7 +76,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
if proceed? do
[domain, port | _] =
String.split(
- Common.get_option(
+ get_option(
options,
:domain,
"What domain will your instance use? (e.g pleroma.soykaf.com)"
@@ -77,16 +85,16 @@ defmodule Mix.Tasks.Pleroma.Instance do
) ++ [443]
name =
- Common.get_option(
+ get_option(
options,
:instance_name,
"What is the name of your instance? (e.g. Pleroma/Soykaf)"
)
- email = Common.get_option(options, :admin_email, "What is your admin email address?")
+ email = get_option(options, :admin_email, "What is your admin email address?")
notify_email =
- Common.get_option(
+ get_option(
options,
:notify_email,
"What email address do you want to use for sending email notifications?",
@@ -94,21 +102,27 @@ defmodule Mix.Tasks.Pleroma.Instance do
)
indexable =
- Common.get_option(
+ get_option(
options,
:indexable,
"Do you want search engines to index your site? (y/n)",
"y"
) === "y"
- dbhost =
- Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
+ db_configurable? =
+ get_option(
+ options,
+ :db_configurable,
+ "Do you want to store the configuration in the database (allows controlling it from admin-fe)? (y/n)",
+ "n"
+ ) === "y"
- dbname =
- Common.get_option(options, :dbname, "What is the name of your database?", "pleroma_dev")
+ dbhost = get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
+
+ dbname = get_option(options, :dbname, "What is the name of your database?", "pleroma")
dbuser =
- Common.get_option(
+ get_option(
options,
:dbuser,
"What is the user used to connect to your database?",
@@ -116,7 +130,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
)
dbpass =
- Common.get_option(
+ get_option(
options,
:dbpass,
"What is the password used to connect to your database?",
@@ -124,14 +138,39 @@ defmodule Mix.Tasks.Pleroma.Instance do
"autogenerated"
)
+ rum_enabled =
+ get_option(
+ options,
+ :rum,
+ "Would you like to use RUM indices?",
+ "n"
+ ) === "y"
+
+ uploads_dir =
+ get_option(
+ options,
+ :upload_dir,
+ "What directory should media uploads go in (when using the local uploader)?",
+ Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads])
+ )
+
+ static_dir =
+ get_option(
+ options,
+ :static_dir,
+ "What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?",
+ Pleroma.Config.get([:instance, :static_dir])
+ )
+
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
+ template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
result_config =
EEx.eval_file(
- "sample_config.eex" |> Path.expand(__DIR__),
+ template_dir <> "/sample_config.eex",
domain: domain,
port: port,
email: email,
@@ -141,47 +180,39 @@ defmodule Mix.Tasks.Pleroma.Instance do
dbname: dbname,
dbuser: dbuser,
dbpass: dbpass,
- version: Pleroma.Mixfile.project() |> Keyword.get(:version),
secret: secret,
jwt_secret: jwt_secret,
signing_salt: signing_salt,
web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
- web_push_private_key: Base.url_encode64(web_push_private_key, padding: false)
+ web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
+ db_configurable?: db_configurable?,
+ static_dir: static_dir,
+ uploads_dir: uploads_dir,
+ rum_enabled: rum_enabled
)
result_psql =
EEx.eval_file(
- "sample_psql.eex" |> Path.expand(__DIR__),
+ template_dir <> "/sample_psql.eex",
dbname: dbname,
dbuser: dbuser,
- dbpass: dbpass
+ dbpass: dbpass,
+ rum_enabled: rum_enabled
)
- Mix.shell().info(
- "Writing config to #{config_path}. You should rename it to config/prod.secret.exs or config/dev.secret.exs."
- )
+ shell_info("Writing config to #{config_path}.")
File.write(config_path, result_config)
- Mix.shell().info("Writing #{psql_path}.")
+ shell_info("Writing the postgres script to #{psql_path}.")
File.write(psql_path, result_psql)
- write_robots_txt(indexable)
-
- Mix.shell().info(
- "\n" <>
- """
- To get started:
- 1. Verify the contents of the generated files.
- 2. Run `sudo -u postgres psql -f #{Common.escape_sh_path(psql_path)}`.
- """ <>
- if config_path in ["config/dev.secret.exs", "config/prod.secret.exs"] do
- ""
- else
- "3. Run `mv #{Common.escape_sh_path(config_path)} 'config/prod.secret.exs'`."
- end
+ write_robots_txt(indexable, template_dir)
+
+ shell_info(
+ "\n All files successfully written! Refer to the installation instructions for your platform for next steps"
)
else
- Mix.shell().error(
+ shell_error(
"The task would have overwritten the following files:\n" <>
(Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <>
"Rerun with `--force` to overwrite them."
@@ -189,10 +220,10 @@ defmodule Mix.Tasks.Pleroma.Instance do
end
end
- defp write_robots_txt(indexable) do
+ defp write_robots_txt(indexable, template_dir) do
robots_txt =
EEx.eval_file(
- Path.expand("robots_txt.eex", __DIR__),
+ template_dir <> "/robots_txt.eex",
indexable: indexable
)
@@ -206,10 +237,10 @@ defmodule Mix.Tasks.Pleroma.Instance do
if File.exists?(robots_txt_path) do
File.cp!(robots_txt_path, "#{robots_txt_path}.bak")
- Mix.shell().info("Backing up existing robots.txt to #{robots_txt_path}.bak")
+ shell_info("Backing up existing robots.txt to #{robots_txt_path}.bak")
end
File.write(robots_txt_path, robots_txt)
- Mix.shell().info("Writing #{robots_txt_path}.")
+ shell_info("Writing #{robots_txt_path}.")
end
end
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
index fbec473c5..83ed0ed02 100644
--- a/lib/mix/tasks/pleroma/relay.ex
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -4,7 +4,7 @@
defmodule Mix.Tasks.Pleroma.Relay do
use Mix.Task
- alias Mix.Tasks.Pleroma.Common
+ import Mix.Pleroma
alias Pleroma.Web.ActivityPub.Relay
@shortdoc "Manages remote relays"
@@ -24,24 +24,24 @@ defmodule Mix.Tasks.Pleroma.Relay do
Example: ``mix pleroma.relay unfollow https://example.org/relay``
"""
def run(["follow", target]) do
- Common.start_pleroma()
+ start_pleroma()
with {:ok, _activity} <- Relay.follow(target) do
# put this task to sleep to allow the genserver to push out the messages
:timer.sleep(500)
else
- {:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
+ {:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}")
end
end
def run(["unfollow", target]) do
- Common.start_pleroma()
+ start_pleroma()
with {:ok, _activity} <- Relay.unfollow(target) do
# put this task to sleep to allow the genserver to push out the messages
:timer.sleep(500)
else
- {:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
+ {:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}")
end
end
end
diff --git a/lib/mix/tasks/pleroma/robots_txt.eex b/lib/mix/tasks/pleroma/robots_txt.eex
deleted file mode 100644
index 1af3c47ee..000000000
--- a/lib/mix/tasks/pleroma/robots_txt.eex
+++ /dev/null
@@ -1,2 +0,0 @@
-User-Agent: *
-Disallow: <%= if indexable, do: "", else: "/" %>
diff --git a/lib/mix/tasks/pleroma/sample_config.eex b/lib/mix/tasks/pleroma/sample_config.eex
deleted file mode 100644
index ec7d8821e..000000000
--- a/lib/mix/tasks/pleroma/sample_config.eex
+++ /dev/null
@@ -1,80 +0,0 @@
-# Pleroma instance configuration
-
-# NOTE: This file should not be committed to a repo or otherwise made public
-# without removing sensitive information.
-
-use Mix.Config
-
-config :pleroma, Pleroma.Web.Endpoint,
- url: [host: "<%= domain %>", scheme: "https", port: <%= port %>],
- secret_key_base: "<%= secret %>",
- signing_salt: "<%= signing_salt %>"
-
-config :pleroma, :instance,
- name: "<%= name %>",
- email: "<%= email %>",
- notify_email: "<%= notify_email %>",
- limit: 5000,
- registrations_open: true,
- dedupe_media: false
-
-config :pleroma, :media_proxy,
- enabled: false,
- redirect_on_failure: true
- #base_url: "https://cache.pleroma.social"
-
-config :pleroma, Pleroma.Repo,
- adapter: Ecto.Adapters.Postgres,
- username: "<%= dbuser %>",
- password: "<%= dbpass %>",
- database: "<%= dbname %>",
- hostname: "<%= dbhost %>",
- pool_size: 10
-
-# Configure web push notifications
-config :web_push_encryption, :vapid_details,
- subject: "mailto:<%= email %>",
- public_key: "<%= web_push_public_key %>",
- private_key: "<%= web_push_private_key %>"
-
-# Enable Strict-Transport-Security once SSL is working:
-# config :pleroma, :http_security,
-# sts: true
-
-# Configure S3 support if desired.
-# The public S3 endpoint is different depending on region and provider,
-# consult your S3 provider's documentation for details on what to use.
-#
-# config :pleroma, Pleroma.Uploaders.S3,
-# bucket: "some-bucket",
-# public_endpoint: "https://s3.amazonaws.com"
-#
-# Configure S3 credentials:
-# config :ex_aws, :s3,
-# access_key_id: "xxxxxxxxxxxxx",
-# secret_access_key: "yyyyyyyyyyyy",
-# region: "us-east-1",
-# scheme: "https://"
-#
-# For using third-party S3 clones like wasabi, also do:
-# config :ex_aws, :s3,
-# host: "s3.wasabisys.com"
-
-
-# Configure Openstack Swift support if desired.
-#
-# Many openstack deployments are different, so config is left very open with
-# no assumptions made on which provider you're using. This should allow very
-# wide support without needing separate handlers for OVH, Rackspace, etc.
-#
-# config :pleroma, Pleroma.Uploaders.Swift,
-# container: "some-container",
-# username: "api-username-yyyy",
-# password: "api-key-xxxx",
-# tenant_id: "<openstack-project/tenant-id>",
-# auth_url: "https://keystone-endpoint.provider.com",
-# storage_url: "https://swift-endpoint.prodider.com/v1/AUTH_<tenant>/<container>",
-# object_url: "https://cdn-endpoint.provider.com/<container>"
-#
-
-config :joken, default_signer: "<%= jwt_secret %>"
diff --git a/lib/mix/tasks/pleroma/sample_psql.eex b/lib/mix/tasks/pleroma/sample_psql.eex
deleted file mode 100644
index f0ac05e57..000000000
--- a/lib/mix/tasks/pleroma/sample_psql.eex
+++ /dev/null
@@ -1,7 +0,0 @@
-CREATE USER <%= dbuser %> WITH ENCRYPTED PASSWORD '<%= dbpass %>';
-CREATE DATABASE <%= dbname %> OWNER <%= dbuser %>;
-\c <%= dbname %>;
---Extensions made by ecto.migrate that need superuser access
-CREATE EXTENSION IF NOT EXISTS citext;
-CREATE EXTENSION IF NOT EXISTS pg_trgm;
-CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
diff --git a/lib/mix/tasks/pleroma/uploads.ex b/lib/mix/tasks/pleroma/uploads.ex
index 106fcf443..be45383ee 100644
--- a/lib/mix/tasks/pleroma/uploads.ex
+++ b/lib/mix/tasks/pleroma/uploads.ex
@@ -4,7 +4,7 @@
defmodule Mix.Tasks.Pleroma.Uploads do
use Mix.Task
- alias Mix.Tasks.Pleroma.Common
+ import Mix.Pleroma
alias Pleroma.Upload
alias Pleroma.Uploaders.Local
require Logger
@@ -24,7 +24,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do
"""
def run(["migrate_local", target_uploader | args]) do
delete? = Enum.member?(args, "--delete")
- Common.start_pleroma()
+ start_pleroma()
local_path = Pleroma.Config.get!([Local, :uploads])
uploader = Module.concat(Pleroma.Uploaders, target_uploader)
@@ -38,10 +38,10 @@ defmodule Mix.Tasks.Pleroma.Uploads do
Pleroma.Config.put([Upload, :uploader], uploader)
end
- Mix.shell().info("Migrating files from local #{local_path} to #{to_string(uploader)}")
+ shell_info("Migrating files from local #{local_path} to #{to_string(uploader)}")
if delete? do
- Mix.shell().info(
+ shell_info(
"Attention: uploaded files will be deleted, hope you have backups! (--delete ; cancel with ^C)"
)
@@ -78,7 +78,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do
|> Enum.filter(& &1)
total_count = length(uploads)
- Mix.shell().info("Found #{total_count} uploads")
+ shell_info("Found #{total_count} uploads")
uploads
|> Task.async_stream(
@@ -90,7 +90,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do
:ok
error ->
- Mix.shell().error("failed to upload #{inspect(upload.path)}: #{inspect(error)}")
+ shell_error("failed to upload #{inspect(upload.path)}: #{inspect(error)}")
end
end,
timeout: 150_000
@@ -99,10 +99,10 @@ defmodule Mix.Tasks.Pleroma.Uploads do
# credo:disable-for-next-line Credo.Check.Warning.UnusedEnumOperation
|> Enum.reduce(0, fn done, count ->
count = count + length(done)
- Mix.shell().info("Uploaded #{count}/#{total_count} files")
+ shell_info("Uploaded #{count}/#{total_count} files")
count
end)
- Mix.shell().info("Done!")
+ shell_info("Done!")
end
end
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index 25fc40ea7..8a78b4fe6 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -5,9 +5,10 @@
defmodule Mix.Tasks.Pleroma.User do
use Mix.Task
import Ecto.Changeset
- alias Mix.Tasks.Pleroma.Common
+ import Mix.Pleroma
alias Pleroma.User
alias Pleroma.UserInviteToken
+ alias Pleroma.Web.OAuth
@shortdoc "Manages Pleroma users"
@moduledoc """
@@ -49,6 +50,10 @@ defmodule Mix.Tasks.Pleroma.User do
mix pleroma.user delete_activities NICKNAME
+ ## Sign user out from all applications (delete user's OAuth tokens and authorizations).
+
+ mix pleroma.user sign_out NICKNAME
+
## Deactivate or activate the user's account.
mix pleroma.user toggle_activated NICKNAME
@@ -115,7 +120,7 @@ defmodule Mix.Tasks.Pleroma.User do
admin? = Keyword.get(options, :admin, false)
assume_yes? = Keyword.get(options, :assume_yes, false)
- Mix.shell().info("""
+ shell_info("""
A user will be created with the following information:
- nickname: #{nickname}
- email: #{email}
@@ -128,10 +133,10 @@ defmodule Mix.Tasks.Pleroma.User do
- admin: #{if(admin?, do: "true", else: "false")}
""")
- proceed? = assume_yes? or Mix.shell().yes?("Continue?")
+ proceed? = assume_yes? or shell_yes?("Continue?")
if proceed? do
- Common.start_pleroma()
+ start_pleroma()
params = %{
nickname: nickname,
@@ -145,7 +150,7 @@ defmodule Mix.Tasks.Pleroma.User do
changeset = User.register_changeset(%User{}, params, need_confirmation: false)
{:ok, _user} = User.register(changeset)
- Mix.shell().info("User #{nickname} created")
+ shell_info("User #{nickname} created")
if moderator? do
run(["set", nickname, "--moderator"])
@@ -159,64 +164,64 @@ defmodule Mix.Tasks.Pleroma.User do
run(["reset_password", nickname])
end
else
- Mix.shell().info("User will not be created.")
+ shell_info("User will not be created.")
end
end
def run(["rm", nickname]) do
- Common.start_pleroma()
+ start_pleroma()
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
User.perform(:delete, user)
- Mix.shell().info("User #{nickname} deleted.")
+ shell_info("User #{nickname} deleted.")
else
_ ->
- Mix.shell().error("No local user #{nickname}")
+ shell_error("No local user #{nickname}")
end
end
def run(["toggle_activated", nickname]) do
- Common.start_pleroma()
+ start_pleroma()
with %User{} = user <- User.get_cached_by_nickname(nickname) do
{:ok, user} = User.deactivate(user, !user.info.deactivated)
- Mix.shell().info(
+ shell_info(
"Activation status of #{nickname}: #{if(user.info.deactivated, do: "de", else: "")}activated"
)
else
_ ->
- Mix.shell().error("No user #{nickname}")
+ shell_error("No user #{nickname}")
end
end
def run(["reset_password", nickname]) do
- Common.start_pleroma()
+ start_pleroma()
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}")
+ shell_info("Generated password reset token for #{user.nickname}")
IO.puts(
"URL: #{
- Pleroma.Web.Router.Helpers.util_url(
+ Pleroma.Web.Router.Helpers.reset_password_url(
Pleroma.Web.Endpoint,
- :show_password_reset,
+ :reset,
token.token
)
}"
)
else
_ ->
- Mix.shell().error("No local user #{nickname}")
+ shell_error("No local user #{nickname}")
end
end
def run(["unsubscribe", nickname]) do
- Common.start_pleroma()
+ start_pleroma()
with %User{} = user <- User.get_cached_by_nickname(nickname) do
- Mix.shell().info("Deactivating #{user.nickname}")
+ shell_info("Deactivating #{user.nickname}")
User.deactivate(user)
{:ok, friends} = User.get_friends(user)
@@ -224,7 +229,7 @@ defmodule Mix.Tasks.Pleroma.User do
Enum.each(friends, fn friend ->
user = User.get_cached_by_id(user.id)
- Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
+ shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}")
User.unfollow(user, friend)
end)
@@ -233,16 +238,16 @@ defmodule Mix.Tasks.Pleroma.User do
user = User.get_cached_by_id(user.id)
if Enum.empty?(user.following) do
- Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
+ shell_info("Successfully unsubscribed all followers from #{user.nickname}")
end
else
_ ->
- Mix.shell().error("No user #{nickname}")
+ shell_error("No user #{nickname}")
end
end
def run(["set", nickname | rest]) do
- Common.start_pleroma()
+ start_pleroma()
{options, [], []} =
OptionParser.parse(
@@ -274,33 +279,33 @@ defmodule Mix.Tasks.Pleroma.User do
end
else
_ ->
- Mix.shell().error("No local user #{nickname}")
+ shell_error("No local user #{nickname}")
end
end
def run(["tag", nickname | tags]) do
- Common.start_pleroma()
+ start_pleroma()
with %User{} = user <- User.get_cached_by_nickname(nickname) do
user = user |> User.tag(tags)
- Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
+ shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
else
_ ->
- Mix.shell().error("Could not change user tags for #{nickname}")
+ shell_error("Could not change user tags for #{nickname}")
end
end
def run(["untag", nickname | tags]) do
- Common.start_pleroma()
+ start_pleroma()
with %User{} = user <- User.get_cached_by_nickname(nickname) do
user = user |> User.untag(tags)
- Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
+ shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
else
_ ->
- Mix.shell().error("Could not change user tags for #{nickname}")
+ shell_error("Could not change user tags for #{nickname}")
end
end
@@ -321,14 +326,12 @@ defmodule Mix.Tasks.Pleroma.User do
end)
|> Enum.into(%{})
- Common.start_pleroma()
+ start_pleroma()
with {:ok, val} <- options[:expires_at],
options = Map.put(options, :expires_at, val),
{:ok, invite} <- UserInviteToken.create_invite(options) do
- Mix.shell().info(
- "Generated user invite token " <> String.replace(invite.invite_type, "_", " ")
- )
+ shell_info("Generated user invite token " <> String.replace(invite.invite_type, "_", " "))
url =
Pleroma.Web.Router.Helpers.redirect_url(
@@ -340,14 +343,14 @@ defmodule Mix.Tasks.Pleroma.User do
IO.puts(url)
else
error ->
- Mix.shell().error("Could not create invite token: #{inspect(error)}")
+ shell_error("Could not create invite token: #{inspect(error)}")
end
end
def run(["invites"]) do
- Common.start_pleroma()
+ start_pleroma()
- Mix.shell().info("Invites list:")
+ shell_info("Invites list:")
UserInviteToken.list_invites()
|> Enum.each(fn invite ->
@@ -361,7 +364,7 @@ defmodule Mix.Tasks.Pleroma.User do
" | Max use: #{max_use} Left use: #{max_use - invite.uses}"
end
- Mix.shell().info(
+ shell_info(
"ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{
invite.used
}#{expire_info}#{using_info}"
@@ -370,40 +373,54 @@ defmodule Mix.Tasks.Pleroma.User do
end
def run(["revoke_invite", token]) do
- Common.start_pleroma()
+ start_pleroma()
with {:ok, invite} <- UserInviteToken.find_by_token(token),
{:ok, _} <- UserInviteToken.update_invite(invite, %{used: true}) do
- Mix.shell().info("Invite for token #{token} was revoked.")
+ shell_info("Invite for token #{token} was revoked.")
else
- _ -> Mix.shell().error("No invite found with token #{token}")
+ _ -> shell_error("No invite found with token #{token}")
end
end
def run(["delete_activities", nickname]) do
- Common.start_pleroma()
+ start_pleroma()
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
{:ok, _} = User.delete_user_activities(user)
- Mix.shell().info("User #{nickname} statuses deleted.")
+ shell_info("User #{nickname} statuses deleted.")
else
_ ->
- Mix.shell().error("No local user #{nickname}")
+ shell_error("No local user #{nickname}")
end
end
def run(["toggle_confirmed", nickname]) do
- Common.start_pleroma()
+ start_pleroma()
with %User{} = user <- User.get_cached_by_nickname(nickname) do
{:ok, user} = User.toggle_confirmation(user)
message = if user.info.confirmation_pending, do: "needs", else: "doesn't need"
- Mix.shell().info("#{nickname} #{message} confirmation.")
+ shell_info("#{nickname} #{message} confirmation.")
+ else
+ _ ->
+ shell_error("No local user #{nickname}")
+ end
+ end
+
+ def run(["sign_out", nickname]) do
+ start_pleroma()
+
+ with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
+ OAuth.Token.delete_user_tokens(user)
+ OAuth.Authorization.delete_user_authorizations(user)
+
+ shell_info("#{nickname} signed out from all apps.")
else
_ ->
- Mix.shell().error("No local user #{nickname}")
+ shell_error("No local user #{nickname}")
end
end
@@ -416,7 +433,7 @@ defmodule Mix.Tasks.Pleroma.User do
{:ok, user} = User.update_and_set_cache(user_cng)
- Mix.shell().info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
+ shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
user
end
@@ -429,7 +446,7 @@ defmodule Mix.Tasks.Pleroma.User do
{:ok, user} = User.update_and_set_cache(user_cng)
- Mix.shell().info("Admin status of #{user.nickname}: #{user.info.is_admin}")
+ shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}")
user
end
@@ -442,7 +459,7 @@ defmodule Mix.Tasks.Pleroma.User do
{:ok, user} = User.update_and_set_cache(user_cng)
- Mix.shell().info("Locked status of #{user.nickname}: #{user.info.locked}")
+ shell_info("Locked status of #{user.nickname}: #{user.info.locked}")
user
end
end
diff --git a/lib/pleroma/activity/search.ex b/lib/pleroma/activity/search.ex
index 9ccedcd13..0aa2aab23 100644
--- a/lib/pleroma/activity/search.ex
+++ b/lib/pleroma/activity/search.ex
@@ -39,8 +39,7 @@ defmodule Pleroma.Activity.Search do
"to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)",
o.data,
^search_query
- ),
- order_by: [desc: :id]
+ )
)
end
@@ -56,18 +55,19 @@ defmodule Pleroma.Activity.Search do
)
end
- # users can search everything
- defp maybe_restrict_local(q, %User{}), do: q
+ defp maybe_restrict_local(q, user) do
+ limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)
- # unauthenticated users can only search local activities
- defp maybe_restrict_local(q, _) do
- if Pleroma.Config.get([:instance, :limit_unauthenticated_to_local_content], true) do
- where(q, local: true)
- else
- q
+ case {limit, user} do
+ {:all, _} -> restrict_local(q)
+ {:unauthenticated, %User{}} -> q
+ {:unauthenticated, _} -> restrict_local(q)
+ {false, _} -> q
end
end
+ defp restrict_local(q), do: where(q, local: true)
+
defp maybe_fetch(activities, user, search_query) do
with true <- Regex.match?(~r/https?:/, search_query),
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 664cf578e..29cd14477 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -31,6 +31,7 @@ defmodule Pleroma.Application do
[
# Start the Ecto repository
%{id: Pleroma.Repo, start: {Pleroma.Repo, :start_link, []}, type: :supervisor},
+ %{id: Pleroma.Config.TransferTask, start: {Pleroma.Config.TransferTask, :start_link, []}},
%{id: Pleroma.Emoji, start: {Pleroma.Emoji, :start_link, []}},
%{id: Pleroma.Captcha, start: {Pleroma.Captcha, :start_link, []}},
%{
@@ -180,7 +181,6 @@ defmodule Pleroma.Application do
Pleroma.Repo.Instrumenter.setup()
end
- Prometheus.Registry.register_collector(:prometheus_process_collector)
Pleroma.Web.Endpoint.MetricsExporter.setup()
Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
Pleroma.Web.Endpoint.Instrumenter.setup()
@@ -193,14 +193,14 @@ defmodule Pleroma.Application do
else
[]
end ++
- if Pleroma.Config.get([Pleroma.Uploader, :proxy_remote]) do
+ if Pleroma.Config.get([Pleroma.Upload, :proxy_remote]) do
[:upload]
else
[]
end
end
- if Mix.env() == :test do
+ if Pleroma.Config.get(:env) == :test do
defp streamer_child, do: []
defp chat_child, do: []
else
diff --git a/lib/pleroma/config/transfer_task.ex b/lib/pleroma/config/transfer_task.ex
new file mode 100644
index 000000000..cf880aa22
--- /dev/null
+++ b/lib/pleroma/config/transfer_task.ex
@@ -0,0 +1,55 @@
+defmodule Pleroma.Config.TransferTask do
+ use Task
+ alias Pleroma.Web.AdminAPI.Config
+
+ def start_link do
+ load_and_update_env()
+ if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Pleroma.Repo)
+ :ignore
+ end
+
+ def load_and_update_env do
+ if Pleroma.Config.get([:instance, :dynamic_configuration]) and
+ Ecto.Adapters.SQL.table_exists?(Pleroma.Repo, "config") do
+ for_restart =
+ Pleroma.Repo.all(Config)
+ |> Enum.map(&update_env(&1))
+
+ # We need to restart applications for loaded settings take effect
+ for_restart
+ |> Enum.reject(&(&1 in [:pleroma, :ok]))
+ |> Enum.each(fn app ->
+ Application.stop(app)
+ :ok = Application.start(app)
+ end)
+ end
+ end
+
+ defp update_env(setting) do
+ try do
+ key =
+ if String.starts_with?(setting.key, "Pleroma.") do
+ "Elixir." <> setting.key
+ else
+ setting.key
+ end
+
+ group = String.to_existing_atom(setting.group)
+
+ Application.put_env(
+ group,
+ String.to_existing_atom(key),
+ Config.from_binary(setting.value)
+ )
+
+ group
+ rescue
+ e ->
+ require Logger
+
+ Logger.warn(
+ "updating env causes error, key: #{inspect(setting.key)}, error: #{inspect(e)}"
+ )
+ end
+ end
+end
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index 2c13c4b40..5883e4183 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -59,10 +59,10 @@ defmodule Pleroma.Conversation.Participation do
def for_user(user, params \\ %{}) do
from(p in __MODULE__,
where: p.user_id == ^user.id,
- order_by: [desc: p.updated_at]
+ order_by: [desc: p.updated_at],
+ preload: [conversation: [:users]]
)
|> Pleroma.Pagination.fetch_paginated(params)
- |> Repo.preload(conversation: [:users])
end
def for_user_with_last_activity_id(user, params \\ %{}) do
diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex
index 0ad0aed40..49046bb8b 100644
--- a/lib/pleroma/emails/user_email.ex
+++ b/lib/pleroma/emails/user_email.ex
@@ -23,13 +23,8 @@ defmodule Pleroma.Emails.UserEmail do
defp recipient(email, name), do: {name, email}
defp recipient(%Pleroma.User{} = user), do: recipient(user.email, user.name)
- def password_reset_email(user, password_reset_token) when is_binary(password_reset_token) do
- password_reset_url =
- Router.Helpers.util_url(
- Endpoint,
- :show_password_reset,
- password_reset_token
- )
+ def password_reset_email(user, token) when is_binary(token) do
+ password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
html_body = """
<h3>Reset your password at #{instance_name()}</h3>
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index de7fcc1ce..052501642 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -22,7 +22,6 @@ defmodule Pleroma.Emoji do
@ets __MODULE__.Ets
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
- @groups Pleroma.Config.get([:emoji, :groups])
@doc false
def start_link do
@@ -87,6 +86,8 @@ defmodule Pleroma.Emoji do
"emoji"
)
+ emoji_groups = Pleroma.Config.get([:emoji, :groups])
+
case File.ls(emoji_dir_path) do
{:error, :enoent} ->
# The custom emoji directory doesn't exist,
@@ -98,7 +99,9 @@ defmodule Pleroma.Emoji do
Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}")
{:ok, results} ->
- grouped = Enum.group_by(results, &File.dir?/1)
+ grouped =
+ Enum.group_by(results, fn file -> File.dir?(Path.join(emoji_dir_path, file)) end)
+
packs = grouped[true] || []
files = grouped[false] || []
@@ -116,7 +119,7 @@ defmodule Pleroma.Emoji do
emojis =
Enum.flat_map(
packs,
- fn pack -> load_pack(Path.join(emoji_dir_path, pack)) end
+ fn pack -> load_pack(Path.join(emoji_dir_path, pack), emoji_groups) end
)
true = :ets.insert(@ets, emojis)
@@ -127,9 +130,9 @@ defmodule Pleroma.Emoji do
shortcode_globs = Pleroma.Config.get([:emoji, :shortcode_globs], [])
emojis =
- (load_from_file("config/emoji.txt") ++
- load_from_file("config/custom_emoji.txt") ++
- load_from_globs(shortcode_globs))
+ (load_from_file("config/emoji.txt", emoji_groups) ++
+ load_from_file("config/custom_emoji.txt", emoji_groups) ++
+ load_from_globs(shortcode_globs, emoji_groups))
|> Enum.reject(fn value -> value == nil end)
true = :ets.insert(@ets, emojis)
@@ -137,23 +140,25 @@ defmodule Pleroma.Emoji do
:ok
end
- defp load_pack(pack_dir) do
+ defp load_pack(pack_dir, emoji_groups) 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)
+ load_from_file(emoji_txt, emoji_groups)
else
+ extensions = Pleroma.Config.get([:emoji, :pack_extensions])
+
Logger.info(
- "No emoji.txt found for pack \"#{pack_name}\", assuming all .png files are emoji"
+ "No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji"
)
- make_shortcode_to_file_map(pack_dir, [".png"])
+ make_shortcode_to_file_map(pack_dir, extensions)
|> Enum.map(fn {shortcode, rel_file} ->
filename = Path.join("/emoji/#{pack_name}", rel_file)
- {shortcode, filename, [to_string(match_extra(@groups, filename))]}
+ {shortcode, filename, [to_string(match_extra(emoji_groups, filename))]}
end)
end
end
@@ -182,21 +187,21 @@ defmodule Pleroma.Emoji do
|> Enum.filter(fn f -> Path.extname(f) in exts end)
end
- defp load_from_file(file) do
+ defp load_from_file(file, emoji_groups) do
if File.exists?(file) do
- load_from_file_stream(File.stream!(file))
+ load_from_file_stream(File.stream!(file), emoji_groups)
else
[]
end
end
- defp load_from_file_stream(stream) do
+ defp load_from_file_stream(stream, emoji_groups) do
stream
|> Stream.map(&String.trim/1)
|> Stream.map(fn line ->
case String.split(line, ~r/,\s*/) do
[name, file] ->
- {name, file, [to_string(match_extra(@groups, file))]}
+ {name, file, [to_string(match_extra(emoji_groups, file))]}
[name, file | tags] ->
{name, file, tags}
@@ -208,7 +213,7 @@ defmodule Pleroma.Emoji do
|> Enum.to_list()
end
- defp load_from_globs(globs) do
+ defp load_from_globs(globs, emoji_groups) do
static_path = Path.join(:code.priv_dir(:pleroma), "static")
paths =
@@ -219,7 +224,7 @@ defmodule Pleroma.Emoji do
|> Enum.concat()
Enum.map(paths, fn path ->
- tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path)))
+ tag = match_extra(emoji_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)]}
diff --git a/lib/pleroma/helpers/uri_helper.ex b/lib/pleroma/helpers/uri_helper.ex
new file mode 100644
index 000000000..8a79b44c4
--- /dev/null
+++ b/lib/pleroma/helpers/uri_helper.ex
@@ -0,0 +1,27 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Helpers.UriHelper do
+ def append_uri_params(uri, appended_params) do
+ uri = URI.parse(uri)
+ appended_params = for {k, v} <- appended_params, into: %{}, do: {to_string(k), v}
+ existing_params = URI.query_decoder(uri.query || "") |> Enum.into(%{})
+ updated_params_keys = Enum.uniq(Map.keys(existing_params) ++ Map.keys(appended_params))
+
+ updated_params =
+ for k <- updated_params_keys, do: {k, appended_params[k] || existing_params[k]}
+
+ uri
+ |> Map.put(:query, URI.encode_query(updated_params))
+ |> URI.to_string()
+ end
+
+ def append_param_if_present(%{} = params, param_name, param_value) do
+ if param_value do
+ Map.put(params, param_name, param_value)
+ else
+ params
+ end
+ end
+end
diff --git a/lib/pleroma/html.ex b/lib/pleroma/html.ex
index e5e78ee4f..2fae7281c 100644
--- a/lib/pleroma/html.ex
+++ b/lib/pleroma/html.ex
@@ -89,7 +89,7 @@ defmodule Pleroma.HTML do
Cachex.fetch!(:scrubber_cache, key, fn _key ->
result =
content
- |> Floki.filter_out("a.mention")
+ |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]")
|> Floki.attribute("a", "href")
|> Enum.at(0)
diff --git a/lib/pleroma/instances.ex b/lib/pleroma/instances.ex
index 5e107f4c9..fa5043bc5 100644
--- a/lib/pleroma/instances.ex
+++ b/lib/pleroma/instances.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Instances do
def reachability_datetime_threshold do
federation_reachability_timeout_days =
- Pleroma.Config.get(:instance)[:federation_reachability_timeout_days] || 0
+ Pleroma.Config.get([:instance, :federation_reachability_timeout_days], 0)
if federation_reachability_timeout_days > 0 do
NaiveDateTime.add(
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index 736ebc3af..118560d34 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -13,6 +13,8 @@ defmodule Pleroma.Notification do
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.Push
+ alias Pleroma.Web.Streamer
import Ecto.Query
import Ecto.Changeset
@@ -149,8 +151,7 @@ defmodule Pleroma.Notification do
end
end
- def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
- when type in ["Create", "Like", "Announce", "Follow"] do
+ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
object = Object.normalize(activity)
unless object && object.data["type"] == "Answer" do
@@ -162,6 +163,13 @@ defmodule Pleroma.Notification do
end
end
+ def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
+ when type in ["Like", "Announce", "Follow"] do
+ users = get_notified_from_activity(activity)
+ notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
+ {:ok, notifications}
+ end
+
def create_notifications(_), do: {:ok, []}
# TODO move to sql, too.
@@ -169,8 +177,9 @@ defmodule Pleroma.Notification do
unless skip?(activity, user) do
notification = %Notification{user_id: user.id, activity: activity}
{:ok, notification} = Repo.insert(notification)
- Pleroma.Web.Streamer.stream("user", notification)
- Pleroma.Web.Push.send(notification)
+ Streamer.stream("user", notification)
+ Streamer.stream("user:notification", notification)
+ Push.send(notification)
notification
end
end
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index 2f4687fa2..ada9da0bb 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Object.Containment do
@moduledoc """
This module contains some useful functions for containing objects to specific
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index ca980c629..c422490ac 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -85,6 +85,9 @@ defmodule Pleroma.Object.Fetcher do
:ok <- Containment.contain_origin_from_id(id, data) do
{:ok, data}
else
+ {:ok, %{status: code}} when code in [404, 410] ->
+ {:error, "Object has been deleted"}
+
e ->
{:error, e}
end
diff --git a/lib/pleroma/PasswordResetToken.ex b/lib/pleroma/password_reset_token.ex
index f31ea5bc5..4a833f6a5 100644
--- a/lib/pleroma/PasswordResetToken.ex
+++ b/lib/pleroma/password_reset_token.ex
@@ -37,6 +37,7 @@ defmodule Pleroma.PasswordResetToken do
|> put_change(:used, true)
end
+ @spec reset_password(binary(), map()) :: {:ok, User.t()} | {:error, binary()}
def reset_password(token, data) do
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
%User{} = user <- User.get_cached_by_id(token.user_id),
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
index 485ddfbc7..a7cc22831 100644
--- a/lib/pleroma/plugs/http_security_plug.ex
+++ b/lib/pleroma/plugs/http_security_plug.ex
@@ -56,14 +56,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
connect_src =
- if Mix.env() == :dev do
+ if Pleroma.Config.get(:env) == :dev do
connect_src <> " http://localhost:3035/"
else
connect_src
end
script_src =
- if Mix.env() == :dev do
+ if Pleroma.Config.get(:env) == :dev do
"script-src 'self' 'unsafe-eval'"
else
"script-src 'self'"
diff --git a/lib/pleroma/plugs/idempotency_plug.ex b/lib/pleroma/plugs/idempotency_plug.ex
new file mode 100644
index 000000000..e99c5d279
--- /dev/null
+++ b/lib/pleroma/plugs/idempotency_plug.ex
@@ -0,0 +1,84 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.IdempotencyPlug do
+ import Phoenix.Controller, only: [json: 2]
+ import Plug.Conn
+
+ @behaviour Plug
+
+ @impl true
+ def init(opts), do: opts
+
+ # Sending idempotency keys in `GET` and `DELETE` requests has no effect
+ # and should be avoided, as these requests are idempotent by definition.
+
+ @impl true
+ def call(%{method: method} = conn, _) when method in ["POST", "PUT", "PATCH"] do
+ case get_req_header(conn, "idempotency-key") do
+ [key] -> process_request(conn, key)
+ _ -> conn
+ end
+ end
+
+ def call(conn, _), do: conn
+
+ def process_request(conn, key) do
+ case Cachex.get(:idempotency_cache, key) do
+ {:ok, nil} ->
+ cache_resposnse(conn, key)
+
+ {:ok, record} ->
+ send_cached(conn, key, record)
+
+ {atom, message} when atom in [:ignore, :error] ->
+ render_error(conn, message)
+ end
+ end
+
+ defp cache_resposnse(conn, key) do
+ register_before_send(conn, fn conn ->
+ [request_id] = get_resp_header(conn, "x-request-id")
+ content_type = get_content_type(conn)
+
+ record = {request_id, content_type, conn.status, conn.resp_body}
+ {:ok, _} = Cachex.put(:idempotency_cache, key, record)
+
+ conn
+ |> put_resp_header("idempotency-key", key)
+ |> put_resp_header("x-original-request-id", request_id)
+ end)
+ end
+
+ defp send_cached(conn, key, record) do
+ {request_id, content_type, status, body} = record
+
+ conn
+ |> put_resp_header("idempotency-key", key)
+ |> put_resp_header("idempotent-replayed", "true")
+ |> put_resp_header("x-original-request-id", request_id)
+ |> put_resp_content_type(content_type)
+ |> send_resp(status, body)
+ |> halt()
+ end
+
+ defp render_error(conn, message) do
+ conn
+ |> put_status(:unprocessable_entity)
+ |> json(%{error: message})
+ |> halt()
+ end
+
+ defp get_content_type(conn) do
+ [content_type] = get_resp_header(conn, "content-type")
+
+ if String.contains?(content_type, ";") do
+ content_type
+ |> String.split(";")
+ |> hd()
+ else
+ content_type
+ end
+ end
+end
diff --git a/lib/pleroma/plugs/rate_limit_plug.ex b/lib/pleroma/plugs/rate_limit_plug.ex
deleted file mode 100644
index 466f64a79..000000000
--- a/lib/pleroma/plugs/rate_limit_plug.ex
+++ /dev/null
@@ -1,36 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.RateLimitPlug do
- import Phoenix.Controller, only: [json: 2]
- import Plug.Conn
-
- def init(opts), do: opts
-
- def call(conn, opts) do
- enabled? = Pleroma.Config.get([:app_account_creation, :enabled])
-
- case check_rate(conn, Map.put(opts, :enabled, enabled?)) do
- {:ok, _count} -> conn
- {:error, _count} -> render_error(conn)
- %Plug.Conn{} = conn -> conn
- end
- end
-
- defp check_rate(conn, %{enabled: true} = opts) do
- max_requests = opts[:max_requests]
- bucket_name = conn.remote_ip |> Tuple.to_list() |> Enum.join(".")
-
- ExRated.check_rate(bucket_name, opts[:interval] * 1000, max_requests)
- end
-
- defp check_rate(conn, _), do: conn
-
- defp render_error(conn) do
- conn
- |> put_status(:forbidden)
- |> json(%{error: "Rate limit exceeded."})
- |> halt()
- end
-end
diff --git a/lib/pleroma/plugs/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter.ex
new file mode 100644
index 000000000..9ba5875fa
--- /dev/null
+++ b/lib/pleroma/plugs/rate_limiter.ex
@@ -0,0 +1,94 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Plugs.RateLimiter do
+ @moduledoc """
+
+ ## Configuration
+
+ A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
+
+ * The first element: `scale` (Integer). The time scale in milliseconds.
+ * The second element: `limit` (Integer). How many requests to limit in the time scale provided.
+
+ It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
+
+ To disable a limiter set its value to `nil`.
+
+ ### Example
+
+ config :pleroma, :rate_limit,
+ one: {1000, 10},
+ two: [{10_000, 10}, {10_000, 50}],
+ foobar: nil
+
+ Here we have three limiters:
+
+ * `one` which is not over 10req/1s
+ * `two` which has two limits: 10req/10s for unauthenticated users and 50req/10s for authenticated users
+ * `foobar` which is disabled
+
+ ## Usage
+
+ Inside a controller:
+
+ plug(Pleroma.Plugs.RateLimiter, :one when action == :one)
+ plug(Pleroma.Plugs.RateLimiter, :two when action in [:two, :three])
+
+ or inside a router pipiline:
+
+ pipeline :api do
+ ...
+ plug(Pleroma.Plugs.RateLimiter, :one)
+ ...
+ end
+ """
+
+ import Phoenix.Controller, only: [json: 2]
+ import Plug.Conn
+
+ alias Pleroma.User
+
+ def init(limiter_name) do
+ case Pleroma.Config.get([:rate_limit, limiter_name]) do
+ nil -> nil
+ config -> {limiter_name, config}
+ end
+ end
+
+ # do not limit if there is no limiter configuration
+ def call(conn, nil), do: conn
+
+ def call(conn, opts) do
+ case check_rate(conn, opts) do
+ {:ok, _count} -> conn
+ {:error, _count} -> render_error(conn)
+ end
+ end
+
+ defp check_rate(%{assigns: %{user: %User{id: user_id}}}, {limiter_name, [_, {scale, limit}]}) do
+ ExRated.check_rate("#{limiter_name}:#{user_id}", scale, limit)
+ end
+
+ defp check_rate(conn, {limiter_name, [{scale, limit} | _]}) do
+ ExRated.check_rate("#{limiter_name}:#{ip(conn)}", scale, limit)
+ end
+
+ defp check_rate(conn, {limiter_name, {scale, limit}}) do
+ check_rate(conn, {limiter_name, [{scale, limit}]})
+ end
+
+ def ip(%{remote_ip: remote_ip}) do
+ remote_ip
+ |> Tuple.to_list()
+ |> Enum.join(".")
+ end
+
+ defp render_error(conn) do
+ conn
+ |> put_status(:too_many_requests)
+ |> json(%{error: "Throttled"})
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/plugs/uploaded_media.ex
index fd77b8d8f..8d0fac7ee 100644
--- a/lib/pleroma/plugs/uploaded_media.ex
+++ b/lib/pleroma/plugs/uploaded_media.ex
@@ -36,7 +36,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
conn
end
- config = Pleroma.Config.get([Pleroma.Upload])
+ config = Pleroma.Config.get(Pleroma.Upload)
with uploader <- Keyword.fetch!(config, :uploader),
proxy_remote = Keyword.get(config, :proxy_remote, false),
diff --git a/lib/pleroma/release_tasks.ex b/lib/pleroma/release_tasks.ex
new file mode 100644
index 000000000..8afabf463
--- /dev/null
+++ b/lib/pleroma/release_tasks.ex
@@ -0,0 +1,66 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ReleaseTasks do
+ @repo Pleroma.Repo
+
+ def run(args) do
+ [task | args] = String.split(args)
+
+ case task do
+ "migrate" -> migrate(args)
+ "create" -> create()
+ "rollback" -> rollback(args)
+ task -> mix_task(task, args)
+ end
+ end
+
+ defp mix_task(task, args) do
+ Application.load(:pleroma)
+ {:ok, modules} = :application.get_key(:pleroma, :modules)
+
+ module =
+ Enum.find(modules, fn module ->
+ module = Module.split(module)
+
+ match?(["Mix", "Tasks", "Pleroma" | _], module) and
+ String.downcase(List.last(module)) == task
+ end)
+
+ if module do
+ module.run(args)
+ else
+ IO.puts("The task #{task} does not exist")
+ end
+ end
+
+ def migrate(args) do
+ Mix.Tasks.Pleroma.Ecto.Migrate.run(args)
+ end
+
+ def rollback(args) do
+ Mix.Tasks.Pleroma.Ecto.Rollback.run(args)
+ end
+
+ def create do
+ Application.load(:pleroma)
+
+ case @repo.__adapter__.storage_up(@repo.config) do
+ :ok ->
+ IO.puts("The database for #{inspect(@repo)} has been created")
+
+ {:error, :already_up} ->
+ IO.puts("The database for #{inspect(@repo)} has already been created")
+
+ {:error, term} when is_binary(term) ->
+ IO.puts(:stderr, "The database for #{inspect(@repo)} couldn't be created: #{term}")
+
+ {:error, term} ->
+ IO.puts(
+ :stderr,
+ "The database for #{inspect(@repo)} couldn't be created: #{inspect(term)}"
+ )
+ end
+ end
+end
diff --git a/lib/pleroma/repo_streamer.ex b/lib/pleroma/repo_streamer.ex
new file mode 100644
index 000000000..a4b71a1bb
--- /dev/null
+++ b/lib/pleroma/repo_streamer.ex
@@ -0,0 +1,34 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.RepoStreamer do
+ alias Pleroma.Repo
+ import Ecto.Query
+
+ def chunk_stream(query, chunk_size) do
+ Stream.unfold(0, fn
+ :halt ->
+ {[], :halt}
+
+ last_id ->
+ query
+ |> order_by(asc: :id)
+ |> where([r], r.id > ^last_id)
+ |> limit(^chunk_size)
+ |> Repo.all()
+ |> case do
+ [] ->
+ {[], :halt}
+
+ records ->
+ last_id = List.last(records).id
+ {records, last_id}
+ end
+ end)
+ |> Stream.take_while(fn
+ [] -> false
+ _ -> true
+ end)
+ end
+end
diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex
index 285d57309..de0f6e1bc 100644
--- a/lib/pleroma/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy.ex
@@ -146,7 +146,7 @@ defmodule Pleroma.ReverseProxy do
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
method = method |> String.downcase() |> String.to_existing_atom()
- case :hackney.request(method, url, headers, "", hackney_opts) do
+ case hackney().request(method, url, headers, "", hackney_opts) do
{:ok, code, headers, client} when code in @valid_resp_codes ->
{:ok, code, downcase_headers(headers), client}
@@ -196,7 +196,7 @@ defmodule Pleroma.ReverseProxy do
duration,
Keyword.get(opts, :max_read_duration, @max_read_duration)
),
- {:ok, data} <- :hackney.stream_body(client),
+ {:ok, data} <- hackney().stream_body(client),
{:ok, duration} <- increase_read_duration(duration),
sent_so_far = sent_so_far + byte_size(data),
:ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)),
@@ -377,4 +377,6 @@ defmodule Pleroma.ReverseProxy do
defp increase_read_duration(_) do
{:ok, :no_duration_limit, :no_duration_limit}
end
+
+ defp hackney, do: Pleroma.Config.get(:hackney, :hackney)
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 3993a93e6..9be4b1483 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -9,12 +9,14 @@ defmodule Pleroma.User do
import Ecto.Query
alias Comeonin.Pbkdf2
+ alias Ecto.Multi
alias Pleroma.Activity
alias Pleroma.Keys
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Registration
alias Pleroma.Repo
+ alias Pleroma.RepoStreamer
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -194,29 +196,26 @@ defmodule Pleroma.User do
end
def password_update_changeset(struct, params) do
- changeset =
- struct
- |> cast(params, [:password, :password_confirmation])
- |> validate_required([:password, :password_confirmation])
- |> validate_confirmation(:password)
-
- OAuth.Token.delete_user_tokens(struct)
- OAuth.Authorization.delete_user_authorizations(struct)
-
- if changeset.valid? do
- hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
-
- changeset
- |> put_change(:password_hash, hashed)
- else
- changeset
+ struct
+ |> cast(params, [:password, :password_confirmation])
+ |> validate_required([:password, :password_confirmation])
+ |> validate_confirmation(:password)
+ |> put_password_hash
+ end
+
+ def reset_password(%User{id: user_id} = user, data) do
+ multi =
+ Multi.new()
+ |> Multi.update(:user, password_update_changeset(user, data))
+ |> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
+ |> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
+
+ case Repo.transaction(multi) do
+ {:ok, %{user: user} = _} -> set_cache(user)
+ {:error, _, changeset, _} -> {:error, changeset}
end
end
- def reset_password(user, data) do
- update_and_set_cache(password_update_changeset(user, data))
- end
-
def register_changeset(struct, params \\ %{}, opts \\ []) do
need_confirmation? =
if is_nil(opts[:need_confirmation]) do
@@ -250,12 +249,11 @@ defmodule Pleroma.User do
end
if changeset.valid? do
- hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
changeset
- |> put_change(:password_hash, hashed)
+ |> put_password_hash
|> put_change(:ap_id, ap_id)
|> unique_constraint(:ap_id)
|> put_change(:following, [followers])
@@ -933,18 +931,24 @@ defmodule Pleroma.User do
@spec perform(atom(), User.t()) :: {:ok, User.t()}
def perform(:delete, %User{} = user) do
- {:ok, user} = User.deactivate(user)
-
# Remove all relationships
{:ok, followers} = User.get_followers(user)
- Enum.each(followers, fn follower -> User.unfollow(follower, user) end)
+ Enum.each(followers, fn follower ->
+ ActivityPub.unfollow(follower, user)
+ User.unfollow(follower, user)
+ end)
{:ok, friends} = User.get_friends(user)
- Enum.each(friends, fn followed -> User.unfollow(user, followed) end)
+ Enum.each(friends, fn followed ->
+ ActivityPub.unfollow(user, followed)
+ User.unfollow(user, followed)
+ end)
delete_user_activities(user)
+
+ {:ok, _user} = Repo.delete(user)
end
@spec perform(atom(), User.t()) :: {:ok, User.t()}
@@ -1017,18 +1021,35 @@ defmodule Pleroma.User do
])
def delete_user_activities(%User{ap_id: ap_id} = user) do
- stream =
- ap_id
- |> Activity.query_by_actor()
- |> Repo.stream()
-
- Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
+ ap_id
+ |> Activity.query_by_actor()
+ |> RepoStreamer.chunk_stream(50)
+ |> Stream.each(fn activities ->
+ Enum.each(activities, &delete_activity(&1))
+ end)
+ |> Stream.run()
{:ok, user}
end
defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
- Object.normalize(activity) |> ActivityPub.delete()
+ activity
+ |> Object.normalize()
+ |> ActivityPub.delete()
+ end
+
+ defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
+ user = get_cached_by_ap_id(activity.actor)
+ object = Object.normalize(activity)
+
+ ActivityPub.unlike(user, object)
+ end
+
+ defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
+ user = get_cached_by_ap_id(activity.actor)
+ object = Object.normalize(activity)
+
+ ActivityPub.unannounce(user, object)
end
defp delete_activity(_activity), do: "Doing nothing"
@@ -1037,9 +1058,7 @@ defmodule Pleroma.User do
Pleroma.HTML.Scrubber.TwitterText
end
- @default_scrubbers Pleroma.Config.get([:markup, :scrub_policy])
-
- def html_filter_policy(_), do: @default_scrubbers
+ def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
def fetch_by_ap_id(ap_id) do
ap_try = ActivityPub.make_user_from_ap_id(ap_id)
@@ -1402,4 +1421,12 @@ defmodule Pleroma.User do
end
defdelegate search(query, opts \\ []), to: User.Search
+
+ defp put_password_hash(
+ %Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
+ ) do
+ change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
+ end
+
+ defp put_password_hash(changeset), do: changeset
end
diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex
index add6a0bbf..ed06c2ab9 100644
--- a/lib/pleroma/user/search.ex
+++ b/lib/pleroma/user/search.ex
@@ -7,74 +7,97 @@ defmodule Pleroma.User.Search do
alias Pleroma.User
import Ecto.Query
- def search(query, opts \\ []) do
+ @similarity_threshold 0.25
+ @limit 20
+
+ def search(query_string, opts \\ []) do
resolve = Keyword.get(opts, :resolve, false)
+ following = Keyword.get(opts, :following, false)
+ result_limit = Keyword.get(opts, :limit, @limit)
+ offset = Keyword.get(opts, :offset, 0)
+
for_user = Keyword.get(opts, :for_user)
# Strip the beginning @ off if there is a query
- query = String.trim_leading(query, "@")
+ query_string = String.trim_leading(query_string, "@")
- maybe_resolve(resolve, for_user, query)
+ maybe_resolve(resolve, for_user, query_string)
{:ok, results} =
Repo.transaction(fn ->
- Ecto.Adapters.SQL.query(Repo, "select set_limit(0.25)", [])
+ Ecto.Adapters.SQL.query(
+ Repo,
+ "select set_limit(#{@similarity_threshold})",
+ []
+ )
- query
- |> search_query(for_user)
+ query_string
+ |> search_query(for_user, following)
+ |> paginate(result_limit, offset)
|> Repo.all()
end)
results
end
- defp maybe_resolve(true, %User{}, query) do
- User.get_or_fetch(query)
- end
-
- defp maybe_resolve(true, _, query) do
- unless restrict_local?(), do: User.get_or_fetch(query)
- end
-
- defp maybe_resolve(_, _, _), do: :noop
-
- defp search_query(query, for_user) do
- query
- |> union_query()
+ defp search_query(query_string, for_user, following) do
+ for_user
+ |> base_query(following)
+ |> search_subqueries(query_string)
+ |> union_subqueries
|> distinct_query()
|> boost_search_rank_query(for_user)
|> subquery()
|> order_by(desc: :search_rank)
- |> limit(20)
|> maybe_restrict_local(for_user)
end
- defp restrict_local? do
- Pleroma.Config.get([:instance, :limit_unauthenticated_to_local_content], true)
- end
+ defp base_query(_user, false), do: User
+ defp base_query(user, true), do: User.get_followers_query(user)
- defp union_query(query) do
- fts_subquery = fts_search_subquery(query)
- trigram_subquery = trigram_search_subquery(query)
+ defp paginate(query, limit, offset) do
+ from(q in query, limit: ^limit, offset: ^offset)
+ end
+ defp union_subqueries({fts_subquery, trigram_subquery}) do
from(s in trigram_subquery, union_all: ^fts_subquery)
end
+ defp search_subqueries(base_query, query_string) do
+ {
+ fts_search_subquery(base_query, query_string),
+ trigram_search_subquery(base_query, query_string)
+ }
+ end
+
defp distinct_query(q) do
from(s in subquery(q), order_by: s.search_type, distinct: s.id)
end
- # unauthenticated users can only search local activities
- defp maybe_restrict_local(q, %User{}), do: q
+ defp maybe_resolve(true, user, query) do
+ case {limit(), user} do
+ {:all, _} -> :noop
+ {:unauthenticated, %User{}} -> User.get_or_fetch(query)
+ {:unauthenticated, _} -> :noop
+ {false, _} -> User.get_or_fetch(query)
+ end
+ end
+
+ defp maybe_resolve(_, _, _), do: :noop
- defp maybe_restrict_local(q, _) do
- if restrict_local?() do
- where(q, [u], u.local == true)
- else
- q
+ defp maybe_restrict_local(q, user) do
+ case {limit(), user} do
+ {:all, _} -> restrict_local(q)
+ {:unauthenticated, %User{}} -> q
+ {:unauthenticated, _} -> restrict_local(q)
+ {false, _} -> q
end
end
+ defp limit, do: Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)
+
+ defp restrict_local(q), do: where(q, [u], u.local == true)
+
defp boost_search_rank_query(query, nil), do: query
defp boost_search_rank_query(query, for_user) do
@@ -103,7 +126,8 @@ defmodule Pleroma.User.Search do
)
end
- defp fts_search_subquery(term, query \\ User) do
+ @spec fts_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
+ defp fts_search_subquery(query, term) do
processed_query =
term
|> String.replace(~r/\W+/, " ")
@@ -145,9 +169,10 @@ defmodule Pleroma.User.Search do
|> User.restrict_deactivated()
end
- defp trigram_search_subquery(term) do
+ @spec trigram_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
+ defp trigram_search_subquery(query, term) do
from(
- u in User,
+ u in query,
select_merge: %{
# ^1 gives 'Postgrex expected a binary, got 1' for some weird reason
search_type: fragment("?", 1),
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index c0e3d1478..55315d66e 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -189,6 +189,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end)
end
+ def stream_out_participations(%Object{data: %{"context" => context}}, user) do
+ with %Conversation{} = conversation <- Conversation.get_for_ap_id(context),
+ conversation = Repo.preload(conversation, :participations),
+ last_activity_id =
+ fetch_latest_activity_id_for_context(conversation.ap_id, %{
+ "user" => user,
+ "blocking_user" => user
+ }) do
+ if last_activity_id do
+ stream_out_participations(conversation.participations)
+ end
+ end
+ end
+
+ def stream_out_participations(_, _), do: :noop
+
def stream_out(activity) do
public = "https://www.w3.org/ns/activitystreams#Public"
@@ -401,7 +417,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"to" => to,
"deleted_activity_id" => activity && activity.id
},
- {:ok, activity} <- insert(data, local),
+ {:ok, activity} <- insert(data, local, false),
+ stream_out_participations(object, user),
_ <- decrease_replies_count_if_reply(object),
# Changing note count prior to enqueuing federation task in order to avoid
# race conditions on updating user.info
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
new file mode 100644
index 000000000..2da3eac2f
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
@@ -0,0 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
+ alias Pleroma.User
+
+ require Logger
+
+ # has the user successfully posted before?
+ defp old_user?(%User{} = u) do
+ u.info.note_count > 0 || u.info.follower_count > 0
+ end
+
+ # does the post contain links?
+ defp contains_links?(%{"content" => content} = _object) do
+ content
+ |> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"],a.zrl")
+ |> Floki.attribute("a", "href")
+ |> length() > 0
+ end
+
+ defp contains_links?(_), do: false
+
+ def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
+ with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
+ {:contains_links, true} <- {:contains_links, contains_links?(object)},
+ {:old_user, true} <- {:old_user, old_user?(u)} do
+ {:ok, message}
+ else
+ {:contains_links, false} ->
+ {:ok, message}
+
+ {:old_user, false} ->
+ {:reject, nil}
+
+ {:error, _} ->
+ {:reject, nil}
+
+ e ->
+ Logger.warn("[MRF anti-link-spam] WTF: unhandled error #{inspect(e)}")
+ {:reject, nil}
+ end
+ end
+
+ # in all other cases, pass through
+ def filter(message), do: {:ok, message}
+end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 8f1399ce6..a05e03263 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -88,7 +88,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
true
else
inbox_info = URI.parse(inbox)
- !Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
+ !Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host)
end
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index ff031a16e..3bb8b40b5 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -339,7 +339,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_type(%{"inReplyTo" => reply_id} = object) when is_binary(reply_id) do
reply = Object.normalize(reply_id)
- if reply.data["type"] == "Question" and object["name"] do
+ if reply && (reply.data["type"] == "Question" and object["name"]) do
Map.put(object, "type", "Answer")
else
object
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 10ff572a2..514266cee 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -151,16 +151,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def create_context(context) do
context = context || generate_id("contexts")
- changeset = Object.context_mapping(context)
- case Repo.insert(changeset) do
- {:ok, object} ->
- object
+ # Ecto has problems accessing the constraint inside the jsonb,
+ # so we explicitly check for the existed object before insert
+ object = Object.get_cached_by_ap_id(context)
- # This should be solved by an upsert, but it seems ecto
- # has problems accessing the constraint inside the jsonb.
- {:error, _} ->
- Object.get_cached_by_ap_id(context)
+ with true <- is_nil(object),
+ changeset <- Object.context_mapping(context),
+ {:ok, inserted_object} <- Repo.insert(changeset) do
+ inserted_object
+ else
+ _ ->
+ object
end
end
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
index de2a13c01..498beb56a 100644
--- a/lib/pleroma/web/admin_api/admin_api_controller.ex
+++ b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -10,6 +10,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.AdminAPI.AccountView
+ alias Pleroma.Web.AdminAPI.Config
+ alias Pleroma.Web.AdminAPI.ConfigView
alias Pleroma.Web.AdminAPI.ReportView
alias Pleroma.Web.AdminAPI.Search
alias Pleroma.Web.CommonAPI
@@ -362,6 +364,41 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
end
end
+ def config_show(conn, _params) do
+ configs = Pleroma.Repo.all(Config)
+
+ conn
+ |> put_view(ConfigView)
+ |> render("index.json", %{configs: configs})
+ end
+
+ def config_update(conn, %{"configs" => configs}) do
+ updated =
+ if Pleroma.Config.get([:instance, :dynamic_configuration]) do
+ updated =
+ Enum.map(configs, fn
+ %{"group" => group, "key" => key, "value" => value} ->
+ {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
+ config
+
+ %{"group" => group, "key" => key, "delete" => "true"} ->
+ {:ok, _} = Config.delete(%{group: group, key: key})
+ nil
+ end)
+ |> Enum.reject(&is_nil(&1))
+
+ Pleroma.Config.TransferTask.load_and_update_env()
+ Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
+ updated
+ else
+ []
+ end
+
+ conn
+ |> put_view(ConfigView)
+ |> render("index.json", %{configs: updated})
+ end
+
def errors(conn, {:error, :not_found}) do
conn
|> put_status(404)
diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex
new file mode 100644
index 000000000..8b9b658a9
--- /dev/null
+++ b/lib/pleroma/web/admin_api/config.ex
@@ -0,0 +1,160 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.Config do
+ use Ecto.Schema
+ import Ecto.Changeset
+ alias __MODULE__
+ alias Pleroma.Repo
+
+ @type t :: %__MODULE__{}
+
+ schema "config" do
+ field(:key, :string)
+ field(:group, :string)
+ field(:value, :binary)
+
+ timestamps()
+ end
+
+ @spec get_by_params(map()) :: Config.t() | nil
+ def get_by_params(params), do: Repo.get_by(Config, params)
+
+ @spec changeset(Config.t(), map()) :: Changeset.t()
+ def changeset(config, params \\ %{}) do
+ config
+ |> cast(params, [:key, :group, :value])
+ |> validate_required([:key, :group, :value])
+ |> unique_constraint(:key, name: :config_group_key_index)
+ end
+
+ @spec create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()}
+ def create(params) do
+ %Config{}
+ |> changeset(Map.put(params, :value, transform(params[:value])))
+ |> Repo.insert()
+ end
+
+ @spec update(Config.t(), map()) :: {:ok, Config} | {:error, Changeset.t()}
+ def update(%Config{} = config, %{value: value}) do
+ config
+ |> change(value: transform(value))
+ |> Repo.update()
+ end
+
+ @spec update_or_create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()}
+ def update_or_create(params) do
+ with %Config{} = config <- Config.get_by_params(Map.take(params, [:group, :key])) do
+ Config.update(config, params)
+ else
+ nil -> Config.create(params)
+ end
+ end
+
+ @spec delete(map()) :: {:ok, Config.t()} | {:error, Changeset.t()}
+ def delete(params) do
+ with %Config{} = config <- Config.get_by_params(params) do
+ Repo.delete(config)
+ else
+ nil -> {:error, "Config with params #{inspect(params)} not found"}
+ end
+ end
+
+ @spec from_binary(binary()) :: term()
+ def from_binary(value), do: :erlang.binary_to_term(value)
+
+ @spec from_binary_to_map(binary()) :: any()
+ def from_binary_to_map(binary) do
+ from_binary(binary)
+ |> do_convert()
+ end
+
+ defp do_convert([{k, v}] = value) when is_list(value) and length(value) == 1,
+ do: %{k => do_convert(v)}
+
+ defp do_convert(values) when is_list(values), do: for(val <- values, do: do_convert(val))
+
+ defp do_convert({k, v} = value) when is_tuple(value),
+ do: %{k => do_convert(v)}
+
+ defp do_convert(value) when is_tuple(value), do: %{"tuple" => do_convert(Tuple.to_list(value))}
+
+ defp do_convert(value) when is_binary(value) or is_map(value) or is_number(value), do: value
+
+ defp do_convert(value) when is_atom(value) do
+ string = to_string(value)
+
+ if String.starts_with?(string, "Elixir."),
+ do: String.trim_leading(string, "Elixir."),
+ else: value
+ end
+
+ @spec transform(any()) :: binary()
+ def transform(%{"tuple" => _} = entity), do: :erlang.term_to_binary(do_transform(entity))
+
+ def transform(entity) when is_map(entity) do
+ tuples =
+ for {k, v} <- entity,
+ into: [],
+ do: {if(is_atom(k), do: k, else: String.to_atom(k)), do_transform(v)}
+
+ Enum.reject(tuples, fn {_k, v} -> is_nil(v) end)
+ |> Enum.sort()
+ |> :erlang.term_to_binary()
+ end
+
+ def transform(entity) when is_list(entity) do
+ list = Enum.map(entity, &do_transform(&1))
+ :erlang.term_to_binary(list)
+ end
+
+ def transform(entity), do: :erlang.term_to_binary(entity)
+
+ defp do_transform(%Regex{} = value) when is_map(value), do: value
+
+ defp do_transform(%{"tuple" => [k, values] = entity}) when length(entity) == 2 do
+ {do_transform(k), do_transform(values)}
+ end
+
+ defp do_transform(%{"tuple" => values}) do
+ Enum.reduce(values, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
+ end
+
+ defp do_transform(value) when is_map(value) do
+ values = for {key, val} <- value, into: [], do: {String.to_atom(key), do_transform(val)}
+
+ Enum.sort(values)
+ end
+
+ defp do_transform(value) when is_list(value) do
+ Enum.map(value, &do_transform(&1))
+ end
+
+ defp do_transform(entity) when is_list(entity) and length(entity) == 1, do: hd(entity)
+
+ defp do_transform(value) when is_binary(value) do
+ String.trim(value)
+ |> do_transform_string()
+ end
+
+ defp do_transform(value), do: value
+
+ defp do_transform_string(value) when byte_size(value) == 0, do: nil
+
+ defp do_transform_string(value) do
+ cond do
+ String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix") ->
+ String.to_existing_atom("Elixir." <> value)
+
+ String.starts_with?(value, ":") ->
+ String.replace(value, ":", "") |> String.to_existing_atom()
+
+ String.starts_with?(value, "i:") ->
+ String.replace(value, "i:", "") |> String.to_integer()
+
+ true ->
+ value
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/config_view.ex b/lib/pleroma/web/admin_api/views/config_view.ex
new file mode 100644
index 000000000..3ccc9ca46
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/config_view.ex
@@ -0,0 +1,17 @@
+defmodule Pleroma.Web.AdminAPI.ConfigView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{configs: configs}) do
+ %{
+ configs: render_many(configs, __MODULE__, "show.json", as: :config)
+ }
+ end
+
+ def render("show.json", %{config: config}) do
+ %{
+ key: config.key,
+ group: config.group,
+ value: Pleroma.Web.AdminAPI.Config.from_binary_to_map(config.value)
+ }
+ end
+end
diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex
index 47a73dc7e..e7db3a8ff 100644
--- a/lib/pleroma/web/admin_api/views/report_view.ex
+++ b/lib/pleroma/web/admin_api/views/report_view.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.AdminAPI.ReportView do
use Pleroma.Web, :view
alias Pleroma.Activity
+ alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
@@ -23,6 +24,13 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
[account_ap_id | status_ap_ids] = report.data["object"]
account = User.get_cached_by_ap_id(account_ap_id)
+ content =
+ unless is_nil(report.data["content"]) do
+ HTML.filter_tags(report.data["content"])
+ else
+ nil
+ end
+
statuses =
Enum.map(status_ap_ids, fn ap_id ->
Activity.get_by_ap_id_with_object(ap_id)
@@ -32,7 +40,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
id: report.id,
account: AccountView.render("account.json", %{user: account}),
actor: AccountView.render("account.json", %{user: user}),
- content: report.data["content"],
+ content: content,
created_at: created_at,
statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
state: report.data["state"]
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index f5193512e..f8df1e2ea 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -212,7 +212,7 @@ defmodule Pleroma.Web.CommonAPI do
cw <- data["spoiler_text"] || "",
sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
full_payload <- String.trim(status <> cw),
- length when length in 1..limit <- String.length(full_payload),
+ :ok <- validate_character_limit(full_payload, attachments, limit),
object <-
make_note_data(
user.ap_id,
@@ -247,6 +247,8 @@ defmodule Pleroma.Web.CommonAPI do
res
else
+ {:private_to_public, true} -> {:error, "The message visibility must be direct"}
+ {:error, _} = e -> e
e -> {:error, e}
end
end
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 6d82c0bd2..8b9477927 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -504,4 +504,18 @@ defmodule Pleroma.Web.CommonAPI.Utils do
"inReplyTo" => object.data["id"]
}
end
+
+ def validate_character_limit(full_payload, attachments, limit) do
+ length = String.length(full_payload)
+
+ if length < limit do
+ if length > 0 or Enum.count(attachments) > 0 do
+ :ok
+ else
+ {:error, "Cannot post an empty status without attachments"}
+ end
+ else
+ {:error, "The status is over the character limit"}
+ end
+ end
end
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 55706eeb8..8a753bb4f 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -15,4 +15,22 @@ defmodule Pleroma.Web.ControllerHelper do
|> put_status(status)
|> json(json)
end
+
+ @spec fetch_integer_param(map(), String.t(), integer() | nil) :: integer() | nil
+ def fetch_integer_param(params, name, default \\ nil) do
+ params
+ |> Map.get(name, default)
+ |> param_to_integer(default)
+ end
+
+ defp param_to_integer(val, _) when is_integer(val), do: val
+
+ defp param_to_integer(val, default) when is_binary(val) do
+ case Integer.parse(val) do
+ {res, _} -> res
+ _ -> default
+ end
+ end
+
+ defp param_to_integer(_, default), do: default
end
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index bd76e4295..ddaf88f1d 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -91,7 +91,7 @@ defmodule Pleroma.Web.Endpoint do
Plug.Session,
store: :cookie,
key: cookie_name,
- signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
+ signing_salt: Pleroma.Config.get([__MODULE__, :signing_salt], "CqaoopA2"),
http_only: true,
secure: secure_cookies,
extra: extra
diff --git a/lib/pleroma/web/federator/retry_queue.ex b/lib/pleroma/web/federator/retry_queue.ex
index 71e49494f..3db948c2e 100644
--- a/lib/pleroma/web/federator/retry_queue.ex
+++ b/lib/pleroma/web/federator/retry_queue.ex
@@ -15,7 +15,9 @@ defmodule Pleroma.Web.Federator.RetryQueue do
def start_link do
enabled =
- if Mix.env() == :test, do: true, else: Pleroma.Config.get([__MODULE__, :enabled], false)
+ if Pleroma.Config.get(:env) == :test,
+ do: true,
+ else: Pleroma.Config.get([__MODULE__, :enabled], false)
if enabled do
Logger.info("Starting retry queue")
@@ -219,7 +221,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do
{:noreply, state}
end
- if Mix.env() == :test do
+ if Pleroma.Config.get(:env) == :test do
defp growth_function(_retries) do
_shutit = Pleroma.Config.get([__MODULE__, :initial_timeout])
DateTime.to_unix(DateTime.utc_now()) - 1
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 92cd77f62..7cdba4cc0 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -46,14 +46,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
require Logger
- plug(
- Pleroma.Plugs.RateLimitPlug,
- %{
- max_requests: Config.get([:app_account_creation, :max_requests]),
- interval: Config.get([:app_account_creation, :interval])
- }
- when action in [:account_register]
- )
+ plug(Pleroma.Plugs.RateLimiter, :app_account_creation when action == :account_register)
+ plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
@local_mastodon_name "Mastodon-Local"
@@ -142,6 +136,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
_ -> :error
end
end)
+ |> add_if_present(params, "pleroma_background_image", :background, fn value ->
+ with %Plug.Upload{} <- value,
+ {:ok, object} <- ActivityPub.upload(value, type: :background) do
+ {:ok, object.data}
+ else
+ _ -> :error
+ end
+ end)
|> Map.put(:emoji, user_info_emojis)
info_cng = User.Info.profile_update(user.info, info_params)
@@ -166,8 +168,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def verify_credentials(%{assigns: %{user: user}} = conn, _) do
+ chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
+
account =
- AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
+ AccountView.render("account.json", %{
+ user: user,
+ for: user,
+ with_pleroma_settings: true,
+ with_chat_token: chat_token
+ })
json(conn, account)
end
@@ -445,12 +454,26 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
+ defp get_cached_vote_or_vote(user, object, choices) do
+ idempotency_key = "polls:#{user.id}:#{object.data["id"]}"
+
+ {_, res} =
+ Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
+ case CommonAPI.vote(user, object, choices) do
+ {:error, _message} = res -> {:ignore, res}
+ res -> {:commit, res}
+ end
+ end)
+
+ res
+ end
+
def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
with %Object{} = object <- Object.get_by_id(id),
true <- object.data["type"] == "Question",
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user),
- {:ok, _activities, object} <- CommonAPI.vote(user, object, choices) do
+ {:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do
conn
|> put_view(StatusView)
|> try_render("poll.json", %{object: object, for: user})
@@ -521,15 +544,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
- def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
- when length(media_ids) > 0 do
- params =
- params
- |> Map.put("status", ".")
-
- post_status(conn, params)
- end
-
def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
params =
params
@@ -547,18 +561,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
else
params = Map.drop(params, ["scheduled_at"])
- case get_cached_status_or_post(conn, params) do
- {:ignore, message} ->
- conn
- |> put_status(422)
- |> json(%{error: message})
-
+ case CommonAPI.post(user, params) do
{:error, message} ->
conn
- |> put_status(422)
+ |> put_status(:unprocessable_entity)
|> json(%{error: message})
- {_, activity} ->
+ {:ok, activity} ->
conn
|> put_view(StatusView)
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
@@ -566,21 +575,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
- defp get_cached_status_or_post(%{assigns: %{user: user}} = conn, params) do
- idempotency_key =
- case get_req_header(conn, "idempotency-key") do
- [key] -> key
- _ -> Ecto.UUID.generate()
- end
-
- Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
- case CommonAPI.post(user, params) do
- {:ok, activity} -> activity
- {:error, message} -> {:ignore, message}
- end
- end)
- end
-
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
json(conn, %{})
@@ -830,7 +824,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
conn
|> put_view(AccountView)
- |> render(AccountView, "accounts.json", %{for: user, users: users, as: :user})
+ |> render("accounts.json", %{for: user, users: users, as: :user})
else
_ -> json(conn, [])
end
@@ -1124,58 +1118,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
- def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
- accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
- statuses = Activity.search(user, query)
- tags_path = Web.base_url() <> "/tag/"
-
- tags =
- query
- |> String.split()
- |> Enum.uniq()
- |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
- |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
- |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
-
- res = %{
- "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
- "statuses" =>
- StatusView.render("index.json", activities: statuses, for: user, as: :activity),
- "hashtags" => tags
- }
-
- json(conn, res)
- end
-
- def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
- accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
- statuses = Activity.search(user, query)
-
- tags =
- query
- |> String.split()
- |> Enum.uniq()
- |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
- |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
-
- res = %{
- "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
- "statuses" =>
- StatusView.render("index.json", activities: statuses, for: user, as: :activity),
- "hashtags" => tags
- }
-
- json(conn, res)
- end
-
- def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
- accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
-
- res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
-
- json(conn, res)
- end
-
def favourites(%{assigns: %{user: user}} = conn, params) do
params =
params
diff --git a/lib/pleroma/web/mastodon_api/search_controller.ex b/lib/pleroma/web/mastodon_api/search_controller.ex
new file mode 100644
index 000000000..0d1e2355d
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/search_controller.ex
@@ -0,0 +1,79 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.SearchController do
+ use Pleroma.Web, :controller
+ alias Pleroma.Activity
+ alias Pleroma.User
+ alias Pleroma.Web
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ alias Pleroma.Web.ControllerHelper
+
+ require Logger
+
+ plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
+
+ def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+ accounts = User.search(query, search_options(params, user))
+ statuses = Activity.search(user, query)
+ tags_path = Web.base_url() <> "/tag/"
+
+ tags =
+ query
+ |> String.split()
+ |> Enum.uniq()
+ |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
+ |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
+ |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
+
+ res = %{
+ "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
+ "statuses" =>
+ StatusView.render("index.json", activities: statuses, for: user, as: :activity),
+ "hashtags" => tags
+ }
+
+ json(conn, res)
+ end
+
+ def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+ accounts = User.search(query, search_options(params, user))
+ statuses = Activity.search(user, query)
+
+ tags =
+ query
+ |> String.split()
+ |> Enum.uniq()
+ |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
+ |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
+
+ res = %{
+ "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
+ "statuses" =>
+ StatusView.render("index.json", activities: statuses, for: user, as: :activity),
+ "hashtags" => tags
+ }
+
+ json(conn, res)
+ end
+
+ def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+ accounts = User.search(query, search_options(params, user))
+ res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
+
+ json(conn, res)
+ end
+
+ defp search_options(params, user) do
+ [
+ resolve: params["resolve"] == "true",
+ following: params["following"] == "true",
+ limit: ControllerHelper.fetch_integer_param(params, "limit"),
+ offset: ControllerHelper.fetch_integer_param(params, "offset"),
+ for_user: user
+ ]
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index b91726b45..62c516f8e 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -66,6 +66,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end
defp do_render("account.json", %{user: user} = opts) do
+ display_name = HTML.strip_tags(user.name || user.nickname)
+
image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url()
user_info = User.get_cached_user_info(user)
@@ -96,7 +98,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
id: to_string(user.id),
username: username_from_nickname(user.nickname),
acct: user.nickname,
- display_name: user.name || user.nickname,
+ display_name: display_name,
locked: user_info.locked,
created_at: Utils.to_masto_date(user.inserted_at),
followers_count: user_info.follower_count,
@@ -125,13 +127,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
hide_follows: user.info.hide_follows,
hide_favorites: user.info.hide_favorites,
relationship: relationship,
- skip_thread_containment: user.info.skip_thread_containment
+ skip_thread_containment: user.info.skip_thread_containment,
+ background_image: image_url(user.info.background) |> MediaProxy.url()
}
}
|> maybe_put_role(user, opts[:for])
|> maybe_put_settings(user, opts[:for], user_info)
|> maybe_put_notification_settings(user, opts[:for])
|> maybe_put_settings_store(user, opts[:for], opts)
+ |> maybe_put_chat_token(user, opts[:for], opts)
end
defp username_from_nickname(string) when is_binary(string) do
@@ -163,6 +167,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_settings_store(data, _, _, _), do: data
+ defp maybe_put_chat_token(data, %User{id: id}, %User{id: id}, %{
+ with_chat_token: token
+ }) do
+ data
+ |> Kernel.put_in([:pleroma, :chat_token], token)
+ end
+
+ defp maybe_put_chat_token(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)
@@ -182,4 +195,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end
defp maybe_put_notification_settings(data, _, _), do: data
+
+ defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
+ defp image_url(_), do: nil
end
diff --git a/lib/pleroma/web/mastodon_api/websocket_handler.ex b/lib/pleroma/web/mastodon_api/websocket_handler.ex
index abfa26754..3299e1721 100644
--- a/lib/pleroma/web/mastodon_api/websocket_handler.ex
+++ b/lib/pleroma/web/mastodon_api/websocket_handler.ex
@@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
"public:media",
"public:local:media",
"user",
+ "user:notification",
"direct",
"list",
"hashtag"
diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex
index 18973413e..d53e20d12 100644
--- a/lib/pleroma/web/oauth/authorization.ex
+++ b/lib/pleroma/web/oauth/authorization.ex
@@ -76,14 +76,16 @@ defmodule Pleroma.Web.OAuth.Authorization do
def use_token(%Authorization{used: true}), do: {:error, "already used"}
@spec delete_user_authorizations(User.t()) :: {integer(), any()}
- def delete_user_authorizations(%User{id: user_id}) do
- from(
- a in Pleroma.Web.OAuth.Authorization,
- where: a.user_id == ^user_id
- )
+ def delete_user_authorizations(%User{} = user) do
+ user
+ |> delete_by_user_query
|> Repo.delete_all()
end
+ def delete_by_user_query(%User{id: user_id}) do
+ from(a in __MODULE__, where: a.user_id == ^user_id)
+ end
+
@doc "gets auth for app by token"
@spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
def get_by_token(%App{id: app_id} = _app, token) do
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 79d803295..3f8e3b074 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.OAuth.OAuthController do
use Pleroma.Web, :controller
+ alias Pleroma.Helpers.UriHelper
alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
@@ -26,34 +27,25 @@ defmodule Pleroma.Web.OAuth.OAuthController do
action_fallback(Pleroma.Web.OAuth.FallbackController)
+ @oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
+
# Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg
- def authorize(conn, %{"authorization" => _} = params) do
+ def authorize(%Plug.Conn{} = 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
+ def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, params) do
if ControllerHelper.truthy_param?(params["force_login"]) do
do_authorize(conn, params)
else
- redirect_uri =
- if is_binary(params["redirect_uri"]) do
- params["redirect_uri"]
- else
- app = Repo.preload(token, :app).app
-
- app.redirect_uris
- |> String.split()
- |> Enum.at(0)
- end
-
- redirect(conn, external: redirect_uri(conn, redirect_uri))
+ handle_existing_authorization(conn, params)
end
end
- def authorize(conn, params), do: do_authorize(conn, params)
+ def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params)
- defp do_authorize(conn, params) do
+ defp do_authorize(%Plug.Conn{} = conn, params) do
app = Repo.get_by(App, client_id: params["client_id"])
available_scopes = (app && app.scopes) || []
scopes = Scopes.fetch_scopes(params, available_scopes)
@@ -70,8 +62,41 @@ defmodule Pleroma.Web.OAuth.OAuthController do
})
end
+ defp handle_existing_authorization(
+ %Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
+ %{"redirect_uri" => @oob_token_redirect_uri}
+ ) do
+ render(conn, "oob_token_exists.html", %{token: token})
+ end
+
+ defp handle_existing_authorization(
+ %Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
+ %{} = params
+ ) do
+ app = Repo.preload(token, :app).app
+
+ redirect_uri =
+ if is_binary(params["redirect_uri"]) do
+ params["redirect_uri"]
+ else
+ default_redirect_uri(app)
+ end
+
+ if redirect_uri in String.split(app.redirect_uris) do
+ redirect_uri = redirect_uri(conn, redirect_uri)
+ url_params = %{access_token: token.token}
+ url_params = UriHelper.append_param_if_present(url_params, :state, params["state"])
+ url = UriHelper.append_uri_params(redirect_uri, url_params)
+ redirect(conn, external: url)
+ else
+ conn
+ |> put_flash(:error, "Unlisted redirect_uri.")
+ |> redirect(external: redirect_uri(conn, redirect_uri))
+ end
+ end
+
def create_authorization(
- conn,
+ %Plug.Conn{} = conn,
%{"authorization" => _} = params,
opts \\ []
) do
@@ -83,35 +108,33 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
- def after_create_authorization(conn, auth, %{
- "authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs
+ def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
+ "authorization" => %{"redirect_uri" => @oob_token_redirect_uri}
}) do
- redirect_uri = redirect_uri(conn, redirect_uri)
-
- if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
- render(conn, "results.html", %{
- auth: auth
- })
- else
- connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
- url = "#{redirect_uri}#{connector}"
- url_params = %{:code => auth.token}
-
- url_params =
- if auth_attrs["state"] do
- Map.put(url_params, :state, auth_attrs["state"])
- else
- url_params
- end
-
- url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
+ render(conn, "oob_authorization_created.html", %{auth: auth})
+ end
+ def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
+ "authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs
+ }) do
+ app = Repo.preload(auth, :app).app
+
+ # An extra safety measure before we redirect (also done in `do_create_authorization/2`)
+ if redirect_uri in String.split(app.redirect_uris) do
+ redirect_uri = redirect_uri(conn, redirect_uri)
+ url_params = %{code: auth.token}
+ url_params = UriHelper.append_param_if_present(url_params, :state, auth_attrs["state"])
+ url = UriHelper.append_uri_params(redirect_uri, url_params)
redirect(conn, external: url)
+ else
+ conn
+ |> put_flash(:error, "Unlisted redirect_uri.")
+ |> redirect(external: redirect_uri(conn, redirect_uri))
end
end
defp handle_create_authorization_error(
- conn,
+ %Plug.Conn{} = conn,
{:error, scopes_issue},
%{"authorization" => _} = params
)
@@ -125,7 +148,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
defp handle_create_authorization_error(
- conn,
+ %Plug.Conn{} = conn,
{:auth_active, false},
%{"authorization" => _} = params
) do
@@ -137,13 +160,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|> authorize(params)
end
- defp handle_create_authorization_error(conn, error, %{"authorization" => _}) do
+ defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do
Authenticator.handle_error(conn, error)
end
@doc "Renew access_token with refresh_token"
def token_exchange(
- conn,
+ %Plug.Conn{} = conn,
%{"grant_type" => "refresh_token", "refresh_token" => token} = _params
) do
with {:ok, app} <- Token.Utils.fetch_app(conn),
@@ -159,7 +182,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
- def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
+ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"} = params) do
with {:ok, app} <- Token.Utils.fetch_app(conn),
fixed_token = Token.Utils.fix_padding(params["code"]),
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
@@ -176,7 +199,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
def token_exchange(
- conn,
+ %Plug.Conn{} = conn,
%{"grant_type" => "password"} = params
) do
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
@@ -207,7 +230,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
def token_exchange(
- conn,
+ %Plug.Conn{} = conn,
%{"grant_type" => "password", "name" => name, "password" => _password} = params
) do
params =
@@ -218,7 +241,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
token_exchange(conn, params)
end
- def token_exchange(conn, %{"grant_type" => "client_credentials"} = _params) do
+ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} = _params) do
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
{:ok, token} <- Token.exchange_token(app, auth) do
@@ -231,9 +254,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
# Bad request
- def token_exchange(conn, params), do: bad_request(conn, params)
+ def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
- def token_revoke(conn, %{"token" => _token} = params) do
+ def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do
with {:ok, app} <- Token.Utils.fetch_app(conn),
{:ok, _token} <- RevokeToken.revoke(app, params) do
json(conn, %{})
@@ -244,17 +267,20 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
- def token_revoke(conn, params), do: bad_request(conn, params)
+ def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
# Response for bad request
- defp bad_request(conn, _) do
+ defp bad_request(%Plug.Conn{} = conn, _) do
conn
|> put_status(500)
|> json(%{error: "Bad request"})
end
@doc "Prepares OAuth request to provider for Ueberauth"
- def prepare_request(conn, %{"provider" => provider, "authorization" => auth_attrs}) do
+ def prepare_request(%Plug.Conn{} = conn, %{
+ "provider" => provider,
+ "authorization" => auth_attrs
+ }) do
scope =
auth_attrs
|> Scopes.fetch_scopes([])
@@ -275,7 +301,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
redirect(conn, to: o_auth_path(conn, :request, provider, params))
end
- def request(conn, params) do
+ def request(%Plug.Conn{} = conn, params) do
message =
if params["provider"] do
"Unsupported OAuth provider: #{params["provider"]}."
@@ -288,7 +314,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|> redirect(to: "/")
end
- def callback(%{assigns: %{ueberauth_failure: failure}} = conn, params) do
+ def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do
params = callback_params(params)
messages = for e <- Map.get(failure, :errors, []), do: e.message
message = Enum.join(messages, "; ")
@@ -298,7 +324,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
end
- def callback(conn, params) do
+ def callback(%Plug.Conn{} = conn, params) do
params = callback_params(params)
with {:ok, registration} <- Authenticator.get_registration(conn) do
@@ -316,7 +342,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
})
conn
- |> put_session(:registration_id, registration.id)
+ |> put_session_registration_id(registration.id)
|> registration_details(%{"authorization" => registration_params})
end
else
@@ -333,7 +359,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
Map.merge(params, Jason.decode!(state))
end
- def registration_details(conn, %{"authorization" => auth_attrs}) do
+ def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do
render(conn, "register.html", %{
client_id: auth_attrs["client_id"],
redirect_uri: auth_attrs["redirect_uri"],
@@ -344,7 +370,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
})
end
- def register(conn, %{"authorization" => _, "op" => "connect"} = params) do
+ def register(%Plug.Conn{} = 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}} <-
@@ -363,7 +389,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
end
- def register(conn, %{"authorization" => _, "op" => "register"} = params) do
+ def register(%Plug.Conn{} = 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, registration) do
@@ -399,7 +425,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
defp do_create_authorization(
- conn,
+ %Plug.Conn{} = conn,
%{
"authorization" =>
%{
@@ -420,13 +446,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
# Special case: Local MastodonFE
- defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
+ defp redirect_uri(%Plug.Conn{} = conn, "."), do: mastodon_api_url(conn, :login)
- defp redirect_uri(_conn, redirect_uri), do: redirect_uri
+ defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
- defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
+ defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
- defp put_session_registration_id(conn, registration_id),
+ defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
do: put_session(conn, :registration_id, registration_id)
@spec validate_scopes(App.t(), map()) ::
@@ -436,4 +462,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|> Scopes.fetch_scopes(app.scopes)
|> Scopes.validates(app.scopes)
end
+
+ def default_redirect_uri(%App{} = app) do
+ app.redirect_uris
+ |> String.split()
+ |> Enum.at(0)
+ end
end
diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex
index f412f7eb2..90c304487 100644
--- a/lib/pleroma/web/oauth/token.ex
+++ b/lib/pleroma/web/oauth/token.ex
@@ -14,7 +14,6 @@ defmodule Pleroma.Web.OAuth.Token do
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.Token.Query
- @expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
@type t :: %__MODULE__{}
schema "oauth_tokens" do
@@ -78,7 +77,7 @@ defmodule Pleroma.Web.OAuth.Token do
defp put_valid_until(changeset, attrs) do
expires_in =
- Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), @expires_in))
+ Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), expires_in()))
changeset
|> change(%{valid_until: expires_in})
@@ -123,4 +122,6 @@ defmodule Pleroma.Web.OAuth.Token do
end
def is_expired?(_), do: false
+
+ defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
end
diff --git a/lib/pleroma/web/oauth/token/response.ex b/lib/pleroma/web/oauth/token/response.ex
index 64e78b183..2648571ad 100644
--- a/lib/pleroma/web/oauth/token/response.ex
+++ b/lib/pleroma/web/oauth/token/response.ex
@@ -4,15 +4,13 @@ defmodule Pleroma.Web.OAuth.Token.Response do
alias Pleroma.User
alias Pleroma.Web.OAuth.Token.Utils
- @expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
-
@doc false
def build(%User{} = user, token, opts \\ %{}) do
%{
token_type: "Bearer",
access_token: token.token,
refresh_token: token.refresh_token,
- expires_in: @expires_in,
+ expires_in: expires_in(),
scope: Enum.join(token.scopes, " "),
me: user.ap_id
}
@@ -25,8 +23,10 @@ defmodule Pleroma.Web.OAuth.Token.Response do
access_token: token.token,
refresh_token: token.refresh_token,
created_at: Utils.format_created_at(token),
- expires_in: @expires_in,
+ expires_in: expires_in(),
scope: Enum.join(token.scopes, " ")
}
end
+
+ defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
end
diff --git a/lib/pleroma/web/rel_me.ex b/lib/pleroma/web/rel_me.ex
index 26eb614a6..d376e2069 100644
--- a/lib/pleroma/web/rel_me.ex
+++ b/lib/pleroma/web/rel_me.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.RelMe do
with_body: true
]
- if Mix.env() == :test do
+ if Pleroma.Config.get(:env) == :test do
def parse(url) when is_binary(url), do: parse_url(url)
else
def parse(url) when is_binary(url) do
diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex
index 9bc8f2559..6506de46c 100644
--- a/lib/pleroma/web/rich_media/helpers.ex
+++ b/lib/pleroma/web/rich_media/helpers.ex
@@ -4,25 +4,53 @@
defmodule Pleroma.Web.RichMedia.Helpers do
alias Pleroma.Activity
+ alias Pleroma.Config
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Web.RichMedia.Parser
+ @spec validate_page_url(any()) :: :ok | :error
defp validate_page_url(page_url) when is_binary(page_url) do
- if AutoLinker.Parser.is_url?(page_url, true) do
- URI.parse(page_url) |> validate_page_url
- else
- :error
+ validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld]
+
+ page_url
+ |> AutoLinker.Parser.url?(scheme: true, validate_tld: validate_tld)
+ |> parse_uri(page_url)
+ end
+
+ defp validate_page_url(%URI{host: host, scheme: scheme, authority: authority})
+ when scheme == "https" and not is_nil(authority) do
+ cond do
+ host in Config.get([:rich_media, :ignore_hosts], []) ->
+ :error
+
+ get_tld(host) in Config.get([:rich_media, :ignore_tld], []) ->
+ :error
+
+ true ->
+ :ok
end
end
- defp validate_page_url(%URI{authority: nil}), do: :error
- defp validate_page_url(%URI{scheme: nil}), do: :error
- defp validate_page_url(%URI{}), do: :ok
defp validate_page_url(_), do: :error
+ defp parse_uri(true, url) do
+ url
+ |> URI.parse()
+ |> validate_page_url
+ end
+
+ defp parse_uri(_, _), do: :error
+
+ defp get_tld(host) do
+ host
+ |> String.split(".")
+ |> Enum.reverse()
+ |> hd
+ end
+
def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do
- with true <- Pleroma.Config.get([:rich_media, :enabled]),
+ with true <- Config.get([:rich_media, :enabled]),
%Object{} = object <- Object.normalize(activity),
false <- object.data["sensitive"] || false,
{:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]),
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index e4595800c..21cd47890 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -18,7 +18,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
def parse(nil), do: {:error, "No URL provided"}
- if Mix.env() == :test do
+ if Pleroma.Config.get(:env) == :test do
def parse(url), do: parse_url(url)
else
def parse(url) do
diff --git a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
index 4a7c5eae0..fb79630e4 100644
--- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
+++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex
@@ -1,15 +1,19 @@
defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
def parse(html, data, prefix, error_message, key_name, value_name \\ "content") do
- with elements = [_ | _] <- get_elements(html, key_name, prefix),
- meta_data =
- Enum.reduce(elements, data, fn el, acc ->
- attributes = normalize_attributes(el, prefix, key_name, value_name)
+ meta_data =
+ html
+ |> get_elements(key_name, prefix)
+ |> Enum.reduce(data, fn el, acc ->
+ attributes = normalize_attributes(el, prefix, key_name, value_name)
- Map.merge(acc, attributes)
- end) do
- {:ok, meta_data}
+ Map.merge(acc, attributes)
+ end)
+ |> maybe_put_title(html)
+
+ if Enum.empty?(meta_data) do
+ {:error, error_message}
else
- _e -> {:error, error_message}
+ {:ok, meta_data}
end
end
@@ -27,4 +31,19 @@ defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
%{String.to_atom(data[key_name]) => data[value_name]}
end
+
+ defp maybe_put_title(%{title: _} = meta, _), do: meta
+
+ defp maybe_put_title(meta, html) when meta != %{} do
+ case get_page_title(html) do
+ "" -> meta
+ title -> Map.put_new(meta, :title, title)
+ end
+ end
+
+ defp maybe_put_title(meta, _), do: meta
+
+ defp get_page_title(html) do
+ Floki.find(html, "title") |> Floki.text()
+ end
end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index ddab99254..9cb8db7fd 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -27,6 +27,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.UserEnabledPlug)
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureUserKeyPlug)
+ plug(Pleroma.Plugs.IdempotencyPlug)
end
pipeline :authenticated_api do
@@ -41,6 +42,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.UserEnabledPlug)
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
+ plug(Pleroma.Plugs.IdempotencyPlug)
end
pipeline :admin_api do
@@ -57,6 +59,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
plug(Pleroma.Plugs.UserIsAdminPlug)
+ plug(Pleroma.Plugs.IdempotencyPlug)
end
pipeline :mastodon_html do
@@ -133,8 +136,8 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:pleroma_api)
- get("/password_reset/:token", UtilController, :show_password_reset)
- post("/password_reset", UtilController, :password_reset)
+ get("/password_reset/:token", PasswordController, :reset, as: :reset_password)
+ post("/password_reset", PasswordController, :do_reset, as: :reset_password)
get("/emoji", UtilController, :emoji)
get("/captcha", UtilController, :captcha)
get("/healthcheck", UtilController, :healthcheck)
@@ -202,6 +205,9 @@ defmodule Pleroma.Web.Router do
put("/statuses/:id", AdminAPIController, :status_update)
delete("/statuses/:id", AdminAPIController, :status_delete)
+
+ get("/config", AdminAPIController, :config_show)
+ post("/config", AdminAPIController, :config_update)
end
scope "/", Pleroma.Web.TwitterAPI do
@@ -412,7 +418,7 @@ defmodule Pleroma.Web.Router do
get("/trends", MastodonAPIController, :empty_array)
- get("/accounts/search", MastodonAPIController, :account_search)
+ get("/accounts/search", SearchController, :account_search)
scope [] do
pipe_through(:oauth_read_or_public)
@@ -431,7 +437,7 @@ defmodule Pleroma.Web.Router do
get("/accounts/:id/following", MastodonAPIController, :following)
get("/accounts/:id", MastodonAPIController, :user)
- get("/search", MastodonAPIController, :search)
+ get("/search", SearchController, :search)
get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
end
@@ -439,7 +445,7 @@ defmodule Pleroma.Web.Router do
scope "/api/v2", Pleroma.Web.MastodonAPI do
pipe_through([:api, :oauth_read_or_public])
- get("/search", MastodonAPIController, :search2)
+ get("/search", SearchController, :search2)
end
scope "/api", Pleroma.Web do
@@ -606,12 +612,6 @@ defmodule Pleroma.Web.Router do
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
end
- scope "/", Pleroma.Web do
- pipe_through(:oembed)
-
- get("/oembed", OEmbed.OEmbedController, :url)
- end
-
pipeline :activitypub do
plug(:accepts, ["activity+json", "json"])
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
@@ -701,7 +701,7 @@ defmodule Pleroma.Web.Router do
get("/:sig/:url/:filename", MediaProxyController, :remote)
end
- if Mix.env() == :dev do
+ if Pleroma.Config.get(:env) == :dev do
scope "/dev" do
pipe_through([:mailbox_preview])
diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex
index 9e91a5a40..e96e4e1e4 100644
--- a/lib/pleroma/web/salmon/salmon.ex
+++ b/lib/pleroma/web/salmon/salmon.ex
@@ -146,7 +146,7 @@ defmodule Pleroma.Web.Salmon do
do: Instances.set_reachable(url)
Logger.debug(fn -> "Pushed to #{url}, code #{code}" end)
- :ok
+ {:ok, code}
else
e ->
unless params[:unreachable_since], do: Instances.set_reachable(url)
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
index a23f80f26..4f325113a 100644
--- a/lib/pleroma/web/streamer.ex
+++ b/lib/pleroma/web/streamer.ex
@@ -110,23 +110,18 @@ defmodule Pleroma.Web.Streamer do
{:noreply, topics}
end
- def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
- topic = "user:#{item.user_id}"
-
- Enum.each(topics[topic] || [], fn socket ->
- json =
- %{
- event: "notification",
- payload:
- NotificationView.render("show.json", %{
- notification: item,
- for: socket.assigns["user"]
- })
- |> Jason.encode!()
- }
- |> Jason.encode!()
-
- send(socket.transport_pid, {:text, json})
+ def handle_cast(
+ %{action: :stream, topic: topic, item: %Notification{} = item},
+ topics
+ )
+ when topic in ["user", "user:notification"] do
+ topics
+ |> Map.get("#{topic}:#{item.user_id}", [])
+ |> Enum.each(fn socket ->
+ send(
+ socket.transport_pid,
+ {:text, represent_notification(socket.assigns[:user], item)}
+ )
end)
{:noreply, topics}
@@ -216,6 +211,20 @@ defmodule Pleroma.Web.Streamer do
|> Jason.encode!()
end
+ @spec represent_notification(User.t(), Notification.t()) :: binary()
+ defp represent_notification(%User{} = user, %Notification{} = notify) do
+ %{
+ event: "notification",
+ payload:
+ NotificationView.render(
+ "show.json",
+ %{notification: notify, for: user}
+ )
+ |> Jason.encode!()
+ }
+ |> Jason.encode!()
+ end
+
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
Enum.each(topics[topic] || [], fn socket ->
# Get the current user so we have up-to-date blocks etc.
@@ -274,7 +283,7 @@ defmodule Pleroma.Web.Streamer do
end)
end
- defp internal_topic(topic, socket) when topic in ~w[user direct] do
+ defp internal_topic(topic, socket) when topic in ~w[user user:notification direct] do
"#{topic}:#{socket.assigns[:user].id}"
end
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/results.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
index 8443d906b..8443d906b 100644
--- a/lib/pleroma/web/templates/o_auth/o_auth/results.html.eex
+++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex
diff --git a/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
new file mode 100644
index 000000000..961aad976
--- /dev/null
+++ b/lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex
@@ -0,0 +1,2 @@
+<h1>Authorization exists</h1>
+<h2>Access token is <%= @token.token %></h2>
diff --git a/lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex
index ee84750c7..ee84750c7 100644
--- a/lib/pleroma/web/templates/twitter_api/util/invalid_token.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex
diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
index a3facf017..7d3ef6b0d 100644
--- a/lib/pleroma/web/templates/twitter_api/util/password_reset.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex
@@ -1,5 +1,5 @@
<h2>Password Reset for <%= @user.nickname %></h2>
-<%= form_for @conn, util_path(@conn, :password_reset), [as: "data"], fn f -> %>
+<%= form_for @conn, reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
<div class="form-row">
<%= label f, :password, "Password" %>
<%= password_input f, :password %>
diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
index df037c01e..df037c01e 100644
--- a/lib/pleroma/web/templates/twitter_api/util/password_reset_failed.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex
diff --git a/lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
index f30ba3274..f30ba3274 100644
--- a/lib/pleroma/web/templates/twitter_api/util/password_reset_success.html.eex
+++ b/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex
diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/twitter_api/controllers/password_controller.ex
new file mode 100644
index 000000000..1941e6143
--- /dev/null
+++ b/lib/pleroma/web/twitter_api/controllers/password_controller.ex
@@ -0,0 +1,37 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.TwitterAPI.PasswordController do
+ @moduledoc """
+ The module containts functions for reset password.
+ """
+
+ use Pleroma.Web, :controller
+
+ require Logger
+
+ alias Pleroma.PasswordResetToken
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ def reset(conn, %{"token" => token}) do
+ with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
+ %User{} = user <- User.get_cached_by_id(token.user_id) do
+ render(conn, "reset.html", %{
+ token: token,
+ user: user
+ })
+ else
+ _e -> render(conn, "invalid_token.html")
+ end
+ end
+
+ def do_reset(conn, %{"data" => data}) do
+ with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
+ render(conn, "reset_success.html")
+ else
+ _e -> render(conn, "reset_failed.html")
+ end
+ end
+end
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 489170d80..b1863528f 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -11,8 +11,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Activity
alias Pleroma.Emoji
alias Pleroma.Notification
- alias Pleroma.PasswordResetToken
- alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -20,26 +18,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Web.OStatus
alias Pleroma.Web.WebFinger
- def show_password_reset(conn, %{"token" => token}) do
- with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
- %User{} = user <- User.get_cached_by_id(token.user_id) do
- render(conn, "password_reset.html", %{
- token: token,
- user: user
- })
- else
- _e -> render(conn, "invalid_token.html")
- end
- end
-
- def password_reset(conn, %{"data" => data}) do
- with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
- render(conn, "password_reset_success.html")
- else
- _e -> render(conn, "password_reset_failed.html")
- end
- end
-
def help_test(conn, _params) do
json(conn, "ok")
end
diff --git a/lib/pleroma/web/twitter_api/views/password_view.ex b/lib/pleroma/web/twitter_api/views/password_view.ex
new file mode 100644
index 000000000..b166b925d
--- /dev/null
+++ b/lib/pleroma/web/twitter_api/views/password_view.ex
@@ -0,0 +1,8 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.TwitterAPI.PasswordView do
+ use Pleroma.Web, :view
+ import Phoenix.HTML.Form
+end
diff --git a/lib/pleroma/web/views/error_view.ex b/lib/pleroma/web/views/error_view.ex
index f4c04131c..5cb8669fe 100644
--- a/lib/pleroma/web/views/error_view.ex
+++ b/lib/pleroma/web/views/error_view.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.ErrorView do
def render("500.json", assigns) do
Logger.error("Internal server error: #{inspect(assigns[:reason])}")
- if Mix.env() != :prod do
+ if Pleroma.Config.get(:env) != :prod do
%{errors: %{detail: "Internal server error", reason: inspect(assigns[:reason])}}
else
%{errors: %{detail: "Internal server error"}}