commit: 8b6221d4ecd1d7e354e7de831dd46e285cb85077
parent eea879eb362d3310d4fe047fb6412b69dd8711fe
Author: feld <feld@feld.me>
Date: Tue, 13 Oct 2020 14:47:29 +0000
Merge branch 'feature/1822-files-consistency' into 'develop'
Feature/1822 files consistency
Closes #1822
See merge request pleroma/pleroma!2680
Diffstat:
577 files changed, 8395 insertions(+), 8187 deletions(-)
diff --git a/.credo.exs b/.credo.exs
@@ -25,7 +25,7 @@
#
# If you create your own checks, you must specify the source files for
# them here, so they can be loaded by Credo before running the analysis.
- requires: [],
+ requires: ["./lib/credo/check/consistency/file_location.ex"],
#
# Credo automatically checks for updates, like e.g. Hex does.
# You can disable this behaviour below:
@@ -71,7 +71,6 @@
# set this value to 0 (zero).
{Credo.Check.Design.TagTODO, exit_status: 0},
{Credo.Check.Design.TagFIXME, exit_status: 0},
-
{Credo.Check.Readability.FunctionNames},
{Credo.Check.Readability.LargeNumbers},
{Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 100},
@@ -91,7 +90,6 @@
{Credo.Check.Readability.VariableNames},
{Credo.Check.Readability.Semicolons},
{Credo.Check.Readability.SpaceAfterCommas},
-
{Credo.Check.Refactor.DoubleBooleanNegation},
{Credo.Check.Refactor.CondStatements},
{Credo.Check.Refactor.CyclomaticComplexity},
@@ -102,7 +100,6 @@
{Credo.Check.Refactor.Nesting},
{Credo.Check.Refactor.PipeChainStart},
{Credo.Check.Refactor.UnlessWithElse},
-
{Credo.Check.Warning.BoolOperationOnSameValues},
{Credo.Check.Warning.IExPry},
{Credo.Check.Warning.IoInspect},
@@ -131,6 +128,7 @@
# Custom checks can be created using `mix credo.gen.check`.
#
+ {Credo.Check.Consistency.FileLocation}
]
}
]
diff --git a/config/config.exs b/config/config.exs
@@ -677,7 +677,7 @@ config :pleroma, :rate_limit,
config :pleroma, Pleroma.Workers.PurgeExpiredActivity, enabled: true, min_lifetime: 600
-config :pleroma, Pleroma.Plugs.RemoteIp,
+config :pleroma, Pleroma.Web.Plugs.RemoteIp,
enabled: true,
headers: ["x-forwarded-for"],
proxies: [],
diff --git a/config/description.exs b/config/description.exs
@@ -3250,10 +3250,10 @@ config :pleroma, :config_description, [
},
%{
group: :pleroma,
- key: Pleroma.Plugs.RemoteIp,
+ key: Pleroma.Web.Plugs.RemoteIp,
type: :group,
description: """
- `Pleroma.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
+ `Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
**If your instance is not behind at least one reverse proxy, you should not enable this plug.**
""",
children: [
diff --git a/config/test.exs b/config/test.exs
@@ -113,7 +113,7 @@ config :pleroma, Pleroma.Gun, Pleroma.GunMock
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true
-config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
+config :pleroma, Pleroma.Web.Plugs.RemoteIp, enabled: false
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: true
diff --git a/coveralls.json b/coveralls.json
@@ -1,6 +1,7 @@
{
"skip_files": [
"test/support",
- "lib/mix/tasks/pleroma/benchmark.ex"
+ "lib/mix/tasks/pleroma/benchmark.ex",
+ "lib/credo/check/consistency/file_location.ex"
]
}
\ No newline at end of file
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
@@ -113,7 +113,7 @@ To add configuration to your config file, you can copy it from the base config.
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)).
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)).
* `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
- * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.ActivityExpiration` to be enabled for processing the scheduled delections.
+ * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
* `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
@@ -219,12 +219,6 @@ config :pleroma, :mrf_user_allowlist, %{
* `total_user_limit`: the number of scheduled activities a user is allowed to create in total (Default: `300`)
* `enabled`: whether scheduled activities are sent to the job queue to be executed
-## Pleroma.ActivityExpiration
-
-Enables the worker which processes posts scheduled for deletion. Pinned posts are exempt from expiration.
-
-* `enabled`: whether expired activities will be sent to the job queue to be deleted
-
## FedSockets
FedSockets is an experimental feature allowing for Pleroma backends to federate using a persistant websocket connection as opposed to making each federation a seperate http connection. This feature is currently off by default. It is configurable throught he following options.
@@ -416,12 +410,12 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start
* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
* ``report_uri``: Adds the specified url to `report-uri` and `report-to` group in CSP header.
-### Pleroma.Plugs.RemoteIp
+### Pleroma.Web.Plugs.RemoteIp
!!! warning
If your instance is not behind at least one reverse proxy, you should not enable this plug.
-`Pleroma.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
+`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
Available options:
@@ -434,7 +428,7 @@ Available options:
### :rate_limit
!!! note
- If your instance is behind a reverse proxy ensure [`Pleroma.Plugs.RemoteIp`](#pleroma-plugs-remoteip) is enabled (it is enabled by default).
+ If your instance is behind a reverse proxy ensure [`Pleroma.Web.Plugs.RemoteIp`](#pleroma-plugs-remoteip) is enabled (it is enabled by default).
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:
diff --git a/docs/dev.md b/docs/dev.md
@@ -6,7 +6,7 @@ This document contains notes and guidelines for Pleroma developers.
* Pleroma supports hierarchical OAuth scopes, just like Mastodon but with added granularity of admin scopes. For a reference, see [Mastodon OAuth scopes](https://docs.joinmastodon.org/api/oauth-scopes/).
-* It is important to either define OAuth scope restrictions or explicitly mark OAuth scope check as skipped, for every controller action. To define scopes, call `plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: [...]})`. To explicitly set OAuth scopes check skipped, call `plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug <when ...>)`.
+* It is important to either define OAuth scope restrictions or explicitly mark OAuth scope check as skipped, for every controller action. To define scopes, call `plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: [...]})`. To explicitly set OAuth scopes check skipped, call `plug(:skip_plug, Pleroma.Web.Plugs.OAuthScopesPlug <when ...>)`.
* In controllers, `use Pleroma.Web, :controller` will result in `action/2` (see `Pleroma.Web.controller/0` for definition) be called prior to actual controller action, and it'll perform security / privacy checks before passing control to actual controller action.
@@ -16,7 +16,7 @@ This document contains notes and guidelines for Pleroma developers.
## [HTTP Basic Authentication](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization)
-* With HTTP Basic Auth, OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways). `Pleroma.Plugs.AuthenticationPlug` and `Pleroma.Plugs.LegacyAuthenticationPlug` both call `Pleroma.Plugs.OAuthScopesPlug.skip_plug(conn)` when password is provided.
+* With HTTP Basic Auth, OAuth scopes check is _not_ performed for any action (since password is provided during the auth, requester is able to obtain a token with full permissions anyways). `Pleroma.Web.Plugs.AuthenticationPlug` and `Pleroma.Web.Plugs.LegacyAuthenticationPlug` both call `Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug(conn)` when password is provided.
## Auth-related configuration, OAuth consumer mode etc.
diff --git a/lib/credo/check/consistency/file_location.ex b/lib/credo/check/consistency/file_location.ex
@@ -0,0 +1,166 @@
+# Pleroma: A lightweight social networking server
+# Originally taken from
+# https://github.com/VeryBigThings/elixir_common/blob/master/lib/vbt/credo/check/consistency/file_location.ex
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Credo.Check.Consistency.FileLocation do
+ @moduledoc false
+
+ # credo:disable-for-this-file Credo.Check.Readability.Specs
+
+ @checkdoc """
+ File location should follow the namespace hierarchy of the module it defines.
+
+ Examples:
+
+ - `lib/my_system.ex` should define the `MySystem` module
+ - `lib/my_system/accounts.ex` should define the `MySystem.Accounts` module
+ """
+ @explanation [warning: @checkdoc]
+
+ @special_namespaces [
+ "controllers",
+ "views",
+ "operations",
+ "channels"
+ ]
+
+ # `use Credo.Check` required that module attributes are already defined, so we need
+ # to place these attributes
+ # before use/alias expressions.
+ # credo:disable-for-next-line VBT.Credo.Check.Consistency.ModuleLayout
+ use Credo.Check, category: :warning, base_priority: :high
+
+ alias Credo.Code
+
+ def run(source_file, params \\ []) do
+ case verify(source_file, params) do
+ :ok ->
+ []
+
+ {:error, module, expected_file} ->
+ error(IssueMeta.for(source_file, params), module, expected_file)
+ end
+ end
+
+ defp verify(source_file, params) do
+ source_file.filename
+ |> Path.relative_to_cwd()
+ |> verify(Code.ast(source_file), params)
+ end
+
+ @doc false
+ def verify(relative_path, ast, params) do
+ if verify_path?(relative_path, params),
+ do: ast |> main_module() |> verify_module(relative_path, params),
+ else: :ok
+ end
+
+ defp verify_path?(relative_path, params) do
+ case Path.split(relative_path) do
+ ["lib" | _] -> not exclude?(relative_path, params)
+ ["test", "support" | _] -> false
+ ["test", "test_helper.exs"] -> false
+ ["test" | _] -> not exclude?(relative_path, params)
+ _ -> false
+ end
+ end
+
+ defp exclude?(relative_path, params) do
+ params
+ |> Keyword.get(:exclude, [])
+ |> Enum.any?(&String.starts_with?(relative_path, &1))
+ end
+
+ defp main_module(ast) do
+ {_ast, modules} = Macro.prewalk(ast, [], &traverse/2)
+ Enum.at(modules, -1)
+ end
+
+ defp traverse({:defmodule, _meta, args}, modules) do
+ [{:__aliases__, _, name_parts}, _module_body] = args
+ {args, [Module.concat(name_parts) | modules]}
+ end
+
+ defp traverse(ast, state), do: {ast, state}
+
+ # empty file - shouldn't really happen, but we'll let it through
+ defp verify_module(nil, _relative_path, _params), do: :ok
+
+ defp verify_module(main_module, relative_path, params) do
+ parsed_path = parsed_path(relative_path, params)
+
+ expected_file =
+ expected_file_base(parsed_path.root, main_module) <>
+ Path.extname(parsed_path.allowed)
+
+ cond do
+ expected_file == parsed_path.allowed ->
+ :ok
+
+ special_namespaces?(parsed_path.allowed) ->
+ original_path = parsed_path.allowed
+
+ namespace =
+ Enum.find(@special_namespaces, original_path, fn namespace ->
+ String.contains?(original_path, namespace)
+ end)
+
+ allowed = String.replace(original_path, "/" <> namespace, "")
+
+ if expected_file == allowed,
+ do: :ok,
+ else: {:error, main_module, expected_file}
+
+ true ->
+ {:error, main_module, expected_file}
+ end
+ end
+
+ defp special_namespaces?(path), do: String.contains?(path, @special_namespaces)
+
+ defp parsed_path(relative_path, params) do
+ parts = Path.split(relative_path)
+
+ allowed =
+ Keyword.get(params, :ignore_folder_namespace, %{})
+ |> Stream.flat_map(fn {root, folders} -> Enum.map(folders, &Path.join([root, &1])) end)
+ |> Stream.map(&Path.split/1)
+ |> Enum.find(&List.starts_with?(parts, &1))
+ |> case do
+ nil ->
+ relative_path
+
+ ignore_parts ->
+ Stream.drop(ignore_parts, -1)
+ |> Enum.concat(Stream.drop(parts, length(ignore_parts)))
+ |> Path.join()
+ end
+
+ %{root: hd(parts), allowed: allowed}
+ end
+
+ defp expected_file_base(root_folder, module) do
+ {parent_namespace, module_name} = module |> Module.split() |> Enum.split(-1)
+
+ relative_path =
+ if parent_namespace == [],
+ do: "",
+ else: parent_namespace |> Module.concat() |> Macro.underscore()
+
+ file_name = module_name |> Module.concat() |> Macro.underscore()
+
+ Path.join([root_folder, relative_path, file_name])
+ end
+
+ defp error(issue_meta, module, expected_file) do
+ format_issue(issue_meta,
+ message:
+ "Mismatch between file name and main module #{inspect(module)}. " <>
+ "Expected file path to be #{expected_file}. " <>
+ "Either move the file or rename the module.",
+ line_no: 1
+ )
+ end
+end
diff --git a/lib/mix/tasks/pleroma/ecto/ecto.ex b/lib/mix/tasks/pleroma/ecto.ex
diff --git a/lib/mix/tasks/pleroma/robotstxt.ex b/lib/mix/tasks/pleroma/robots_txt.ex
diff --git a/lib/transports.ex b/lib/phoenix/transports/web_socket/raw.ex
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
@@ -52,7 +52,7 @@ defmodule Pleroma.Application do
Pleroma.HTML.compile_scrubbers()
Pleroma.Config.Oban.warn()
Config.DeprecationWarnings.warn()
- Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled()
+ Pleroma.Web.Plugs.HTTPSecurityPlug.warn_if_disabled()
Pleroma.ApplicationRequirements.verify!()
setup_instrumenters()
load_custom_modules()
@@ -88,7 +88,7 @@ defmodule Pleroma.Application do
Pleroma.Repo,
Config.TransferTask,
Pleroma.Emoji,
- Pleroma.Plugs.RateLimiter.Supervisor
+ Pleroma.Web.Plugs.RateLimiter.Supervisor
] ++
cachex_children() ++
http_children(adapter, @env) ++
diff --git a/lib/pleroma/bbs/authenticator.ex b/lib/pleroma/bbs/authenticator.ex
@@ -4,8 +4,8 @@
defmodule Pleroma.BBS.Authenticator do
use Sshd.PasswordAuthenticator
- alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.User
+ alias Pleroma.Web.Plugs.AuthenticationPlug
def authenticate(username, password) do
username = to_string(username)
diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha.ex
diff --git a/lib/pleroma/captcha/captcha_service.ex b/lib/pleroma/captcha/service.ex
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
@@ -39,7 +39,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
:ok <- check_media_proxy_whitelist_config(),
:ok <- check_welcome_message_config(),
:ok <- check_gun_pool_options(),
- :ok <- check_activity_expiration_config() do
+ :ok <- check_activity_expiration_config(),
+ :ok <- check_remote_ip_plug_name() do
:ok
else
_ ->
@@ -176,4 +177,20 @@ defmodule Pleroma.Config.DeprecationWarnings do
warning_preface
)
end
+
+ @spec check_remote_ip_plug_name() :: :ok | nil
+ def check_remote_ip_plug_name do
+ warning_preface = """
+ !!!DEPRECATION WARNING!!!
+ Your config is using old namespace for RemoteIp Plug. Setting should work for now, but you are advised to change to new namespace to prevent possible issues later:
+ """
+
+ move_namespace_and_warn(
+ [
+ {Pleroma.Plugs.RemoteIp, Pleroma.Web.Plugs.RemoteIp,
+ "\n* `config :pleroma, Pleroma.Plugs.RemoteIp` is now `config :pleroma, Pleroma.Web.Plugs.RemoteIp`"}
+ ],
+ warning_preface
+ )
+ end
end
diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config_db.ex
diff --git a/lib/pleroma/conversation/participation_recipient_ship.ex b/lib/pleroma/conversation/participation/recipient_ship.ex
diff --git a/lib/pleroma/gun/gun.ex b/lib/pleroma/gun.ex
diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http.ex
diff --git a/lib/pleroma/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/plugs/admin_secret_authentication_plug.ex
@@ -1,60 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.AdminSecretAuthenticationPlug do
- import Plug.Conn
-
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
- alias Pleroma.User
-
- def init(options) do
- options
- end
-
- def secret_token do
- case Pleroma.Config.get(:admin_token) do
- blank when blank in [nil, ""] -> nil
- token -> token
- end
- end
-
- def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
-
- def call(conn, _) do
- if secret_token() do
- authenticate(conn)
- else
- conn
- end
- end
-
- def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
- if admin_token == secret_token() do
- assign_admin_user(conn)
- else
- handle_bad_token(conn)
- end
- end
-
- def authenticate(conn) do
- token = secret_token()
-
- case get_req_header(conn, "x-admin-token") do
- blank when blank in [[], [""]] -> conn
- [^token] -> assign_admin_user(conn)
- _ -> handle_bad_token(conn)
- end
- end
-
- defp assign_admin_user(conn) do
- conn
- |> assign(:user, %User{is_admin: true})
- |> OAuthScopesPlug.skip_plug()
- end
-
- defp handle_bad_token(conn) do
- RateLimiter.call(conn, name: :authentication)
- end
-end
diff --git a/lib/pleroma/plugs/authentication_plug.ex b/lib/pleroma/plugs/authentication_plug.ex
@@ -1,80 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.AuthenticationPlug do
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.User
-
- import Plug.Conn
-
- require Logger
-
- def init(options), do: options
-
- def checkpw(password, "$6" <> _ = password_hash) do
- :crypt.crypt(password, password_hash) == password_hash
- end
-
- def checkpw(password, "$2" <> _ = password_hash) do
- # Handle bcrypt passwords for Mastodon migration
- Bcrypt.verify_pass(password, password_hash)
- end
-
- def checkpw(password, "$pbkdf2" <> _ = password_hash) do
- Pbkdf2.verify_pass(password, password_hash)
- end
-
- def checkpw(_password, _password_hash) do
- Logger.error("Password hash not recognized")
- false
- end
-
- def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
- do_update_password(user, password)
- end
-
- def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
- do_update_password(user, password)
- end
-
- def maybe_update_password(user, _), do: {:ok, user}
-
- defp do_update_password(user, password) do
- user
- |> User.password_update_changeset(%{
- "password" => password,
- "password_confirmation" => password
- })
- |> Pleroma.Repo.update()
- end
-
- def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
-
- def call(
- %{
- assigns: %{
- auth_user: %{password_hash: password_hash} = auth_user,
- auth_credentials: %{password: password}
- }
- } = conn,
- _
- ) do
- if checkpw(password, password_hash) do
- {:ok, auth_user} = maybe_update_password(auth_user, password)
-
- conn
- |> assign(:user, auth_user)
- |> OAuthScopesPlug.skip_plug()
- else
- conn
- end
- end
-
- def call(%{assigns: %{auth_credentials: %{password: _}}} = conn, _) do
- Pbkdf2.no_user_verify()
- conn
- end
-
- def call(conn, _), do: conn
-end
diff --git a/lib/pleroma/plugs/basic_auth_decoder_plug.ex b/lib/pleroma/plugs/basic_auth_decoder_plug.ex
@@ -1,25 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.BasicAuthDecoderPlug do
- import Plug.Conn
-
- def init(options) do
- options
- end
-
- def call(conn, _opts) do
- with ["Basic " <> header] <- get_req_header(conn, "authorization"),
- {:ok, userinfo} <- Base.decode64(header),
- [username, password] <- String.split(userinfo, ":", parts: 2) do
- conn
- |> assign(:auth_credentials, %{
- username: username,
- password: password
- })
- else
- _ -> conn
- end
- end
-end
diff --git a/lib/pleroma/plugs/cache.ex b/lib/pleroma/plugs/cache.ex
@@ -1,136 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.Cache do
- @moduledoc """
- Caches successful GET responses.
-
- To enable the cache add the plug to a router pipeline or controller:
-
- plug(Pleroma.Plugs.Cache)
-
- ## Configuration
-
- To configure the plug you need to pass settings as the second argument to the `plug/2` macro:
-
- plug(Pleroma.Plugs.Cache, [ttl: nil, query_params: true])
-
- Available options:
-
- - `ttl`: An expiration time (time-to-live). This value should be in milliseconds or `nil` to disable expiration. Defaults to `nil`.
- - `query_params`: Take URL query string into account (`true`), ignore it (`false`) or limit to specific params only (list). Defaults to `true`.
- - `tracking_fun`: A function that is called on successfull responses, no matter if the request is cached or not. It should accept a conn as the first argument and the value assigned to `tracking_fun_data` as the second.
-
- Additionally, you can overwrite the TTL inside a controller action by assigning `cache_ttl` to the connection struct:
-
- def index(conn, _params) do
- ttl = 60_000 # one minute
-
- conn
- |> assign(:cache_ttl, ttl)
- |> render("index.html")
- end
-
- """
-
- import Phoenix.Controller, only: [current_path: 1, json: 2]
- import Plug.Conn
-
- @behaviour Plug
-
- @defaults %{ttl: nil, query_params: true}
-
- @impl true
- def init([]), do: @defaults
-
- def init(opts) do
- opts = Map.new(opts)
- Map.merge(@defaults, opts)
- end
-
- @impl true
- def call(%{method: "GET"} = conn, opts) do
- key = cache_key(conn, opts)
-
- case Cachex.get(:web_resp_cache, key) do
- {:ok, nil} ->
- cache_resp(conn, opts)
-
- {:ok, {content_type, body, tracking_fun_data}} ->
- conn = opts.tracking_fun.(conn, tracking_fun_data)
-
- send_cached(conn, {content_type, body})
-
- {:ok, record} ->
- send_cached(conn, record)
-
- {atom, message} when atom in [:ignore, :error] ->
- render_error(conn, message)
- end
- end
-
- def call(conn, _), do: conn
-
- # full path including query params
- defp cache_key(conn, %{query_params: true}), do: current_path(conn)
-
- # request path without query params
- defp cache_key(conn, %{query_params: false}), do: conn.request_path
-
- # request path with specific query params
- defp cache_key(conn, %{query_params: query_params}) when is_list(query_params) do
- query_string =
- conn.params
- |> Map.take(query_params)
- |> URI.encode_query()
-
- conn.request_path <> "?" <> query_string
- end
-
- defp cache_resp(conn, opts) do
- register_before_send(conn, fn
- %{status: 200, resp_body: body} = conn ->
- ttl = Map.get(conn.assigns, :cache_ttl, opts.ttl)
- key = cache_key(conn, opts)
- content_type = content_type(conn)
-
- conn =
- unless opts[:tracking_fun] do
- Cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl)
- conn
- else
- tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil)
- Cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl)
-
- opts.tracking_fun.(conn, tracking_fun_data)
- end
-
- put_resp_header(conn, "x-cache", "MISS from Pleroma")
-
- conn ->
- conn
- end)
- end
-
- defp content_type(conn) do
- conn
- |> Plug.Conn.get_resp_header("content-type")
- |> hd()
- end
-
- defp send_cached(conn, {content_type, body}) do
- conn
- |> put_resp_content_type(content_type, nil)
- |> put_resp_header("x-cache", "HIT from Pleroma")
- |> send_resp(:ok, body)
- |> halt()
- end
-
- defp render_error(conn, message) do
- conn
- |> put_status(:internal_server_error)
- |> json(%{error: message})
- |> halt()
- end
-end
diff --git a/lib/pleroma/plugs/ensure_authenticated_plug.ex b/lib/pleroma/plugs/ensure_authenticated_plug.ex
@@ -1,41 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do
- import Plug.Conn
- import Pleroma.Web.TranslationHelpers
-
- alias Pleroma.User
-
- use Pleroma.Web, :plug
-
- def init(options) do
- options
- end
-
- @impl true
- def perform(
- %{
- assigns: %{
- auth_credentials: %{password: _},
- user: %User{multi_factor_authentication_settings: %{enabled: true}}
- }
- } = conn,
- _
- ) do
- conn
- |> render_error(:forbidden, "Two-factor authentication enabled, you must use a access token.")
- |> halt()
- end
-
- def perform(%{assigns: %{user: %User{}}} = conn, _) do
- conn
- end
-
- def perform(conn, _) do
- conn
- |> render_error(:forbidden, "Invalid credentials.")
- |> halt()
- end
-end
diff --git a/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex b/lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex
@@ -1,35 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
- import Pleroma.Web.TranslationHelpers
- import Plug.Conn
-
- alias Pleroma.Config
- alias Pleroma.User
-
- use Pleroma.Web, :plug
-
- def init(options) do
- options
- end
-
- @impl true
- def perform(conn, _) do
- public? = Config.get!([:instance, :public])
-
- case {public?, conn} do
- {true, _} ->
- conn
-
- {false, %{assigns: %{user: %User{}}}} ->
- conn
-
- {false, _} ->
- conn
- |> render_error(:forbidden, "This resource requires authentication.")
- |> halt
- end
- end
-end
diff --git a/lib/pleroma/plugs/ensure_user_key_plug.ex b/lib/pleroma/plugs/ensure_user_key_plug.ex
@@ -1,18 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.EnsureUserKeyPlug do
- import Plug.Conn
-
- def init(opts) do
- opts
- end
-
- def call(%{assigns: %{user: _}} = conn, _), do: conn
-
- def call(conn, _) do
- conn
- |> assign(:user, nil)
- end
-end
diff --git a/lib/pleroma/plugs/expect_authenticated_check_plug.ex b/lib/pleroma/plugs/expect_authenticated_check_plug.ex
@@ -1,20 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.ExpectAuthenticatedCheckPlug do
- @moduledoc """
- Marks `Pleroma.Plugs.EnsureAuthenticatedPlug` as expected to be executed later in plug chain.
-
- No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`).
- """
-
- use Pleroma.Web, :plug
-
- def init(options), do: options
-
- @impl true
- def perform(conn, _) do
- conn
- end
-end
diff --git a/lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex b/lib/pleroma/plugs/expect_public_or_authenticated_check_plug.ex
@@ -1,21 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug do
- @moduledoc """
- Marks `Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug` as expected to be executed later in plug
- chain.
-
- No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`).
- """
-
- use Pleroma.Web, :plug
-
- def init(options), do: options
-
- @impl true
- def perform(conn, _) do
- conn
- end
-end
diff --git a/lib/pleroma/plugs/federating_plug.ex b/lib/pleroma/plugs/federating_plug.ex
@@ -1,32 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FederatingPlug do
- import Plug.Conn
-
- def init(options) do
- options
- end
-
- def call(conn, _opts) do
- if federating?() do
- conn
- else
- fail(conn)
- end
- end
-
- def federating?, do: Pleroma.Config.get([:instance, :federating])
-
- # Definition for the use in :if_func / :unless_func plug options
- def federating?(_conn), do: federating?()
-
- defp fail(conn) do
- conn
- |> put_status(404)
- |> Phoenix.Controller.put_view(Pleroma.Web.ErrorView)
- |> Phoenix.Controller.render("404.json")
- |> halt()
- end
-end
diff --git a/lib/pleroma/plugs/frontend_static.ex b/lib/pleroma/plugs/frontend_static.ex
@@ -1,55 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.FrontendStatic do
- require Pleroma.Constants
-
- @moduledoc """
- This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends.
- """
- @behaviour Plug
-
- def file_path(path, frontend_type \\ :primary) do
- if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
- instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
-
- Path.join([
- instance_static_path,
- "frontends",
- configuration["name"],
- configuration["ref"],
- path
- ])
- else
- nil
- end
- end
-
- def init(opts) do
- opts
- |> Keyword.put(:from, "__unconfigured_frontend_static_plug")
- |> Plug.Static.init()
- |> Map.put(:frontend_type, opts[:frontend_type])
- end
-
- def call(conn, opts) do
- frontend_type = Map.get(opts, :frontend_type, :primary)
- path = file_path("", frontend_type)
-
- if path do
- conn
- |> call_static(opts, path)
- else
- conn
- end
- end
-
- defp call_static(conn, opts, from) do
- opts =
- opts
- |> Map.put(:from, from)
-
- Plug.Static.call(conn, opts)
- end
-end
diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex
@@ -1,225 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.HTTPSecurityPlug do
- alias Pleroma.Config
- import Plug.Conn
-
- require Logger
-
- def init(opts), do: opts
-
- def call(conn, _options) do
- if Config.get([:http_security, :enabled]) do
- conn
- |> merge_resp_headers(headers())
- |> maybe_send_sts_header(Config.get([:http_security, :sts]))
- else
- conn
- end
- end
-
- defp headers do
- referrer_policy = Config.get([:http_security, :referrer_policy])
- report_uri = Config.get([:http_security, :report_uri])
-
- headers = [
- {"x-xss-protection", "1; mode=block"},
- {"x-permitted-cross-domain-policies", "none"},
- {"x-frame-options", "DENY"},
- {"x-content-type-options", "nosniff"},
- {"referrer-policy", referrer_policy},
- {"x-download-options", "noopen"},
- {"content-security-policy", csp_string()}
- ]
-
- if report_uri do
- report_group = %{
- "group" => "csp-endpoint",
- "max-age" => 10_886_400,
- "endpoints" => [
- %{"url" => report_uri}
- ]
- }
-
- [{"reply-to", Jason.encode!(report_group)} | headers]
- else
- headers
- end
- end
-
- static_csp_rules = [
- "default-src 'none'",
- "base-uri 'self'",
- "frame-ancestors 'none'",
- "style-src 'self' 'unsafe-inline'",
- "font-src 'self'",
- "manifest-src 'self'"
- ]
-
- @csp_start [Enum.join(static_csp_rules, ";") <> ";"]
-
- defp csp_string do
- scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
- static_url = Pleroma.Web.Endpoint.static_url()
- websocket_url = Pleroma.Web.Endpoint.websocket_url()
- report_uri = Config.get([:http_security, :report_uri])
-
- img_src = "img-src 'self' data: blob:"
- media_src = "media-src 'self'"
-
- # Strict multimedia CSP enforcement only when MediaProxy is enabled
- {img_src, media_src} =
- if Config.get([:media_proxy, :enabled]) &&
- !Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
- sources = build_csp_multimedia_source_list()
- {[img_src, sources], [media_src, sources]}
- else
- {[img_src, " https:"], [media_src, " https:"]}
- end
-
- connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
-
- connect_src =
- if Config.get(:env) == :dev do
- [connect_src, " http://localhost:3035/"]
- else
- connect_src
- end
-
- script_src =
- if Config.get(:env) == :dev do
- "script-src 'self' 'unsafe-eval'"
- else
- "script-src 'self'"
- end
-
- report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
- insecure = if scheme == "https", do: "upgrade-insecure-requests"
-
- @csp_start
- |> add_csp_param(img_src)
- |> add_csp_param(media_src)
- |> add_csp_param(connect_src)
- |> add_csp_param(script_src)
- |> add_csp_param(insecure)
- |> add_csp_param(report)
- |> :erlang.iolist_to_binary()
- end
-
- defp build_csp_from_whitelist([], acc), do: acc
-
- defp build_csp_from_whitelist([last], acc) do
- [build_csp_param_from_whitelist(last) | acc]
- end
-
- defp build_csp_from_whitelist([head | tail], acc) do
- build_csp_from_whitelist(tail, [[?\s, build_csp_param_from_whitelist(head)] | acc])
- end
-
- # TODO: use `build_csp_param/1` after removing support bare domains for media proxy whitelist
- defp build_csp_param_from_whitelist("http" <> _ = url) do
- build_csp_param(url)
- end
-
- defp build_csp_param_from_whitelist(url), do: url
-
- defp build_csp_multimedia_source_list do
- media_proxy_whitelist =
- [:media_proxy, :whitelist]
- |> Config.get()
- |> build_csp_from_whitelist([])
-
- captcha_method = Config.get([Pleroma.Captcha, :method])
- captcha_endpoint = Config.get([captcha_method, :endpoint])
-
- base_endpoints =
- [
- [:media_proxy, :base_url],
- [Pleroma.Upload, :base_url],
- [Pleroma.Uploaders.S3, :public_endpoint]
- ]
- |> Enum.map(&Config.get/1)
-
- [captcha_endpoint | base_endpoints]
- |> Enum.map(&build_csp_param/1)
- |> Enum.reduce([], &add_source(&2, &1))
- |> add_source(media_proxy_whitelist)
- end
-
- defp add_source(iodata, nil), do: iodata
- defp add_source(iodata, []), do: iodata
- defp add_source(iodata, source), do: [[?\s, source] | iodata]
-
- defp add_csp_param(csp_iodata, nil), do: csp_iodata
-
- defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
-
- defp build_csp_param(nil), do: nil
-
- defp build_csp_param(url) when is_binary(url) do
- %{host: host, scheme: scheme} = URI.parse(url)
-
- if scheme do
- [scheme, "://", host]
- end
- end
-
- def warn_if_disabled do
- unless Config.get([:http_security, :enabled]) do
- Logger.warn("
- .i;;;;i.
- iYcviii;vXY:
- .YXi .i1c.
- .YC. . in7.
- .vc. ...... ;1c.
- i7, .. .;1;
- i7, .. ... .Y1i
- ,7v .6MMM@; .YX,
- .7;. ..IMMMMMM1 :t7.
- .;Y. ;$MMMMMM9. :tc.
- vY. .. .nMMM@MMU. ;1v.
- i7i ... .#MM@M@C. .....:71i
- it: .... $MMM@9;.,i;;;i,;tti
- :t7. ..... 0MMMWv.,iii:::,,;St.
- .nC. ..... IMMMQ..,::::::,.,czX.
- .ct: ....... .ZMMMI..,:::::::,,:76Y.
- c2: ......,i..Y$M@t..:::::::,,..inZY
- vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
- i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
- iIS. ......:ii::..;@MI....,............;Ez.
- .I9. ......:i::::...8M1..................C0z.
- .z9; ......:i::::,.. .i:...................zWX.
- vbv ......,i::::,,. ................. :AQY
- c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
- :6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
- :6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
- .n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
- 7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
- 7C...::::::::::::,,,,.. .................... vSi.
- ;1;...,,::::::,......... .................. Yz:
- v97,......... .voC.
- izAotX7777777777777777777777777777777777777777Y7n92:
- .;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
-
-HTTP Security is disabled. Please re-enable it to prevent users from attacking
-your instance and your users via malicious posts:
-
- config :pleroma, :http_security, enabled: true
- ")
- end
- end
-
- defp maybe_send_sts_header(conn, true) do
- max_age_sts = Config.get([:http_security, :sts_max_age])
- max_age_ct = Config.get([:http_security, :ct_max_age])
-
- merge_resp_headers(conn, [
- {"strict-transport-security", "max-age=#{max_age_sts}; includeSubDomains"},
- {"expect-ct", "enforce, max-age=#{max_age_ct}"}
- ])
- end
-
- defp maybe_send_sts_header(conn, _), do: conn
-end
diff --git a/lib/pleroma/plugs/idempotency_plug.ex b/lib/pleroma/plugs/idempotency_plug.ex
@@ -1,84 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.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/instance_static.ex b/lib/pleroma/plugs/instance_static.ex
@@ -1,53 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.InstanceStatic do
- require Pleroma.Constants
-
- @moduledoc """
- This is a shim to call `Plug.Static` but with runtime `from` configuration.
-
- Mountpoints are defined directly in the module to avoid calling the configuration for every request including non-static ones.
- """
- @behaviour Plug
-
- def file_path(path) do
- instance_path =
- Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path)
-
- frontend_path = Pleroma.Plugs.FrontendStatic.file_path(path, :primary)
-
- (File.exists?(instance_path) && instance_path) ||
- (frontend_path && File.exists?(frontend_path) && frontend_path) ||
- Path.join(Application.app_dir(:pleroma, "priv/static/"), path)
- end
-
- def init(opts) do
- opts
- |> Keyword.put(:from, "__unconfigured_instance_static_plug")
- |> Plug.Static.init()
- end
-
- for only <- Pleroma.Constants.static_only_files() do
- def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do
- call_static(
- conn,
- opts,
- Pleroma.Config.get([:instance, :static_dir], "instance/static")
- )
- end
- end
-
- def call(conn, _) do
- conn
- end
-
- defp call_static(conn, opts, from) do
- opts =
- opts
- |> Map.put(:from, from)
-
- Plug.Static.call(conn, opts)
- end
-end
diff --git a/lib/pleroma/plugs/legacy_authentication_plug.ex b/lib/pleroma/plugs/legacy_authentication_plug.ex
@@ -1,42 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.LegacyAuthenticationPlug do
- import Plug.Conn
-
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.User
-
- def init(options) do
- options
- end
-
- def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
-
- def call(
- %{
- assigns: %{
- auth_user: %{password_hash: "$6$" <> _ = password_hash} = auth_user,
- auth_credentials: %{password: password}
- }
- } = conn,
- _
- ) do
- with ^password_hash <- :crypt.crypt(password, password_hash),
- {:ok, user} <-
- User.reset_password(auth_user, %{password: password, password_confirmation: password}) do
- conn
- |> assign(:auth_user, user)
- |> assign(:user, user)
- |> OAuthScopesPlug.skip_plug()
- else
- _ ->
- conn
- end
- end
-
- def call(conn, _) do
- conn
- end
-end
diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex
@@ -1,120 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.OAuthPlug do
- import Plug.Conn
- import Ecto.Query
-
- alias Pleroma.Repo
- alias Pleroma.User
- alias Pleroma.Web.OAuth.App
- alias Pleroma.Web.OAuth.Token
-
- @realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")
-
- def init(options), do: options
-
- def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
-
- def call(%{params: %{"access_token" => access_token}} = conn, _) do
- with {:ok, user, token_record} <- fetch_user_and_token(access_token) do
- conn
- |> assign(:token, token_record)
- |> assign(:user, user)
- else
- _ ->
- # token found, but maybe only with app
- with {:ok, app, token_record} <- fetch_app_and_token(access_token) do
- conn
- |> assign(:token, token_record)
- |> assign(:app, app)
- else
- _ -> conn
- end
- end
- end
-
- def call(conn, _) do
- case fetch_token_str(conn) do
- {:ok, token} ->
- with {:ok, user, token_record} <- fetch_user_and_token(token) do
- conn
- |> assign(:token, token_record)
- |> assign(:user, user)
- else
- _ ->
- # token found, but maybe only with app
- with {:ok, app, token_record} <- fetch_app_and_token(token) do
- conn
- |> assign(:token, token_record)
- |> assign(:app, app)
- else
- _ -> conn
- end
- end
-
- _ ->
- conn
- end
- end
-
- # Gets user by token
- #
- @spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil
- defp fetch_user_and_token(token) do
- query =
- from(t in Token,
- where: t.token == ^token,
- join: user in assoc(t, :user),
- preload: [user: user]
- )
-
- # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
- with %Token{user: user} = token_record <- Repo.one(query) do
- {:ok, user, token_record}
- end
- end
-
- @spec fetch_app_and_token(String.t()) :: {:ok, App.t(), Token.t()} | nil
- defp fetch_app_and_token(token) do
- query =
- from(t in Token, where: t.token == ^token, join: app in assoc(t, :app), preload: [app: app])
-
- with %Token{app: app} = token_record <- Repo.one(query) do
- {:ok, app, token_record}
- end
- end
-
- # Gets token from session by :oauth_token key
- #
- @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
- defp fetch_token_from_session(conn) do
- case get_session(conn, :oauth_token) do
- nil -> :no_token_found
- token -> {:ok, token}
- end
- end
-
- # Gets token from headers
- #
- @spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
- defp fetch_token_str(%Plug.Conn{} = conn) do
- headers = get_req_header(conn, "authorization")
-
- with :no_token_found <- fetch_token_str(headers),
- do: fetch_token_from_session(conn)
- end
-
- @spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()}
- defp fetch_token_str([]), do: :no_token_found
-
- defp fetch_token_str([token | tail]) do
- trimmed_token = String.trim(token)
-
- case Regex.run(@realm_reg, trimmed_token) do
- [_, match] -> {:ok, String.trim(match)}
- _ -> fetch_token_str(tail)
- end
- end
-end
diff --git a/lib/pleroma/plugs/oauth_scopes_plug.ex b/lib/pleroma/plugs/oauth_scopes_plug.ex
@@ -1,77 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.OAuthScopesPlug do
- import Plug.Conn
- import Pleroma.Web.Gettext
-
- alias Pleroma.Config
-
- use Pleroma.Web, :plug
-
- def init(%{scopes: _} = options), do: options
-
- @impl true
- def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
- op = options[:op] || :|
- token = assigns[:token]
-
- scopes = transform_scopes(scopes, options)
- matched_scopes = (token && filter_descendants(scopes, token.scopes)) || []
-
- cond do
- token && op == :| && Enum.any?(matched_scopes) ->
- conn
-
- token && op == :& && matched_scopes == scopes ->
- conn
-
- options[:fallback] == :proceed_unauthenticated ->
- drop_auth_info(conn)
-
- true ->
- missing_scopes = scopes -- matched_scopes
- permissions = Enum.join(missing_scopes, " #{op} ")
-
- error_message =
- dgettext("errors", "Insufficient permissions: %{permissions}.", permissions: permissions)
-
- conn
- |> put_resp_content_type("application/json")
- |> send_resp(:forbidden, Jason.encode!(%{error: error_message}))
- |> halt()
- end
- end
-
- @doc "Drops authentication info from connection"
- def drop_auth_info(conn) do
- # To simplify debugging, setting a private variable on `conn` if auth info is dropped
- conn
- |> put_private(:authentication_ignored, true)
- |> assign(:user, nil)
- |> assign(:token, nil)
- end
-
- @doc "Keeps those of `scopes` which are descendants of `supported_scopes`"
- def filter_descendants(scopes, supported_scopes) do
- Enum.filter(
- scopes,
- fn scope ->
- Enum.find(
- supported_scopes,
- &(scope == &1 || String.starts_with?(scope, &1 <> ":"))
- )
- end
- )
- end
-
- @doc "Transforms scopes by applying supported options (e.g. :admin)"
- def transform_scopes(scopes, options) do
- if options[:admin] do
- Config.oauth_admin_scopes(scopes)
- else
- scopes
- end
- end
-end
diff --git a/lib/pleroma/plugs/plug_helper.ex b/lib/pleroma/plugs/plug_helper.ex
@@ -1,40 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.PlugHelper do
- @moduledoc "Pleroma Plug helper"
-
- @called_plugs_list_id :called_plugs
- def called_plugs_list_id, do: @called_plugs_list_id
-
- @skipped_plugs_list_id :skipped_plugs
- def skipped_plugs_list_id, do: @skipped_plugs_list_id
-
- @doc "Returns `true` if specified plug was called."
- def plug_called?(conn, plug_module) do
- contained_in_private_list?(conn, @called_plugs_list_id, plug_module)
- end
-
- @doc "Returns `true` if specified plug was explicitly marked as skipped."
- def plug_skipped?(conn, plug_module) do
- contained_in_private_list?(conn, @skipped_plugs_list_id, plug_module)
- end
-
- @doc "Returns `true` if specified plug was either called or explicitly marked as skipped."
- def plug_called_or_skipped?(conn, plug_module) do
- plug_called?(conn, plug_module) || plug_skipped?(conn, plug_module)
- end
-
- # Appends plug to known list (skipped, called). Intended to be used from within plug code only.
- def append_to_private_list(conn, list_id, value) do
- list = conn.private[list_id] || []
- modified_list = Enum.uniq(list ++ [value])
- Plug.Conn.put_private(conn, list_id, modified_list)
- end
-
- defp contained_in_private_list?(conn, private_variable, value) do
- list = conn.private[private_variable] || []
- value in list
- end
-end
diff --git a/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex b/lib/pleroma/plugs/rate_limiter/limiter_supervisor.ex
@@ -1,54 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.RateLimiter.LimiterSupervisor do
- use DynamicSupervisor
-
- import Cachex.Spec
-
- def start_link(init_arg) do
- DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
- end
-
- def add_or_return_limiter(limiter_name, expiration) do
- result =
- DynamicSupervisor.start_child(
- __MODULE__,
- %{
- id: String.to_atom("rl_#{limiter_name}"),
- start:
- {Cachex, :start_link,
- [
- limiter_name,
- [
- expiration:
- expiration(
- default: expiration,
- interval: check_interval(expiration),
- lazy: true
- )
- ]
- ]}
- }
- )
-
- case result do
- {:ok, _pid} = result -> result
- {:error, {:already_started, pid}} -> {:ok, pid}
- _ -> result
- end
- end
-
- @impl true
- def init(_init_arg) do
- DynamicSupervisor.init(strategy: :one_for_one)
- end
-
- defp check_interval(exp) do
- (exp / 2)
- |> Kernel.trunc()
- |> Kernel.min(5000)
- |> Kernel.max(1)
- end
-end
diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex
@@ -1,267 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.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
-
- AllowedSyntax:
-
- plug(Pleroma.Plugs.RateLimiter, name: :limiter_name)
- plug(Pleroma.Plugs.RateLimiter, options) # :name is a required option
-
- Allowed options:
-
- * `name` required, always used to fetch the limit values from the config
- * `bucket_name` overrides name for counting purposes (e.g. to have a separate limit for a set of actions)
- * `params` appends values of specified request params (e.g. ["id"]) to bucket name
-
- Inside a controller:
-
- plug(Pleroma.Plugs.RateLimiter, [name: :one] when action == :one)
- plug(Pleroma.Plugs.RateLimiter, [name: :two] when action in [:two, :three])
-
- plug(
- Pleroma.Plugs.RateLimiter,
- [name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]]
- when action in ~w(fav_status unfav_status)a
- )
-
- or inside a router pipeline:
-
- pipeline :api do
- ...
- plug(Pleroma.Plugs.RateLimiter, name: :one)
- ...
- end
- """
- import Pleroma.Web.TranslationHelpers
- import Plug.Conn
-
- alias Pleroma.Config
- alias Pleroma.Plugs.RateLimiter.LimiterSupervisor
- alias Pleroma.User
-
- require Logger
-
- @doc false
- def init(plug_opts) do
- plug_opts
- end
-
- def call(conn, plug_opts) do
- if disabled?(conn) do
- handle_disabled(conn)
- else
- action_settings = action_settings(plug_opts)
- handle(conn, action_settings)
- end
- end
-
- defp handle_disabled(conn) do
- Logger.warn(
- "Rate limiter disabled due to forwarded IP not being found. Please ensure your reverse proxy is providing the X-Forwarded-For header or disable the RemoteIP plug/rate limiter."
- )
-
- conn
- end
-
- defp handle(conn, nil), do: conn
-
- defp handle(conn, action_settings) do
- action_settings
- |> incorporate_conn_info(conn)
- |> check_rate()
- |> case do
- {:ok, _count} ->
- conn
-
- {:error, _count} ->
- render_throttled_error(conn)
- end
- end
-
- def disabled?(conn) do
- if Map.has_key?(conn.assigns, :remote_ip_found),
- do: !conn.assigns.remote_ip_found,
- else: false
- end
-
- @inspect_bucket_not_found {:error, :not_found}
-
- def inspect_bucket(conn, bucket_name_root, plug_opts) do
- with %{name: _} = action_settings <- action_settings(plug_opts) do
- action_settings = incorporate_conn_info(action_settings, conn)
- bucket_name = make_bucket_name(%{action_settings | name: bucket_name_root})
- key_name = make_key_name(action_settings)
- limit = get_limits(action_settings)
-
- case Cachex.get(bucket_name, key_name) do
- {:error, :no_cache} ->
- @inspect_bucket_not_found
-
- {:ok, nil} ->
- {0, limit}
-
- {:ok, value} ->
- {value, limit - value}
- end
- else
- _ -> @inspect_bucket_not_found
- end
- end
-
- def action_settings(plug_opts) do
- with limiter_name when is_atom(limiter_name) <- plug_opts[:name],
- limits when not is_nil(limits) <- Config.get([:rate_limit, limiter_name]) do
- bucket_name_root = Keyword.get(plug_opts, :bucket_name, limiter_name)
-
- %{
- name: bucket_name_root,
- limits: limits,
- opts: plug_opts
- }
- end
- end
-
- defp check_rate(action_settings) do
- bucket_name = make_bucket_name(action_settings)
- key_name = make_key_name(action_settings)
- limit = get_limits(action_settings)
-
- case Cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do
- {:commit, value} ->
- {:ok, value}
-
- {:ignore, value} ->
- {:error, value}
-
- {:error, :no_cache} ->
- initialize_buckets!(action_settings)
- check_rate(action_settings)
- end
- end
-
- defp increment_value(nil, _limit), do: {:commit, 1}
-
- defp increment_value(val, limit) when val >= limit, do: {:ignore, val}
-
- defp increment_value(val, _limit), do: {:commit, val + 1}
-
- defp incorporate_conn_info(action_settings, %{
- assigns: %{user: %User{id: user_id}},
- params: params
- }) do
- Map.merge(action_settings, %{
- mode: :user,
- conn_params: params,
- conn_info: "#{user_id}"
- })
- end
-
- defp incorporate_conn_info(action_settings, %{params: params} = conn) do
- Map.merge(action_settings, %{
- mode: :anon,
- conn_params: params,
- conn_info: "#{ip(conn)}"
- })
- end
-
- defp ip(%{remote_ip: remote_ip}) do
- remote_ip
- |> Tuple.to_list()
- |> Enum.join(".")
- end
-
- defp render_throttled_error(conn) do
- conn
- |> render_error(:too_many_requests, "Throttled")
- |> halt()
- end
-
- defp make_key_name(action_settings) do
- ""
- |> attach_selected_params(action_settings)
- |> attach_identity(action_settings)
- end
-
- defp get_scale(_, {scale, _}), do: scale
-
- defp get_scale(:anon, [{scale, _}, {_, _}]), do: scale
-
- defp get_scale(:user, [{_, _}, {scale, _}]), do: scale
-
- defp get_limits(%{limits: {_scale, limit}}), do: limit
-
- defp get_limits(%{mode: :user, limits: [_, {_, limit}]}), do: limit
-
- defp get_limits(%{limits: [{_, limit}, _]}), do: limit
-
- defp make_bucket_name(%{mode: :user, name: bucket_name_root}),
- do: user_bucket_name(bucket_name_root)
-
- defp make_bucket_name(%{mode: :anon, name: bucket_name_root}),
- do: anon_bucket_name(bucket_name_root)
-
- defp attach_selected_params(input, %{conn_params: conn_params, opts: plug_opts}) do
- params_string =
- plug_opts
- |> Keyword.get(:params, [])
- |> Enum.sort()
- |> Enum.map(&Map.get(conn_params, &1, ""))
- |> Enum.join(":")
-
- [input, params_string]
- |> Enum.join(":")
- |> String.replace_leading(":", "")
- end
-
- defp initialize_buckets!(%{name: _name, limits: nil}), do: :ok
-
- defp initialize_buckets!(%{name: name, limits: limits}) do
- {:ok, _pid} =
- LimiterSupervisor.add_or_return_limiter(anon_bucket_name(name), get_scale(:anon, limits))
-
- {:ok, _pid} =
- LimiterSupervisor.add_or_return_limiter(user_bucket_name(name), get_scale(:user, limits))
-
- :ok
- end
-
- defp attach_identity(base, %{mode: :user, conn_info: conn_info}),
- do: "user:#{base}:#{conn_info}"
-
- defp attach_identity(base, %{mode: :anon, conn_info: conn_info}),
- do: "ip:#{base}:#{conn_info}"
-
- defp user_bucket_name(bucket_name_root), do: "user:#{bucket_name_root}" |> String.to_atom()
- defp anon_bucket_name(bucket_name_root), do: "anon:#{bucket_name_root}" |> String.to_atom()
-end
diff --git a/lib/pleroma/plugs/rate_limiter/supervisor.ex b/lib/pleroma/plugs/rate_limiter/supervisor.ex
@@ -1,20 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.RateLimiter.Supervisor do
- use Supervisor
-
- def start_link(opts) do
- Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
- end
-
- def init(_args) do
- children = [
- Pleroma.Plugs.RateLimiter.LimiterSupervisor
- ]
-
- opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor]
- Supervisor.init(children, opts)
- end
-end
diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex
@@ -1,48 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.RemoteIp do
- @moduledoc """
- This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
- """
-
- alias Pleroma.Config
- import Plug.Conn
-
- @behaviour Plug
-
- def init(_), do: nil
-
- def call(%{remote_ip: original_remote_ip} = conn, _) do
- if Config.get([__MODULE__, :enabled]) do
- %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts())
- assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip)
- else
- conn
- end
- end
-
- defp remote_ip_opts do
- headers = Config.get([__MODULE__, :headers], []) |> MapSet.new()
- reserved = Config.get([__MODULE__, :reserved], [])
-
- proxies =
- Config.get([__MODULE__, :proxies], [])
- |> Enum.concat(reserved)
- |> Enum.map(&maybe_add_cidr/1)
-
- {headers, proxies}
- end
-
- defp maybe_add_cidr(proxy) when is_binary(proxy) do
- proxy =
- cond do
- "/" in String.codepoints(proxy) -> proxy
- InetCidr.v4?(InetCidr.parse_address!(proxy)) -> proxy <> "/32"
- InetCidr.v6?(InetCidr.parse_address!(proxy)) -> proxy <> "/128"
- end
-
- InetCidr.parse(proxy, true)
- end
-end
diff --git a/lib/pleroma/plugs/session_authentication_plug.ex b/lib/pleroma/plugs/session_authentication_plug.ex
@@ -1,21 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.SessionAuthenticationPlug do
- import Plug.Conn
-
- def init(options) do
- options
- end
-
- def call(conn, _) do
- with saved_user_id <- get_session(conn, :user_id),
- %{auth_user: %{id: ^saved_user_id}} <- conn.assigns do
- conn
- |> assign(:user, conn.assigns.auth_user)
- else
- _ -> conn
- end
- end
-end
diff --git a/lib/pleroma/plugs/set_format_plug.ex b/lib/pleroma/plugs/set_format_plug.ex
@@ -1,24 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.SetFormatPlug do
- import Plug.Conn, only: [assign: 3, fetch_query_params: 1]
-
- def init(_), do: nil
-
- def call(conn, _) do
- case get_format(conn) do
- nil -> conn
- format -> assign(conn, :format, format)
- end
- end
-
- defp get_format(conn) do
- conn.private[:phoenix_format] ||
- case fetch_query_params(conn) do
- %{query_params: %{"_format" => format}} -> format
- _ -> nil
- end
- end
-end
diff --git a/lib/pleroma/plugs/set_locale_plug.ex b/lib/pleroma/plugs/set_locale_plug.ex
@@ -1,63 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-# NOTE: this module is based on https://github.com/smeevil/set_locale
-defmodule Pleroma.Plugs.SetLocalePlug do
- import Plug.Conn, only: [get_req_header: 2, assign: 3]
-
- def init(_), do: nil
-
- def call(conn, _) do
- locale = get_locale_from_header(conn) || Gettext.get_locale()
- Gettext.put_locale(locale)
- assign(conn, :locale, locale)
- end
-
- defp get_locale_from_header(conn) do
- conn
- |> extract_accept_language()
- |> Enum.find(&supported_locale?/1)
- end
-
- defp extract_accept_language(conn) do
- case get_req_header(conn, "accept-language") do
- [value | _] ->
- value
- |> String.split(",")
- |> Enum.map(&parse_language_option/1)
- |> Enum.sort(&(&1.quality > &2.quality))
- |> Enum.map(& &1.tag)
- |> Enum.reject(&is_nil/1)
- |> ensure_language_fallbacks()
-
- _ ->
- []
- end
- end
-
- defp supported_locale?(locale) do
- Pleroma.Web.Gettext
- |> Gettext.known_locales()
- |> Enum.member?(locale)
- end
-
- defp parse_language_option(string) do
- captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
-
- quality =
- case Float.parse(captures["quality"] || "1.0") do
- {val, _} -> val
- :error -> 1.0
- end
-
- %{tag: captures["tag"], quality: quality}
- end
-
- defp ensure_language_fallbacks(tags) do
- Enum.flat_map(tags, fn tag ->
- [language | _] = String.split(tag, "-")
- if Enum.member?(tags, language), do: [tag], else: [tag, language]
- end)
- end
-end
diff --git a/lib/pleroma/plugs/set_user_session_id_plug.ex b/lib/pleroma/plugs/set_user_session_id_plug.ex
@@ -1,19 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.SetUserSessionIdPlug do
- import Plug.Conn
- alias Pleroma.User
-
- def init(opts) do
- opts
- end
-
- def call(%{assigns: %{user: %User{id: id}}} = conn, _) do
- conn
- |> put_session(:user_id, id)
- end
-
- def call(conn, _), do: conn
-end
diff --git a/lib/pleroma/plugs/static_fe_plug.ex b/lib/pleroma/plugs/static_fe_plug.ex
@@ -1,26 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.StaticFEPlug do
- import Plug.Conn
- alias Pleroma.Web.StaticFE.StaticFEController
-
- def init(options), do: options
-
- def call(conn, _) do
- if enabled?() and requires_html?(conn) do
- conn
- |> StaticFEController.call(:show)
- |> halt()
- else
- conn
- end
- end
-
- defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false)
-
- defp requires_html?(conn) do
- Phoenix.Controller.get_format(conn) == "html"
- end
-end
diff --git a/lib/pleroma/plugs/trailing_format_plug.ex b/lib/pleroma/plugs/trailing_format_plug.ex
@@ -1,42 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.TrailingFormatPlug do
- @moduledoc "Calls TrailingFormatPlug for specific paths. Ideally we would just do this in the router, but TrailingFormatPlug needs to be called before Plug.Parsers."
-
- @behaviour Plug
- @paths [
- "/api/statusnet",
- "/api/statuses",
- "/api/qvitter",
- "/api/search",
- "/api/account",
- "/api/friends",
- "/api/mutes",
- "/api/media",
- "/api/favorites",
- "/api/blocks",
- "/api/friendships",
- "/api/users",
- "/users",
- "/nodeinfo",
- "/api/help",
- "/api/externalprofile",
- "/notice",
- "/api/pleroma/emoji",
- "/api/oauth_tokens"
- ]
-
- def init(opts) do
- TrailingFormatPlug.init(opts)
- end
-
- for path <- @paths do
- def call(%{request_path: unquote(path) <> _} = conn, opts) do
- TrailingFormatPlug.call(conn, opts)
- end
- end
-
- def call(conn, _opts), do: conn
-end
diff --git a/lib/pleroma/plugs/uploaded_media.ex b/lib/pleroma/plugs/uploaded_media.ex
@@ -1,107 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.UploadedMedia do
- @moduledoc """
- """
-
- import Plug.Conn
- import Pleroma.Web.Gettext
- require Logger
-
- alias Pleroma.Web.MediaProxy
-
- @behaviour Plug
- # no slashes
- @path "media"
-
- @default_cache_control_header "public, max-age=1209600"
-
- def init(_opts) do
- static_plug_opts =
- [
- headers: %{"cache-control" => @default_cache_control_header},
- cache_control_for_etags: @default_cache_control_header
- ]
- |> Keyword.put(:from, "__unconfigured_media_plug")
- |> Keyword.put(:at, "/__unconfigured_media_plug")
- |> Plug.Static.init()
-
- %{static_plug_opts: static_plug_opts}
- end
-
- def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
- conn =
- case fetch_query_params(conn) do
- %{query_params: %{"name" => name}} = conn ->
- name = String.replace(name, "\"", "\\\"")
-
- put_resp_header(conn, "content-disposition", "filename=\"#{name}\"")
-
- conn ->
- conn
- end
- |> merge_resp_headers([{"content-security-policy", "sandbox"}])
-
- config = Pleroma.Config.get(Pleroma.Upload)
-
- with uploader <- Keyword.fetch!(config, :uploader),
- proxy_remote = Keyword.get(config, :proxy_remote, false),
- {:ok, get_method} <- uploader.get_file(file),
- false <- media_is_banned(conn, get_method) do
- get_media(conn, get_method, proxy_remote, opts)
- else
- _ ->
- conn
- |> send_resp(:internal_server_error, dgettext("errors", "Failed"))
- |> halt()
- end
- end
-
- def call(conn, _opts), do: conn
-
- defp media_is_banned(%{request_path: path} = _conn, {:static_dir, _}) do
- MediaProxy.in_banned_urls(Pleroma.Web.base_url() <> path)
- end
-
- defp media_is_banned(_, {:url, url}), do: MediaProxy.in_banned_urls(url)
-
- defp media_is_banned(_, _), do: false
-
- defp get_media(conn, {:static_dir, directory}, _, opts) do
- static_opts =
- Map.get(opts, :static_plug_opts)
- |> Map.put(:at, [@path])
- |> Map.put(:from, directory)
-
- conn = Plug.Static.call(conn, static_opts)
-
- if conn.halted do
- conn
- else
- conn
- |> send_resp(:not_found, dgettext("errors", "Not found"))
- |> halt()
- end
- end
-
- defp get_media(conn, {:url, url}, true, _) do
- conn
- |> Pleroma.ReverseProxy.call(url, Pleroma.Config.get([Pleroma.Upload, :proxy_opts], []))
- end
-
- defp get_media(conn, {:url, url}, _, _) do
- conn
- |> Phoenix.Controller.redirect(external: url)
- |> halt()
- end
-
- defp get_media(conn, unknown, _, _) do
- Logger.error("#{__MODULE__}: Unknown get startegy: #{inspect(unknown)}")
-
- conn
- |> send_resp(:internal_server_error, dgettext("errors", "Internal Error"))
- |> halt()
- end
-end
diff --git a/lib/pleroma/plugs/user_enabled_plug.ex b/lib/pleroma/plugs/user_enabled_plug.ex
@@ -1,23 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.UserEnabledPlug do
- import Plug.Conn
- alias Pleroma.User
-
- def init(options) do
- options
- end
-
- def call(%{assigns: %{user: %User{} = user}} = conn, _) do
- case User.account_status(user) do
- :active -> conn
- _ -> assign(conn, :user, nil)
- end
- end
-
- def call(conn, _) do
- conn
- end
-end
diff --git a/lib/pleroma/plugs/user_fetcher_plug.ex b/lib/pleroma/plugs/user_fetcher_plug.ex
@@ -1,21 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.UserFetcherPlug do
- alias Pleroma.User
- import Plug.Conn
-
- def init(options) do
- options
- end
-
- def call(conn, _options) do
- with %{auth_credentials: %{username: username}} <- conn.assigns,
- %User{} = user <- User.get_by_nickname_or_email(username) do
- assign(conn, :auth_user, user)
- else
- _ -> conn
- end
- end
-end
diff --git a/lib/pleroma/plugs/user_is_admin_plug.ex b/lib/pleroma/plugs/user_is_admin_plug.ex
@@ -1,24 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.UserIsAdminPlug do
- import Pleroma.Web.TranslationHelpers
- import Plug.Conn
-
- alias Pleroma.User
-
- def init(options) do
- options
- end
-
- def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do
- conn
- end
-
- def call(conn, _) do
- conn
- |> render_error(:forbidden, "User is not an admin.")
- |> halt()
- end
-end
diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex
diff --git a/lib/pleroma/tests/auth_test_controller.ex b/lib/pleroma/tests/auth_test_controller.ex
@@ -8,9 +8,9 @@ defmodule Pleroma.Tests.AuthTestController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
# Serves only with proper OAuth token (:api and :authenticated_api)
# Skipping EnsurePublicOrAuthenticatedPlug has no effect in this case
diff --git a/lib/pleroma/uploaders/uploader.ex b/lib/pleroma/uploaders/uploader.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Uploaders.Uploader do
@doc """
Instructs how to get the file from the backend.
- Used by `Pleroma.Plugs.UploadedMedia`.
+ Used by `Pleroma.Web.Plugs.UploadedMedia`.
"""
@type get_method :: {:static_dir, directory :: String.t()} | {:url, url :: String.t()}
@callback get_file(file :: String.t()) :: {:ok, get_method()}
diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex
@@ -0,0 +1,234 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web do
+ @moduledoc """
+ A module that keeps using definitions for controllers,
+ views and so on.
+
+ This can be used in your application as:
+
+ use Pleroma.Web, :controller
+ use Pleroma.Web, :view
+
+ The definitions below will be executed for every view,
+ controller, etc, so keep them short and clean, focused
+ on imports, uses and aliases.
+
+ Do NOT define functions inside the quoted expressions
+ below.
+ """
+
+ alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug
+ alias Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.PlugHelper
+
+ def controller do
+ quote do
+ use Phoenix.Controller, namespace: Pleroma.Web
+
+ import Plug.Conn
+
+ import Pleroma.Web.Gettext
+ import Pleroma.Web.Router.Helpers
+ import Pleroma.Web.TranslationHelpers
+
+ plug(:set_put_layout)
+
+ defp set_put_layout(conn, _) do
+ put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
+ end
+
+ # Marks plugs intentionally skipped and blocks their execution if present in plugs chain
+ defp skip_plug(conn, plug_modules) do
+ plug_modules
+ |> List.wrap()
+ |> Enum.reduce(
+ conn,
+ fn plug_module, conn ->
+ try do
+ plug_module.skip_plug(conn)
+ rescue
+ UndefinedFunctionError ->
+ raise "`#{plug_module}` is not skippable. Append `use Pleroma.Web, :plug` to its code."
+ end
+ end
+ )
+ end
+
+ # Executed just before actual controller action, invokes before-action hooks (callbacks)
+ defp action(conn, params) do
+ with %{halted: false} = conn <- maybe_drop_authentication_if_oauth_check_ignored(conn),
+ %{halted: false} = conn <- maybe_perform_public_or_authenticated_check(conn),
+ %{halted: false} = conn <- maybe_perform_authenticated_check(conn),
+ %{halted: false} = conn <- maybe_halt_on_missing_oauth_scopes_check(conn) do
+ super(conn, params)
+ end
+ end
+
+ # For non-authenticated API actions, drops auth info if OAuth scopes check was ignored
+ # (neither performed nor explicitly skipped)
+ defp maybe_drop_authentication_if_oauth_check_ignored(conn) do
+ if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and
+ not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
+ OAuthScopesPlug.drop_auth_info(conn)
+ else
+ conn
+ end
+ end
+
+ # Ensures instance is public -or- user is authenticated if such check was scheduled
+ defp maybe_perform_public_or_authenticated_check(conn) do
+ if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) do
+ EnsurePublicOrAuthenticatedPlug.call(conn, %{})
+ else
+ conn
+ end
+ end
+
+ # Ensures user is authenticated if such check was scheduled
+ # Note: runs prior to action even if it was already executed earlier in plug chain
+ # (since OAuthScopesPlug has option of proceeding unauthenticated)
+ defp maybe_perform_authenticated_check(conn) do
+ if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) do
+ EnsureAuthenticatedPlug.call(conn, %{})
+ else
+ conn
+ end
+ end
+
+ # Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check
+ defp maybe_halt_on_missing_oauth_scopes_check(conn) do
+ if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) and
+ not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
+ conn
+ |> render_error(
+ :forbidden,
+ "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
+ )
+ |> halt()
+ else
+ conn
+ end
+ end
+ end
+ end
+
+ def view do
+ quote do
+ use Phoenix.View,
+ root: "lib/pleroma/web/templates",
+ namespace: Pleroma.Web
+
+ # Import convenience functions from controllers
+ import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
+
+ import Pleroma.Web.ErrorHelpers
+ import Pleroma.Web.Gettext
+ import Pleroma.Web.Router.Helpers
+
+ require Logger
+
+ @doc "Same as `render/3` but wrapped in a rescue block"
+ def safe_render(view, template, assigns \\ %{}) do
+ Phoenix.View.render(view, template, assigns)
+ rescue
+ error ->
+ Logger.error(
+ "#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
+ Exception.format(:error, error, __STACKTRACE__)
+ )
+
+ nil
+ end
+
+ @doc """
+ Same as `render_many/4` but wrapped in rescue block.
+ """
+ def safe_render_many(collection, view, template, assigns \\ %{}) do
+ Enum.map(collection, fn resource ->
+ as = Map.get(assigns, :as) || view.__resource__
+ assigns = Map.put(assigns, as, resource)
+ safe_render(view, template, assigns)
+ end)
+ |> Enum.filter(& &1)
+ end
+ end
+ end
+
+ def router do
+ quote do
+ use Phoenix.Router
+ # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
+ import Plug.Conn
+ import Phoenix.Controller
+ end
+ end
+
+ def channel do
+ quote do
+ # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
+ use Phoenix.Channel
+ import Pleroma.Web.Gettext
+ end
+ end
+
+ def plug do
+ quote do
+ @behaviour Pleroma.Web.Plug
+ @behaviour Plug
+
+ @doc """
+ Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain.
+ """
+ def skip_plug(conn) do
+ PlugHelper.append_to_private_list(
+ conn,
+ PlugHelper.skipped_plugs_list_id(),
+ __MODULE__
+ )
+ end
+
+ @impl Plug
+ @doc """
+ Before-plug hook that
+ * ensures the plug is not skipped
+ * processes `:if_func` / `:unless_func` functional pre-run conditions
+ * adds plug to the list of called plugs and calls `perform/2` if checks are passed
+
+ Note: multiple invocations of the same plug (with different or same options) are allowed.
+ """
+ def call(%Plug.Conn{} = conn, options) do
+ if PlugHelper.plug_skipped?(conn, __MODULE__) ||
+ (options[:if_func] && !options[:if_func].(conn)) ||
+ (options[:unless_func] && options[:unless_func].(conn)) do
+ conn
+ else
+ conn =
+ PlugHelper.append_to_private_list(
+ conn,
+ PlugHelper.called_plugs_list_id(),
+ __MODULE__
+ )
+
+ apply(__MODULE__, :perform, [conn, options])
+ end
+ end
+ end
+ end
+
+ @doc """
+ When used, dispatch to the appropriate controller/view/etc.
+ """
+ defmacro __using__(which) when is_atom(which) do
+ apply(__MODULE__, which, [])
+ end
+
+ def base_url do
+ Pleroma.Web.Endpoint.url()
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -9,7 +9,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Delivery
alias Pleroma.Object
alias Pleroma.Object.Fetcher
- alias Pleroma.Plugs.EnsureAuthenticatedPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
@@ -23,8 +22,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ControllerHelper
alias Pleroma.Web.Endpoint
- alias Pleroma.Web.FederatingPlug
alias Pleroma.Web.Federator
+ alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
+ alias Pleroma.Web.Plugs.FederatingPlug
require Logger
@@ -46,7 +46,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
)
plug(
- Pleroma.Plugs.Cache,
+ Pleroma.Web.Plugs.Cache,
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
when action in [:activity, :object]
)
diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex
@@ -10,7 +10,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Config
alias Pleroma.MFA
alias Pleroma.ModerationLog
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -21,6 +20,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Web.AdminAPI.ModerationLogView
alias Pleroma.Web.AdminAPI.Search
alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Router
@users_page_size 50
diff --git a/lib/pleroma/web/admin_api/controllers/chat_controller.ex b/lib/pleroma/web/admin_api/controllers/chat_controller.ex
@@ -10,10 +10,10 @@ defmodule Pleroma.Web.AdminAPI.ChatController do
alias Pleroma.Chat.MessageReference
alias Pleroma.ModerationLog
alias Pleroma.Pagination
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
alias Pleroma.Config
alias Pleroma.ConfigDB
- alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["write"], admin: true} when action == :update)
diff --git a/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex b/lib/pleroma/web/admin_api/controllers/instance_document_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.AdminAPI.InstanceDocumentController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.InstanceStatic
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.InstanceDocument
+ alias Pleroma.Web.Plugs.InstanceStatic
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/admin_api/controllers/invite_controller.ex b/lib/pleroma/web/admin_api/controllers/invite_controller.ex
@@ -8,8 +8,8 @@ defmodule Pleroma.Web.AdminAPI.InviteController do
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
alias Pleroma.Config
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.UserInviteToken
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
diff --git a/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex b/lib/pleroma/web/admin_api/controllers/media_proxy_cache_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.AdminAPI.MediaProxyCacheController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ApiSpec.Admin, as: Spec
alias Pleroma.Web.MediaProxy
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/o_auth_app_controller.ex
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.OAuthAppController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [json_response: 3]
+
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ require Logger
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(:put_view, Pleroma.Web.MastodonAPI.AppView)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write"], admin: true}
+ when action in [:create, :index, :update, :delete]
+ )
+
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.OAuthAppOperation
+
+ def index(conn, params) do
+ search_params =
+ params
+ |> Map.take([:client_id, :page, :page_size, :trusted])
+ |> Map.put(:client_name, params[:name])
+
+ with {:ok, apps, count} <- App.search(search_params) do
+ render(conn, "index.json",
+ apps: apps,
+ count: count,
+ page_size: params.page_size,
+ admin: true
+ )
+ end
+ end
+
+ def create(%{body_params: params} = conn, _) do
+ params = Pleroma.Maps.put_if_present(params, :client_name, params[:name])
+
+ case App.create(params) do
+ {:ok, app} ->
+ render(conn, "show.json", app: app, admin: true)
+
+ {:error, changeset} ->
+ json(conn, App.errors(changeset))
+ end
+ end
+
+ def update(%{body_params: params} = conn, %{id: id}) do
+ params = Pleroma.Maps.put_if_present(params, :client_name, params[:name])
+
+ with {:ok, app} <- App.update(id, params) do
+ render(conn, "show.json", app: app, admin: true)
+ else
+ {:error, changeset} ->
+ json(conn, App.errors(changeset))
+
+ nil ->
+ json_response(conn, :bad_request, "")
+ end
+ end
+
+ def delete(conn, params) do
+ with {:ok, _app} <- App.destroy(params.id) do
+ json_response(conn, :no_content, "")
+ else
+ _ -> json_response(conn, :bad_request, "")
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex b/lib/pleroma/web/admin_api/controllers/oauth_app_controller.ex
@@ -1,77 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.AdminAPI.OAuthAppController do
- use Pleroma.Web, :controller
-
- import Pleroma.Web.ControllerHelper, only: [json_response: 3]
-
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Web.OAuth.App
-
- require Logger
-
- plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(:put_view, Pleroma.Web.MastodonAPI.AppView)
-
- plug(
- OAuthScopesPlug,
- %{scopes: ["write"], admin: true}
- when action in [:create, :index, :update, :delete]
- )
-
- action_fallback(Pleroma.Web.AdminAPI.FallbackController)
-
- defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.OAuthAppOperation
-
- def index(conn, params) do
- search_params =
- params
- |> Map.take([:client_id, :page, :page_size, :trusted])
- |> Map.put(:client_name, params[:name])
-
- with {:ok, apps, count} <- App.search(search_params) do
- render(conn, "index.json",
- apps: apps,
- count: count,
- page_size: params.page_size,
- admin: true
- )
- end
- end
-
- def create(%{body_params: params} = conn, _) do
- params = Pleroma.Maps.put_if_present(params, :client_name, params[:name])
-
- case App.create(params) do
- {:ok, app} ->
- render(conn, "show.json", app: app, admin: true)
-
- {:error, changeset} ->
- json(conn, App.errors(changeset))
- end
- end
-
- def update(%{body_params: params} = conn, %{id: id}) do
- params = Pleroma.Maps.put_if_present(params, :client_name, params[:name])
-
- with {:ok, app} <- App.update(id, params) do
- render(conn, "show.json", app: app, admin: true)
- else
- {:error, changeset} ->
- json(conn, App.errors(changeset))
-
- nil ->
- json_response(conn, :bad_request, "")
- end
- end
-
- def delete(conn, params) do
- with {:ok, _app} <- App.destroy(params.id) do
- json_response(conn, :no_content, "")
- else
- _ -> json_response(conn, :bad_request, "")
- end
- end
-end
diff --git a/lib/pleroma/web/admin_api/controllers/relay_controller.ex b/lib/pleroma/web/admin_api/controllers/relay_controller.ex
@@ -6,8 +6,8 @@ defmodule Pleroma.Web.AdminAPI.RelayController do
use Pleroma.Web, :controller
alias Pleroma.ModerationLog
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.Relay
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
diff --git a/lib/pleroma/web/admin_api/controllers/report_controller.ex b/lib/pleroma/web/admin_api/controllers/report_controller.ex
@@ -9,12 +9,12 @@ defmodule Pleroma.Web.AdminAPI.ReportController do
alias Pleroma.Activity
alias Pleroma.ModerationLog
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.ReportNote
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.Report
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
diff --git a/lib/pleroma/web/admin_api/controllers/status_controller.ex b/lib/pleroma/web/admin_api/controllers/status_controller.ex
@@ -7,10 +7,10 @@ defmodule Pleroma.Web.AdminAPI.StatusController do
alias Pleroma.Activity
alias Pleroma.ModerationLog
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
require Logger
diff --git a/lib/pleroma/web/api_spec/operations/admin/oauth_app_operation.ex b/lib/pleroma/web/api_spec/operations/admin/o_auth_app_operation.ex
diff --git a/lib/pleroma/web/auth/pleroma_authenticator.ex b/lib/pleroma/web/auth/pleroma_authenticator.ex
@@ -3,10 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Auth.PleromaAuthenticator do
- alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Registration
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Web.Plugs.AuthenticationPlug
import Pleroma.Web.Auth.Authenticator,
only: [fetch_credentials: 1, fetch_user: 1]
diff --git a/lib/pleroma/web/auth/totp_authenticator.ex b/lib/pleroma/web/auth/totp_authenticator.ex
@@ -5,8 +5,8 @@
defmodule Pleroma.Web.Auth.TOTPAuthenticator do
alias Pleroma.MFA
alias Pleroma.MFA.TOTP
- alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.User
+ alias Pleroma.Web.Plugs.AuthenticationPlug
@doc "Verify code or check backup code."
@spec verify(String.t(), User.t()) ::
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api.ex
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
@@ -12,12 +12,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.Conversation.Participation
alias Pleroma.Formatter
alias Pleroma.Object
- alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.MediaProxy
+ alias Pleroma.Web.Plugs.AuthenticationPlug
require Logger
require Pleroma.Constants
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
@@ -9,17 +9,17 @@ defmodule Pleroma.Web.Endpoint do
socket("/socket", Pleroma.Web.UserSocket)
- plug(Pleroma.Plugs.SetLocalePlug)
+ plug(Pleroma.Web.Plugs.SetLocalePlug)
plug(CORSPlug)
- plug(Pleroma.Plugs.HTTPSecurityPlug)
- plug(Pleroma.Plugs.UploadedMedia)
+ plug(Pleroma.Web.Plugs.HTTPSecurityPlug)
+ plug(Pleroma.Web.Plugs.UploadedMedia)
@static_cache_control "public, no-cache"
# InstanceStatic needs to be before Plug.Static to be able to override shipped-static files
# If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well
# Cache-control headers are duplicated in case we turn off etags in the future
- plug(Pleroma.Plugs.InstanceStatic,
+ plug(Pleroma.Web.Plugs.InstanceStatic,
at: "/",
gzip: true,
cache_control_for_etags: @static_cache_control,
@@ -29,7 +29,7 @@ defmodule Pleroma.Web.Endpoint do
)
# Careful! No `only` restriction here, as we don't know what frontends contain.
- plug(Pleroma.Plugs.FrontendStatic,
+ plug(Pleroma.Web.Plugs.FrontendStatic,
at: "/",
frontend_type: :primary,
gzip: true,
@@ -41,7 +41,7 @@ defmodule Pleroma.Web.Endpoint do
plug(Plug.Static.IndexHtml, at: "/pleroma/admin/")
- plug(Pleroma.Plugs.FrontendStatic,
+ plug(Pleroma.Web.Plugs.FrontendStatic,
at: "/pleroma/admin",
frontend_type: :admin,
gzip: true,
@@ -79,7 +79,7 @@ defmodule Pleroma.Web.Endpoint do
plug(Phoenix.CodeReloader)
end
- plug(Pleroma.Plugs.TrailingFormatPlug)
+ plug(Pleroma.Web.Plugs.TrailingFormatPlug)
plug(Plug.RequestId)
plug(Plug.Logger, log: :debug)
@@ -122,7 +122,7 @@ defmodule Pleroma.Web.Endpoint do
extra: extra
)
- plug(Pleroma.Plugs.RemoteIp)
+ plug(Pleroma.Web.Plugs.RemoteIp)
defmodule Instrumenter do
use Prometheus.PhoenixInstrumenter
diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex
@@ -0,0 +1,108 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Fallback.RedirectController do
+ use Pleroma.Web, :controller
+
+ require Logger
+
+ alias Pleroma.User
+ alias Pleroma.Web.Metadata
+ alias Pleroma.Web.Preload
+
+ def api_not_implemented(conn, _params) do
+ conn
+ |> put_status(404)
+ |> json(%{error: "Not implemented"})
+ end
+
+ def redirector(conn, _params, code \\ 200) do
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_file(code, index_file_path())
+ end
+
+ def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
+ redirector_with_meta(conn, %{user: user})
+ else
+ nil ->
+ redirector(conn, params)
+ end
+ end
+
+ def redirector_with_meta(conn, params) do
+ {:ok, index_content} = File.read(index_file_path())
+
+ tags = build_tags(conn, params)
+ preloads = preload_data(conn, params)
+
+ response =
+ index_content
+ |> String.replace("<!--server-generated-meta-->", tags <> preloads)
+
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_resp(200, response)
+ end
+
+ def redirector_with_preload(conn, %{"path" => ["pleroma", "admin"]}) do
+ redirect(conn, to: "/pleroma/admin/")
+ end
+
+ def redirector_with_preload(conn, params) do
+ {:ok, index_content} = File.read(index_file_path())
+ preloads = preload_data(conn, params)
+
+ response =
+ index_content
+ |> String.replace("<!--server-generated-meta-->", preloads)
+
+ conn
+ |> put_resp_content_type("text/html")
+ |> send_resp(200, response)
+ end
+
+ def registration_page(conn, params) do
+ redirector(conn, params)
+ end
+
+ def empty(conn, _params) do
+ conn
+ |> put_status(204)
+ |> text("")
+ end
+
+ defp index_file_path do
+ Pleroma.Web.Plugs.InstanceStatic.file_path("index.html")
+ end
+
+ defp build_tags(conn, params) do
+ try do
+ Metadata.build_tags(params)
+ rescue
+ e ->
+ Logger.error(
+ "Metadata rendering for #{conn.request_path} failed.\n" <>
+ Exception.format(:error, e, __STACKTRACE__)
+ )
+
+ ""
+ end
+ end
+
+ defp preload_data(conn, params) do
+ try do
+ Preload.build_tags(conn, params)
+ rescue
+ e ->
+ Logger.error(
+ "Preloading for #{conn.request_path} failed.\n" <>
+ Exception.format(:error, e, __STACKTRACE__)
+ )
+
+ ""
+ end
+ end
+end
diff --git a/lib/pleroma/web/fallback_redirect_controller.ex b/lib/pleroma/web/fallback_redirect_controller.ex
@@ -1,108 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Fallback.RedirectController do
- use Pleroma.Web, :controller
-
- require Logger
-
- alias Pleroma.User
- alias Pleroma.Web.Metadata
- alias Pleroma.Web.Preload
-
- def api_not_implemented(conn, _params) do
- conn
- |> put_status(404)
- |> json(%{error: "Not implemented"})
- end
-
- def redirector(conn, _params, code \\ 200) do
- conn
- |> put_resp_content_type("text/html")
- |> send_file(code, index_file_path())
- end
-
- def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
- redirector_with_meta(conn, %{user: user})
- else
- nil ->
- redirector(conn, params)
- end
- end
-
- def redirector_with_meta(conn, params) do
- {:ok, index_content} = File.read(index_file_path())
-
- tags = build_tags(conn, params)
- preloads = preload_data(conn, params)
-
- response =
- index_content
- |> String.replace("<!--server-generated-meta-->", tags <> preloads)
-
- conn
- |> put_resp_content_type("text/html")
- |> send_resp(200, response)
- end
-
- def redirector_with_preload(conn, %{"path" => ["pleroma", "admin"]}) do
- redirect(conn, to: "/pleroma/admin/")
- end
-
- def redirector_with_preload(conn, params) do
- {:ok, index_content} = File.read(index_file_path())
- preloads = preload_data(conn, params)
-
- response =
- index_content
- |> String.replace("<!--server-generated-meta-->", preloads)
-
- conn
- |> put_resp_content_type("text/html")
- |> send_resp(200, response)
- end
-
- def registration_page(conn, params) do
- redirector(conn, params)
- end
-
- def empty(conn, _params) do
- conn
- |> put_status(204)
- |> text("")
- end
-
- defp index_file_path do
- Pleroma.Plugs.InstanceStatic.file_path("index.html")
- end
-
- defp build_tags(conn, params) do
- try do
- Metadata.build_tags(params)
- rescue
- e ->
- Logger.error(
- "Metadata rendering for #{conn.request_path} failed.\n" <>
- Exception.format(:error, e, __STACKTRACE__)
- )
-
- ""
- end
- end
-
- defp preload_data(conn, params) do
- try do
- Preload.build_tags(conn, params)
- rescue
- e ->
- Logger.error(
- "Preloading for #{conn.request_path} failed.\n" <>
- Exception.format(:error, e, __STACKTRACE__)
- )
-
- ""
- end
- end
-end
diff --git a/lib/pleroma/web/fed_sockets/fed_sockets.ex b/lib/pleroma/web/fed_sockets.ex
diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator.ex
diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex
@@ -5,27 +5,26 @@
defmodule Pleroma.Web.Feed.UserController do
use Pleroma.Web, :controller
- alias Fallback.RedirectController
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.Feed.FeedView
- plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect])
+ plug(Pleroma.Web.Plugs.SetFormatPlug when action in [:feed_redirect])
action_fallback(:errors)
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
- RedirectController.redirector_with_meta(conn, %{user: user})
+ Pleroma.Web.Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
end
end
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do
with %{halted: false} = conn <-
- Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn,
- unless_func: &Pleroma.Web.FederatingPlug.federating?/1
+ Pleroma.Web.Plugs.EnsureAuthenticatedPlug.call(conn,
+ unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
) do
ActivityPubController.call(conn, :user)
end
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.MastoFEController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -15,9 +15,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
]
alias Pleroma.Maps
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
@@ -29,6 +26,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.OAuth.OAuthController
alias Pleroma.Web.OAuth.OAuthView
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
alias Pleroma.Web.TwitterAPI.TwitterAPI
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
@@ -5,12 +5,12 @@
defmodule Pleroma.Web.MastodonAPI.AppController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo
alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Scopes
alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
@@ -15,7 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
- plug(Pleroma.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)
+ plug(Pleroma.Web.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)
@local_mastodon_name "Mastodon-Local"
diff --git a/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex b/lib/pleroma/web/mastodon_api/controllers/conversation_controller.ex
@@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Conversation.Participation
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do
plug(
:skip_plug,
- [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
+ [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
when action == :index
)
diff --git a/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex b/lib/pleroma/web/mastodon_api/controllers/domain_block_controller.ex
@@ -5,8 +5,8 @@
defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.DomainBlockOperation
diff --git a/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex b/lib/pleroma/web/mastodon_api/controllers/filter_controller.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
use Pleroma.Web, :controller
alias Pleroma.Filter
- alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
@oauth_read_actions [:show, :index]
diff --git a/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex b/lib/pleroma/web/mastodon_api/controllers/follow_request_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
@@ -9,7 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do
plug(
:skip_plug,
- [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
+ [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
when action in [:show, :peers]
)
diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.MastodonAPI.ListController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
@oauth_read_actions [:index, :show, :list_accounts]
diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex
@@ -4,7 +4,7 @@
defmodule Pleroma.Web.MastodonAPI.MarkerController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
plug(
:skip_plug,
- [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
+ [Pleroma.Web.Plugs.OAuthScopesPlug, Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug]
when action in [:empty_array, :empty_object]
)
diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
@@ -6,9 +6,9 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
use Pleroma.Web, :controller
alias Pleroma.Object
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
@@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Notification
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.MastodonAPI.MastodonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
@oauth_read_actions [:show, :index]
diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
@@ -9,9 +9,9 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
alias Pleroma.Activity
alias Pleroma.Object
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex
@@ -3,14 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ReportController do
- alias Pleroma.Plugs.OAuthScopesPlug
-
use Pleroma.Web, :controller
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
+ plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation
diff --git a/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex b/lib/pleroma/web/mastodon_api/controllers/scheduled_activity_controller.ex
@@ -7,9 +7,9 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.ScheduledActivity
alias Pleroma.Web.MastodonAPI.MastodonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
@oauth_read_actions [:show, :index]
diff --git a/lib/pleroma/web/mastodon_api/controllers/search_controller.ex b/lib/pleroma/web/mastodon_api/controllers/search_controller.ex
@@ -6,14 +6,14 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller
alias Pleroma.Activity
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.ControllerHelper
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
require Logger
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -13,8 +13,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Object
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
alias Pleroma.User
@@ -23,9 +21,15 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show])
+
+ plug(
+ :skip_plug,
+ Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show]
+ )
@unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
diff --git a/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex b/lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex
@@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:restrict_push_enabled)
- plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
+ plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.SubscriptionOperation
diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
@@ -8,7 +8,7 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do
require Logger
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
+ plug(Pleroma.Web.Plugs.OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
@@ -10,11 +10,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
alias Pleroma.Config
alias Pleroma.Pagination
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag])
diff --git a/lib/pleroma/web/media_proxy/media_proxy.ex b/lib/pleroma/web/media_proxy.ex
diff --git a/lib/pleroma/web/media_proxy/invalidations/http.ex b/lib/pleroma/web/media_proxy/invalidation/http.ex
diff --git a/lib/pleroma/web/media_proxy/invalidations/script.ex b/lib/pleroma/web/media_proxy/invalidation/script.ex
diff --git a/lib/pleroma/web/metadata/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex
diff --git a/lib/pleroma/web/metadata/opengraph.ex b/lib/pleroma/web/metadata/providers/open_graph.ex
diff --git a/lib/pleroma/web/metadata/provider.ex b/lib/pleroma/web/metadata/providers/provider.ex
diff --git a/lib/pleroma/web/metadata/rel_me.ex b/lib/pleroma/web/metadata/providers/rel_me.ex
diff --git a/lib/pleroma/web/metadata/restrict_indexing.ex b/lib/pleroma/web/metadata/providers/restrict_indexing.ex
diff --git a/lib/pleroma/web/metadata/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex
diff --git a/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex b/lib/pleroma/web/mongoose_im/mongoose_im_controller.ex
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MongooseIM.MongooseIMController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.AuthenticationPlug
+ alias Pleroma.Web.Plugs.RateLimiter
+
+ plug(RateLimiter, [name: :authentication] when action in [:user_exists, :check_password])
+ plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password)
+
+ def user_exists(conn, %{"user" => username}) do
+ with %User{} <- Repo.get_by(User, nickname: username, local: true, deactivated: false) do
+ conn
+ |> json(true)
+ else
+ _ ->
+ conn
+ |> put_status(:not_found)
+ |> json(false)
+ end
+ end
+
+ def check_password(conn, %{"user" => username, "pass" => password}) do
+ with %User{password_hash: password_hash, deactivated: false} <-
+ Repo.get_by(User, nickname: username, local: true),
+ true <- AuthenticationPlug.checkpw(password, password_hash) do
+ conn
+ |> json(true)
+ else
+ false ->
+ conn
+ |> put_status(:forbidden)
+ |> json(false)
+
+ _ ->
+ conn
+ |> put_status(:not_found)
+ |> json(false)
+ end
+ end
+end
diff --git a/lib/pleroma/web/mongooseim/mongoose_im_controller.ex b/lib/pleroma/web/mongooseim/mongoose_im_controller.ex
@@ -1,46 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.MongooseIM.MongooseIMController do
- use Pleroma.Web, :controller
-
- alias Pleroma.Plugs.AuthenticationPlug
- alias Pleroma.Plugs.RateLimiter
- alias Pleroma.Repo
- alias Pleroma.User
-
- plug(RateLimiter, [name: :authentication] when action in [:user_exists, :check_password])
- plug(RateLimiter, [name: :authentication, params: ["user"]] when action == :check_password)
-
- def user_exists(conn, %{"user" => username}) do
- with %User{} <- Repo.get_by(User, nickname: username, local: true, deactivated: false) do
- conn
- |> json(true)
- else
- _ ->
- conn
- |> put_status(:not_found)
- |> json(false)
- end
- end
-
- def check_password(conn, %{"user" => username, "pass" => password}) do
- with %User{password_hash: password_hash, deactivated: false} <-
- Repo.get_by(User, nickname: username, local: true),
- true <- AuthenticationPlug.checkpw(password, password_hash) do
- conn
- |> json(true)
- else
- false ->
- conn
- |> put_status(:forbidden)
- |> json(false)
-
- _ ->
- conn
- |> put_status(:not_found)
- |> json(false)
- end
- end
-end
diff --git a/lib/pleroma/web/oauth.ex b/lib/pleroma/web/o_auth.ex
diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/o_auth/app.ex
diff --git a/lib/pleroma/web/oauth/authorization.ex b/lib/pleroma/web/o_auth/authorization.ex
diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/o_auth/fallback_controller.ex
diff --git a/lib/pleroma/web/oauth/mfa_controller.ex b/lib/pleroma/web/o_auth/mfa_controller.ex
diff --git a/lib/pleroma/web/oauth/mfa_view.ex b/lib/pleroma/web/o_auth/mfa_view.ex
diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex
@@ -0,0 +1,613 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.OAuth.OAuthController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Helpers.UriHelper
+ alias Pleroma.Maps
+ alias Pleroma.MFA
+ alias Pleroma.Registration
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.Auth.Authenticator
+ alias Pleroma.Web.ControllerHelper
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Authorization
+ alias Pleroma.Web.OAuth.MFAController
+ alias Pleroma.Web.OAuth.MFAView
+ alias Pleroma.Web.OAuth.OAuthView
+ alias Pleroma.Web.OAuth.Scopes
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
+ alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
+ alias Pleroma.Web.Plugs.RateLimiter
+
+ require Logger
+
+ if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
+
+ plug(:fetch_session)
+ plug(:fetch_flash)
+
+ plug(:skip_plug, [
+ Pleroma.Web.Plugs.OAuthScopesPlug,
+ Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ ])
+
+ plug(RateLimiter, [name: :authentication] when action == :create_authorization)
+
+ 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(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
+ {auth_attrs, params} = Map.pop(params, "authorization")
+ authorize(conn, Map.merge(params, auth_attrs))
+ end
+
+ def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do
+ if ControllerHelper.truthy_param?(params["force_login"]) do
+ do_authorize(conn, params)
+ else
+ handle_existing_authorization(conn, params)
+ end
+ end
+
+ # Note: the token is set in oauth_plug, but the token and client do not always go together.
+ # For example, MastodonFE's token is set if user requests with another client,
+ # after user already authorized to MastodonFE.
+ # So we have to check client and token.
+ def authorize(
+ %Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
+ %{"client_id" => client_id} = params
+ ) do
+ with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app),
+ ^client_id <- t.app.client_id do
+ handle_existing_authorization(conn, params)
+ else
+ _ -> do_authorize(conn, params)
+ end
+ end
+
+ def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params)
+
+ 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)
+
+ scopes =
+ if scopes == [] do
+ available_scopes
+ else
+ scopes
+ end
+
+ # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template
+ render(conn, Authenticator.auth_template(), %{
+ response_type: params["response_type"],
+ client_id: params["client_id"],
+ available_scopes: available_scopes,
+ scopes: scopes,
+ redirect_uri: params["redirect_uri"],
+ state: params["state"],
+ params: params
+ })
+ 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 = Maps.put_if_present(url_params, :state, params["state"])
+ url = UriHelper.modify_uri_params(redirect_uri, url_params)
+ redirect(conn, external: url)
+ else
+ conn
+ |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
+ |> redirect(external: redirect_uri(conn, redirect_uri))
+ end
+ end
+
+ def create_authorization(
+ %Plug.Conn{} = conn,
+ %{"authorization" => _} = params,
+ opts \\ []
+ ) do
+ with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
+ {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
+ after_create_authorization(conn, auth, params)
+ else
+ error ->
+ handle_create_authorization_error(conn, error, params)
+ end
+ end
+
+ def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
+ "authorization" => %{"redirect_uri" => @oob_token_redirect_uri}
+ }) do
+ # Enforcing the view to reuse the template when calling from other controllers
+ conn
+ |> put_view(OAuthView)
+ |> render("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 = Maps.put_if_present(url_params, :state, auth_attrs["state"])
+ url = UriHelper.modify_uri_params(redirect_uri, url_params)
+ redirect(conn, external: url)
+ else
+ conn
+ |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
+ |> redirect(external: redirect_uri(conn, redirect_uri))
+ end
+ end
+
+ defp handle_create_authorization_error(
+ %Plug.Conn{} = conn,
+ {:error, scopes_issue},
+ %{"authorization" => _} = params
+ )
+ when scopes_issue in [:unsupported_scopes, :missing_scopes] do
+ # Per https://github.com/tootsuite/mastodon/blob/
+ # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
+ conn
+ |> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes"))
+ |> put_status(:unauthorized)
+ |> authorize(params)
+ end
+
+ defp handle_create_authorization_error(
+ %Plug.Conn{} = conn,
+ {:account_status, :confirmation_pending},
+ %{"authorization" => _} = params
+ ) do
+ conn
+ |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
+ |> put_status(:forbidden)
+ |> authorize(params)
+ end
+
+ defp handle_create_authorization_error(
+ %Plug.Conn{} = conn,
+ {:mfa_required, user, auth, _},
+ params
+ ) do
+ {:ok, token} = MFA.Token.create(user, auth)
+
+ data = %{
+ "mfa_token" => token.token,
+ "redirect_uri" => params["authorization"]["redirect_uri"],
+ "state" => params["authorization"]["state"]
+ }
+
+ MFAController.show(conn, data)
+ end
+
+ defp handle_create_authorization_error(
+ %Plug.Conn{} = conn,
+ {:account_status, :password_reset_pending},
+ %{"authorization" => _} = params
+ ) do
+ conn
+ |> put_flash(:error, dgettext("errors", "Password reset is required"))
+ |> put_status(:forbidden)
+ |> authorize(params)
+ end
+
+ defp handle_create_authorization_error(
+ %Plug.Conn{} = conn,
+ {:account_status, :deactivated},
+ %{"authorization" => _} = params
+ ) do
+ conn
+ |> put_flash(:error, dgettext("errors", "Your account is currently disabled"))
+ |> put_status(:forbidden)
+ |> authorize(params)
+ end
+
+ 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(
+ %Plug.Conn{} = conn,
+ %{"grant_type" => "refresh_token", "refresh_token" => token} = _params
+ ) do
+ with {:ok, app} <- Token.Utils.fetch_app(conn),
+ {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
+ {:ok, token} <- RefreshToken.grant(token) do
+ json(conn, OAuthView.render("token.json", %{user: user, token: token}))
+ else
+ _error -> render_invalid_credentials_error(conn)
+ end
+ end
+
+ 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),
+ %User{} = user <- User.get_cached_by_id(auth.user_id),
+ {:ok, token} <- Token.exchange_token(app, auth) do
+ json(conn, OAuthView.render("token.json", %{user: user, token: token}))
+ else
+ error ->
+ handle_token_exchange_error(conn, error)
+ end
+ end
+
+ def token_exchange(
+ %Plug.Conn{} = conn,
+ %{"grant_type" => "password"} = params
+ ) do
+ with {:ok, %User{} = user} <- Authenticator.get_user(conn),
+ {:ok, app} <- Token.Utils.fetch_app(conn),
+ requested_scopes <- Scopes.fetch_scopes(params, app.scopes),
+ {:ok, token} <- login(user, app, requested_scopes) do
+ json(conn, OAuthView.render("token.json", %{user: user, token: token}))
+ else
+ error ->
+ handle_token_exchange_error(conn, error)
+ end
+ end
+
+ def token_exchange(
+ %Plug.Conn{} = conn,
+ %{"grant_type" => "password", "name" => name, "password" => _password} = params
+ ) do
+ params =
+ params
+ |> Map.delete("name")
+ |> Map.put("username", name)
+
+ token_exchange(conn, params)
+ end
+
+ 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
+ json(conn, OAuthView.render("token.json", %{token: token}))
+ else
+ _error ->
+ handle_token_exchange_error(conn, :invalid_credentails)
+ end
+ end
+
+ # Bad request
+ def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
+
+ defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do
+ conn
+ |> put_status(:forbidden)
+ |> json(build_and_response_mfa_token(user, auth))
+ end
+
+ defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do
+ render_error(
+ conn,
+ :forbidden,
+ "Your account is currently disabled",
+ %{},
+ "account_is_disabled"
+ )
+ end
+
+ defp handle_token_exchange_error(
+ %Plug.Conn{} = conn,
+ {:account_status, :password_reset_pending}
+ ) do
+ render_error(
+ conn,
+ :forbidden,
+ "Password reset is required",
+ %{},
+ "password_reset_required"
+ )
+ end
+
+ defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do
+ render_error(
+ conn,
+ :forbidden,
+ "Your login is missing a confirmed e-mail address",
+ %{},
+ "missing_confirmed_email"
+ )
+ end
+
+ defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do
+ render_error(
+ conn,
+ :forbidden,
+ "Your account is awaiting approval.",
+ %{},
+ "awaiting_approval"
+ )
+ end
+
+ defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
+ render_invalid_credentials_error(conn)
+ end
+
+ 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, %{})
+ else
+ _error ->
+ # RFC 7009: invalid tokens [in the request] do not cause an error response
+ json(conn, %{})
+ end
+ end
+
+ def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
+
+ # Response for bad request
+ defp bad_request(%Plug.Conn{} = conn, _) do
+ render_error(conn, :internal_server_error, "Bad request")
+ end
+
+ @doc "Prepares OAuth request to provider for Ueberauth"
+ def prepare_request(%Plug.Conn{} = conn, %{
+ "provider" => provider,
+ "authorization" => auth_attrs
+ }) do
+ scope =
+ auth_attrs
+ |> Scopes.fetch_scopes([])
+ |> Scopes.to_string()
+
+ state =
+ auth_attrs
+ |> Map.delete("scopes")
+ |> Map.put("scope", scope)
+ |> Jason.encode!()
+
+ params =
+ auth_attrs
+ |> Map.drop(~w(scope scopes client_id redirect_uri))
+ |> Map.put("state", state)
+
+ # Handing the request to Ueberauth
+ redirect(conn, to: o_auth_path(conn, :request, provider, params))
+ end
+
+ def request(%Plug.Conn{} = conn, params) do
+ message =
+ if params["provider"] do
+ dgettext("errors", "Unsupported OAuth provider: %{provider}.",
+ provider: params["provider"]
+ )
+ else
+ dgettext("errors", "Bad OAuth request.")
+ end
+
+ conn
+ |> put_flash(:error, message)
+ |> redirect(to: "/")
+ end
+
+ 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, "; ")
+
+ conn
+ |> put_flash(
+ :error,
+ dgettext("errors", "Failed to authenticate: %{message}.", message: message)
+ )
+ |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
+ end
+
+ def callback(%Plug.Conn{} = conn, params) do
+ params = callback_params(params)
+
+ with {:ok, registration} <- Authenticator.get_registration(conn) do
+ auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
+
+ case Repo.get_assoc(registration, :user) do
+ {:ok, user} ->
+ create_authorization(conn, %{"authorization" => auth_attrs}, user: user)
+
+ _ ->
+ registration_params =
+ Map.merge(auth_attrs, %{
+ "nickname" => Registration.nickname(registration),
+ "email" => Registration.email(registration)
+ })
+
+ conn
+ |> put_session_registration_id(registration.id)
+ |> registration_details(%{"authorization" => registration_params})
+ end
+ else
+ error ->
+ Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns]))
+
+ conn
+ |> put_flash(:error, dgettext("errors", "Failed to set up user account."))
+ |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
+ end
+ end
+
+ defp callback_params(%{"state" => state} = params) do
+ Map.merge(params, Jason.decode!(state))
+ end
+
+ 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"],
+ state: auth_attrs["state"],
+ scopes: Scopes.fetch_scopes(auth_attrs, []),
+ nickname: auth_attrs["nickname"],
+ email: auth_attrs["email"]
+ })
+ end
+
+ 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, _user}} <-
+ {:create_authorization, do_create_authorization(conn, params)},
+ %User{} = user <- Repo.preload(auth, :user).user,
+ {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
+ conn
+ |> put_session_registration_id(nil)
+ |> after_create_authorization(auth, params)
+ else
+ {:create_authorization, error} ->
+ {:register, handle_create_authorization_error(conn, error, params)}
+
+ _ ->
+ {:register, :generic_error}
+ end
+ end
+
+ 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
+ conn
+ |> put_session_registration_id(nil)
+ |> create_authorization(
+ params,
+ user: user
+ )
+ else
+ {:error, changeset} ->
+ message =
+ Enum.map(changeset.errors, fn {field, {error, _}} ->
+ "#{field} #{error}"
+ end)
+ |> Enum.join("; ")
+
+ message =
+ String.replace(
+ message,
+ "ap_id has already been taken",
+ "nickname has already been taken"
+ )
+
+ conn
+ |> put_status(:forbidden)
+ |> put_flash(:error, "Error: #{message}.")
+ |> registration_details(params)
+
+ _ ->
+ {:register, :generic_error}
+ end
+ end
+
+ defp do_create_authorization(conn, auth_attrs, user \\ nil)
+
+ defp do_create_authorization(
+ %Plug.Conn{} = conn,
+ %{
+ "authorization" =>
+ %{
+ "client_id" => client_id,
+ "redirect_uri" => redirect_uri
+ } = auth_attrs
+ },
+ user
+ ) do
+ with {_, {:ok, %User{} = user}} <-
+ {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},
+ %App{} = app <- Repo.get_by(App, client_id: client_id),
+ true <- redirect_uri in String.split(app.redirect_uris),
+ requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes),
+ {:ok, auth} <- do_create_authorization(user, app, requested_scopes) do
+ {:ok, auth, user}
+ end
+ end
+
+ defp do_create_authorization(%User{} = user, %App{} = app, requested_scopes)
+ when is_list(requested_scopes) do
+ with {:account_status, :active} <- {:account_status, User.account_status(user)},
+ {:ok, scopes} <- validate_scopes(app, requested_scopes),
+ {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
+ {:ok, auth}
+ end
+ end
+
+ # Note: intended to be a private function but opened for AccountController that logs in on signup
+ @doc "If checks pass, creates authorization and token for given user, app and requested scopes."
+ def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do
+ with {:ok, auth} <- do_create_authorization(user, app, requested_scopes),
+ {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
+ {:ok, token} <- Token.exchange_token(app, auth) do
+ {:ok, token}
+ end
+ end
+
+ # Special case: Local MastodonFE
+ defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
+
+ defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
+
+ defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
+
+ defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
+ do: put_session(conn, :registration_id, registration_id)
+
+ defp build_and_response_mfa_token(user, auth) do
+ with {:ok, token} <- MFA.Token.create(user, auth) do
+ MFAView.render("mfa_response.json", %{token: token, user: user})
+ end
+ end
+
+ @spec validate_scopes(App.t(), map() | list()) ::
+ {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
+ defp validate_scopes(%App{} = app, params) when is_map(params) do
+ requested_scopes = Scopes.fetch_scopes(params, app.scopes)
+ validate_scopes(app, requested_scopes)
+ end
+
+ defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do
+ Scopes.validate(requested_scopes, app.scopes)
+ end
+
+ def default_redirect_uri(%App{} = app) do
+ app.redirect_uris
+ |> String.split()
+ |> Enum.at(0)
+ end
+
+ defp render_invalid_credentials_error(conn) do
+ render_error(conn, :bad_request, "Invalid credentials")
+ end
+end
diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/o_auth/o_auth_view.ex
diff --git a/lib/pleroma/web/o_auth/scopes.ex b/lib/pleroma/web/o_auth/scopes.ex
@@ -0,0 +1,76 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.OAuth.Scopes do
+ @moduledoc """
+ Functions for dealing with scopes.
+ """
+
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ @doc """
+ Fetch scopes from request params.
+
+ Note: `scopes` is used by Mastodon — supporting it but sticking to
+ OAuth's standard `scope` wherever we control it
+ """
+ @spec fetch_scopes(map() | struct(), list()) :: list()
+
+ def fetch_scopes(params, default) do
+ parse_scopes(params["scope"] || params["scopes"] || params[:scopes], default)
+ end
+
+ def parse_scopes(scopes, _default) when is_list(scopes) do
+ Enum.filter(scopes, &(&1 not in [nil, ""]))
+ end
+
+ def parse_scopes(scopes, default) when is_binary(scopes) do
+ scopes
+ |> to_list
+ |> parse_scopes(default)
+ end
+
+ def parse_scopes(_, default) do
+ default
+ end
+
+ @doc """
+ Convert scopes string to list
+ """
+ @spec to_list(binary()) :: [binary()]
+ def to_list(nil), do: []
+
+ def to_list(str) do
+ str
+ |> String.trim()
+ |> String.split(~r/[\s,]+/)
+ end
+
+ @doc """
+ Convert scopes list to string
+ """
+ @spec to_string(list()) :: binary()
+ def to_string(scopes), do: Enum.join(scopes, " ")
+
+ @doc """
+ Validates scopes.
+ """
+ @spec validate(list() | nil, list()) ::
+ {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
+ def validate(blank_scopes, _app_scopes) when blank_scopes in [nil, []],
+ do: {:error, :missing_scopes}
+
+ def validate(scopes, app_scopes) do
+ case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do
+ ^scopes -> {:ok, scopes}
+ _ -> {:error, :unsupported_scopes}
+ end
+ end
+
+ def contains_admin_scopes?(scopes) do
+ scopes
+ |> OAuthScopesPlug.filter_descendants(["admin"])
+ |> Enum.any?()
+ end
+end
diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/o_auth/token.ex
diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/o_auth/token/query.ex
diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/o_auth/token/strategy/refresh_token.ex
diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/o_auth/token/strategy/revoke.ex
diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/o_auth/token/utils.ex
diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex
@@ -0,0 +1,151 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.OStatus.OStatusController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPubController
+ alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.Endpoint
+ alias Pleroma.Web.Fallback.RedirectController
+ alias Pleroma.Web.Metadata.PlayerView
+ alias Pleroma.Web.Plugs.RateLimiter
+ alias Pleroma.Web.Router
+
+ plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug,
+ unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
+ )
+
+ plug(
+ RateLimiter,
+ [name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]
+ )
+
+ plug(
+ Pleroma.Web.Plugs.SetFormatPlug
+ when action in [:object, :activity, :notice]
+ )
+
+ action_fallback(:errors)
+
+ def object(%{assigns: %{format: format}} = conn, _params)
+ when format in ["json", "activity+json"] do
+ ActivityPubController.call(conn, :object)
+ end
+
+ def object(%{assigns: %{format: format}} = conn, _params) do
+ with id <- Endpoint.url() <> conn.request_path,
+ {_, %Activity{} = activity} <-
+ {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
+ {_, true} <- {:public?, Visibility.is_public?(activity)} do
+ case format do
+ _ -> redirect(conn, to: "/notice/#{activity.id}")
+ end
+ else
+ reason when reason in [{:public?, false}, {:activity, nil}] ->
+ {:error, :not_found}
+
+ e ->
+ e
+ end
+ end
+
+ def activity(%{assigns: %{format: format}} = conn, _params)
+ when format in ["json", "activity+json"] do
+ ActivityPubController.call(conn, :activity)
+ end
+
+ def activity(%{assigns: %{format: format}} = conn, _params) do
+ with id <- Endpoint.url() <> conn.request_path,
+ {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
+ {_, true} <- {:public?, Visibility.is_public?(activity)} do
+ case format do
+ _ -> redirect(conn, to: "/notice/#{activity.id}")
+ end
+ else
+ reason when reason in [{:public?, false}, {:activity, nil}] ->
+ {:error, :not_found}
+
+ e ->
+ e
+ end
+ end
+
+ def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
+ with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
+ {_, true} <- {:public?, Visibility.is_public?(activity)},
+ %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
+ cond do
+ format in ["json", "activity+json"] ->
+ if activity.local do
+ %{data: %{"id" => redirect_url}} = Object.normalize(activity)
+ redirect(conn, external: redirect_url)
+ else
+ {:error, :not_found}
+ end
+
+ activity.data["type"] == "Create" ->
+ %Object{} = object = Object.normalize(activity)
+
+ RedirectController.redirector_with_meta(
+ conn,
+ %{
+ activity_id: activity.id,
+ object: object,
+ url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
+ user: user
+ }
+ )
+
+ true ->
+ RedirectController.redirector(conn, nil)
+ end
+ else
+ reason when reason in [{:public?, false}, {:activity, nil}] ->
+ conn
+ |> put_status(404)
+ |> RedirectController.redirector(nil, 404)
+
+ e ->
+ e
+ end
+ end
+
+ # Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
+ def notice_player(conn, %{"id" => id}) do
+ with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
+ true <- Visibility.is_public?(activity),
+ %Object{} = object <- Object.normalize(activity),
+ %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
+ true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
+ conn
+ |> put_layout(:metadata_player)
+ |> put_resp_header("x-frame-options", "ALLOW")
+ |> put_resp_header(
+ "content-security-policy",
+ "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
+ )
+ |> put_view(PlayerView)
+ |> render("player.html", url)
+ else
+ _error ->
+ conn
+ |> put_status(404)
+ |> RedirectController.redirector(nil, 404)
+ end
+ end
+
+ defp errors(conn, {:error, :not_found}) do
+ render_error(conn, :not_found, "Not found")
+ end
+
+ defp errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
+
+ defp errors(conn, _) do
+ render_error(conn, :internal_server_error, "Something went wrong")
+ end
+end
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -1,610 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.OAuthController do
- use Pleroma.Web, :controller
-
- alias Pleroma.Helpers.UriHelper
- alias Pleroma.Maps
- alias Pleroma.MFA
- alias Pleroma.Plugs.RateLimiter
- alias Pleroma.Registration
- alias Pleroma.Repo
- alias Pleroma.User
- alias Pleroma.Web.Auth.Authenticator
- alias Pleroma.Web.ControllerHelper
- alias Pleroma.Web.OAuth.App
- alias Pleroma.Web.OAuth.Authorization
- alias Pleroma.Web.OAuth.MFAController
- alias Pleroma.Web.OAuth.MFAView
- alias Pleroma.Web.OAuth.OAuthView
- alias Pleroma.Web.OAuth.Scopes
- alias Pleroma.Web.OAuth.Token
- alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
- alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
-
- require Logger
-
- if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
-
- plug(:fetch_session)
- plug(:fetch_flash)
-
- plug(:skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug])
-
- plug(RateLimiter, [name: :authentication] when action == :create_authorization)
-
- 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(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
- {auth_attrs, params} = Map.pop(params, "authorization")
- authorize(conn, Map.merge(params, auth_attrs))
- end
-
- def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do
- if ControllerHelper.truthy_param?(params["force_login"]) do
- do_authorize(conn, params)
- else
- handle_existing_authorization(conn, params)
- end
- end
-
- # Note: the token is set in oauth_plug, but the token and client do not always go together.
- # For example, MastodonFE's token is set if user requests with another client,
- # after user already authorized to MastodonFE.
- # So we have to check client and token.
- def authorize(
- %Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
- %{"client_id" => client_id} = params
- ) do
- with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app),
- ^client_id <- t.app.client_id do
- handle_existing_authorization(conn, params)
- else
- _ -> do_authorize(conn, params)
- end
- end
-
- def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params)
-
- 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)
-
- scopes =
- if scopes == [] do
- available_scopes
- else
- scopes
- end
-
- # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template
- render(conn, Authenticator.auth_template(), %{
- response_type: params["response_type"],
- client_id: params["client_id"],
- available_scopes: available_scopes,
- scopes: scopes,
- redirect_uri: params["redirect_uri"],
- state: params["state"],
- params: params
- })
- 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 = Maps.put_if_present(url_params, :state, params["state"])
- url = UriHelper.modify_uri_params(redirect_uri, url_params)
- redirect(conn, external: url)
- else
- conn
- |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
- |> redirect(external: redirect_uri(conn, redirect_uri))
- end
- end
-
- def create_authorization(
- %Plug.Conn{} = conn,
- %{"authorization" => _} = params,
- opts \\ []
- ) do
- with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
- {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
- after_create_authorization(conn, auth, params)
- else
- error ->
- handle_create_authorization_error(conn, error, params)
- end
- end
-
- def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
- "authorization" => %{"redirect_uri" => @oob_token_redirect_uri}
- }) do
- # Enforcing the view to reuse the template when calling from other controllers
- conn
- |> put_view(OAuthView)
- |> render("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 = Maps.put_if_present(url_params, :state, auth_attrs["state"])
- url = UriHelper.modify_uri_params(redirect_uri, url_params)
- redirect(conn, external: url)
- else
- conn
- |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
- |> redirect(external: redirect_uri(conn, redirect_uri))
- end
- end
-
- defp handle_create_authorization_error(
- %Plug.Conn{} = conn,
- {:error, scopes_issue},
- %{"authorization" => _} = params
- )
- when scopes_issue in [:unsupported_scopes, :missing_scopes] do
- # Per https://github.com/tootsuite/mastodon/blob/
- # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
- conn
- |> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes"))
- |> put_status(:unauthorized)
- |> authorize(params)
- end
-
- defp handle_create_authorization_error(
- %Plug.Conn{} = conn,
- {:account_status, :confirmation_pending},
- %{"authorization" => _} = params
- ) do
- conn
- |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
- |> put_status(:forbidden)
- |> authorize(params)
- end
-
- defp handle_create_authorization_error(
- %Plug.Conn{} = conn,
- {:mfa_required, user, auth, _},
- params
- ) do
- {:ok, token} = MFA.Token.create(user, auth)
-
- data = %{
- "mfa_token" => token.token,
- "redirect_uri" => params["authorization"]["redirect_uri"],
- "state" => params["authorization"]["state"]
- }
-
- MFAController.show(conn, data)
- end
-
- defp handle_create_authorization_error(
- %Plug.Conn{} = conn,
- {:account_status, :password_reset_pending},
- %{"authorization" => _} = params
- ) do
- conn
- |> put_flash(:error, dgettext("errors", "Password reset is required"))
- |> put_status(:forbidden)
- |> authorize(params)
- end
-
- defp handle_create_authorization_error(
- %Plug.Conn{} = conn,
- {:account_status, :deactivated},
- %{"authorization" => _} = params
- ) do
- conn
- |> put_flash(:error, dgettext("errors", "Your account is currently disabled"))
- |> put_status(:forbidden)
- |> authorize(params)
- end
-
- 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(
- %Plug.Conn{} = conn,
- %{"grant_type" => "refresh_token", "refresh_token" => token} = _params
- ) do
- with {:ok, app} <- Token.Utils.fetch_app(conn),
- {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
- {:ok, token} <- RefreshToken.grant(token) do
- json(conn, OAuthView.render("token.json", %{user: user, token: token}))
- else
- _error -> render_invalid_credentials_error(conn)
- end
- end
-
- 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),
- %User{} = user <- User.get_cached_by_id(auth.user_id),
- {:ok, token} <- Token.exchange_token(app, auth) do
- json(conn, OAuthView.render("token.json", %{user: user, token: token}))
- else
- error ->
- handle_token_exchange_error(conn, error)
- end
- end
-
- def token_exchange(
- %Plug.Conn{} = conn,
- %{"grant_type" => "password"} = params
- ) do
- with {:ok, %User{} = user} <- Authenticator.get_user(conn),
- {:ok, app} <- Token.Utils.fetch_app(conn),
- requested_scopes <- Scopes.fetch_scopes(params, app.scopes),
- {:ok, token} <- login(user, app, requested_scopes) do
- json(conn, OAuthView.render("token.json", %{user: user, token: token}))
- else
- error ->
- handle_token_exchange_error(conn, error)
- end
- end
-
- def token_exchange(
- %Plug.Conn{} = conn,
- %{"grant_type" => "password", "name" => name, "password" => _password} = params
- ) do
- params =
- params
- |> Map.delete("name")
- |> Map.put("username", name)
-
- token_exchange(conn, params)
- end
-
- 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
- json(conn, OAuthView.render("token.json", %{token: token}))
- else
- _error ->
- handle_token_exchange_error(conn, :invalid_credentails)
- end
- end
-
- # Bad request
- def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
-
- defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do
- conn
- |> put_status(:forbidden)
- |> json(build_and_response_mfa_token(user, auth))
- end
-
- defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do
- render_error(
- conn,
- :forbidden,
- "Your account is currently disabled",
- %{},
- "account_is_disabled"
- )
- end
-
- defp handle_token_exchange_error(
- %Plug.Conn{} = conn,
- {:account_status, :password_reset_pending}
- ) do
- render_error(
- conn,
- :forbidden,
- "Password reset is required",
- %{},
- "password_reset_required"
- )
- end
-
- defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do
- render_error(
- conn,
- :forbidden,
- "Your login is missing a confirmed e-mail address",
- %{},
- "missing_confirmed_email"
- )
- end
-
- defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do
- render_error(
- conn,
- :forbidden,
- "Your account is awaiting approval.",
- %{},
- "awaiting_approval"
- )
- end
-
- defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
- render_invalid_credentials_error(conn)
- end
-
- 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, %{})
- else
- _error ->
- # RFC 7009: invalid tokens [in the request] do not cause an error response
- json(conn, %{})
- end
- end
-
- def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
-
- # Response for bad request
- defp bad_request(%Plug.Conn{} = conn, _) do
- render_error(conn, :internal_server_error, "Bad request")
- end
-
- @doc "Prepares OAuth request to provider for Ueberauth"
- def prepare_request(%Plug.Conn{} = conn, %{
- "provider" => provider,
- "authorization" => auth_attrs
- }) do
- scope =
- auth_attrs
- |> Scopes.fetch_scopes([])
- |> Scopes.to_string()
-
- state =
- auth_attrs
- |> Map.delete("scopes")
- |> Map.put("scope", scope)
- |> Jason.encode!()
-
- params =
- auth_attrs
- |> Map.drop(~w(scope scopes client_id redirect_uri))
- |> Map.put("state", state)
-
- # Handing the request to Ueberauth
- redirect(conn, to: o_auth_path(conn, :request, provider, params))
- end
-
- def request(%Plug.Conn{} = conn, params) do
- message =
- if params["provider"] do
- dgettext("errors", "Unsupported OAuth provider: %{provider}.",
- provider: params["provider"]
- )
- else
- dgettext("errors", "Bad OAuth request.")
- end
-
- conn
- |> put_flash(:error, message)
- |> redirect(to: "/")
- end
-
- 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, "; ")
-
- conn
- |> put_flash(
- :error,
- dgettext("errors", "Failed to authenticate: %{message}.", message: message)
- )
- |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
- end
-
- def callback(%Plug.Conn{} = conn, params) do
- params = callback_params(params)
-
- with {:ok, registration} <- Authenticator.get_registration(conn) do
- auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
-
- case Repo.get_assoc(registration, :user) do
- {:ok, user} ->
- create_authorization(conn, %{"authorization" => auth_attrs}, user: user)
-
- _ ->
- registration_params =
- Map.merge(auth_attrs, %{
- "nickname" => Registration.nickname(registration),
- "email" => Registration.email(registration)
- })
-
- conn
- |> put_session_registration_id(registration.id)
- |> registration_details(%{"authorization" => registration_params})
- end
- else
- error ->
- Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns]))
-
- conn
- |> put_flash(:error, dgettext("errors", "Failed to set up user account."))
- |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
- end
- end
-
- defp callback_params(%{"state" => state} = params) do
- Map.merge(params, Jason.decode!(state))
- end
-
- 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"],
- state: auth_attrs["state"],
- scopes: Scopes.fetch_scopes(auth_attrs, []),
- nickname: auth_attrs["nickname"],
- email: auth_attrs["email"]
- })
- end
-
- 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, _user}} <-
- {:create_authorization, do_create_authorization(conn, params)},
- %User{} = user <- Repo.preload(auth, :user).user,
- {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
- conn
- |> put_session_registration_id(nil)
- |> after_create_authorization(auth, params)
- else
- {:create_authorization, error} ->
- {:register, handle_create_authorization_error(conn, error, params)}
-
- _ ->
- {:register, :generic_error}
- end
- end
-
- 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
- conn
- |> put_session_registration_id(nil)
- |> create_authorization(
- params,
- user: user
- )
- else
- {:error, changeset} ->
- message =
- Enum.map(changeset.errors, fn {field, {error, _}} ->
- "#{field} #{error}"
- end)
- |> Enum.join("; ")
-
- message =
- String.replace(
- message,
- "ap_id has already been taken",
- "nickname has already been taken"
- )
-
- conn
- |> put_status(:forbidden)
- |> put_flash(:error, "Error: #{message}.")
- |> registration_details(params)
-
- _ ->
- {:register, :generic_error}
- end
- end
-
- defp do_create_authorization(conn, auth_attrs, user \\ nil)
-
- defp do_create_authorization(
- %Plug.Conn{} = conn,
- %{
- "authorization" =>
- %{
- "client_id" => client_id,
- "redirect_uri" => redirect_uri
- } = auth_attrs
- },
- user
- ) do
- with {_, {:ok, %User{} = user}} <-
- {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},
- %App{} = app <- Repo.get_by(App, client_id: client_id),
- true <- redirect_uri in String.split(app.redirect_uris),
- requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes),
- {:ok, auth} <- do_create_authorization(user, app, requested_scopes) do
- {:ok, auth, user}
- end
- end
-
- defp do_create_authorization(%User{} = user, %App{} = app, requested_scopes)
- when is_list(requested_scopes) do
- with {:account_status, :active} <- {:account_status, User.account_status(user)},
- {:ok, scopes} <- validate_scopes(app, requested_scopes),
- {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
- {:ok, auth}
- end
- end
-
- # Note: intended to be a private function but opened for AccountController that logs in on signup
- @doc "If checks pass, creates authorization and token for given user, app and requested scopes."
- def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do
- with {:ok, auth} <- do_create_authorization(user, app, requested_scopes),
- {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
- {:ok, token} <- Token.exchange_token(app, auth) do
- {:ok, token}
- end
- end
-
- # Special case: Local MastodonFE
- defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
-
- defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
-
- defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
-
- defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
- do: put_session(conn, :registration_id, registration_id)
-
- defp build_and_response_mfa_token(user, auth) do
- with {:ok, token} <- MFA.Token.create(user, auth) do
- MFAView.render("mfa_response.json", %{token: token, user: user})
- end
- end
-
- @spec validate_scopes(App.t(), map() | list()) ::
- {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
- defp validate_scopes(%App{} = app, params) when is_map(params) do
- requested_scopes = Scopes.fetch_scopes(params, app.scopes)
- validate_scopes(app, requested_scopes)
- end
-
- defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do
- Scopes.validate(requested_scopes, app.scopes)
- end
-
- def default_redirect_uri(%App{} = app) do
- app.redirect_uris
- |> String.split()
- |> Enum.at(0)
- end
-
- defp render_invalid_credentials_error(conn) do
- render_error(conn, :bad_request, "Invalid credentials")
- end
-end
diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex
@@ -1,76 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Scopes do
- @moduledoc """
- Functions for dealing with scopes.
- """
-
- alias Pleroma.Plugs.OAuthScopesPlug
-
- @doc """
- Fetch scopes from request params.
-
- Note: `scopes` is used by Mastodon — supporting it but sticking to
- OAuth's standard `scope` wherever we control it
- """
- @spec fetch_scopes(map() | struct(), list()) :: list()
-
- def fetch_scopes(params, default) do
- parse_scopes(params["scope"] || params["scopes"] || params[:scopes], default)
- end
-
- def parse_scopes(scopes, _default) when is_list(scopes) do
- Enum.filter(scopes, &(&1 not in [nil, ""]))
- end
-
- def parse_scopes(scopes, default) when is_binary(scopes) do
- scopes
- |> to_list
- |> parse_scopes(default)
- end
-
- def parse_scopes(_, default) do
- default
- end
-
- @doc """
- Convert scopes string to list
- """
- @spec to_list(binary()) :: [binary()]
- def to_list(nil), do: []
-
- def to_list(str) do
- str
- |> String.trim()
- |> String.split(~r/[\s,]+/)
- end
-
- @doc """
- Convert scopes list to string
- """
- @spec to_string(list()) :: binary()
- def to_string(scopes), do: Enum.join(scopes, " ")
-
- @doc """
- Validates scopes.
- """
- @spec validate(list() | nil, list()) ::
- {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
- def validate(blank_scopes, _app_scopes) when blank_scopes in [nil, []],
- do: {:error, :missing_scopes}
-
- def validate(scopes, app_scopes) do
- case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do
- ^scopes -> {:ok, scopes}
- _ -> {:error, :unsupported_scopes}
- end
- end
-
- def contains_admin_scopes?(scopes) do
- scopes
- |> OAuthScopesPlug.filter_descendants(["admin"])
- |> Enum.any?()
- end
-end
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex
@@ -1,151 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OStatus.OStatusController do
- use Pleroma.Web, :controller
-
- alias Fallback.RedirectController
- alias Pleroma.Activity
- alias Pleroma.Object
- alias Pleroma.Plugs.RateLimiter
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPubController
- alias Pleroma.Web.ActivityPub.Visibility
- alias Pleroma.Web.Endpoint
- alias Pleroma.Web.Metadata.PlayerView
- alias Pleroma.Web.Router
-
- plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
- unless_func: &Pleroma.Web.FederatingPlug.federating?/1
- )
-
- plug(
- RateLimiter,
- [name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]
- )
-
- plug(
- Pleroma.Plugs.SetFormatPlug
- when action in [:object, :activity, :notice]
- )
-
- action_fallback(:errors)
-
- def object(%{assigns: %{format: format}} = conn, _params)
- when format in ["json", "activity+json"] do
- ActivityPubController.call(conn, :object)
- end
-
- def object(%{assigns: %{format: format}} = conn, _params) do
- with id <- Endpoint.url() <> conn.request_path,
- {_, %Activity{} = activity} <-
- {:activity, Activity.get_create_by_object_ap_id_with_object(id)},
- {_, true} <- {:public?, Visibility.is_public?(activity)} do
- case format do
- _ -> redirect(conn, to: "/notice/#{activity.id}")
- end
- else
- reason when reason in [{:public?, false}, {:activity, nil}] ->
- {:error, :not_found}
-
- e ->
- e
- end
- end
-
- def activity(%{assigns: %{format: format}} = conn, _params)
- when format in ["json", "activity+json"] do
- ActivityPubController.call(conn, :activity)
- end
-
- def activity(%{assigns: %{format: format}} = conn, _params) do
- with id <- Endpoint.url() <> conn.request_path,
- {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
- {_, true} <- {:public?, Visibility.is_public?(activity)} do
- case format do
- _ -> redirect(conn, to: "/notice/#{activity.id}")
- end
- else
- reason when reason in [{:public?, false}, {:activity, nil}] ->
- {:error, :not_found}
-
- e ->
- e
- end
- end
-
- def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
- with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
- {_, true} <- {:public?, Visibility.is_public?(activity)},
- %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
- cond do
- format in ["json", "activity+json"] ->
- if activity.local do
- %{data: %{"id" => redirect_url}} = Object.normalize(activity)
- redirect(conn, external: redirect_url)
- else
- {:error, :not_found}
- end
-
- activity.data["type"] == "Create" ->
- %Object{} = object = Object.normalize(activity)
-
- RedirectController.redirector_with_meta(
- conn,
- %{
- activity_id: activity.id,
- object: object,
- url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
- user: user
- }
- )
-
- true ->
- RedirectController.redirector(conn, nil)
- end
- else
- reason when reason in [{:public?, false}, {:activity, nil}] ->
- conn
- |> put_status(404)
- |> RedirectController.redirector(nil, 404)
-
- e ->
- e
- end
- end
-
- # Returns an HTML embedded <audio> or <video> player suitable for embed iframes.
- def notice_player(conn, %{"id" => id}) do
- with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id_with_object(id),
- true <- Visibility.is_public?(activity),
- %Object{} = object <- Object.normalize(activity),
- %{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object,
- true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do
- conn
- |> put_layout(:metadata_player)
- |> put_resp_header("x-frame-options", "ALLOW")
- |> put_resp_header(
- "content-security-policy",
- "default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
- )
- |> put_view(PlayerView)
- |> render("player.html", url)
- else
- _error ->
- conn
- |> put_status(404)
- |> RedirectController.redirector(nil, 404)
- end
- end
-
- defp errors(conn, {:error, :not_found}) do
- render_error(conn, :not_found, "Not found")
- end
-
- defp errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
-
- defp errors(conn, _) do
- render_error(conn, :internal_server_error, "Something went wrong")
- end
-end
diff --git a/lib/pleroma/web/pleroma_api/controllers/account_controller.ex b/lib/pleroma/web/pleroma_api/controllers/account_controller.ex
@@ -8,12 +8,12 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
import Pleroma.Web.ControllerHelper,
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
require Pleroma.Constants
diff --git a/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex b/lib/pleroma/web/pleroma_api/controllers/chat_controller.ex
@@ -11,12 +11,12 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
alias Pleroma.Chat.MessageReference
alias Pleroma.Object
alias Pleroma.Pagination
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
alias Pleroma.Web.PleromaAPI.ChatView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
import Ecto.Query
diff --git a/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex b/lib/pleroma/web/pleroma_api/controllers/conversation_controller.ex
@@ -8,9 +8,9 @@ defmodule Pleroma.Web.PleromaAPI.ConversationController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Conversation.Participation
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(:put_view, Pleroma.Web.MastodonAPI.ConversationView)
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_file_controller.ex
@@ -11,7 +11,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileController do
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(
- Pleroma.Plugs.OAuthScopesPlug,
+ Pleroma.Web.Plugs.OAuthScopesPlug,
%{scopes: ["write"], admin: true}
when action in [
:create,
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_pack_controller.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(
- Pleroma.Plugs.OAuthScopesPlug,
+ Pleroma.Web.Plugs.OAuthScopesPlug,
%{scopes: ["write"], admin: true}
when action in [
:import_from_filesystem,
@@ -22,8 +22,11 @@ defmodule Pleroma.Web.PleromaAPI.EmojiPackController do
]
)
- @skip_plugs [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
- plug(:skip_plug, @skip_plugs when action in [:index, :show, :archive])
+ @skip_plugs [
+ Pleroma.Web.Plugs.OAuthScopesPlug,
+ Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ ]
+ plug(:skip_plug, @skip_plugs when action in [:index, :archive, :show])
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaEmojiPackOperation
diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_reaction_controller.ex
@@ -7,9 +7,9 @@ defmodule Pleroma.Web.PleromaAPI.EmojiReactionController do
alias Pleroma.Activity
alias Pleroma.Object
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action in [:create, :delete])
diff --git a/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex b/lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex
@@ -5,9 +5,9 @@
defmodule Pleroma.Web.PleromaAPI.MascotController do
use Pleroma.Web, :controller
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show)
diff --git a/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex b/lib/pleroma/web/pleroma_api/controllers/notification_controller.ex
@@ -6,10 +6,14 @@ defmodule Pleroma.Web.PleromaAPI.NotificationController do
use Pleroma.Web, :controller
alias Pleroma.Notification
- alias Pleroma.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
- plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :mark_as_read)
+
+ plug(
+ Pleroma.Web.Plugs.OAuthScopesPlug,
+ %{scopes: ["write:notifications"]} when action == :mark_as_read
+ )
+
plug(:put_view, Pleroma.Web.MastodonAPI.NotificationView)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaNotificationOperation
diff --git a/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex b/lib/pleroma/web/pleroma_api/controllers/scrobble_controller.ex
@@ -7,10 +7,10 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(Pleroma.Web.ApiSpec.CastAndValidate)
diff --git a/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex b/lib/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller.ex
@@ -10,8 +10,8 @@ defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationController do
alias Pleroma.MFA
alias Pleroma.MFA.TOTP
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.CommonAPI.Utils
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(OAuthScopesPlug, %{scopes: ["read:security"]} when action in [:settings])
diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex
@@ -7,9 +7,9 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do
require Logger
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.ApiSpec
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
plug(OAuthScopesPlug, %{scopes: ["follow", "write:follows"]} when action == :follow)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks)
diff --git a/lib/pleroma/web/plug.ex b/lib/pleroma/web/plug.ex
@@ -0,0 +1,8 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plug do
+ # Substitute for `call/2` which is defined with `use Pleroma.Web, :plug`
+ @callback perform(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()
+end
diff --git a/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex b/lib/pleroma/web/plugs/admin_secret_authentication_plug.ex
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlug do
+ import Plug.Conn
+
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.RateLimiter
+
+ def init(options) do
+ options
+ end
+
+ def secret_token do
+ case Pleroma.Config.get(:admin_token) do
+ blank when blank in [nil, ""] -> nil
+ token -> token
+ end
+ end
+
+ def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+
+ def call(conn, _) do
+ if secret_token() do
+ authenticate(conn)
+ else
+ conn
+ end
+ end
+
+ def authenticate(%{params: %{"admin_token" => admin_token}} = conn) do
+ if admin_token == secret_token() do
+ assign_admin_user(conn)
+ else
+ handle_bad_token(conn)
+ end
+ end
+
+ def authenticate(conn) do
+ token = secret_token()
+
+ case get_req_header(conn, "x-admin-token") do
+ blank when blank in [[], [""]] -> conn
+ [^token] -> assign_admin_user(conn)
+ _ -> handle_bad_token(conn)
+ end
+ end
+
+ defp assign_admin_user(conn) do
+ conn
+ |> assign(:user, %User{is_admin: true})
+ |> OAuthScopesPlug.skip_plug()
+ end
+
+ defp handle_bad_token(conn) do
+ RateLimiter.call(conn, name: :authentication)
+ end
+end
diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex
@@ -0,0 +1,79 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.AuthenticationPlug do
+ alias Pleroma.User
+
+ import Plug.Conn
+
+ require Logger
+
+ def init(options), do: options
+
+ def checkpw(password, "$6" <> _ = password_hash) do
+ :crypt.crypt(password, password_hash) == password_hash
+ end
+
+ def checkpw(password, "$2" <> _ = password_hash) do
+ # Handle bcrypt passwords for Mastodon migration
+ Bcrypt.verify_pass(password, password_hash)
+ end
+
+ def checkpw(password, "$pbkdf2" <> _ = password_hash) do
+ Pbkdf2.verify_pass(password, password_hash)
+ end
+
+ def checkpw(_password, _password_hash) do
+ Logger.error("Password hash not recognized")
+ false
+ end
+
+ def maybe_update_password(%User{password_hash: "$2" <> _} = user, password) do
+ do_update_password(user, password)
+ end
+
+ def maybe_update_password(%User{password_hash: "$6" <> _} = user, password) do
+ do_update_password(user, password)
+ end
+
+ def maybe_update_password(user, _), do: {:ok, user}
+
+ defp do_update_password(user, password) do
+ user
+ |> User.password_update_changeset(%{
+ "password" => password,
+ "password_confirmation" => password
+ })
+ |> Pleroma.Repo.update()
+ end
+
+ def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+
+ def call(
+ %{
+ assigns: %{
+ auth_user: %{password_hash: password_hash} = auth_user,
+ auth_credentials: %{password: password}
+ }
+ } = conn,
+ _
+ ) do
+ if checkpw(password, password_hash) do
+ {:ok, auth_user} = maybe_update_password(auth_user, password)
+
+ conn
+ |> assign(:user, auth_user)
+ |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug()
+ else
+ conn
+ end
+ end
+
+ def call(%{assigns: %{auth_credentials: %{password: _}}} = conn, _) do
+ Pbkdf2.no_user_verify()
+ conn
+ end
+
+ def call(conn, _), do: conn
+end
diff --git a/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex b/lib/pleroma/web/plugs/basic_auth_decoder_plug.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlug do
+ import Plug.Conn
+
+ def init(options) do
+ options
+ end
+
+ def call(conn, _opts) do
+ with ["Basic " <> header] <- get_req_header(conn, "authorization"),
+ {:ok, userinfo} <- Base.decode64(header),
+ [username, password] <- String.split(userinfo, ":", parts: 2) do
+ conn
+ |> assign(:auth_credentials, %{
+ username: username,
+ password: password
+ })
+ else
+ _ -> conn
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/cache.ex b/lib/pleroma/web/plugs/cache.ex
@@ -0,0 +1,136 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.Cache do
+ @moduledoc """
+ Caches successful GET responses.
+
+ To enable the cache add the plug to a router pipeline or controller:
+
+ plug(Pleroma.Web.Plugs.Cache)
+
+ ## Configuration
+
+ To configure the plug you need to pass settings as the second argument to the `plug/2` macro:
+
+ plug(Pleroma.Web.Plugs.Cache, [ttl: nil, query_params: true])
+
+ Available options:
+
+ - `ttl`: An expiration time (time-to-live). This value should be in milliseconds or `nil` to disable expiration. Defaults to `nil`.
+ - `query_params`: Take URL query string into account (`true`), ignore it (`false`) or limit to specific params only (list). Defaults to `true`.
+ - `tracking_fun`: A function that is called on successfull responses, no matter if the request is cached or not. It should accept a conn as the first argument and the value assigned to `tracking_fun_data` as the second.
+
+ Additionally, you can overwrite the TTL inside a controller action by assigning `cache_ttl` to the connection struct:
+
+ def index(conn, _params) do
+ ttl = 60_000 # one minute
+
+ conn
+ |> assign(:cache_ttl, ttl)
+ |> render("index.html")
+ end
+
+ """
+
+ import Phoenix.Controller, only: [current_path: 1, json: 2]
+ import Plug.Conn
+
+ @behaviour Plug
+
+ @defaults %{ttl: nil, query_params: true}
+
+ @impl true
+ def init([]), do: @defaults
+
+ def init(opts) do
+ opts = Map.new(opts)
+ Map.merge(@defaults, opts)
+ end
+
+ @impl true
+ def call(%{method: "GET"} = conn, opts) do
+ key = cache_key(conn, opts)
+
+ case Cachex.get(:web_resp_cache, key) do
+ {:ok, nil} ->
+ cache_resp(conn, opts)
+
+ {:ok, {content_type, body, tracking_fun_data}} ->
+ conn = opts.tracking_fun.(conn, tracking_fun_data)
+
+ send_cached(conn, {content_type, body})
+
+ {:ok, record} ->
+ send_cached(conn, record)
+
+ {atom, message} when atom in [:ignore, :error] ->
+ render_error(conn, message)
+ end
+ end
+
+ def call(conn, _), do: conn
+
+ # full path including query params
+ defp cache_key(conn, %{query_params: true}), do: current_path(conn)
+
+ # request path without query params
+ defp cache_key(conn, %{query_params: false}), do: conn.request_path
+
+ # request path with specific query params
+ defp cache_key(conn, %{query_params: query_params}) when is_list(query_params) do
+ query_string =
+ conn.params
+ |> Map.take(query_params)
+ |> URI.encode_query()
+
+ conn.request_path <> "?" <> query_string
+ end
+
+ defp cache_resp(conn, opts) do
+ register_before_send(conn, fn
+ %{status: 200, resp_body: body} = conn ->
+ ttl = Map.get(conn.assigns, :cache_ttl, opts.ttl)
+ key = cache_key(conn, opts)
+ content_type = content_type(conn)
+
+ conn =
+ unless opts[:tracking_fun] do
+ Cachex.put(:web_resp_cache, key, {content_type, body}, ttl: ttl)
+ conn
+ else
+ tracking_fun_data = Map.get(conn.assigns, :tracking_fun_data, nil)
+ Cachex.put(:web_resp_cache, key, {content_type, body, tracking_fun_data}, ttl: ttl)
+
+ opts.tracking_fun.(conn, tracking_fun_data)
+ end
+
+ put_resp_header(conn, "x-cache", "MISS from Pleroma")
+
+ conn ->
+ conn
+ end)
+ end
+
+ defp content_type(conn) do
+ conn
+ |> Plug.Conn.get_resp_header("content-type")
+ |> hd()
+ end
+
+ defp send_cached(conn, {content_type, body}) do
+ conn
+ |> put_resp_content_type(content_type, nil)
+ |> put_resp_header("x-cache", "HIT from Pleroma")
+ |> send_resp(:ok, body)
+ |> halt()
+ end
+
+ defp render_error(conn, message) do
+ conn
+ |> put_status(:internal_server_error)
+ |> json(%{error: message})
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/plugs/digest.ex b/lib/pleroma/web/plugs/digest_plug.ex
diff --git a/lib/pleroma/web/plugs/ensure_authenticated_plug.ex b/lib/pleroma/web/plugs/ensure_authenticated_plug.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsureAuthenticatedPlug do
+ import Plug.Conn
+ import Pleroma.Web.TranslationHelpers
+
+ alias Pleroma.User
+
+ use Pleroma.Web, :plug
+
+ def init(options) do
+ options
+ end
+
+ @impl true
+ def perform(
+ %{
+ assigns: %{
+ auth_credentials: %{password: _},
+ user: %User{multi_factor_authentication_settings: %{enabled: true}}
+ }
+ } = conn,
+ _
+ ) do
+ conn
+ |> render_error(:forbidden, "Two-factor authentication enabled, you must use a access token.")
+ |> halt()
+ end
+
+ def perform(%{assigns: %{user: %User{}}} = conn, _) do
+ conn
+ end
+
+ def perform(conn, _) do
+ conn
+ |> render_error(:forbidden, "Invalid credentials.")
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex b/lib/pleroma/web/plugs/ensure_public_or_authenticated_plug.ex
@@ -0,0 +1,35 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug do
+ import Pleroma.Web.TranslationHelpers
+ import Plug.Conn
+
+ alias Pleroma.Config
+ alias Pleroma.User
+
+ use Pleroma.Web, :plug
+
+ def init(options) do
+ options
+ end
+
+ @impl true
+ def perform(conn, _) do
+ public? = Config.get!([:instance, :public])
+
+ case {public?, conn} do
+ {true, _} ->
+ conn
+
+ {false, %{assigns: %{user: %User{}}}} ->
+ conn
+
+ {false, _} ->
+ conn
+ |> render_error(:forbidden, "This resource requires authentication.")
+ |> halt
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/ensure_user_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_key_plug.ex
@@ -0,0 +1,18 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsureUserKeyPlug do
+ import Plug.Conn
+
+ def init(opts) do
+ opts
+ end
+
+ def call(%{assigns: %{user: _}} = conn, _), do: conn
+
+ def call(conn, _) do
+ conn
+ |> assign(:user, nil)
+ end
+end
diff --git a/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex b/lib/pleroma/web/plugs/expect_authenticated_check_plug.ex
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug do
+ @moduledoc """
+ Marks `Pleroma.Web.Plugs.EnsureAuthenticatedPlug` as expected to be executed later in plug chain.
+
+ No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`).
+ """
+
+ use Pleroma.Web, :plug
+
+ def init(options), do: options
+
+ @impl true
+ def perform(conn, _) do
+ conn
+ end
+end
diff --git a/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex b/lib/pleroma/web/plugs/expect_public_or_authenticated_check_plug.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug do
+ @moduledoc """
+ Marks `Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug` as expected to be executed later in plug
+ chain.
+
+ No-op plug which affects `Pleroma.Web` operation (is checked with `PlugHelper.plug_called?/2`).
+ """
+
+ use Pleroma.Web, :plug
+
+ def init(options), do: options
+
+ @impl true
+ def perform(conn, _) do
+ conn
+ end
+end
diff --git a/lib/pleroma/web/plugs/federating_plug.ex b/lib/pleroma/web/plugs/federating_plug.ex
@@ -0,0 +1,32 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.FederatingPlug do
+ import Plug.Conn
+
+ def init(options) do
+ options
+ end
+
+ def call(conn, _opts) do
+ if federating?() do
+ conn
+ else
+ fail(conn)
+ end
+ end
+
+ def federating?, do: Pleroma.Config.get([:instance, :federating])
+
+ # Definition for the use in :if_func / :unless_func plug options
+ def federating?(_conn), do: federating?()
+
+ defp fail(conn) do
+ conn
+ |> put_status(404)
+ |> Phoenix.Controller.put_view(Pleroma.Web.ErrorView)
+ |> Phoenix.Controller.render("404.json")
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex
@@ -0,0 +1,55 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.FrontendStatic do
+ require Pleroma.Constants
+
+ @moduledoc """
+ This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends.
+ """
+ @behaviour Plug
+
+ def file_path(path, frontend_type \\ :primary) do
+ if configuration = Pleroma.Config.get([:frontends, frontend_type]) do
+ instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static")
+
+ Path.join([
+ instance_static_path,
+ "frontends",
+ configuration["name"],
+ configuration["ref"],
+ path
+ ])
+ else
+ nil
+ end
+ end
+
+ def init(opts) do
+ opts
+ |> Keyword.put(:from, "__unconfigured_frontend_static_plug")
+ |> Plug.Static.init()
+ |> Map.put(:frontend_type, opts[:frontend_type])
+ end
+
+ def call(conn, opts) do
+ frontend_type = Map.get(opts, :frontend_type, :primary)
+ path = file_path("", frontend_type)
+
+ if path do
+ conn
+ |> call_static(opts, path)
+ else
+ conn
+ end
+ end
+
+ defp call_static(conn, opts, from) do
+ opts =
+ opts
+ |> Map.put(:from, from)
+
+ Plug.Static.call(conn, opts)
+ end
+end
diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex
@@ -0,0 +1,225 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do
+ alias Pleroma.Config
+ import Plug.Conn
+
+ require Logger
+
+ def init(opts), do: opts
+
+ def call(conn, _options) do
+ if Config.get([:http_security, :enabled]) do
+ conn
+ |> merge_resp_headers(headers())
+ |> maybe_send_sts_header(Config.get([:http_security, :sts]))
+ else
+ conn
+ end
+ end
+
+ defp headers do
+ referrer_policy = Config.get([:http_security, :referrer_policy])
+ report_uri = Config.get([:http_security, :report_uri])
+
+ headers = [
+ {"x-xss-protection", "1; mode=block"},
+ {"x-permitted-cross-domain-policies", "none"},
+ {"x-frame-options", "DENY"},
+ {"x-content-type-options", "nosniff"},
+ {"referrer-policy", referrer_policy},
+ {"x-download-options", "noopen"},
+ {"content-security-policy", csp_string()}
+ ]
+
+ if report_uri do
+ report_group = %{
+ "group" => "csp-endpoint",
+ "max-age" => 10_886_400,
+ "endpoints" => [
+ %{"url" => report_uri}
+ ]
+ }
+
+ [{"reply-to", Jason.encode!(report_group)} | headers]
+ else
+ headers
+ end
+ end
+
+ static_csp_rules = [
+ "default-src 'none'",
+ "base-uri 'self'",
+ "frame-ancestors 'none'",
+ "style-src 'self' 'unsafe-inline'",
+ "font-src 'self'",
+ "manifest-src 'self'"
+ ]
+
+ @csp_start [Enum.join(static_csp_rules, ";") <> ";"]
+
+ defp csp_string do
+ scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme]
+ static_url = Pleroma.Web.Endpoint.static_url()
+ websocket_url = Pleroma.Web.Endpoint.websocket_url()
+ report_uri = Config.get([:http_security, :report_uri])
+
+ img_src = "img-src 'self' data: blob:"
+ media_src = "media-src 'self'"
+
+ # Strict multimedia CSP enforcement only when MediaProxy is enabled
+ {img_src, media_src} =
+ if Config.get([:media_proxy, :enabled]) &&
+ !Config.get([:media_proxy, :proxy_opts, :redirect_on_failure]) do
+ sources = build_csp_multimedia_source_list()
+ {[img_src, sources], [media_src, sources]}
+ else
+ {[img_src, " https:"], [media_src, " https:"]}
+ end
+
+ connect_src = ["connect-src 'self' blob: ", static_url, ?\s, websocket_url]
+
+ connect_src =
+ if Config.get(:env) == :dev do
+ [connect_src, " http://localhost:3035/"]
+ else
+ connect_src
+ end
+
+ script_src =
+ if Config.get(:env) == :dev do
+ "script-src 'self' 'unsafe-eval'"
+ else
+ "script-src 'self'"
+ end
+
+ report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"]
+ insecure = if scheme == "https", do: "upgrade-insecure-requests"
+
+ @csp_start
+ |> add_csp_param(img_src)
+ |> add_csp_param(media_src)
+ |> add_csp_param(connect_src)
+ |> add_csp_param(script_src)
+ |> add_csp_param(insecure)
+ |> add_csp_param(report)
+ |> :erlang.iolist_to_binary()
+ end
+
+ defp build_csp_from_whitelist([], acc), do: acc
+
+ defp build_csp_from_whitelist([last], acc) do
+ [build_csp_param_from_whitelist(last) | acc]
+ end
+
+ defp build_csp_from_whitelist([head | tail], acc) do
+ build_csp_from_whitelist(tail, [[?\s, build_csp_param_from_whitelist(head)] | acc])
+ end
+
+ # TODO: use `build_csp_param/1` after removing support bare domains for media proxy whitelist
+ defp build_csp_param_from_whitelist("http" <> _ = url) do
+ build_csp_param(url)
+ end
+
+ defp build_csp_param_from_whitelist(url), do: url
+
+ defp build_csp_multimedia_source_list do
+ media_proxy_whitelist =
+ [:media_proxy, :whitelist]
+ |> Config.get()
+ |> build_csp_from_whitelist([])
+
+ captcha_method = Config.get([Pleroma.Captcha, :method])
+ captcha_endpoint = Config.get([captcha_method, :endpoint])
+
+ base_endpoints =
+ [
+ [:media_proxy, :base_url],
+ [Pleroma.Upload, :base_url],
+ [Pleroma.Uploaders.S3, :public_endpoint]
+ ]
+ |> Enum.map(&Config.get/1)
+
+ [captcha_endpoint | base_endpoints]
+ |> Enum.map(&build_csp_param/1)
+ |> Enum.reduce([], &add_source(&2, &1))
+ |> add_source(media_proxy_whitelist)
+ end
+
+ defp add_source(iodata, nil), do: iodata
+ defp add_source(iodata, []), do: iodata
+ defp add_source(iodata, source), do: [[?\s, source] | iodata]
+
+ defp add_csp_param(csp_iodata, nil), do: csp_iodata
+
+ defp add_csp_param(csp_iodata, param), do: [[param, ?;] | csp_iodata]
+
+ defp build_csp_param(nil), do: nil
+
+ defp build_csp_param(url) when is_binary(url) do
+ %{host: host, scheme: scheme} = URI.parse(url)
+
+ if scheme do
+ [scheme, "://", host]
+ end
+ end
+
+ def warn_if_disabled do
+ unless Config.get([:http_security, :enabled]) do
+ Logger.warn("
+ .i;;;;i.
+ iYcviii;vXY:
+ .YXi .i1c.
+ .YC. . in7.
+ .vc. ...... ;1c.
+ i7, .. .;1;
+ i7, .. ... .Y1i
+ ,7v .6MMM@; .YX,
+ .7;. ..IMMMMMM1 :t7.
+ .;Y. ;$MMMMMM9. :tc.
+ vY. .. .nMMM@MMU. ;1v.
+ i7i ... .#MM@M@C. .....:71i
+ it: .... $MMM@9;.,i;;;i,;tti
+ :t7. ..... 0MMMWv.,iii:::,,;St.
+ .nC. ..... IMMMQ..,::::::,.,czX.
+ .ct: ....... .ZMMMI..,:::::::,,:76Y.
+ c2: ......,i..Y$M@t..:::::::,,..inZY
+ vov ......:ii..c$MBc..,,,,,,,,,,..iI9i
+ i9Y ......iii:..7@MA,..,,,,,,,,,....;AA:
+ iIS. ......:ii::..;@MI....,............;Ez.
+ .I9. ......:i::::...8M1..................C0z.
+ .z9; ......:i::::,.. .i:...................zWX.
+ vbv ......,i::::,,. ................. :AQY
+ c6Y. .,...,::::,,..:t0@@QY. ................ :8bi
+ :6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ,
+ :6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2.
+ .n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn
+ 7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi
+ 7C...::::::::::::,,,,.. .................... vSi.
+ ;1;...,,::::::,......... .................. Yz:
+ v97,......... .voC.
+ izAotX7777777777777777777777777777777777777777Y7n92:
+ .;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov.
+
+HTTP Security is disabled. Please re-enable it to prevent users from attacking
+your instance and your users via malicious posts:
+
+ config :pleroma, :http_security, enabled: true
+ ")
+ end
+ end
+
+ defp maybe_send_sts_header(conn, true) do
+ max_age_sts = Config.get([:http_security, :sts_max_age])
+ max_age_ct = Config.get([:http_security, :ct_max_age])
+
+ merge_resp_headers(conn, [
+ {"strict-transport-security", "max-age=#{max_age_sts}; includeSubDomains"},
+ {"expect-ct", "enforce, max-age=#{max_age_ct}"}
+ ])
+ end
+
+ defp maybe_send_sts_header(conn, _), do: conn
+end
diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/web/plugs/http_signature_plug.ex
diff --git a/lib/pleroma/web/plugs/idempotency_plug.ex b/lib/pleroma/web/plugs/idempotency_plug.ex
@@ -0,0 +1,84 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.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/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.InstanceStatic do
+ require Pleroma.Constants
+
+ @moduledoc """
+ This is a shim to call `Plug.Static` but with runtime `from` configuration.
+
+ Mountpoints are defined directly in the module to avoid calling the configuration for every request including non-static ones.
+ """
+ @behaviour Plug
+
+ def file_path(path) do
+ instance_path =
+ Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path)
+
+ frontend_path = Pleroma.Web.Plugs.FrontendStatic.file_path(path, :primary)
+
+ (File.exists?(instance_path) && instance_path) ||
+ (frontend_path && File.exists?(frontend_path) && frontend_path) ||
+ Path.join(Application.app_dir(:pleroma, "priv/static/"), path)
+ end
+
+ def init(opts) do
+ opts
+ |> Keyword.put(:from, "__unconfigured_instance_static_plug")
+ |> Plug.Static.init()
+ end
+
+ for only <- Pleroma.Constants.static_only_files() do
+ def call(%{request_path: "/" <> unquote(only) <> _} = conn, opts) do
+ call_static(
+ conn,
+ opts,
+ Pleroma.Config.get([:instance, :static_dir], "instance/static")
+ )
+ end
+ end
+
+ def call(conn, _) do
+ conn
+ end
+
+ defp call_static(conn, opts, from) do
+ opts =
+ opts
+ |> Map.put(:from, from)
+
+ Plug.Static.call(conn, opts)
+ end
+end
diff --git a/lib/pleroma/web/plugs/legacy_authentication_plug.ex b/lib/pleroma/web/plugs/legacy_authentication_plug.ex
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlug do
+ import Plug.Conn
+
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+
+ def call(
+ %{
+ assigns: %{
+ auth_user: %{password_hash: "$6$" <> _ = password_hash} = auth_user,
+ auth_credentials: %{password: password}
+ }
+ } = conn,
+ _
+ ) do
+ with ^password_hash <- :crypt.crypt(password, password_hash),
+ {:ok, user} <-
+ User.reset_password(auth_user, %{password: password, password_confirmation: password}) do
+ conn
+ |> assign(:auth_user, user)
+ |> assign(:user, user)
+ |> Pleroma.Web.Plugs.OAuthScopesPlug.skip_plug()
+ else
+ _ ->
+ conn
+ end
+ end
+
+ def call(conn, _) do
+ conn
+ end
+end
diff --git a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex
diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex
@@ -0,0 +1,120 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.OAuthPlug do
+ import Plug.Conn
+ import Ecto.Query
+
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Token
+
+ @realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i")
+
+ def init(options), do: options
+
+ def call(%{assigns: %{user: %User{}}} = conn, _), do: conn
+
+ def call(%{params: %{"access_token" => access_token}} = conn, _) do
+ with {:ok, user, token_record} <- fetch_user_and_token(access_token) do
+ conn
+ |> assign(:token, token_record)
+ |> assign(:user, user)
+ else
+ _ ->
+ # token found, but maybe only with app
+ with {:ok, app, token_record} <- fetch_app_and_token(access_token) do
+ conn
+ |> assign(:token, token_record)
+ |> assign(:app, app)
+ else
+ _ -> conn
+ end
+ end
+ end
+
+ def call(conn, _) do
+ case fetch_token_str(conn) do
+ {:ok, token} ->
+ with {:ok, user, token_record} <- fetch_user_and_token(token) do
+ conn
+ |> assign(:token, token_record)
+ |> assign(:user, user)
+ else
+ _ ->
+ # token found, but maybe only with app
+ with {:ok, app, token_record} <- fetch_app_and_token(token) do
+ conn
+ |> assign(:token, token_record)
+ |> assign(:app, app)
+ else
+ _ -> conn
+ end
+ end
+
+ _ ->
+ conn
+ end
+ end
+
+ # Gets user by token
+ #
+ @spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil
+ defp fetch_user_and_token(token) do
+ query =
+ from(t in Token,
+ where: t.token == ^token,
+ join: user in assoc(t, :user),
+ preload: [user: user]
+ )
+
+ # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength
+ with %Token{user: user} = token_record <- Repo.one(query) do
+ {:ok, user, token_record}
+ end
+ end
+
+ @spec fetch_app_and_token(String.t()) :: {:ok, App.t(), Token.t()} | nil
+ defp fetch_app_and_token(token) do
+ query =
+ from(t in Token, where: t.token == ^token, join: app in assoc(t, :app), preload: [app: app])
+
+ with %Token{app: app} = token_record <- Repo.one(query) do
+ {:ok, app, token_record}
+ end
+ end
+
+ # Gets token from session by :oauth_token key
+ #
+ @spec fetch_token_from_session(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
+ defp fetch_token_from_session(conn) do
+ case get_session(conn, :oauth_token) do
+ nil -> :no_token_found
+ token -> {:ok, token}
+ end
+ end
+
+ # Gets token from headers
+ #
+ @spec fetch_token_str(Plug.Conn.t()) :: :no_token_found | {:ok, String.t()}
+ defp fetch_token_str(%Plug.Conn{} = conn) do
+ headers = get_req_header(conn, "authorization")
+
+ with :no_token_found <- fetch_token_str(headers),
+ do: fetch_token_from_session(conn)
+ end
+
+ @spec fetch_token_str(Keyword.t()) :: :no_token_found | {:ok, String.t()}
+ defp fetch_token_str([]), do: :no_token_found
+
+ defp fetch_token_str([token | tail]) do
+ trimmed_token = String.trim(token)
+
+ case Regex.run(@realm_reg, trimmed_token) do
+ [_, match] -> {:ok, String.trim(match)}
+ _ -> fetch_token_str(tail)
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex
@@ -0,0 +1,77 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.OAuthScopesPlug do
+ import Plug.Conn
+ import Pleroma.Web.Gettext
+
+ alias Pleroma.Config
+
+ use Pleroma.Web, :plug
+
+ def init(%{scopes: _} = options), do: options
+
+ @impl true
+ def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
+ op = options[:op] || :|
+ token = assigns[:token]
+
+ scopes = transform_scopes(scopes, options)
+ matched_scopes = (token && filter_descendants(scopes, token.scopes)) || []
+
+ cond do
+ token && op == :| && Enum.any?(matched_scopes) ->
+ conn
+
+ token && op == :& && matched_scopes == scopes ->
+ conn
+
+ options[:fallback] == :proceed_unauthenticated ->
+ drop_auth_info(conn)
+
+ true ->
+ missing_scopes = scopes -- matched_scopes
+ permissions = Enum.join(missing_scopes, " #{op} ")
+
+ error_message =
+ dgettext("errors", "Insufficient permissions: %{permissions}.", permissions: permissions)
+
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(:forbidden, Jason.encode!(%{error: error_message}))
+ |> halt()
+ end
+ end
+
+ @doc "Drops authentication info from connection"
+ def drop_auth_info(conn) do
+ # To simplify debugging, setting a private variable on `conn` if auth info is dropped
+ conn
+ |> put_private(:authentication_ignored, true)
+ |> assign(:user, nil)
+ |> assign(:token, nil)
+ end
+
+ @doc "Keeps those of `scopes` which are descendants of `supported_scopes`"
+ def filter_descendants(scopes, supported_scopes) do
+ Enum.filter(
+ scopes,
+ fn scope ->
+ Enum.find(
+ supported_scopes,
+ &(scope == &1 || String.starts_with?(scope, &1 <> ":"))
+ )
+ end
+ )
+ end
+
+ @doc "Transforms scopes by applying supported options (e.g. :admin)"
+ def transform_scopes(scopes, options) do
+ if options[:admin] do
+ Config.oauth_admin_scopes(scopes)
+ else
+ scopes
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/plug_helper.ex b/lib/pleroma/web/plugs/plug_helper.ex
@@ -0,0 +1,40 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.PlugHelper do
+ @moduledoc "Pleroma Plug helper"
+
+ @called_plugs_list_id :called_plugs
+ def called_plugs_list_id, do: @called_plugs_list_id
+
+ @skipped_plugs_list_id :skipped_plugs
+ def skipped_plugs_list_id, do: @skipped_plugs_list_id
+
+ @doc "Returns `true` if specified plug was called."
+ def plug_called?(conn, plug_module) do
+ contained_in_private_list?(conn, @called_plugs_list_id, plug_module)
+ end
+
+ @doc "Returns `true` if specified plug was explicitly marked as skipped."
+ def plug_skipped?(conn, plug_module) do
+ contained_in_private_list?(conn, @skipped_plugs_list_id, plug_module)
+ end
+
+ @doc "Returns `true` if specified plug was either called or explicitly marked as skipped."
+ def plug_called_or_skipped?(conn, plug_module) do
+ plug_called?(conn, plug_module) || plug_skipped?(conn, plug_module)
+ end
+
+ # Appends plug to known list (skipped, called). Intended to be used from within plug code only.
+ def append_to_private_list(conn, list_id, value) do
+ list = conn.private[list_id] || []
+ modified_list = Enum.uniq(list ++ [value])
+ Plug.Conn.put_private(conn, list_id, modified_list)
+ end
+
+ defp contained_in_private_list?(conn, private_variable, value) do
+ list = conn.private[private_variable] || []
+ value in list
+ end
+end
diff --git a/lib/pleroma/web/plugs/rate_limiter.ex b/lib/pleroma/web/plugs/rate_limiter.ex
@@ -0,0 +1,267 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.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
+
+ AllowedSyntax:
+
+ plug(Pleroma.Web.Plugs.RateLimiter, name: :limiter_name)
+ plug(Pleroma.Web.Plugs.RateLimiter, options) # :name is a required option
+
+ Allowed options:
+
+ * `name` required, always used to fetch the limit values from the config
+ * `bucket_name` overrides name for counting purposes (e.g. to have a separate limit for a set of actions)
+ * `params` appends values of specified request params (e.g. ["id"]) to bucket name
+
+ Inside a controller:
+
+ plug(Pleroma.Web.Plugs.RateLimiter, [name: :one] when action == :one)
+ plug(Pleroma.Web.Plugs.RateLimiter, [name: :two] when action in [:two, :three])
+
+ plug(
+ Pleroma.Web.Plugs.RateLimiter,
+ [name: :status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]]
+ when action in ~w(fav_status unfav_status)a
+ )
+
+ or inside a router pipeline:
+
+ pipeline :api do
+ ...
+ plug(Pleroma.Web.Plugs.RateLimiter, name: :one)
+ ...
+ end
+ """
+ import Pleroma.Web.TranslationHelpers
+ import Plug.Conn
+
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor
+
+ require Logger
+
+ @doc false
+ def init(plug_opts) do
+ plug_opts
+ end
+
+ def call(conn, plug_opts) do
+ if disabled?(conn) do
+ handle_disabled(conn)
+ else
+ action_settings = action_settings(plug_opts)
+ handle(conn, action_settings)
+ end
+ end
+
+ defp handle_disabled(conn) do
+ Logger.warn(
+ "Rate limiter disabled due to forwarded IP not being found. Please ensure your reverse proxy is providing the X-Forwarded-For header or disable the RemoteIP plug/rate limiter."
+ )
+
+ conn
+ end
+
+ defp handle(conn, nil), do: conn
+
+ defp handle(conn, action_settings) do
+ action_settings
+ |> incorporate_conn_info(conn)
+ |> check_rate()
+ |> case do
+ {:ok, _count} ->
+ conn
+
+ {:error, _count} ->
+ render_throttled_error(conn)
+ end
+ end
+
+ def disabled?(conn) do
+ if Map.has_key?(conn.assigns, :remote_ip_found),
+ do: !conn.assigns.remote_ip_found,
+ else: false
+ end
+
+ @inspect_bucket_not_found {:error, :not_found}
+
+ def inspect_bucket(conn, bucket_name_root, plug_opts) do
+ with %{name: _} = action_settings <- action_settings(plug_opts) do
+ action_settings = incorporate_conn_info(action_settings, conn)
+ bucket_name = make_bucket_name(%{action_settings | name: bucket_name_root})
+ key_name = make_key_name(action_settings)
+ limit = get_limits(action_settings)
+
+ case Cachex.get(bucket_name, key_name) do
+ {:error, :no_cache} ->
+ @inspect_bucket_not_found
+
+ {:ok, nil} ->
+ {0, limit}
+
+ {:ok, value} ->
+ {value, limit - value}
+ end
+ else
+ _ -> @inspect_bucket_not_found
+ end
+ end
+
+ def action_settings(plug_opts) do
+ with limiter_name when is_atom(limiter_name) <- plug_opts[:name],
+ limits when not is_nil(limits) <- Config.get([:rate_limit, limiter_name]) do
+ bucket_name_root = Keyword.get(plug_opts, :bucket_name, limiter_name)
+
+ %{
+ name: bucket_name_root,
+ limits: limits,
+ opts: plug_opts
+ }
+ end
+ end
+
+ defp check_rate(action_settings) do
+ bucket_name = make_bucket_name(action_settings)
+ key_name = make_key_name(action_settings)
+ limit = get_limits(action_settings)
+
+ case Cachex.get_and_update(bucket_name, key_name, &increment_value(&1, limit)) do
+ {:commit, value} ->
+ {:ok, value}
+
+ {:ignore, value} ->
+ {:error, value}
+
+ {:error, :no_cache} ->
+ initialize_buckets!(action_settings)
+ check_rate(action_settings)
+ end
+ end
+
+ defp increment_value(nil, _limit), do: {:commit, 1}
+
+ defp increment_value(val, limit) when val >= limit, do: {:ignore, val}
+
+ defp increment_value(val, _limit), do: {:commit, val + 1}
+
+ defp incorporate_conn_info(action_settings, %{
+ assigns: %{user: %User{id: user_id}},
+ params: params
+ }) do
+ Map.merge(action_settings, %{
+ mode: :user,
+ conn_params: params,
+ conn_info: "#{user_id}"
+ })
+ end
+
+ defp incorporate_conn_info(action_settings, %{params: params} = conn) do
+ Map.merge(action_settings, %{
+ mode: :anon,
+ conn_params: params,
+ conn_info: "#{ip(conn)}"
+ })
+ end
+
+ defp ip(%{remote_ip: remote_ip}) do
+ remote_ip
+ |> Tuple.to_list()
+ |> Enum.join(".")
+ end
+
+ defp render_throttled_error(conn) do
+ conn
+ |> render_error(:too_many_requests, "Throttled")
+ |> halt()
+ end
+
+ defp make_key_name(action_settings) do
+ ""
+ |> attach_selected_params(action_settings)
+ |> attach_identity(action_settings)
+ end
+
+ defp get_scale(_, {scale, _}), do: scale
+
+ defp get_scale(:anon, [{scale, _}, {_, _}]), do: scale
+
+ defp get_scale(:user, [{_, _}, {scale, _}]), do: scale
+
+ defp get_limits(%{limits: {_scale, limit}}), do: limit
+
+ defp get_limits(%{mode: :user, limits: [_, {_, limit}]}), do: limit
+
+ defp get_limits(%{limits: [{_, limit}, _]}), do: limit
+
+ defp make_bucket_name(%{mode: :user, name: bucket_name_root}),
+ do: user_bucket_name(bucket_name_root)
+
+ defp make_bucket_name(%{mode: :anon, name: bucket_name_root}),
+ do: anon_bucket_name(bucket_name_root)
+
+ defp attach_selected_params(input, %{conn_params: conn_params, opts: plug_opts}) do
+ params_string =
+ plug_opts
+ |> Keyword.get(:params, [])
+ |> Enum.sort()
+ |> Enum.map(&Map.get(conn_params, &1, ""))
+ |> Enum.join(":")
+
+ [input, params_string]
+ |> Enum.join(":")
+ |> String.replace_leading(":", "")
+ end
+
+ defp initialize_buckets!(%{name: _name, limits: nil}), do: :ok
+
+ defp initialize_buckets!(%{name: name, limits: limits}) do
+ {:ok, _pid} =
+ LimiterSupervisor.add_or_return_limiter(anon_bucket_name(name), get_scale(:anon, limits))
+
+ {:ok, _pid} =
+ LimiterSupervisor.add_or_return_limiter(user_bucket_name(name), get_scale(:user, limits))
+
+ :ok
+ end
+
+ defp attach_identity(base, %{mode: :user, conn_info: conn_info}),
+ do: "user:#{base}:#{conn_info}"
+
+ defp attach_identity(base, %{mode: :anon, conn_info: conn_info}),
+ do: "ip:#{base}:#{conn_info}"
+
+ defp user_bucket_name(bucket_name_root), do: "user:#{bucket_name_root}" |> String.to_atom()
+ defp anon_bucket_name(bucket_name_root), do: "anon:#{bucket_name_root}" |> String.to_atom()
+end
diff --git a/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/limiter_supervisor.ex
@@ -0,0 +1,54 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor do
+ use DynamicSupervisor
+
+ import Cachex.Spec
+
+ def start_link(init_arg) do
+ DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__)
+ end
+
+ def add_or_return_limiter(limiter_name, expiration) do
+ result =
+ DynamicSupervisor.start_child(
+ __MODULE__,
+ %{
+ id: String.to_atom("rl_#{limiter_name}"),
+ start:
+ {Cachex, :start_link,
+ [
+ limiter_name,
+ [
+ expiration:
+ expiration(
+ default: expiration,
+ interval: check_interval(expiration),
+ lazy: true
+ )
+ ]
+ ]}
+ }
+ )
+
+ case result do
+ {:ok, _pid} = result -> result
+ {:error, {:already_started, pid}} -> {:ok, pid}
+ _ -> result
+ end
+ end
+
+ @impl true
+ def init(_init_arg) do
+ DynamicSupervisor.init(strategy: :one_for_one)
+ end
+
+ defp check_interval(exp) do
+ (exp / 2)
+ |> Kernel.trunc()
+ |> Kernel.min(5000)
+ |> Kernel.max(1)
+ end
+end
diff --git a/lib/pleroma/web/plugs/rate_limiter/supervisor.ex b/lib/pleroma/web/plugs/rate_limiter/supervisor.ex
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.RateLimiter.Supervisor do
+ use Supervisor
+
+ def start_link(opts) do
+ Supervisor.start_link(__MODULE__, opts, name: __MODULE__)
+ end
+
+ def init(_args) do
+ children = [
+ Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor
+ ]
+
+ opts = [strategy: :one_for_one, name: Pleroma.Web.Streamer.Supervisor]
+ Supervisor.init(children, opts)
+ end
+end
diff --git a/lib/pleroma/web/plugs/remote_ip.ex b/lib/pleroma/web/plugs/remote_ip.ex
@@ -0,0 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.RemoteIp do
+ @moduledoc """
+ This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
+ """
+
+ alias Pleroma.Config
+ import Plug.Conn
+
+ @behaviour Plug
+
+ def init(_), do: nil
+
+ def call(%{remote_ip: original_remote_ip} = conn, _) do
+ if Config.get([__MODULE__, :enabled]) do
+ %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts())
+ assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip)
+ else
+ conn
+ end
+ end
+
+ defp remote_ip_opts do
+ headers = Config.get([__MODULE__, :headers], []) |> MapSet.new()
+ reserved = Config.get([__MODULE__, :reserved], [])
+
+ proxies =
+ Config.get([__MODULE__, :proxies], [])
+ |> Enum.concat(reserved)
+ |> Enum.map(&maybe_add_cidr/1)
+
+ {headers, proxies}
+ end
+
+ defp maybe_add_cidr(proxy) when is_binary(proxy) do
+ proxy =
+ cond do
+ "/" in String.codepoints(proxy) -> proxy
+ InetCidr.v4?(InetCidr.parse_address!(proxy)) -> proxy <> "/32"
+ InetCidr.v6?(InetCidr.parse_address!(proxy)) -> proxy <> "/128"
+ end
+
+ InetCidr.parse(proxy, true)
+ end
+end
diff --git a/lib/pleroma/web/plugs/session_authentication_plug.ex b/lib/pleroma/web/plugs/session_authentication_plug.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.SessionAuthenticationPlug do
+ import Plug.Conn
+
+ def init(options) do
+ options
+ end
+
+ def call(conn, _) do
+ with saved_user_id <- get_session(conn, :user_id),
+ %{auth_user: %{id: ^saved_user_id}} <- conn.assigns do
+ conn
+ |> assign(:user, conn.assigns.auth_user)
+ else
+ _ -> conn
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/set_format_plug.ex b/lib/pleroma/web/plugs/set_format_plug.ex
@@ -0,0 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.SetFormatPlug do
+ import Plug.Conn, only: [assign: 3, fetch_query_params: 1]
+
+ def init(_), do: nil
+
+ def call(conn, _) do
+ case get_format(conn) do
+ nil -> conn
+ format -> assign(conn, :format, format)
+ end
+ end
+
+ defp get_format(conn) do
+ conn.private[:phoenix_format] ||
+ case fetch_query_params(conn) do
+ %{query_params: %{"_format" => format}} -> format
+ _ -> nil
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/set_locale_plug.ex b/lib/pleroma/web/plugs/set_locale_plug.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+# NOTE: this module is based on https://github.com/smeevil/set_locale
+defmodule Pleroma.Web.Plugs.SetLocalePlug do
+ import Plug.Conn, only: [get_req_header: 2, assign: 3]
+
+ def init(_), do: nil
+
+ def call(conn, _) do
+ locale = get_locale_from_header(conn) || Gettext.get_locale()
+ Gettext.put_locale(locale)
+ assign(conn, :locale, locale)
+ end
+
+ defp get_locale_from_header(conn) do
+ conn
+ |> extract_accept_language()
+ |> Enum.find(&supported_locale?/1)
+ end
+
+ defp extract_accept_language(conn) do
+ case get_req_header(conn, "accept-language") do
+ [value | _] ->
+ value
+ |> String.split(",")
+ |> Enum.map(&parse_language_option/1)
+ |> Enum.sort(&(&1.quality > &2.quality))
+ |> Enum.map(& &1.tag)
+ |> Enum.reject(&is_nil/1)
+ |> ensure_language_fallbacks()
+
+ _ ->
+ []
+ end
+ end
+
+ defp supported_locale?(locale) do
+ Pleroma.Web.Gettext
+ |> Gettext.known_locales()
+ |> Enum.member?(locale)
+ end
+
+ defp parse_language_option(string) do
+ captures = Regex.named_captures(~r/^\s?(?<tag>[\w\-]+)(?:;q=(?<quality>[\d\.]+))?$/i, string)
+
+ quality =
+ case Float.parse(captures["quality"] || "1.0") do
+ {val, _} -> val
+ :error -> 1.0
+ end
+
+ %{tag: captures["tag"], quality: quality}
+ end
+
+ defp ensure_language_fallbacks(tags) do
+ Enum.flat_map(tags, fn tag ->
+ [language | _] = String.split(tag, "-")
+ if Enum.member?(tags, language), do: [tag], else: [tag, language]
+ end)
+ end
+end
diff --git a/lib/pleroma/web/plugs/set_user_session_id_plug.ex b/lib/pleroma/web/plugs/set_user_session_id_plug.ex
@@ -0,0 +1,19 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.SetUserSessionIdPlug do
+ import Plug.Conn
+ alias Pleroma.User
+
+ def init(opts) do
+ opts
+ end
+
+ def call(%{assigns: %{user: %User{id: id}}} = conn, _) do
+ conn
+ |> put_session(:user_id, id)
+ end
+
+ def call(conn, _), do: conn
+end
diff --git a/lib/pleroma/web/plugs/static_fe_plug.ex b/lib/pleroma/web/plugs/static_fe_plug.ex
@@ -0,0 +1,26 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.StaticFEPlug do
+ import Plug.Conn
+ alias Pleroma.Web.StaticFE.StaticFEController
+
+ def init(options), do: options
+
+ def call(conn, _) do
+ if enabled?() and requires_html?(conn) do
+ conn
+ |> StaticFEController.call(:show)
+ |> halt()
+ else
+ conn
+ end
+ end
+
+ defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false)
+
+ defp requires_html?(conn) do
+ Phoenix.Controller.get_format(conn) == "html"
+ end
+end
diff --git a/lib/pleroma/web/plugs/trailing_format_plug.ex b/lib/pleroma/web/plugs/trailing_format_plug.ex
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.TrailingFormatPlug do
+ @moduledoc "Calls TrailingFormatPlug for specific paths. Ideally we would just do this in the router, but TrailingFormatPlug needs to be called before Plug.Parsers."
+
+ @behaviour Plug
+ @paths [
+ "/api/statusnet",
+ "/api/statuses",
+ "/api/qvitter",
+ "/api/search",
+ "/api/account",
+ "/api/friends",
+ "/api/mutes",
+ "/api/media",
+ "/api/favorites",
+ "/api/blocks",
+ "/api/friendships",
+ "/api/users",
+ "/users",
+ "/nodeinfo",
+ "/api/help",
+ "/api/externalprofile",
+ "/notice",
+ "/api/pleroma/emoji",
+ "/api/oauth_tokens"
+ ]
+
+ def init(opts) do
+ TrailingFormatPlug.init(opts)
+ end
+
+ for path <- @paths do
+ def call(%{request_path: unquote(path) <> _} = conn, opts) do
+ TrailingFormatPlug.call(conn, opts)
+ end
+ end
+
+ def call(conn, _opts), do: conn
+end
diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex
@@ -0,0 +1,107 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UploadedMedia do
+ @moduledoc """
+ """
+
+ import Plug.Conn
+ import Pleroma.Web.Gettext
+ require Logger
+
+ alias Pleroma.Web.MediaProxy
+
+ @behaviour Plug
+ # no slashes
+ @path "media"
+
+ @default_cache_control_header "public, max-age=1209600"
+
+ def init(_opts) do
+ static_plug_opts =
+ [
+ headers: %{"cache-control" => @default_cache_control_header},
+ cache_control_for_etags: @default_cache_control_header
+ ]
+ |> Keyword.put(:from, "__unconfigured_media_plug")
+ |> Keyword.put(:at, "/__unconfigured_media_plug")
+ |> Plug.Static.init()
+
+ %{static_plug_opts: static_plug_opts}
+ end
+
+ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
+ conn =
+ case fetch_query_params(conn) do
+ %{query_params: %{"name" => name}} = conn ->
+ name = String.replace(name, "\"", "\\\"")
+
+ put_resp_header(conn, "content-disposition", "filename=\"#{name}\"")
+
+ conn ->
+ conn
+ end
+ |> merge_resp_headers([{"content-security-policy", "sandbox"}])
+
+ config = Pleroma.Config.get(Pleroma.Upload)
+
+ with uploader <- Keyword.fetch!(config, :uploader),
+ proxy_remote = Keyword.get(config, :proxy_remote, false),
+ {:ok, get_method} <- uploader.get_file(file),
+ false <- media_is_banned(conn, get_method) do
+ get_media(conn, get_method, proxy_remote, opts)
+ else
+ _ ->
+ conn
+ |> send_resp(:internal_server_error, dgettext("errors", "Failed"))
+ |> halt()
+ end
+ end
+
+ def call(conn, _opts), do: conn
+
+ defp media_is_banned(%{request_path: path} = _conn, {:static_dir, _}) do
+ MediaProxy.in_banned_urls(Pleroma.Web.base_url() <> path)
+ end
+
+ defp media_is_banned(_, {:url, url}), do: MediaProxy.in_banned_urls(url)
+
+ defp media_is_banned(_, _), do: false
+
+ defp get_media(conn, {:static_dir, directory}, _, opts) do
+ static_opts =
+ Map.get(opts, :static_plug_opts)
+ |> Map.put(:at, [@path])
+ |> Map.put(:from, directory)
+
+ conn = Plug.Static.call(conn, static_opts)
+
+ if conn.halted do
+ conn
+ else
+ conn
+ |> send_resp(:not_found, dgettext("errors", "Not found"))
+ |> halt()
+ end
+ end
+
+ defp get_media(conn, {:url, url}, true, _) do
+ conn
+ |> Pleroma.ReverseProxy.call(url, Pleroma.Config.get([Pleroma.Upload, :proxy_opts], []))
+ end
+
+ defp get_media(conn, {:url, url}, _, _) do
+ conn
+ |> Phoenix.Controller.redirect(external: url)
+ |> halt()
+ end
+
+ defp get_media(conn, unknown, _, _) do
+ Logger.error("#{__MODULE__}: Unknown get startegy: #{inspect(unknown)}")
+
+ conn
+ |> send_resp(:internal_server_error, dgettext("errors", "Internal Error"))
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/plugs/user_enabled_plug.ex b/lib/pleroma/web/plugs/user_enabled_plug.ex
@@ -0,0 +1,23 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserEnabledPlug do
+ import Plug.Conn
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{user: %User{} = user}} = conn, _) do
+ case User.account_status(user) do
+ :active -> conn
+ _ -> assign(conn, :user, nil)
+ end
+ end
+
+ def call(conn, _) do
+ conn
+ end
+end
diff --git a/lib/pleroma/web/plugs/user_fetcher_plug.ex b/lib/pleroma/web/plugs/user_fetcher_plug.ex
@@ -0,0 +1,21 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserFetcherPlug do
+ alias Pleroma.User
+ import Plug.Conn
+
+ def init(options) do
+ options
+ end
+
+ def call(conn, _options) do
+ with %{auth_credentials: %{username: username}} <- conn.assigns,
+ %User{} = user <- User.get_by_nickname_or_email(username) do
+ assign(conn, :auth_user, user)
+ else
+ _ -> conn
+ end
+ end
+end
diff --git a/lib/pleroma/web/plugs/user_is_admin_plug.ex b/lib/pleroma/web/plugs/user_is_admin_plug.ex
@@ -0,0 +1,24 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserIsAdminPlug do
+ import Pleroma.Web.TranslationHelpers
+ import Plug.Conn
+
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _) do
+ conn
+ end
+
+ def call(conn, _) do
+ conn
+ |> render_error(:forbidden, "User is not an admin.")
+ |> halt()
+ end
+end
diff --git a/lib/pleroma/web/preload/instance.ex b/lib/pleroma/web/preload/instance.ex
@@ -1,59 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Preload.Providers.Instance do
- alias Pleroma.Plugs.InstanceStatic
- alias Pleroma.Web.MastodonAPI.InstanceView
- alias Pleroma.Web.Nodeinfo.Nodeinfo
- alias Pleroma.Web.Preload.Providers.Provider
- alias Pleroma.Web.TwitterAPI.UtilView
-
- @behaviour Provider
- @instance_url "/api/v1/instance"
- @panel_url "/instance/panel.html"
- @nodeinfo_url "/nodeinfo/2.0.json"
- @fe_config_url "/api/pleroma/frontend_configurations"
-
- @impl Provider
- def generate_terms(_params) do
- %{}
- |> build_info_tag()
- |> build_panel_tag()
- |> build_nodeinfo_tag()
- |> build_fe_config_tag()
- end
-
- defp build_info_tag(acc) do
- info_data = InstanceView.render("show.json", %{})
-
- Map.put(acc, @instance_url, info_data)
- end
-
- defp build_panel_tag(acc) do
- instance_path = InstanceStatic.file_path(@panel_url |> to_string())
-
- if File.exists?(instance_path) do
- panel_data = File.read!(instance_path)
- Map.put(acc, @panel_url, panel_data)
- else
- acc
- end
- end
-
- defp build_nodeinfo_tag(acc) do
- case Nodeinfo.get_nodeinfo("2.0") do
- {:error, _} ->
- acc
-
- nodeinfo_data ->
- Map.put(acc, @nodeinfo_url, nodeinfo_data)
- end
- end
-
- defp build_fe_config_tag(acc) do
- fe_data = UtilView.render("frontend_configurations.json", %{})
-
- Map.put(acc, @fe_config_url, fe_data)
- end
-end
diff --git a/lib/pleroma/web/preload/providers/instance.ex b/lib/pleroma/web/preload/providers/instance.ex
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Preload.Providers.Instance do
+ alias Pleroma.Web.MastodonAPI.InstanceView
+ alias Pleroma.Web.Nodeinfo.Nodeinfo
+ alias Pleroma.Web.Plugs.InstanceStatic
+ alias Pleroma.Web.Preload.Providers.Provider
+ alias Pleroma.Web.TwitterAPI.UtilView
+
+ @behaviour Provider
+ @instance_url "/api/v1/instance"
+ @panel_url "/instance/panel.html"
+ @nodeinfo_url "/nodeinfo/2.0.json"
+ @fe_config_url "/api/pleroma/frontend_configurations"
+
+ @impl Provider
+ def generate_terms(_params) do
+ %{}
+ |> build_info_tag()
+ |> build_panel_tag()
+ |> build_nodeinfo_tag()
+ |> build_fe_config_tag()
+ end
+
+ defp build_info_tag(acc) do
+ info_data = InstanceView.render("show.json", %{})
+
+ Map.put(acc, @instance_url, info_data)
+ end
+
+ defp build_panel_tag(acc) do
+ instance_path = InstanceStatic.file_path(@panel_url |> to_string())
+
+ if File.exists?(instance_path) do
+ panel_data = File.read!(instance_path)
+ Map.put(acc, @panel_url, panel_data)
+ else
+ acc
+ end
+ end
+
+ defp build_nodeinfo_tag(acc) do
+ case Nodeinfo.get_nodeinfo("2.0") do
+ {:error, _} ->
+ acc
+
+ nodeinfo_data ->
+ Map.put(acc, @nodeinfo_url, nodeinfo_data)
+ end
+ end
+
+ defp build_fe_config_tag(acc) do
+ fe_data = UtilView.render("frontend_configurations.json", %{})
+
+ Map.put(acc, @fe_config_url, fe_data)
+ end
+end
diff --git a/lib/pleroma/web/preload/provider.ex b/lib/pleroma/web/preload/providers/provider.ex
diff --git a/lib/pleroma/web/preload/timelines.ex b/lib/pleroma/web/preload/providers/timelines.ex
diff --git a/lib/pleroma/web/preload/user.ex b/lib/pleroma/web/preload/providers/user.ex
diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push.ex
diff --git a/lib/pleroma/web/rich_media/parser/ttl.ex b/lib/pleroma/web/rich_media/parser/ttl.ex
@@ -0,0 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.Parser.TTL do
+ @callback ttl(Map.t(), String.t()) :: Integer.t() | nil
+end
diff --git a/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parser/ttl/aws_signed_url.ex
@@ -0,0 +1,50 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do
+ @behaviour Pleroma.Web.RichMedia.Parser.TTL
+
+ @impl true
+ def ttl(data, _url) do
+ image = Map.get(data, :image)
+
+ if is_aws_signed_url(image) do
+ image
+ |> parse_query_params()
+ |> format_query_params()
+ |> get_expiration_timestamp()
+ else
+ {:error, "Not aws signed url #{inspect(image)}"}
+ end
+ end
+
+ defp is_aws_signed_url(image) when is_binary(image) and image != "" do
+ %URI{host: host, query: query} = URI.parse(image)
+
+ String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires")
+ end
+
+ defp is_aws_signed_url(_), do: nil
+
+ defp parse_query_params(image) do
+ %URI{query: query} = URI.parse(image)
+ query
+ end
+
+ defp format_query_params(query) do
+ query
+ |> String.split(~r/&|=/)
+ |> Enum.chunk_every(2)
+ |> Map.new(fn [k, v] -> {k, v} end)
+ end
+
+ defp get_expiration_timestamp(params) when is_map(params) do
+ {:ok, date} =
+ params
+ |> Map.get("X-Amz-Date")
+ |> Timex.parse("{ISO:Basic:Z}")
+
+ {:ok, Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))}
+ end
+end
diff --git a/lib/pleroma/web/rich_media/parsers/oembed_parser.ex b/lib/pleroma/web/rich_media/parsers/o_embed.ex
diff --git a/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex b/lib/pleroma/web/rich_media/parsers/ttl/aws_signed_url.ex
@@ -1,50 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do
- @behaviour Pleroma.Web.RichMedia.Parser.TTL
-
- @impl Pleroma.Web.RichMedia.Parser.TTL
- def ttl(data, _url) do
- image = Map.get(data, :image)
-
- if is_aws_signed_url(image) do
- image
- |> parse_query_params()
- |> format_query_params()
- |> get_expiration_timestamp()
- else
- {:error, "Not aws signed url #{inspect(image)}"}
- end
- end
-
- defp is_aws_signed_url(image) when is_binary(image) and image != "" do
- %URI{host: host, query: query} = URI.parse(image)
-
- String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires")
- end
-
- defp is_aws_signed_url(_), do: nil
-
- defp parse_query_params(image) do
- %URI{query: query} = URI.parse(image)
- query
- end
-
- defp format_query_params(query) do
- query
- |> String.split(~r/&|=/)
- |> Enum.chunk_every(2)
- |> Map.new(fn [k, v] -> {k, v} end)
- end
-
- defp get_expiration_timestamp(params) when is_map(params) do
- {:ok, date} =
- params
- |> Map.get("X-Amz-Date")
- |> Timex.parse("{ISO:Basic:Z}")
-
- {:ok, Timex.to_unix(date) + String.to_integer(Map.get(params, "X-Amz-Expires"))}
- end
-end
diff --git a/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex b/lib/pleroma/web/rich_media/parsers/ttl/ttl.ex
@@ -1,7 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.RichMedia.Parser.TTL do
- @callback ttl(Map.t(), String.t()) :: {:ok, Integer.t()} | {:error, String.t()}
-end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
@@ -12,31 +12,31 @@ defmodule Pleroma.Web.Router do
pipeline :oauth do
plug(:fetch_session)
- plug(Pleroma.Plugs.OAuthPlug)
- plug(Pleroma.Plugs.UserEnabledPlug)
+ plug(Pleroma.Web.Plugs.OAuthPlug)
+ plug(Pleroma.Web.Plugs.UserEnabledPlug)
end
pipeline :expect_authentication do
- plug(Pleroma.Plugs.ExpectAuthenticatedCheckPlug)
+ plug(Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug)
end
pipeline :expect_public_instance_or_authentication do
- plug(Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug)
+ plug(Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug)
end
pipeline :authenticate do
- plug(Pleroma.Plugs.OAuthPlug)
- plug(Pleroma.Plugs.BasicAuthDecoderPlug)
- plug(Pleroma.Plugs.UserFetcherPlug)
- plug(Pleroma.Plugs.SessionAuthenticationPlug)
- plug(Pleroma.Plugs.LegacyAuthenticationPlug)
- plug(Pleroma.Plugs.AuthenticationPlug)
+ plug(Pleroma.Web.Plugs.OAuthPlug)
+ plug(Pleroma.Web.Plugs.BasicAuthDecoderPlug)
+ plug(Pleroma.Web.Plugs.UserFetcherPlug)
+ plug(Pleroma.Web.Plugs.SessionAuthenticationPlug)
+ plug(Pleroma.Web.Plugs.LegacyAuthenticationPlug)
+ plug(Pleroma.Web.Plugs.AuthenticationPlug)
end
pipeline :after_auth do
- plug(Pleroma.Plugs.UserEnabledPlug)
- plug(Pleroma.Plugs.SetUserSessionIdPlug)
- plug(Pleroma.Plugs.EnsureUserKeyPlug)
+ plug(Pleroma.Web.Plugs.UserEnabledPlug)
+ plug(Pleroma.Web.Plugs.SetUserSessionIdPlug)
+ plug(Pleroma.Web.Plugs.EnsureUserKeyPlug)
end
pipeline :base_api do
@@ -50,25 +50,25 @@ defmodule Pleroma.Web.Router do
plug(:expect_public_instance_or_authentication)
plug(:base_api)
plug(:after_auth)
- plug(Pleroma.Plugs.IdempotencyPlug)
+ plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
pipeline :authenticated_api do
plug(:expect_authentication)
plug(:base_api)
plug(:after_auth)
- plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
- plug(Pleroma.Plugs.IdempotencyPlug)
+ plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug)
+ plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
pipeline :admin_api do
plug(:expect_authentication)
plug(:base_api)
- plug(Pleroma.Plugs.AdminSecretAuthenticationPlug)
+ plug(Pleroma.Web.Plugs.AdminSecretAuthenticationPlug)
plug(:after_auth)
- plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
- plug(Pleroma.Plugs.UserIsAdminPlug)
- plug(Pleroma.Plugs.IdempotencyPlug)
+ plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug)
+ plug(Pleroma.Web.Plugs.UserIsAdminPlug)
+ plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
pipeline :mastodon_html do
@@ -80,7 +80,7 @@ defmodule Pleroma.Web.Router do
pipeline :pleroma_html do
plug(:browser)
plug(:authenticate)
- plug(Pleroma.Plugs.EnsureUserKeyPlug)
+ plug(Pleroma.Web.Plugs.EnsureUserKeyPlug)
end
pipeline :well_known do
@@ -568,7 +568,7 @@ defmodule Pleroma.Web.Router do
pipeline :ostatus do
plug(:accepts, ["html", "xml", "rss", "atom", "activity+json", "json"])
- plug(Pleroma.Plugs.StaticFEPlug)
+ plug(Pleroma.Web.Plugs.StaticFEPlug)
end
pipeline :oembed do
@@ -737,7 +737,7 @@ defmodule Pleroma.Web.Router do
get("/check_password", MongooseIMController, :check_password)
end
- scope "/", Fallback do
+ scope "/", Pleroma.Web.Fallback do
get("/registration/:token", RedirectController, :registration_page)
get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
get("/api*path", RedirectController, :api_not_implemented)
diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex
@@ -17,8 +17,8 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
plug(:assign_id)
- plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
- unless_func: &Pleroma.Web.FederatingPlug.federating?/1
+ plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug,
+ unless_func: &Pleroma.Web.Plugs.FederatingPlug.federating?/1
)
@page_keys ["max_id", "min_id", "limit", "since_id", "order"]
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
@@ -0,0 +1,331 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Streamer do
+ require Logger
+
+ alias Pleroma.Activity
+ alias Pleroma.Chat.MessageReference
+ alias Pleroma.Config
+ alias Pleroma.Conversation.Participation
+ alias Pleroma.Notification
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.StreamerView
+
+ @mix_env Mix.env()
+ @registry Pleroma.Web.StreamerRegistry
+
+ def registry, do: @registry
+
+ @public_streams ["public", "public:local", "public:media", "public:local:media"]
+ @user_streams ["user", "user:notification", "direct", "user:pleroma_chat"]
+
+ @doc "Expands and authorizes a stream, and registers the process for streaming."
+ @spec get_topic_and_add_socket(
+ stream :: String.t(),
+ User.t() | nil,
+ Token.t() | nil,
+ Map.t() | nil
+ ) ::
+ {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
+ def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
+ case get_topic(stream, user, oauth_token, params) do
+ {:ok, topic} -> add_socket(topic, user)
+ error -> error
+ end
+ end
+
+ @doc "Expand and authorizes a stream"
+ @spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) ::
+ {:ok, topic :: String.t()} | {:error, :bad_topic}
+ def get_topic(stream, user, oauth_token, params \\ %{})
+
+ # Allow all public steams.
+ def get_topic(stream, _user, _oauth_token, _params) when stream in @public_streams do
+ {:ok, stream}
+ end
+
+ # Allow all hashtags streams.
+ def get_topic("hashtag", _user, _oauth_token, %{"tag" => tag} = _params) do
+ {:ok, "hashtag:" <> tag}
+ end
+
+ # Expand user streams.
+ def get_topic(
+ stream,
+ %User{id: user_id} = user,
+ %Token{user_id: token_user_id} = oauth_token,
+ _params
+ )
+ when stream in @user_streams and user_id == token_user_id do
+ # Note: "read" works for all user streams (not mentioning it since it's an ancestor scope)
+ required_scopes =
+ if stream == "user:notification" do
+ ["read:notifications"]
+ else
+ ["read:statuses"]
+ end
+
+ if OAuthScopesPlug.filter_descendants(required_scopes, oauth_token.scopes) == [] do
+ {:error, :unauthorized}
+ else
+ {:ok, stream <> ":" <> to_string(user.id)}
+ end
+ end
+
+ def get_topic(stream, _user, _oauth_token, _params) when stream in @user_streams do
+ {:error, :unauthorized}
+ end
+
+ # List streams.
+ def get_topic(
+ "list",
+ %User{id: user_id} = user,
+ %Token{user_id: token_user_id} = oauth_token,
+ %{"list" => id}
+ )
+ when user_id == token_user_id do
+ cond do
+ OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] ->
+ {:error, :unauthorized}
+
+ Pleroma.List.get(id, user) ->
+ {:ok, "list:" <> to_string(id)}
+
+ true ->
+ {:error, :bad_topic}
+ end
+ end
+
+ def get_topic("list", _user, _oauth_token, _params) do
+ {:error, :unauthorized}
+ end
+
+ def get_topic(_stream, _user, _oauth_token, _params) do
+ {:error, :bad_topic}
+ end
+
+ @doc "Registers the process for streaming. Use `get_topic/3` to get the full authorized topic."
+ def add_socket(topic, user) do
+ if should_env_send?() do
+ auth? = if user, do: true
+ Registry.register(@registry, topic, auth?)
+ end
+
+ {:ok, topic}
+ end
+
+ def remove_socket(topic) do
+ if should_env_send?(), do: Registry.unregister(@registry, topic)
+ end
+
+ def stream(topics, items) do
+ if should_env_send?() do
+ List.wrap(topics)
+ |> Enum.each(fn topic ->
+ List.wrap(items)
+ |> Enum.each(fn item ->
+ spawn(fn -> do_stream(topic, item) end)
+ end)
+ end)
+ end
+
+ :ok
+ end
+
+ def filtered_by_user?(user, item, streamed_type \\ :activity)
+
+ def filtered_by_user?(%User{} = user, %Activity{} = item, streamed_type) do
+ %{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
+ User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute])
+
+ recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids)
+ recipients = MapSet.new(item.recipients)
+ domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
+
+ with parent <- Object.normalize(item) || item,
+ true <-
+ Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
+ true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
+ true <-
+ !(streamed_type == :activity && item.data["type"] == "Announce" &&
+ parent.data["actor"] == user.ap_id),
+ true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
+ true <- MapSet.disjoint?(recipients, recipient_blocks),
+ %{host: item_host} <- URI.parse(item.actor),
+ %{host: parent_host} <- URI.parse(parent.data["actor"]),
+ false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
+ false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host),
+ true <- thread_containment(item, user),
+ false <- CommonAPI.thread_muted?(user, parent) do
+ false
+ else
+ _ -> true
+ end
+ end
+
+ def filtered_by_user?(%User{} = user, %Notification{activity: activity}, _) do
+ filtered_by_user?(user, activity, :notification)
+ end
+
+ defp do_stream("direct", item) do
+ recipient_topics =
+ User.get_recipients_from_activity(item)
+ |> Enum.map(fn %{id: id} -> "direct:#{id}" end)
+
+ Enum.each(recipient_topics, fn user_topic ->
+ Logger.debug("Trying to push direct message to #{user_topic}\n\n")
+ push_to_socket(user_topic, item)
+ end)
+ end
+
+ defp do_stream("participation", participation) do
+ user_topic = "direct:#{participation.user_id}"
+ Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n")
+
+ push_to_socket(user_topic, participation)
+ end
+
+ defp do_stream("list", item) do
+ # filter the recipient list if the activity is not public, see #270.
+ recipient_lists =
+ case Visibility.is_public?(item) do
+ true ->
+ Pleroma.List.get_lists_from_activity(item)
+
+ _ ->
+ Pleroma.List.get_lists_from_activity(item)
+ |> Enum.filter(fn list ->
+ owner = User.get_cached_by_id(list.user_id)
+
+ Visibility.visible_for_user?(item, owner)
+ end)
+ end
+
+ recipient_topics =
+ recipient_lists
+ |> Enum.map(fn %{id: id} -> "list:#{id}" end)
+
+ Enum.each(recipient_topics, fn list_topic ->
+ Logger.debug("Trying to push message to #{list_topic}\n\n")
+ push_to_socket(list_topic, item)
+ end)
+ end
+
+ defp do_stream(topic, %Notification{} = item)
+ when topic in ["user", "user:notification"] do
+ Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list ->
+ Enum.each(list, fn {pid, _auth} ->
+ send(pid, {:render_with_user, StreamerView, "notification.json", item})
+ end)
+ end)
+ end
+
+ defp do_stream(topic, {user, %MessageReference{} = cm_ref})
+ when topic in ["user", "user:pleroma_chat"] do
+ topic = "#{topic}:#{user.id}"
+
+ text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
+
+ Registry.dispatch(@registry, topic, fn list ->
+ Enum.each(list, fn {pid, _auth} ->
+ send(pid, {:text, text})
+ end)
+ end)
+ end
+
+ defp do_stream("user", item) do
+ Logger.debug("Trying to push to users")
+
+ recipient_topics =
+ User.get_recipients_from_activity(item)
+ |> Enum.map(fn %{id: id} -> "user:#{id}" end)
+
+ Enum.each(recipient_topics, fn topic ->
+ push_to_socket(topic, item)
+ end)
+ end
+
+ defp do_stream(topic, item) do
+ Logger.debug("Trying to push to #{topic}")
+ Logger.debug("Pushing item to #{topic}")
+ push_to_socket(topic, item)
+ end
+
+ defp push_to_socket(topic, %Participation{} = participation) do
+ rendered = StreamerView.render("conversation.json", participation)
+
+ Registry.dispatch(@registry, topic, fn list ->
+ Enum.each(list, fn {pid, _} ->
+ send(pid, {:text, rendered})
+ end)
+ end)
+ end
+
+ defp push_to_socket(topic, %Activity{
+ data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id}
+ }) do
+ rendered = Jason.encode!(%{event: "delete", payload: to_string(deleted_activity_id)})
+
+ Registry.dispatch(@registry, topic, fn list ->
+ Enum.each(list, fn {pid, _} ->
+ send(pid, {:text, rendered})
+ end)
+ end)
+ end
+
+ defp push_to_socket(_topic, %Activity{data: %{"type" => "Delete"}}), do: :noop
+
+ defp push_to_socket(topic, item) do
+ anon_render = StreamerView.render("update.json", item)
+
+ Registry.dispatch(@registry, topic, fn list ->
+ Enum.each(list, fn {pid, auth?} ->
+ if auth? do
+ send(pid, {:render_with_user, StreamerView, "update.json", item})
+ else
+ send(pid, {:text, anon_render})
+ end
+ end)
+ end)
+ end
+
+ defp thread_containment(_activity, %User{skip_thread_containment: true}), do: true
+
+ defp thread_containment(activity, user) do
+ if Config.get([:instance, :skip_thread_containment]) do
+ true
+ else
+ ActivityPub.contain_activity(activity, user)
+ end
+ end
+
+ # In test environement, only return true if the registry is started.
+ # In benchmark environment, returns false.
+ # In any other environment, always returns true.
+ cond do
+ @mix_env == :test ->
+ def should_env_send? do
+ case Process.whereis(@registry) do
+ nil ->
+ false
+
+ pid ->
+ Process.alive?(pid)
+ end
+ end
+
+ @mix_env == :benchmark ->
+ def should_env_send?, do: false
+
+ true ->
+ def should_env_send?, do: true
+ end
+end
diff --git a/lib/pleroma/web/streamer/streamer.ex b/lib/pleroma/web/streamer/streamer.ex
@@ -1,331 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Streamer do
- require Logger
-
- alias Pleroma.Activity
- alias Pleroma.Chat.MessageReference
- alias Pleroma.Config
- alias Pleroma.Conversation.Participation
- alias Pleroma.Notification
- alias Pleroma.Object
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Visibility
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.OAuth.Token
- alias Pleroma.Web.StreamerView
-
- @mix_env Mix.env()
- @registry Pleroma.Web.StreamerRegistry
-
- def registry, do: @registry
-
- @public_streams ["public", "public:local", "public:media", "public:local:media"]
- @user_streams ["user", "user:notification", "direct", "user:pleroma_chat"]
-
- @doc "Expands and authorizes a stream, and registers the process for streaming."
- @spec get_topic_and_add_socket(
- stream :: String.t(),
- User.t() | nil,
- Token.t() | nil,
- Map.t() | nil
- ) ::
- {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
- def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
- case get_topic(stream, user, oauth_token, params) do
- {:ok, topic} -> add_socket(topic, user)
- error -> error
- end
- end
-
- @doc "Expand and authorizes a stream"
- @spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) ::
- {:ok, topic :: String.t()} | {:error, :bad_topic}
- def get_topic(stream, user, oauth_token, params \\ %{})
-
- # Allow all public steams.
- def get_topic(stream, _user, _oauth_token, _params) when stream in @public_streams do
- {:ok, stream}
- end
-
- # Allow all hashtags streams.
- def get_topic("hashtag", _user, _oauth_token, %{"tag" => tag} = _params) do
- {:ok, "hashtag:" <> tag}
- end
-
- # Expand user streams.
- def get_topic(
- stream,
- %User{id: user_id} = user,
- %Token{user_id: token_user_id} = oauth_token,
- _params
- )
- when stream in @user_streams and user_id == token_user_id do
- # Note: "read" works for all user streams (not mentioning it since it's an ancestor scope)
- required_scopes =
- if stream == "user:notification" do
- ["read:notifications"]
- else
- ["read:statuses"]
- end
-
- if OAuthScopesPlug.filter_descendants(required_scopes, oauth_token.scopes) == [] do
- {:error, :unauthorized}
- else
- {:ok, stream <> ":" <> to_string(user.id)}
- end
- end
-
- def get_topic(stream, _user, _oauth_token, _params) when stream in @user_streams do
- {:error, :unauthorized}
- end
-
- # List streams.
- def get_topic(
- "list",
- %User{id: user_id} = user,
- %Token{user_id: token_user_id} = oauth_token,
- %{"list" => id}
- )
- when user_id == token_user_id do
- cond do
- OAuthScopesPlug.filter_descendants(["read", "read:lists"], oauth_token.scopes) == [] ->
- {:error, :unauthorized}
-
- Pleroma.List.get(id, user) ->
- {:ok, "list:" <> to_string(id)}
-
- true ->
- {:error, :bad_topic}
- end
- end
-
- def get_topic("list", _user, _oauth_token, _params) do
- {:error, :unauthorized}
- end
-
- def get_topic(_stream, _user, _oauth_token, _params) do
- {:error, :bad_topic}
- end
-
- @doc "Registers the process for streaming. Use `get_topic/3` to get the full authorized topic."
- def add_socket(topic, user) do
- if should_env_send?() do
- auth? = if user, do: true
- Registry.register(@registry, topic, auth?)
- end
-
- {:ok, topic}
- end
-
- def remove_socket(topic) do
- if should_env_send?(), do: Registry.unregister(@registry, topic)
- end
-
- def stream(topics, items) do
- if should_env_send?() do
- List.wrap(topics)
- |> Enum.each(fn topic ->
- List.wrap(items)
- |> Enum.each(fn item ->
- spawn(fn -> do_stream(topic, item) end)
- end)
- end)
- end
-
- :ok
- end
-
- def filtered_by_user?(user, item, streamed_type \\ :activity)
-
- def filtered_by_user?(%User{} = user, %Activity{} = item, streamed_type) do
- %{block: blocked_ap_ids, mute: muted_ap_ids, reblog_mute: reblog_muted_ap_ids} =
- User.outgoing_relationships_ap_ids(user, [:block, :mute, :reblog_mute])
-
- recipient_blocks = MapSet.new(blocked_ap_ids ++ muted_ap_ids)
- recipients = MapSet.new(item.recipients)
- domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks)
-
- with parent <- Object.normalize(item) || item,
- true <-
- Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
- true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
- true <-
- !(streamed_type == :activity && item.data["type"] == "Announce" &&
- parent.data["actor"] == user.ap_id),
- true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
- true <- MapSet.disjoint?(recipients, recipient_blocks),
- %{host: item_host} <- URI.parse(item.actor),
- %{host: parent_host} <- URI.parse(parent.data["actor"]),
- false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
- false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host),
- true <- thread_containment(item, user),
- false <- CommonAPI.thread_muted?(user, parent) do
- false
- else
- _ -> true
- end
- end
-
- def filtered_by_user?(%User{} = user, %Notification{activity: activity}, _) do
- filtered_by_user?(user, activity, :notification)
- end
-
- defp do_stream("direct", item) do
- recipient_topics =
- User.get_recipients_from_activity(item)
- |> Enum.map(fn %{id: id} -> "direct:#{id}" end)
-
- Enum.each(recipient_topics, fn user_topic ->
- Logger.debug("Trying to push direct message to #{user_topic}\n\n")
- push_to_socket(user_topic, item)
- end)
- end
-
- defp do_stream("participation", participation) do
- user_topic = "direct:#{participation.user_id}"
- Logger.debug("Trying to push a conversation participation to #{user_topic}\n\n")
-
- push_to_socket(user_topic, participation)
- end
-
- defp do_stream("list", item) do
- # filter the recipient list if the activity is not public, see #270.
- recipient_lists =
- case Visibility.is_public?(item) do
- true ->
- Pleroma.List.get_lists_from_activity(item)
-
- _ ->
- Pleroma.List.get_lists_from_activity(item)
- |> Enum.filter(fn list ->
- owner = User.get_cached_by_id(list.user_id)
-
- Visibility.visible_for_user?(item, owner)
- end)
- end
-
- recipient_topics =
- recipient_lists
- |> Enum.map(fn %{id: id} -> "list:#{id}" end)
-
- Enum.each(recipient_topics, fn list_topic ->
- Logger.debug("Trying to push message to #{list_topic}\n\n")
- push_to_socket(list_topic, item)
- end)
- end
-
- defp do_stream(topic, %Notification{} = item)
- when topic in ["user", "user:notification"] do
- Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list ->
- Enum.each(list, fn {pid, _auth} ->
- send(pid, {:render_with_user, StreamerView, "notification.json", item})
- end)
- end)
- end
-
- defp do_stream(topic, {user, %MessageReference{} = cm_ref})
- when topic in ["user", "user:pleroma_chat"] do
- topic = "#{topic}:#{user.id}"
-
- text = StreamerView.render("chat_update.json", %{chat_message_reference: cm_ref})
-
- Registry.dispatch(@registry, topic, fn list ->
- Enum.each(list, fn {pid, _auth} ->
- send(pid, {:text, text})
- end)
- end)
- end
-
- defp do_stream("user", item) do
- Logger.debug("Trying to push to users")
-
- recipient_topics =
- User.get_recipients_from_activity(item)
- |> Enum.map(fn %{id: id} -> "user:#{id}" end)
-
- Enum.each(recipient_topics, fn topic ->
- push_to_socket(topic, item)
- end)
- end
-
- defp do_stream(topic, item) do
- Logger.debug("Trying to push to #{topic}")
- Logger.debug("Pushing item to #{topic}")
- push_to_socket(topic, item)
- end
-
- defp push_to_socket(topic, %Participation{} = participation) do
- rendered = StreamerView.render("conversation.json", participation)
-
- Registry.dispatch(@registry, topic, fn list ->
- Enum.each(list, fn {pid, _} ->
- send(pid, {:text, rendered})
- end)
- end)
- end
-
- defp push_to_socket(topic, %Activity{
- data: %{"type" => "Delete", "deleted_activity_id" => deleted_activity_id}
- }) do
- rendered = Jason.encode!(%{event: "delete", payload: to_string(deleted_activity_id)})
-
- Registry.dispatch(@registry, topic, fn list ->
- Enum.each(list, fn {pid, _} ->
- send(pid, {:text, rendered})
- end)
- end)
- end
-
- defp push_to_socket(_topic, %Activity{data: %{"type" => "Delete"}}), do: :noop
-
- defp push_to_socket(topic, item) do
- anon_render = StreamerView.render("update.json", item)
-
- Registry.dispatch(@registry, topic, fn list ->
- Enum.each(list, fn {pid, auth?} ->
- if auth? do
- send(pid, {:render_with_user, StreamerView, "update.json", item})
- else
- send(pid, {:text, anon_render})
- end
- end)
- end)
- end
-
- defp thread_containment(_activity, %User{skip_thread_containment: true}), do: true
-
- defp thread_containment(activity, user) do
- if Config.get([:instance, :skip_thread_containment]) do
- true
- else
- ActivityPub.contain_activity(activity, user)
- end
- end
-
- # In test environement, only return true if the registry is started.
- # In benchmark environment, returns false.
- # In any other environment, always returns true.
- cond do
- @mix_env == :test ->
- def should_env_send? do
- case Process.whereis(@registry) do
- nil ->
- false
-
- pid ->
- Process.alive?(pid)
- end
- end
-
- @mix_env == :benchmark ->
- def should_env_send?, do: false
-
- true ->
- def should_env_send?, do: true
- end
-end
diff --git a/lib/pleroma/web/twitter_api/controller.ex b/lib/pleroma/web/twitter_api/controller.ex
@@ -0,0 +1,100 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.TwitterAPI.Controller do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Notification
+ alias Pleroma.User
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.TwitterAPI.TokenView
+
+ require Logger
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
+ )
+
+ plug(
+ :skip_plug,
+ [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirm_email
+ )
+
+ plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])
+
+ action_fallback(:errors)
+
+ def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
+ with %User{} = user <- User.get_cached_by_id(uid),
+ true <- user.local and user.confirmation_pending and user.confirmation_token == token,
+ {:ok, _} <-
+ user
+ |> User.confirmation_changeset(need_confirmation: false)
+ |> User.update_and_set_cache() do
+ redirect(conn, to: "/")
+ end
+ end
+
+ def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do
+ with oauth_tokens <- Token.get_user_tokens(user) do
+ conn
+ |> put_view(TokenView)
+ |> render("index.json", %{tokens: oauth_tokens})
+ end
+ end
+
+ def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
+ Token.delete_user_token(user, id)
+
+ json_reply(conn, 201, "")
+ end
+
+ defp errors(conn, {:param_cast, _}) do
+ conn
+ |> put_status(400)
+ |> json("Invalid parameters")
+ end
+
+ defp errors(conn, _) do
+ conn
+ |> put_status(500)
+ |> json("Something went wrong")
+ end
+
+ defp json_reply(conn, status, json) do
+ conn
+ |> put_resp_content_type("application/json")
+ |> send_resp(status, json)
+ end
+
+ def mark_notifications_as_read(
+ %{assigns: %{user: user}} = conn,
+ %{"latest_id" => latest_id} = params
+ ) do
+ Notification.set_read_up_to(user, latest_id)
+
+ notifications = Notification.for_user(user, params)
+
+ conn
+ # XXX: This is a hack because pleroma-fe still uses that API.
+ |> put_view(Pleroma.Web.MastodonAPI.NotificationView)
+ |> render("index.json", %{notifications: notifications, for: user})
+ end
+
+ def mark_notifications_as_read(%{assigns: %{user: _user}} = conn, _) do
+ bad_request_reply(conn, "You need to specify latest_id")
+ end
+
+ defp bad_request_reply(conn, error_message) do
+ json = error_json(conn, error_message)
+ json_reply(conn, 400, json)
+ end
+
+ defp error_json(conn, error_message) do
+ %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
+ end
+end
diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex
@@ -10,7 +10,6 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
alias Pleroma.Activity
alias Pleroma.MFA
alias Pleroma.Object.Fetcher
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.Auth.Authenticator
alias Pleroma.Web.Auth.TOTPAuthenticator
@@ -18,11 +17,11 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
@status_types ["Article", "Event", "Note", "Video", "Page", "Question"]
- plug(Pleroma.Web.FederatingPlug)
+ plug(Pleroma.Web.Plugs.FederatingPlug)
# Note: follower can submit the form (with password auth) not being signed in (having no token)
plug(
- OAuthScopesPlug,
+ Pleroma.Web.Plugs.OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]}
when action in [:do_follow]
)
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -11,12 +11,12 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Emoji
alias Pleroma.Healthcheck
alias Pleroma.Notification
- alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.WebFinger
- plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
+ plug(Pleroma.Web.Plugs.FederatingPlug when action == :remote_subscribe)
plug(
OAuthScopesPlug,
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -1,100 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.TwitterAPI.Controller do
- use Pleroma.Web, :controller
-
- alias Pleroma.Notification
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.User
- alias Pleroma.Web.OAuth.Token
- alias Pleroma.Web.TwitterAPI.TokenView
-
- require Logger
-
- plug(
- OAuthScopesPlug,
- %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
- )
-
- plug(
- :skip_plug,
- [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirm_email
- )
-
- plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])
-
- action_fallback(:errors)
-
- def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
- with %User{} = user <- User.get_cached_by_id(uid),
- true <- user.local and user.confirmation_pending and user.confirmation_token == token,
- {:ok, _} <-
- user
- |> User.confirmation_changeset(need_confirmation: false)
- |> User.update_and_set_cache() do
- redirect(conn, to: "/")
- end
- end
-
- def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do
- with oauth_tokens <- Token.get_user_tokens(user) do
- conn
- |> put_view(TokenView)
- |> render("index.json", %{tokens: oauth_tokens})
- end
- end
-
- def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
- Token.delete_user_token(user, id)
-
- json_reply(conn, 201, "")
- end
-
- defp errors(conn, {:param_cast, _}) do
- conn
- |> put_status(400)
- |> json("Invalid parameters")
- end
-
- defp errors(conn, _) do
- conn
- |> put_status(500)
- |> json("Something went wrong")
- end
-
- defp json_reply(conn, status, json) do
- conn
- |> put_resp_content_type("application/json")
- |> send_resp(status, json)
- end
-
- def mark_notifications_as_read(
- %{assigns: %{user: user}} = conn,
- %{"latest_id" => latest_id} = params
- ) do
- Notification.set_read_up_to(user, latest_id)
-
- notifications = Notification.for_user(user, params)
-
- conn
- # XXX: This is a hack because pleroma-fe still uses that API.
- |> put_view(Pleroma.Web.MastodonAPI.NotificationView)
- |> render("index.json", %{notifications: notifications, for: user})
- end
-
- def mark_notifications_as_read(%{assigns: %{user: _user}} = conn, _) do
- bad_request_reply(conn, "You need to specify latest_id")
- end
-
- defp bad_request_reply(conn, error_message) do
- json = error_json(conn, error_message)
- json_reply(conn, 400, json)
- end
-
- defp error_json(conn, error_message) do
- %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
- end
-end
diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex
@@ -1,239 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Plug do
- # Substitute for `call/2` which is defined with `use Pleroma.Web, :plug`
- @callback perform(Plug.Conn.t(), Plug.opts()) :: Plug.Conn.t()
-end
-
-defmodule Pleroma.Web do
- @moduledoc """
- A module that keeps using definitions for controllers,
- views and so on.
-
- This can be used in your application as:
-
- use Pleroma.Web, :controller
- use Pleroma.Web, :view
-
- The definitions below will be executed for every view,
- controller, etc, so keep them short and clean, focused
- on imports, uses and aliases.
-
- Do NOT define functions inside the quoted expressions
- below.
- """
-
- alias Pleroma.Plugs.EnsureAuthenticatedPlug
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug
- alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.PlugHelper
-
- def controller do
- quote do
- use Phoenix.Controller, namespace: Pleroma.Web
-
- import Plug.Conn
-
- import Pleroma.Web.Gettext
- import Pleroma.Web.Router.Helpers
- import Pleroma.Web.TranslationHelpers
-
- plug(:set_put_layout)
-
- defp set_put_layout(conn, _) do
- put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
- end
-
- # Marks plugs intentionally skipped and blocks their execution if present in plugs chain
- defp skip_plug(conn, plug_modules) do
- plug_modules
- |> List.wrap()
- |> Enum.reduce(
- conn,
- fn plug_module, conn ->
- try do
- plug_module.skip_plug(conn)
- rescue
- UndefinedFunctionError ->
- raise "`#{plug_module}` is not skippable. Append `use Pleroma.Web, :plug` to its code."
- end
- end
- )
- end
-
- # Executed just before actual controller action, invokes before-action hooks (callbacks)
- defp action(conn, params) do
- with %{halted: false} = conn <- maybe_drop_authentication_if_oauth_check_ignored(conn),
- %{halted: false} = conn <- maybe_perform_public_or_authenticated_check(conn),
- %{halted: false} = conn <- maybe_perform_authenticated_check(conn),
- %{halted: false} = conn <- maybe_halt_on_missing_oauth_scopes_check(conn) do
- super(conn, params)
- end
- end
-
- # For non-authenticated API actions, drops auth info if OAuth scopes check was ignored
- # (neither performed nor explicitly skipped)
- defp maybe_drop_authentication_if_oauth_check_ignored(conn) do
- if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) and
- not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
- OAuthScopesPlug.drop_auth_info(conn)
- else
- conn
- end
- end
-
- # Ensures instance is public -or- user is authenticated if such check was scheduled
- defp maybe_perform_public_or_authenticated_check(conn) do
- if PlugHelper.plug_called?(conn, ExpectPublicOrAuthenticatedCheckPlug) do
- EnsurePublicOrAuthenticatedPlug.call(conn, %{})
- else
- conn
- end
- end
-
- # Ensures user is authenticated if such check was scheduled
- # Note: runs prior to action even if it was already executed earlier in plug chain
- # (since OAuthScopesPlug has option of proceeding unauthenticated)
- defp maybe_perform_authenticated_check(conn) do
- if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) do
- EnsureAuthenticatedPlug.call(conn, %{})
- else
- conn
- end
- end
-
- # Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check
- defp maybe_halt_on_missing_oauth_scopes_check(conn) do
- if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) and
- not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
- conn
- |> render_error(
- :forbidden,
- "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
- )
- |> halt()
- else
- conn
- end
- end
- end
- end
-
- def view do
- quote do
- use Phoenix.View,
- root: "lib/pleroma/web/templates",
- namespace: Pleroma.Web
-
- # Import convenience functions from controllers
- import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
-
- import Pleroma.Web.ErrorHelpers
- import Pleroma.Web.Gettext
- import Pleroma.Web.Router.Helpers
-
- require Logger
-
- @doc "Same as `render/3` but wrapped in a rescue block"
- def safe_render(view, template, assigns \\ %{}) do
- Phoenix.View.render(view, template, assigns)
- rescue
- error ->
- Logger.error(
- "#{__MODULE__} failed to render #{inspect({view, template})}\n" <>
- Exception.format(:error, error, __STACKTRACE__)
- )
-
- nil
- end
-
- @doc """
- Same as `render_many/4` but wrapped in rescue block.
- """
- def safe_render_many(collection, view, template, assigns \\ %{}) do
- Enum.map(collection, fn resource ->
- as = Map.get(assigns, :as) || view.__resource__
- assigns = Map.put(assigns, as, resource)
- safe_render(view, template, assigns)
- end)
- |> Enum.filter(& &1)
- end
- end
- end
-
- def router do
- quote do
- use Phoenix.Router
- # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
- import Plug.Conn
- import Phoenix.Controller
- end
- end
-
- def channel do
- quote do
- # credo:disable-for-next-line Credo.Check.Consistency.MultiAliasImportRequireUse
- use Phoenix.Channel
- import Pleroma.Web.Gettext
- end
- end
-
- def plug do
- quote do
- @behaviour Pleroma.Web.Plug
- @behaviour Plug
-
- @doc """
- Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain.
- """
- def skip_plug(conn) do
- PlugHelper.append_to_private_list(
- conn,
- PlugHelper.skipped_plugs_list_id(),
- __MODULE__
- )
- end
-
- @impl Plug
- @doc """
- Before-plug hook that
- * ensures the plug is not skipped
- * processes `:if_func` / `:unless_func` functional pre-run conditions
- * adds plug to the list of called plugs and calls `perform/2` if checks are passed
-
- Note: multiple invocations of the same plug (with different or same options) are allowed.
- """
- def call(%Plug.Conn{} = conn, options) do
- if PlugHelper.plug_skipped?(conn, __MODULE__) ||
- (options[:if_func] && !options[:if_func].(conn)) ||
- (options[:unless_func] && options[:unless_func].(conn)) do
- conn
- else
- conn =
- PlugHelper.append_to_private_list(
- conn,
- PlugHelper.called_plugs_list_id(),
- __MODULE__
- )
-
- apply(__MODULE__, :perform, [conn, options])
- end
- end
- end
- end
-
- @doc """
- When used, dispatch to the appropriate controller/view/etc.
- """
- defmacro __using__(which) when is_atom(which) do
- apply(__MODULE__, which, [])
- end
-
- def base_url do
- Pleroma.Web.Endpoint.url()
- end
-end
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger.ex
diff --git a/lib/pleroma/web/web_finger/web_finger_controller.ex b/lib/pleroma/web/web_finger/web_finger_controller.ex
@@ -7,8 +7,8 @@ defmodule Pleroma.Web.WebFinger.WebFingerController do
alias Pleroma.Web.WebFinger
- plug(Pleroma.Plugs.SetFormatPlug)
- plug(Pleroma.Web.FederatingPlug)
+ plug(Pleroma.Web.Plugs.SetFormatPlug)
+ plug(Pleroma.Web.Plugs.FederatingPlug)
def host_meta(conn, _params) do
xml = WebFinger.host_meta()
diff --git a/lib/pleroma/web/xml/xml.ex b/lib/pleroma/web/xml.ex
diff --git a/lib/xml_builder.ex b/lib/pleroma/xml_builder.ex
diff --git a/priv/repo/migrations/20200919182636_remoteip_plug_rename.exs b/priv/repo/migrations/20200919182636_remoteip_plug_rename.exs
@@ -0,0 +1,19 @@
+defmodule Pleroma.Repo.Migrations.RemoteipPlugRename do
+ use Ecto.Migration
+
+ import Ecto.Query
+
+ def up do
+ config =
+ from(c in Pleroma.ConfigDB, where: c.group == ^:pleroma and c.key == ^Pleroma.Plugs.RemoteIp)
+ |> Pleroma.Repo.one()
+
+ if config do
+ config
+ |> Ecto.Changeset.change(key: Pleroma.Web.Plugs.RemoteIp)
+ |> Pleroma.Repo.update()
+ end
+ end
+
+ def down, do: :ok
+end
diff --git a/test/application_requirements_test.exs b/test/application_requirements_test.exs
@@ -1,146 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.ApplicationRequirementsTest do
- use Pleroma.DataCase
- import ExUnit.CaptureLog
- import Mock
-
- alias Pleroma.Repo
-
- describe "check_welcome_message_config!/1" do
- setup do: clear_config([:welcome])
- setup do: clear_config([Pleroma.Emails.Mailer])
-
- test "raises if welcome email enabled but mail disabled" do
- Pleroma.Config.put([:welcome, :email, :enabled], true)
- Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
-
- assert_raise Pleroma.ApplicationRequirements.VerifyError, "The mail disabled.", fn ->
- capture_log(&Pleroma.ApplicationRequirements.verify!/0)
- end
- end
- end
-
- describe "check_confirmation_accounts!" do
- setup_with_mocks([
- {Pleroma.ApplicationRequirements, [:passthrough],
- [
- check_migrations_applied!: fn _ -> :ok end
- ]}
- ]) do
- :ok
- end
-
- setup do: clear_config([:instance, :account_activation_required])
-
- test "raises if account confirmation is required but mailer isn't enable" do
- Pleroma.Config.put([:instance, :account_activation_required], true)
- Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
-
- assert_raise Pleroma.ApplicationRequirements.VerifyError,
- "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails.",
- fn ->
- capture_log(&Pleroma.ApplicationRequirements.verify!/0)
- end
- end
-
- test "doesn't do anything if account confirmation is disabled" do
- Pleroma.Config.put([:instance, :account_activation_required], false)
- Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
- assert Pleroma.ApplicationRequirements.verify!() == :ok
- end
-
- test "doesn't do anything if account confirmation is required and mailer is enabled" do
- Pleroma.Config.put([:instance, :account_activation_required], true)
- Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], true)
- assert Pleroma.ApplicationRequirements.verify!() == :ok
- end
- end
-
- describe "check_rum!" do
- setup_with_mocks([
- {Pleroma.ApplicationRequirements, [:passthrough],
- [check_migrations_applied!: fn _ -> :ok end]}
- ]) do
- :ok
- end
-
- setup do: clear_config([:database, :rum_enabled])
-
- test "raises if rum is enabled and detects unapplied rum migrations" do
- Pleroma.Config.put([:database, :rum_enabled], true)
-
- with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do
- assert_raise Pleroma.ApplicationRequirements.VerifyError,
- "Unapplied RUM Migrations detected",
- fn ->
- capture_log(&Pleroma.ApplicationRequirements.verify!/0)
- end
- end
- end
-
- test "raises if rum is disabled and detects rum migrations" do
- Pleroma.Config.put([:database, :rum_enabled], false)
-
- with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do
- assert_raise Pleroma.ApplicationRequirements.VerifyError,
- "RUM Migrations detected",
- fn ->
- capture_log(&Pleroma.ApplicationRequirements.verify!/0)
- end
- end
- end
-
- test "doesn't do anything if rum enabled and applied migrations" do
- Pleroma.Config.put([:database, :rum_enabled], true)
-
- with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do
- assert Pleroma.ApplicationRequirements.verify!() == :ok
- end
- end
-
- test "doesn't do anything if rum disabled" do
- Pleroma.Config.put([:database, :rum_enabled], false)
-
- with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do
- assert Pleroma.ApplicationRequirements.verify!() == :ok
- end
- end
- end
-
- describe "check_migrations_applied!" do
- setup_with_mocks([
- {Ecto.Migrator, [],
- [
- with_repo: fn repo, fun -> passthrough([repo, fun]) end,
- migrations: fn Repo ->
- [
- {:up, 20_191_128_153_944, "fix_missing_following_count"},
- {:up, 20_191_203_043_610, "create_report_notes"},
- {:down, 20_191_220_174_645, "add_scopes_to_pleroma_feo_auth_records"}
- ]
- end
- ]}
- ]) do
- :ok
- end
-
- setup do: clear_config([:i_am_aware_this_may_cause_data_loss, :disable_migration_check])
-
- test "raises if it detects unapplied migrations" do
- assert_raise Pleroma.ApplicationRequirements.VerifyError,
- "Unapplied Migrations detected",
- fn ->
- capture_log(&Pleroma.ApplicationRequirements.verify!/0)
- end
- end
-
- test "doesn't do anything if disabled" do
- Pleroma.Config.put([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true)
-
- assert :ok == Pleroma.ApplicationRequirements.verify!()
- end
- end
-end
diff --git a/test/config/deprecation_warnings_test.exs b/test/config/deprecation_warnings_test.exs
@@ -1,140 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Config.DeprecationWarningsTest do
- use ExUnit.Case
- use Pleroma.Tests.Helpers
-
- import ExUnit.CaptureLog
-
- alias Pleroma.Config
- alias Pleroma.Config.DeprecationWarnings
-
- test "check_old_mrf_config/0" do
- clear_config([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.NoOpPolicy)
- clear_config([:instance, :mrf_transparency], true)
- clear_config([:instance, :mrf_transparency_exclusions], [])
-
- assert capture_log(fn -> DeprecationWarnings.check_old_mrf_config() end) =~
- """
- !!!DEPRECATION WARNING!!!
- Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later:
-
- * `config :pleroma, :instance, rewrite_policy` is now `config :pleroma, :mrf, policies`
- * `config :pleroma, :instance, mrf_transparency` is now `config :pleroma, :mrf, transparency`
- * `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`
- """
- end
-
- test "move_namespace_and_warn/2" do
- old_group1 = [:group, :key]
- old_group2 = [:group, :key2]
- old_group3 = [:group, :key3]
-
- new_group1 = [:another_group, :key4]
- new_group2 = [:another_group, :key5]
- new_group3 = [:another_group, :key6]
-
- clear_config(old_group1, 1)
- clear_config(old_group2, 2)
- clear_config(old_group3, 3)
-
- clear_config(new_group1)
- clear_config(new_group2)
- clear_config(new_group3)
-
- config_map = [
- {old_group1, new_group1, "\n error :key"},
- {old_group2, new_group2, "\n error :key2"},
- {old_group3, new_group3, "\n error :key3"}
- ]
-
- assert capture_log(fn ->
- DeprecationWarnings.move_namespace_and_warn(
- config_map,
- "Warning preface"
- )
- end) =~ "Warning preface\n error :key\n error :key2\n error :key3"
-
- assert Config.get(new_group1) == 1
- assert Config.get(new_group2) == 2
- assert Config.get(new_group3) == 3
- end
-
- test "check_media_proxy_whitelist_config/0" do
- clear_config([:media_proxy, :whitelist], ["https://example.com", "example2.com"])
-
- assert capture_log(fn ->
- DeprecationWarnings.check_media_proxy_whitelist_config()
- end) =~ "Your config is using old format (only domain) for MediaProxy whitelist option"
- end
-
- test "check_welcome_message_config/0" do
- clear_config([:instance, :welcome_user_nickname], "LainChan")
-
- assert capture_log(fn ->
- DeprecationWarnings.check_welcome_message_config()
- end) =~ "Your config is using the old namespace for Welcome messages configuration."
- end
-
- test "check_hellthread_threshold/0" do
- clear_config([:mrf_hellthread, :threshold], 16)
-
- assert capture_log(fn ->
- DeprecationWarnings.check_hellthread_threshold()
- end) =~ "You are using the old configuration mechanism for the hellthread filter."
- end
-
- test "check_activity_expiration_config/0" do
- clear_config([Pleroma.ActivityExpiration, :enabled], true)
-
- assert capture_log(fn ->
- DeprecationWarnings.check_activity_expiration_config()
- end) =~ "Your config is using old namespace for activity expiration configuration."
- end
-
- describe "check_gun_pool_options/0" do
- test "await_up_timeout" do
- config = Config.get(:connections_pool)
- clear_config(:connections_pool, Keyword.put(config, :await_up_timeout, 5_000))
-
- assert capture_log(fn ->
- DeprecationWarnings.check_gun_pool_options()
- end) =~
- "Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`."
- end
-
- test "pool timeout" do
- old_config = [
- federation: [
- size: 50,
- max_waiting: 10,
- timeout: 10_000
- ],
- media: [
- size: 50,
- max_waiting: 10,
- timeout: 10_000
- ],
- upload: [
- size: 25,
- max_waiting: 5,
- timeout: 15_000
- ],
- default: [
- size: 10,
- max_waiting: 2,
- timeout: 5_000
- ]
- ]
-
- clear_config(:pools, old_config)
-
- assert capture_log(fn ->
- DeprecationWarnings.check_gun_pool_options()
- end) =~
- "Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings"
- end
- end
-end
diff --git a/test/fixtures/modules/runtime_module.ex b/test/fixtures/modules/runtime_module.ex
@@ -2,7 +2,7 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule RuntimeModule do
+defmodule Fixtures.Modules.RuntimeModule do
@moduledoc """
This is a dummy module to test custom runtime modules.
"""
diff --git a/test/tasks/pleroma_test.exs b/test/mix/pleroma_test.exs
diff --git a/test/tasks/app_test.exs b/test/mix/tasks/pleroma/app_test.exs
diff --git a/test/tasks/config_test.exs b/test/mix/tasks/pleroma/config_test.exs
diff --git a/test/tasks/count_statuses_test.exs b/test/mix/tasks/pleroma/count_statuses_test.exs
diff --git a/test/tasks/database_test.exs b/test/mix/tasks/pleroma/database_test.exs
diff --git a/test/tasks/digest_test.exs b/test/mix/tasks/pleroma/digest_test.exs
diff --git a/test/tasks/ecto/migrate_test.exs b/test/mix/tasks/pleroma/ecto/migrate_test.exs
diff --git a/test/tasks/ecto/rollback_test.exs b/test/mix/tasks/pleroma/ecto/rollback_test.exs
diff --git a/test/tasks/ecto/ecto_test.exs b/test/mix/tasks/pleroma/ecto_test.exs
diff --git a/test/tasks/email_test.exs b/test/mix/tasks/pleroma/email_test.exs
diff --git a/test/tasks/emoji_test.exs b/test/mix/tasks/pleroma/emoji_test.exs
diff --git a/test/mix/tasks/pleroma/frontend_test.exs b/test/mix/tasks/pleroma/frontend_test.exs
@@ -0,0 +1,85 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.FrontendTest do
+ use Pleroma.DataCase
+ alias Mix.Tasks.Pleroma.Frontend
+
+ import ExUnit.CaptureIO, only: [capture_io: 1]
+
+ @dir "test/frontend_static_test"
+
+ setup do
+ File.mkdir_p!(@dir)
+ clear_config([:instance, :static_dir], @dir)
+
+ on_exit(fn ->
+ File.rm_rf(@dir)
+ end)
+ end
+
+ test "it downloads and unzips a known frontend" do
+ clear_config([:frontends, :available], %{
+ "pleroma" => %{
+ "ref" => "fantasy",
+ "name" => "pleroma",
+ "build_url" => "http://gensokyo.2hu/builds/${ref}"
+ }
+ })
+
+ Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/builds/fantasy"} ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/frontend_dist.zip")}
+ end)
+
+ capture_io(fn ->
+ Frontend.run(["install", "pleroma"])
+ end)
+
+ assert File.exists?(Path.join([@dir, "frontends", "pleroma", "fantasy", "test.txt"]))
+ end
+
+ test "it also works given a file" do
+ clear_config([:frontends, :available], %{
+ "pleroma" => %{
+ "ref" => "fantasy",
+ "name" => "pleroma",
+ "build_dir" => ""
+ }
+ })
+
+ folder = Path.join([@dir, "frontends", "pleroma", "fantasy"])
+ previously_existing = Path.join([folder, "temp"])
+ File.mkdir_p!(folder)
+ File.write!(previously_existing, "yey")
+ assert File.exists?(previously_existing)
+
+ capture_io(fn ->
+ Frontend.run(["install", "pleroma", "--file", "test/fixtures/tesla_mock/frontend.zip"])
+ end)
+
+ assert File.exists?(Path.join([folder, "test.txt"]))
+ refute File.exists?(previously_existing)
+ end
+
+ test "it downloads and unzips unknown frontends" do
+ Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/madeup.zip"} ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/frontend.zip")}
+ end)
+
+ capture_io(fn ->
+ Frontend.run([
+ "install",
+ "unknown",
+ "--ref",
+ "baka",
+ "--build-url",
+ "http://gensokyo.2hu/madeup.zip",
+ "--build-dir",
+ ""
+ ])
+ end)
+
+ assert File.exists?(Path.join([@dir, "frontends", "unknown", "baka", "test.txt"]))
+ end
+end
diff --git a/test/mix/tasks/pleroma/instance_test.exs b/test/mix/tasks/pleroma/instance_test.exs
@@ -0,0 +1,99 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Mix.Tasks.Pleroma.InstanceTest do
+ use ExUnit.Case
+
+ setup do
+ File.mkdir_p!(tmp_path())
+
+ on_exit(fn ->
+ File.rm_rf(tmp_path())
+ static_dir = Pleroma.Config.get([:instance, :static_dir], "test/instance_static/")
+
+ if File.exists?(static_dir) do
+ File.rm_rf(Path.join(static_dir, "robots.txt"))
+ end
+
+ Pleroma.Config.put([:instance, :static_dir], static_dir)
+ end)
+
+ :ok
+ end
+
+ defp tmp_path do
+ "/tmp/generated_files/"
+ end
+
+ test "running gen" do
+ mix_task = fn ->
+ Mix.Tasks.Pleroma.Instance.run([
+ "gen",
+ "--output",
+ tmp_path() <> "generated_config.exs",
+ "--output-psql",
+ tmp_path() <> "setup.psql",
+ "--domain",
+ "test.pleroma.social",
+ "--instance-name",
+ "Pleroma",
+ "--admin-email",
+ "admin@example.com",
+ "--notify-email",
+ "notify@example.com",
+ "--dbhost",
+ "dbhost",
+ "--dbname",
+ "dbname",
+ "--dbuser",
+ "dbuser",
+ "--dbpass",
+ "dbpass",
+ "--indexable",
+ "y",
+ "--db-configurable",
+ "y",
+ "--rum",
+ "y",
+ "--listen-port",
+ "4000",
+ "--listen-ip",
+ "127.0.0.1",
+ "--uploads-dir",
+ "test/uploads",
+ "--static-dir",
+ "./test/../test/instance/static/",
+ "--strip-uploads",
+ "y",
+ "--dedupe-uploads",
+ "n",
+ "--anonymize-uploads",
+ "n"
+ ])
+ end
+
+ ExUnit.CaptureIO.capture_io(fn ->
+ mix_task.()
+ end)
+
+ generated_config = File.read!(tmp_path() <> "generated_config.exs")
+ assert generated_config =~ "host: \"test.pleroma.social\""
+ assert generated_config =~ "name: \"Pleroma\""
+ assert generated_config =~ "email: \"admin@example.com\""
+ assert generated_config =~ "notify_email: \"notify@example.com\""
+ assert generated_config =~ "hostname: \"dbhost\""
+ assert generated_config =~ "database: \"dbname\""
+ assert generated_config =~ "username: \"dbuser\""
+ assert generated_config =~ "password: \"dbpass\""
+ assert generated_config =~ "configurable_from_database: true"
+ assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]"
+ assert generated_config =~ "filters: [Pleroma.Upload.Filter.ExifTool]"
+ assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
+ assert File.exists?(Path.expand("./test/instance/static/robots.txt"))
+ end
+
+ defp generated_setup_psql do
+ ~s(CREATE USER dbuser WITH ENCRYPTED PASSWORD 'dbpass';\nCREATE DATABASE dbname OWNER dbuser;\n\\c dbname;\n--Extensions made by ecto.migrate that need superuser access\nCREATE EXTENSION IF NOT EXISTS citext;\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\nCREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\nCREATE EXTENSION IF NOT EXISTS rum;\n)
+ end
+end
diff --git a/test/tasks/refresh_counter_cache_test.exs b/test/mix/tasks/pleroma/refresh_counter_cache_test.exs
diff --git a/test/tasks/relay_test.exs b/test/mix/tasks/pleroma/relay_test.exs
diff --git a/test/tasks/robots_txt_test.exs b/test/mix/tasks/pleroma/robots_txt_test.exs
diff --git a/test/tasks/uploads_test.exs b/test/mix/tasks/pleroma/uploads_test.exs
diff --git a/test/tasks/user_test.exs b/test/mix/tasks/pleroma/user_test.exs
diff --git a/test/activity/ir/topics_test.exs b/test/pleroma/activity/ir/topics_test.exs
diff --git a/test/activity_test.exs b/test/pleroma/activity_test.exs
diff --git a/test/pleroma/application_requirements_test.exs b/test/pleroma/application_requirements_test.exs
@@ -0,0 +1,149 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.ApplicationRequirementsTest do
+ use Pleroma.DataCase
+
+ import ExUnit.CaptureLog
+ import Mock
+
+ alias Pleroma.ApplicationRequirements
+ alias Pleroma.Config
+ alias Pleroma.Repo
+
+ describe "check_welcome_message_config!/1" do
+ setup do: clear_config([:welcome])
+ setup do: clear_config([Pleroma.Emails.Mailer])
+
+ test "raises if welcome email enabled but mail disabled" do
+ Pleroma.Config.put([:welcome, :email, :enabled], true)
+ Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
+
+ assert_raise Pleroma.ApplicationRequirements.VerifyError, "The mail disabled.", fn ->
+ capture_log(&Pleroma.ApplicationRequirements.verify!/0)
+ end
+ end
+ end
+
+ describe "check_confirmation_accounts!" do
+ setup_with_mocks([
+ {Pleroma.ApplicationRequirements, [:passthrough],
+ [
+ check_migrations_applied!: fn _ -> :ok end
+ ]}
+ ]) do
+ :ok
+ end
+
+ setup do: clear_config([:instance, :account_activation_required])
+
+ test "raises if account confirmation is required but mailer isn't enable" do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+ Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
+
+ assert_raise Pleroma.ApplicationRequirements.VerifyError,
+ "Account activation enabled, but Mailer is disabled. Cannot send confirmation emails.",
+ fn ->
+ capture_log(&Pleroma.ApplicationRequirements.verify!/0)
+ end
+ end
+
+ test "doesn't do anything if account confirmation is disabled" do
+ Pleroma.Config.put([:instance, :account_activation_required], false)
+ Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], false)
+ assert Pleroma.ApplicationRequirements.verify!() == :ok
+ end
+
+ test "doesn't do anything if account confirmation is required and mailer is enabled" do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+ Pleroma.Config.put([Pleroma.Emails.Mailer, :enabled], true)
+ assert Pleroma.ApplicationRequirements.verify!() == :ok
+ end
+ end
+
+ describe "check_rum!" do
+ setup_with_mocks([
+ {Pleroma.ApplicationRequirements, [:passthrough],
+ [check_migrations_applied!: fn _ -> :ok end]}
+ ]) do
+ :ok
+ end
+
+ setup do: clear_config([:database, :rum_enabled])
+
+ test "raises if rum is enabled and detects unapplied rum migrations" do
+ Config.put([:database, :rum_enabled], true)
+
+ with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do
+ assert_raise ApplicationRequirements.VerifyError,
+ "Unapplied RUM Migrations detected",
+ fn ->
+ capture_log(&ApplicationRequirements.verify!/0)
+ end
+ end
+ end
+
+ test "raises if rum is disabled and detects rum migrations" do
+ Config.put([:database, :rum_enabled], false)
+
+ with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do
+ assert_raise ApplicationRequirements.VerifyError,
+ "RUM Migrations detected",
+ fn ->
+ capture_log(&ApplicationRequirements.verify!/0)
+ end
+ end
+ end
+
+ test "doesn't do anything if rum enabled and applied migrations" do
+ Config.put([:database, :rum_enabled], true)
+
+ with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> true end]}]) do
+ assert ApplicationRequirements.verify!() == :ok
+ end
+ end
+
+ test "doesn't do anything if rum disabled" do
+ Config.put([:database, :rum_enabled], false)
+
+ with_mocks([{Repo, [:passthrough], [exists?: fn _, _ -> false end]}]) do
+ assert ApplicationRequirements.verify!() == :ok
+ end
+ end
+ end
+
+ describe "check_migrations_applied!" do
+ setup_with_mocks([
+ {Ecto.Migrator, [],
+ [
+ with_repo: fn repo, fun -> passthrough([repo, fun]) end,
+ migrations: fn Repo ->
+ [
+ {:up, 20_191_128_153_944, "fix_missing_following_count"},
+ {:up, 20_191_203_043_610, "create_report_notes"},
+ {:down, 20_191_220_174_645, "add_scopes_to_pleroma_feo_auth_records"}
+ ]
+ end
+ ]}
+ ]) do
+ :ok
+ end
+
+ setup do: clear_config([:i_am_aware_this_may_cause_data_loss, :disable_migration_check])
+
+ test "raises if it detects unapplied migrations" do
+ assert_raise ApplicationRequirements.VerifyError,
+ "Unapplied Migrations detected",
+ fn ->
+ capture_log(&ApplicationRequirements.verify!/0)
+ end
+ end
+
+ test "doesn't do anything if disabled" do
+ Config.put([:i_am_aware_this_may_cause_data_loss, :disable_migration_check], true)
+
+ assert :ok == ApplicationRequirements.verify!()
+ end
+ end
+end
diff --git a/test/bbs/handler_test.exs b/test/pleroma/bbs/handler_test.exs
diff --git a/test/bookmark_test.exs b/test/pleroma/bookmark_test.exs
diff --git a/test/captcha_test.exs b/test/pleroma/captcha_test.exs
diff --git a/test/chat/message_reference_test.exs b/test/pleroma/chat/message_reference_test.exs
diff --git a/test/chat_test.exs b/test/pleroma/chat_test.exs
diff --git a/test/pleroma/config/deprecation_warnings_test.exs b/test/pleroma/config/deprecation_warnings_test.exs
@@ -0,0 +1,140 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Config.DeprecationWarningsTest do
+ use ExUnit.Case
+ use Pleroma.Tests.Helpers
+
+ import ExUnit.CaptureLog
+
+ alias Pleroma.Config
+ alias Pleroma.Config.DeprecationWarnings
+
+ test "check_old_mrf_config/0" do
+ clear_config([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.NoOpPolicy)
+ clear_config([:instance, :mrf_transparency], true)
+ clear_config([:instance, :mrf_transparency_exclusions], [])
+
+ assert capture_log(fn -> DeprecationWarnings.check_old_mrf_config() end) =~
+ """
+ !!!DEPRECATION WARNING!!!
+ Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later:
+
+ * `config :pleroma, :instance, rewrite_policy` is now `config :pleroma, :mrf, policies`
+ * `config :pleroma, :instance, mrf_transparency` is now `config :pleroma, :mrf, transparency`
+ * `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`
+ """
+ end
+
+ test "move_namespace_and_warn/2" do
+ old_group1 = [:group, :key]
+ old_group2 = [:group, :key2]
+ old_group3 = [:group, :key3]
+
+ new_group1 = [:another_group, :key4]
+ new_group2 = [:another_group, :key5]
+ new_group3 = [:another_group, :key6]
+
+ clear_config(old_group1, 1)
+ clear_config(old_group2, 2)
+ clear_config(old_group3, 3)
+
+ clear_config(new_group1)
+ clear_config(new_group2)
+ clear_config(new_group3)
+
+ config_map = [
+ {old_group1, new_group1, "\n error :key"},
+ {old_group2, new_group2, "\n error :key2"},
+ {old_group3, new_group3, "\n error :key3"}
+ ]
+
+ assert capture_log(fn ->
+ DeprecationWarnings.move_namespace_and_warn(
+ config_map,
+ "Warning preface"
+ )
+ end) =~ "Warning preface\n error :key\n error :key2\n error :key3"
+
+ assert Config.get(new_group1) == 1
+ assert Config.get(new_group2) == 2
+ assert Config.get(new_group3) == 3
+ end
+
+ test "check_media_proxy_whitelist_config/0" do
+ clear_config([:media_proxy, :whitelist], ["https://example.com", "example2.com"])
+
+ assert capture_log(fn ->
+ DeprecationWarnings.check_media_proxy_whitelist_config()
+ end) =~ "Your config is using old format (only domain) for MediaProxy whitelist option"
+ end
+
+ test "check_welcome_message_config/0" do
+ clear_config([:instance, :welcome_user_nickname], "LainChan")
+
+ assert capture_log(fn ->
+ DeprecationWarnings.check_welcome_message_config()
+ end) =~ "Your config is using the old namespace for Welcome messages configuration."
+ end
+
+ test "check_hellthread_threshold/0" do
+ clear_config([:mrf_hellthread, :threshold], 16)
+
+ assert capture_log(fn ->
+ DeprecationWarnings.check_hellthread_threshold()
+ end) =~ "You are using the old configuration mechanism for the hellthread filter."
+ end
+
+ test "check_activity_expiration_config/0" do
+ clear_config(Pleroma.ActivityExpiration, enabled: true)
+
+ assert capture_log(fn ->
+ DeprecationWarnings.check_activity_expiration_config()
+ end) =~ "Your config is using old namespace for activity expiration configuration."
+ end
+
+ describe "check_gun_pool_options/0" do
+ test "await_up_timeout" do
+ config = Config.get(:connections_pool)
+ clear_config(:connections_pool, Keyword.put(config, :await_up_timeout, 5_000))
+
+ assert capture_log(fn ->
+ DeprecationWarnings.check_gun_pool_options()
+ end) =~
+ "Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`."
+ end
+
+ test "pool timeout" do
+ old_config = [
+ federation: [
+ size: 50,
+ max_waiting: 10,
+ timeout: 10_000
+ ],
+ media: [
+ size: 50,
+ max_waiting: 10,
+ timeout: 10_000
+ ],
+ upload: [
+ size: 25,
+ max_waiting: 5,
+ timeout: 15_000
+ ],
+ default: [
+ size: 10,
+ max_waiting: 2,
+ timeout: 5_000
+ ]
+ ]
+
+ clear_config(:pools, old_config)
+
+ assert capture_log(fn ->
+ DeprecationWarnings.check_gun_pool_options()
+ end) =~
+ "Your config is using old setting name `timeout` instead of `recv_timeout` in pool settings"
+ end
+ end
+end
diff --git a/test/config/holder_test.exs b/test/pleroma/config/holder_test.exs
diff --git a/test/config/loader_test.exs b/test/pleroma/config/loader_test.exs
diff --git a/test/config/transfer_task_test.exs b/test/pleroma/config/transfer_task_test.exs
diff --git a/test/config/config_db_test.exs b/test/pleroma/config_db_test.exs
diff --git a/test/config_test.exs b/test/pleroma/config_test.exs
diff --git a/test/conversation/participation_test.exs b/test/pleroma/conversation/participation_test.exs
diff --git a/test/conversation_test.exs b/test/pleroma/conversation_test.exs
diff --git a/test/docs/generator_test.exs b/test/pleroma/docs/generator_test.exs
diff --git a/test/earmark_renderer_test.exs b/test/pleroma/earmark_renderer_test.exs
diff --git a/test/pleroma/ecto_type/activity_pub/object_validators/date_time_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/date_time_test.exs
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.DateTimeTest do
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime
+ use Pleroma.DataCase
+
+ test "it validates an xsd:Datetime" do
+ valid_strings = [
+ "2004-04-12T13:20:00",
+ "2004-04-12T13:20:15.5",
+ "2004-04-12T13:20:00-05:00",
+ "2004-04-12T13:20:00Z"
+ ]
+
+ invalid_strings = [
+ "2004-04-12T13:00",
+ "2004-04-1213:20:00",
+ "99-04-12T13:00",
+ "2004-04-12"
+ ]
+
+ assert {:ok, "2004-04-01T12:00:00Z"} == DateTime.cast("2004-04-01T12:00:00Z")
+
+ Enum.each(valid_strings, fn date_time ->
+ result = DateTime.cast(date_time)
+ assert {:ok, _} = result
+ end)
+
+ Enum.each(invalid_strings, fn date_time ->
+ result = DateTime.cast(date_time)
+ assert :error == result
+ end)
+ end
+end
diff --git a/test/pleroma/ecto_type/activity_pub/object_validators/object_id_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/object_id_test.exs
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectIDTest do
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID
+ use Pleroma.DataCase
+
+ @uris [
+ "http://lain.com/users/lain",
+ "http://lain.com",
+ "https://lain.com/object/1"
+ ]
+
+ @non_uris [
+ "https://",
+ "rin",
+ 1,
+ :x,
+ %{"1" => 2}
+ ]
+
+ test "it accepts http uris" do
+ Enum.each(@uris, fn uri ->
+ assert {:ok, uri} == ObjectID.cast(uri)
+ end)
+ end
+
+ test "it accepts an object with a nested uri id" do
+ Enum.each(@uris, fn uri ->
+ assert {:ok, uri} == ObjectID.cast(%{"id" => uri})
+ end)
+ end
+
+ test "it rejects non-uri strings" do
+ Enum.each(@non_uris, fn non_uri ->
+ assert :error == ObjectID.cast(non_uri)
+ assert :error == ObjectID.cast(%{"id" => non_uri})
+ end)
+ end
+end
diff --git a/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/recipients_test.exs
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.RecipientsTest do
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients
+ use Pleroma.DataCase
+
+ test "it asserts that all elements of the list are object ids" do
+ list = ["https://lain.com/users/lain", "invalid"]
+
+ assert :error == Recipients.cast(list)
+ end
+
+ test "it works with a list" do
+ list = ["https://lain.com/users/lain"]
+ assert {:ok, list} == Recipients.cast(list)
+ end
+
+ test "it works with a list with whole objects" do
+ list = ["https://lain.com/users/lain", %{"id" => "https://gensokyo.2hu/users/raymoo"}]
+ resulting_list = ["https://gensokyo.2hu/users/raymoo", "https://lain.com/users/lain"]
+ assert {:ok, resulting_list} == Recipients.cast(list)
+ end
+
+ test "it turns a single string into a list" do
+ recipient = "https://lain.com/users/lain"
+
+ assert {:ok, [recipient]} == Recipients.cast(recipient)
+ end
+end
diff --git a/test/pleroma/ecto_type/activity_pub/object_validators/safe_text_test.exs b/test/pleroma/ecto_type/activity_pub/object_validators/safe_text_test.exs
@@ -0,0 +1,30 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.SafeTextTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.EctoType.ActivityPub.ObjectValidators.SafeText
+
+ test "it lets normal text go through" do
+ text = "hey how are you"
+ assert {:ok, text} == SafeText.cast(text)
+ end
+
+ test "it removes html tags from text" do
+ text = "hey look xss <script>alert('foo')</script>"
+ assert {:ok, "hey look xss alert('foo')"} == SafeText.cast(text)
+ end
+
+ test "it keeps basic html tags" do
+ text = "hey <a href='http://gensokyo.2hu'>look</a> xss <script>alert('foo')</script>"
+
+ assert {:ok, "hey <a href=\"http://gensokyo.2hu\">look</a> xss alert('foo')"} ==
+ SafeText.cast(text)
+ end
+
+ test "errors for non-text" do
+ assert :error == SafeText.cast(1)
+ end
+end
diff --git a/test/emails/admin_email_test.exs b/test/pleroma/emails/admin_email_test.exs
diff --git a/test/emails/mailer_test.exs b/test/pleroma/emails/mailer_test.exs
diff --git a/test/emails/user_email_test.exs b/test/pleroma/emails/user_email_test.exs
diff --git a/test/emoji/formatter_test.exs b/test/pleroma/emoji/formatter_test.exs
diff --git a/test/emoji/loader_test.exs b/test/pleroma/emoji/loader_test.exs
diff --git a/test/emoji/pack_test.exs b/test/pleroma/emoji/pack_test.exs
diff --git a/test/emoji_test.exs b/test/pleroma/emoji_test.exs
diff --git a/test/filter_test.exs b/test/pleroma/filter_test.exs
diff --git a/test/following_relationship_test.exs b/test/pleroma/following_relationship_test.exs
diff --git a/test/formatter_test.exs b/test/pleroma/formatter_test.exs
diff --git a/test/gun/conneciton_pool_test.exs b/test/pleroma/gun/connection_pool_test.exs
diff --git a/test/healthcheck_test.exs b/test/pleroma/healthcheck_test.exs
diff --git a/test/html_test.exs b/test/pleroma/html_test.exs
diff --git a/test/http/adapter_helper/gun_test.exs b/test/pleroma/http/adapter_helper/gun_test.exs
diff --git a/test/http/adapter_helper/hackney_test.exs b/test/pleroma/http/adapter_helper/hackney_test.exs
diff --git a/test/http/adapter_helper_test.exs b/test/pleroma/http/adapter_helper_test.exs
diff --git a/test/http/ex_aws_test.exs b/test/pleroma/http/ex_aws_test.exs
diff --git a/test/http/request_builder_test.exs b/test/pleroma/http/request_builder_test.exs
diff --git a/test/http/tzdata_test.exs b/test/pleroma/http/tzdata_test.exs
diff --git a/test/http_test.exs b/test/pleroma/http_test.exs
diff --git a/test/web/instances/instance_test.exs b/test/pleroma/instances/instance_test.exs
diff --git a/test/web/instances/instances_test.exs b/test/pleroma/instances_test.exs
diff --git a/test/federation/federation_test.exs b/test/pleroma/integration/federation_test.exs
diff --git a/test/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs
diff --git a/test/job_queue_monitor_test.exs b/test/pleroma/job_queue_monitor_test.exs
diff --git a/test/keys_test.exs b/test/pleroma/keys_test.exs
diff --git a/test/list_test.exs b/test/pleroma/list_test.exs
diff --git a/test/marker_test.exs b/test/pleroma/marker_test.exs
diff --git a/test/mfa/backup_codes_test.exs b/test/pleroma/mfa/backup_codes_test.exs
diff --git a/test/mfa/totp_test.exs b/test/pleroma/mfa/totp_test.exs
diff --git a/test/mfa_test.exs b/test/pleroma/mfa_test.exs
diff --git a/test/migration_helper/notification_backfill_test.exs b/test/pleroma/migration_helper/notification_backfill_test.exs
diff --git a/test/moderation_log_test.exs b/test/pleroma/moderation_log_test.exs
diff --git a/test/notification_test.exs b/test/pleroma/notification_test.exs
diff --git a/test/object/containment_test.exs b/test/pleroma/object/containment_test.exs
diff --git a/test/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs
diff --git a/test/object_test.exs b/test/pleroma/object_test.exs
diff --git a/test/otp_version_test.exs b/test/pleroma/otp_version_test.exs
diff --git a/test/pagination_test.exs b/test/pleroma/pagination_test.exs
diff --git a/test/registration_test.exs b/test/pleroma/registration_test.exs
diff --git a/test/migrations/20200716195806_autolinker_to_linkify_test.exs b/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs
diff --git a/test/migrations/20200802170532_fix_legacy_tags_test.exs b/test/pleroma/repo/migrations/fix_legacy_tags_test.exs
diff --git a/test/migrations/20200722185515_fix_malformed_formatter_config_test.exs b/test/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs
diff --git a/test/migrations/20200724133313_move_welcome_settings_test.exs b/test/pleroma/repo/migrations/move_welcome_settings_test.exs
diff --git a/test/repo_test.exs b/test/pleroma/repo_test.exs
diff --git a/test/report_note_test.exs b/test/pleroma/report_note_test.exs
diff --git a/test/reverse_proxy/reverse_proxy_test.exs b/test/pleroma/reverse_proxy_test.exs
diff --git a/test/pleroma/runtime_test.exs b/test/pleroma/runtime_test.exs
@@ -0,0 +1,12 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.RuntimeTest do
+ use ExUnit.Case, async: true
+
+ test "it loads custom runtime modules" do
+ assert {:module, Fixtures.Modules.RuntimeModule} ==
+ Code.ensure_compiled(Fixtures.Modules.RuntimeModule)
+ end
+end
diff --git a/test/safe_jsonb_set_test.exs b/test/pleroma/safe_jsonb_set_test.exs
diff --git a/test/scheduled_activity_test.exs b/test/pleroma/scheduled_activity_test.exs
diff --git a/test/signature_test.exs b/test/pleroma/signature_test.exs
diff --git a/test/stats_test.exs b/test/pleroma/stats_test.exs
diff --git a/test/upload/filter/anonymize_filename_test.exs b/test/pleroma/upload/filter/anonymize_filename_test.exs
diff --git a/test/upload/filter/dedupe_test.exs b/test/pleroma/upload/filter/dedupe_test.exs
diff --git a/test/upload/filter/exiftool_test.exs b/test/pleroma/upload/filter/exiftool_test.exs
diff --git a/test/upload/filter/mogrifun_test.exs b/test/pleroma/upload/filter/mogrifun_test.exs
diff --git a/test/upload/filter/mogrify_test.exs b/test/pleroma/upload/filter/mogrify_test.exs
diff --git a/test/upload/filter_test.exs b/test/pleroma/upload/filter_test.exs
diff --git a/test/upload_test.exs b/test/pleroma/upload_test.exs
diff --git a/test/uploaders/local_test.exs b/test/pleroma/uploaders/local_test.exs
diff --git a/test/uploaders/s3_test.exs b/test/pleroma/uploaders/s3_test.exs
diff --git a/test/user/import_test.exs b/test/pleroma/user/import_test.exs
diff --git a/test/user/notification_setting_test.exs b/test/pleroma/user/notification_setting_test.exs
diff --git a/test/user/query_test.exs b/test/pleroma/user/query_test.exs
diff --git a/test/user/welcome_chat_massage_test.exs b/test/pleroma/user/welcome_chat_message_test.exs
diff --git a/test/user/welcome_email_test.exs b/test/pleroma/user/welcome_email_test.exs
diff --git a/test/user/welcome_message_test.exs b/test/pleroma/user/welcome_message_test.exs
diff --git a/test/user_invite_token_test.exs b/test/pleroma/user_invite_token_test.exs
diff --git a/test/user_relationship_test.exs b/test/pleroma/user_relationship_test.exs
diff --git a/test/user_search_test.exs b/test/pleroma/user_search_test.exs
diff --git a/test/user_test.exs b/test/pleroma/user_test.exs
diff --git a/test/utils_test.exs b/test/pleroma/utils_test.exs
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
diff --git a/test/web/activity_pub/mrf/activity_expiration_policy_test.exs b/test/pleroma/web/activity_pub/mrf/activity_expiration_policy_test.exs
diff --git a/test/web/activity_pub/mrf/anti_followbot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/anti_followbot_policy_test.exs
diff --git a/test/web/activity_pub/mrf/anti_link_spam_policy_test.exs b/test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs
diff --git a/test/web/activity_pub/mrf/ensure_re_prepended_test.exs b/test/pleroma/web/activity_pub/mrf/ensure_re_prepended_test.exs
diff --git a/test/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs b/test/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy_test.exs
diff --git a/test/web/activity_pub/mrf/hellthread_policy_test.exs b/test/pleroma/web/activity_pub/mrf/hellthread_policy_test.exs
diff --git a/test/web/activity_pub/mrf/keyword_policy_test.exs b/test/pleroma/web/activity_pub/mrf/keyword_policy_test.exs
diff --git a/test/web/activity_pub/mrf/mediaproxy_warming_policy_test.exs b/test/pleroma/web/activity_pub/mrf/media_proxy_warming_policy_test.exs
diff --git a/test/web/activity_pub/mrf/mention_policy_test.exs b/test/pleroma/web/activity_pub/mrf/mention_policy_test.exs
diff --git a/test/web/activity_pub/mrf/no_placeholder_text_policy_test.exs b/test/pleroma/web/activity_pub/mrf/no_placeholder_text_policy_test.exs
diff --git a/test/web/activity_pub/mrf/normalize_markup_test.exs b/test/pleroma/web/activity_pub/mrf/normalize_markup_test.exs
diff --git a/test/web/activity_pub/mrf/object_age_policy_test.exs b/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs
diff --git a/test/web/activity_pub/mrf/reject_non_public_test.exs b/test/pleroma/web/activity_pub/mrf/reject_non_public_test.exs
diff --git a/test/web/activity_pub/mrf/simple_policy_test.exs b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs
diff --git a/test/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs
diff --git a/test/web/activity_pub/mrf/subchain_policy_test.exs b/test/pleroma/web/activity_pub/mrf/subchain_policy_test.exs
diff --git a/test/web/activity_pub/mrf/tag_policy_test.exs b/test/pleroma/web/activity_pub/mrf/tag_policy_test.exs
diff --git a/test/web/activity_pub/mrf/user_allowlist_policy_test.exs b/test/pleroma/web/activity_pub/mrf/user_allow_list_policy_test.exs
diff --git a/test/web/activity_pub/mrf/vocabulary_policy_test.exs b/test/pleroma/web/activity_pub/mrf/vocabulary_policy_test.exs
diff --git a/test/web/activity_pub/mrf/mrf_test.exs b/test/pleroma/web/activity_pub/mrf_test.exs
diff --git a/test/web/activity_pub/object_validators/accept_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/accept_validation_test.exs
diff --git a/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs
@@ -0,0 +1,106 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidationTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Builder
+ alias Pleroma.Web.ActivityPub.ObjectValidator
+ alias Pleroma.Web.CommonAPI
+
+ import Pleroma.Factory
+
+ describe "announces" do
+ setup do
+ user = insert(:user)
+ announcer = insert(:user)
+ {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
+
+ object = Object.normalize(post_activity, false)
+ {:ok, valid_announce, []} = Builder.announce(announcer, object)
+
+ %{
+ valid_announce: valid_announce,
+ user: user,
+ post_activity: post_activity,
+ announcer: announcer
+ }
+ end
+
+ test "returns ok for a valid announce", %{valid_announce: valid_announce} do
+ assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
+ end
+
+ test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
+ without_object =
+ valid_announce
+ |> Map.delete("object")
+
+ {:error, cng} = ObjectValidator.validate(without_object, [])
+
+ assert {:object, {"can't be blank", [validation: :required]}} in cng.errors
+
+ nonexisting_object =
+ valid_announce
+ |> Map.put("object", "https://gensokyo.2hu/objects/99999999")
+
+ {:error, cng} = ObjectValidator.validate(nonexisting_object, [])
+
+ assert {:object, {"can't find object", []}} in cng.errors
+ end
+
+ test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do
+ nonexisting_actor =
+ valid_announce
+ |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
+
+ {:error, cng} = ObjectValidator.validate(nonexisting_actor, [])
+
+ assert {:actor, {"can't find user", []}} in cng.errors
+ end
+
+ test "returns an error if the actor already announced the object", %{
+ valid_announce: valid_announce,
+ announcer: announcer,
+ post_activity: post_activity
+ } do
+ _announce = CommonAPI.repeat(post_activity.id, announcer)
+
+ {:error, cng} = ObjectValidator.validate(valid_announce, [])
+
+ assert {:actor, {"already announced this object", []}} in cng.errors
+ assert {:object, {"already announced by this actor", []}} in cng.errors
+ end
+
+ test "returns an error if the actor can't announce the object", %{
+ announcer: announcer,
+ user: user
+ } do
+ {:ok, post_activity} =
+ CommonAPI.post(user, %{status: "a secret post", visibility: "private"})
+
+ object = Object.normalize(post_activity, false)
+
+ # Another user can't announce it
+ {:ok, announce, []} = Builder.announce(announcer, object, public: false)
+
+ {:error, cng} = ObjectValidator.validate(announce, [])
+
+ assert {:actor, {"can not announce this object", []}} in cng.errors
+
+ # The actor of the object can announce it
+ {:ok, announce, []} = Builder.announce(user, object, public: false)
+
+ assert {:ok, _, _} = ObjectValidator.validate(announce, [])
+
+ # The actor of the object can not announce it publicly
+ {:ok, announce, []} = Builder.announce(user, object, public: true)
+
+ {:error, cng} = ObjectValidator.validate(announce, [])
+
+ assert {:actor, {"can not announce this object publicly", []}} in cng.errors
+ end
+ end
+end
diff --git a/test/web/activity_pub/object_validators/article_note_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_validator_test.exs
diff --git a/test/web/activity_pub/object_validators/attachment_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs
diff --git a/test/web/activity_pub/object_validators/block_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/block_validation_test.exs
diff --git a/test/web/activity_pub/object_validators/chat_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/chat_validation_test.exs
diff --git a/test/web/activity_pub/object_validators/delete_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/delete_validation_test.exs
diff --git a/test/web/activity_pub/object_validators/emoji_react_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/emoji_react_handling_test.exs
diff --git a/test/web/activity_pub/object_validators/follow_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/follow_validation_test.exs
diff --git a/test/web/activity_pub/object_validators/like_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/like_validation_test.exs
diff --git a/test/web/activity_pub/object_validators/reject_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/reject_validation_test.exs
diff --git a/test/web/activity_pub/object_validators/undo_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/undo_handling_test.exs
diff --git a/test/web/activity_pub/object_validators/update_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/update_handling_test.exs
diff --git a/test/web/activity_pub/pipeline_test.exs b/test/pleroma/web/activity_pub/pipeline_test.exs
diff --git a/test/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs
diff --git a/test/web/activity_pub/relay_test.exs b/test/pleroma/web/activity_pub/relay_test.exs
diff --git a/test/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs
diff --git a/test/web/activity_pub/transmogrifier/accept_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/accept_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/announce_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/answer_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/answer_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/article_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/article_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/audio_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/audio_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/block_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/block_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/chat_message_test.exs b/test/pleroma/web/activity_pub/transmogrifier/chat_message_test.exs
diff --git a/test/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/emoji_react_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/emoji_react_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/event_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/event_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/follow_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/follow_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/like_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/like_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/question_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/question_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/reject_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/reject_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/user_update_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier/video_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/video_handling_test.exs
diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
diff --git a/test/web/activity_pub/utils_test.exs b/test/pleroma/web/activity_pub/utils_test.exs
diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/pleroma/web/activity_pub/views/object_view_test.exs
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs
diff --git a/test/web/activity_pub/visibilty_test.exs b/test/pleroma/web/activity_pub/visibility_test.exs
diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
diff --git a/test/web/admin_api/controllers/chat_controller_test.exs b/test/pleroma/web/admin_api/controllers/chat_controller_test.exs
diff --git a/test/web/admin_api/controllers/config_controller_test.exs b/test/pleroma/web/admin_api/controllers/config_controller_test.exs
diff --git a/test/web/admin_api/controllers/instance_document_controller_test.exs b/test/pleroma/web/admin_api/controllers/instance_document_controller_test.exs
diff --git a/test/web/admin_api/controllers/invite_controller_test.exs b/test/pleroma/web/admin_api/controllers/invite_controller_test.exs
diff --git a/test/web/admin_api/controllers/media_proxy_cache_controller_test.exs b/test/pleroma/web/admin_api/controllers/media_proxy_cache_controller_test.exs
diff --git a/test/web/admin_api/controllers/oauth_app_controller_test.exs b/test/pleroma/web/admin_api/controllers/o_auth_app_controller_test.exs
diff --git a/test/web/admin_api/controllers/relay_controller_test.exs b/test/pleroma/web/admin_api/controllers/relay_controller_test.exs
diff --git a/test/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs
diff --git a/test/web/admin_api/controllers/status_controller_test.exs b/test/pleroma/web/admin_api/controllers/status_controller_test.exs
diff --git a/test/web/admin_api/search_test.exs b/test/pleroma/web/admin_api/search_test.exs
diff --git a/test/web/admin_api/views/report_view_test.exs b/test/pleroma/web/admin_api/views/report_view_test.exs
diff --git a/test/web/api_spec/schema_examples_test.exs b/test/pleroma/web/api_spec/schema_examples_test.exs
diff --git a/test/pleroma/web/auth/auth_controller_test.exs b/test/pleroma/web/auth/auth_controller_test.exs
@@ -0,0 +1,242 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Auth.AuthControllerTest do
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+
+ describe "do_oauth_check" do
+ test "serves with proper OAuth token (fulfilling requested scopes)" do
+ %{conn: good_token_conn, user: user} = oauth_access(["read"])
+
+ assert %{"user_id" => user.id} ==
+ good_token_conn
+ |> get("/test/authenticated_api/do_oauth_check")
+ |> json_response(200)
+
+ # Unintended usage (:api) — use with :authenticated_api instead
+ assert %{"user_id" => user.id} ==
+ good_token_conn
+ |> get("/test/api/do_oauth_check")
+ |> json_response(200)
+ end
+
+ test "fails on no token / missing scope(s)" do
+ %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"])
+
+ bad_token_conn
+ |> get("/test/authenticated_api/do_oauth_check")
+ |> json_response(403)
+
+ bad_token_conn
+ |> assign(:token, nil)
+ |> get("/test/api/do_oauth_check")
+ |> json_response(403)
+ end
+ end
+
+ describe "fallback_oauth_check" do
+ test "serves with proper OAuth token (fulfilling requested scopes)" do
+ %{conn: good_token_conn, user: user} = oauth_access(["read"])
+
+ assert %{"user_id" => user.id} ==
+ good_token_conn
+ |> get("/test/api/fallback_oauth_check")
+ |> json_response(200)
+
+ # Unintended usage (:authenticated_api) — use with :api instead
+ assert %{"user_id" => user.id} ==
+ good_token_conn
+ |> get("/test/authenticated_api/fallback_oauth_check")
+ |> json_response(200)
+ end
+
+ test "for :api on public instance, drops :user and renders on no token / missing scope(s)" do
+ clear_config([:instance, :public], true)
+
+ %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"])
+
+ assert %{"user_id" => nil} ==
+ bad_token_conn
+ |> get("/test/api/fallback_oauth_check")
+ |> json_response(200)
+
+ assert %{"user_id" => nil} ==
+ bad_token_conn
+ |> assign(:token, nil)
+ |> get("/test/api/fallback_oauth_check")
+ |> json_response(200)
+ end
+
+ test "for :api on private instance, fails on no token / missing scope(s)" do
+ clear_config([:instance, :public], false)
+
+ %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"])
+
+ bad_token_conn
+ |> get("/test/api/fallback_oauth_check")
+ |> json_response(403)
+
+ bad_token_conn
+ |> assign(:token, nil)
+ |> get("/test/api/fallback_oauth_check")
+ |> json_response(403)
+ end
+ end
+
+ describe "skip_oauth_check" do
+ test "for :authenticated_api, serves if :user is set (regardless of token / token scopes)" do
+ user = insert(:user)
+
+ assert %{"user_id" => user.id} ==
+ build_conn()
+ |> assign(:user, user)
+ |> get("/test/authenticated_api/skip_oauth_check")
+ |> json_response(200)
+
+ %{conn: bad_token_conn, user: user} = oauth_access(["irrelevant_scope"])
+
+ assert %{"user_id" => user.id} ==
+ bad_token_conn
+ |> get("/test/authenticated_api/skip_oauth_check")
+ |> json_response(200)
+ end
+
+ test "serves via :api on public instance if :user is not set" do
+ clear_config([:instance, :public], true)
+
+ assert %{"user_id" => nil} ==
+ build_conn()
+ |> get("/test/api/skip_oauth_check")
+ |> json_response(200)
+
+ build_conn()
+ |> get("/test/authenticated_api/skip_oauth_check")
+ |> json_response(403)
+ end
+
+ test "fails on private instance if :user is not set" do
+ clear_config([:instance, :public], false)
+
+ build_conn()
+ |> get("/test/api/skip_oauth_check")
+ |> json_response(403)
+
+ build_conn()
+ |> get("/test/authenticated_api/skip_oauth_check")
+ |> json_response(403)
+ end
+ end
+
+ describe "fallback_oauth_skip_publicity_check" do
+ test "serves with proper OAuth token (fulfilling requested scopes)" do
+ %{conn: good_token_conn, user: user} = oauth_access(["read"])
+
+ assert %{"user_id" => user.id} ==
+ good_token_conn
+ |> get("/test/api/fallback_oauth_skip_publicity_check")
+ |> json_response(200)
+
+ # Unintended usage (:authenticated_api)
+ assert %{"user_id" => user.id} ==
+ good_token_conn
+ |> get("/test/authenticated_api/fallback_oauth_skip_publicity_check")
+ |> json_response(200)
+ end
+
+ test "for :api on private / public instance, drops :user and renders on token issue" do
+ %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"])
+
+ for is_public <- [true, false] do
+ clear_config([:instance, :public], is_public)
+
+ assert %{"user_id" => nil} ==
+ bad_token_conn
+ |> get("/test/api/fallback_oauth_skip_publicity_check")
+ |> json_response(200)
+
+ assert %{"user_id" => nil} ==
+ bad_token_conn
+ |> assign(:token, nil)
+ |> get("/test/api/fallback_oauth_skip_publicity_check")
+ |> json_response(200)
+ end
+ end
+ end
+
+ describe "skip_oauth_skip_publicity_check" do
+ test "for :authenticated_api, serves if :user is set (regardless of token / token scopes)" do
+ user = insert(:user)
+
+ assert %{"user_id" => user.id} ==
+ build_conn()
+ |> assign(:user, user)
+ |> get("/test/authenticated_api/skip_oauth_skip_publicity_check")
+ |> json_response(200)
+
+ %{conn: bad_token_conn, user: user} = oauth_access(["irrelevant_scope"])
+
+ assert %{"user_id" => user.id} ==
+ bad_token_conn
+ |> get("/test/authenticated_api/skip_oauth_skip_publicity_check")
+ |> json_response(200)
+ end
+
+ test "for :api, serves on private and public instances regardless of whether :user is set" do
+ user = insert(:user)
+
+ for is_public <- [true, false] do
+ clear_config([:instance, :public], is_public)
+
+ assert %{"user_id" => nil} ==
+ build_conn()
+ |> get("/test/api/skip_oauth_skip_publicity_check")
+ |> json_response(200)
+
+ assert %{"user_id" => user.id} ==
+ build_conn()
+ |> assign(:user, user)
+ |> get("/test/api/skip_oauth_skip_publicity_check")
+ |> json_response(200)
+ end
+ end
+ end
+
+ describe "missing_oauth_check_definition" do
+ def test_missing_oauth_check_definition_failure(endpoint, expected_error) do
+ %{conn: conn} = oauth_access(["read", "write", "follow", "push", "admin"])
+
+ assert %{"error" => expected_error} ==
+ conn
+ |> get(endpoint)
+ |> json_response(403)
+ end
+
+ test "fails if served via :authenticated_api" do
+ test_missing_oauth_check_definition_failure(
+ "/test/authenticated_api/missing_oauth_check_definition",
+ "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
+ )
+ end
+
+ test "fails if served via :api and the instance is private" do
+ clear_config([:instance, :public], false)
+
+ test_missing_oauth_check_definition_failure(
+ "/test/api/missing_oauth_check_definition",
+ "This resource requires authentication."
+ )
+ end
+
+ test "succeeds with dropped :user if served via :api on public instance" do
+ %{conn: conn} = oauth_access(["read", "write", "follow", "push", "admin"])
+
+ assert %{"user_id" => nil} ==
+ conn
+ |> get("/test/api/missing_oauth_check_definition")
+ |> json_response(200)
+ end
+ end
+end
diff --git a/test/web/auth/authenticator_test.exs b/test/pleroma/web/auth/authenticator_test.exs
diff --git a/test/web/auth/basic_auth_test.exs b/test/pleroma/web/auth/basic_auth_test.exs
diff --git a/test/web/auth/pleroma_authenticator_test.exs b/test/pleroma/web/auth/pleroma_authenticator_test.exs
diff --git a/test/web/auth/totp_authenticator_test.exs b/test/pleroma/web/auth/totp_authenticator_test.exs
diff --git a/test/web/chat_channel_test.exs b/test/pleroma/web/chat_channel_test.exs
diff --git a/test/web/common_api/common_api_utils_test.exs b/test/pleroma/web/common_api/utils_test.exs
diff --git a/test/web/common_api/common_api_test.exs b/test/pleroma/web/common_api_test.exs
diff --git a/test/web/fallback_test.exs b/test/pleroma/web/fallback_test.exs
diff --git a/test/web/fed_sockets/fed_registry_test.exs b/test/pleroma/web/fed_sockets/fed_registry_test.exs
diff --git a/test/web/fed_sockets/fetch_registry_test.exs b/test/pleroma/web/fed_sockets/fetch_registry_test.exs
diff --git a/test/web/fed_sockets/socket_info_test.exs b/test/pleroma/web/fed_sockets/socket_info_test.exs
diff --git a/test/web/federator_test.exs b/test/pleroma/web/federator_test.exs
diff --git a/test/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs
diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs
@@ -0,0 +1,265 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Feed.UserControllerTest do
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+ import SweetXml
+
+ alias Pleroma.Config
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+
+ setup do: clear_config([:instance, :federating], true)
+
+ describe "feed" do
+ setup do: clear_config([:feed])
+
+ test "gets an atom feed", %{conn: conn} do
+ Config.put(
+ [:feed, :post_title],
+ %{max_length: 10, omission: "..."}
+ )
+
+ activity = insert(:note_activity)
+
+ note =
+ insert(:note,
+ data: %{
+ "content" => "This is :moominmamma: note ",
+ "attachment" => [
+ %{
+ "url" => [
+ %{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}
+ ]
+ }
+ ],
+ "inReplyTo" => activity.data["id"]
+ }
+ )
+
+ note_activity = insert(:note_activity, note: note)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ note2 =
+ insert(:note,
+ user: user,
+ data: %{
+ "content" => "42 This is :moominmamma: note ",
+ "inReplyTo" => activity.data["id"]
+ }
+ )
+
+ note_activity2 = insert(:note_activity, note: note2)
+ object = Object.normalize(note_activity)
+
+ resp =
+ conn
+ |> put_req_header("accept", "application/atom+xml")
+ |> get(user_feed_path(conn, :feed, user.nickname))
+ |> response(200)
+
+ activity_titles =
+ resp
+ |> SweetXml.parse()
+ |> SweetXml.xpath(~x"//entry/title/text()"l)
+
+ assert activity_titles == ['42 This...', 'This is...']
+ assert resp =~ object.data["content"]
+
+ resp =
+ conn
+ |> put_req_header("accept", "application/atom+xml")
+ |> get("/users/#{user.nickname}/feed", %{"max_id" => note_activity2.id})
+ |> response(200)
+
+ activity_titles =
+ resp
+ |> SweetXml.parse()
+ |> SweetXml.xpath(~x"//entry/title/text()"l)
+
+ assert activity_titles == ['This is...']
+ end
+
+ test "gets a rss feed", %{conn: conn} do
+ Pleroma.Config.put(
+ [:feed, :post_title],
+ %{max_length: 10, omission: "..."}
+ )
+
+ activity = insert(:note_activity)
+
+ note =
+ insert(:note,
+ data: %{
+ "content" => "This is :moominmamma: note ",
+ "attachment" => [
+ %{
+ "url" => [
+ %{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}
+ ]
+ }
+ ],
+ "inReplyTo" => activity.data["id"]
+ }
+ )
+
+ note_activity = insert(:note_activity, note: note)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ note2 =
+ insert(:note,
+ user: user,
+ data: %{
+ "content" => "42 This is :moominmamma: note ",
+ "inReplyTo" => activity.data["id"]
+ }
+ )
+
+ note_activity2 = insert(:note_activity, note: note2)
+ object = Object.normalize(note_activity)
+
+ resp =
+ conn
+ |> put_req_header("accept", "application/rss+xml")
+ |> get("/users/#{user.nickname}/feed.rss")
+ |> response(200)
+
+ activity_titles =
+ resp
+ |> SweetXml.parse()
+ |> SweetXml.xpath(~x"//item/title/text()"l)
+
+ assert activity_titles == ['42 This...', 'This is...']
+ assert resp =~ object.data["content"]
+
+ resp =
+ conn
+ |> put_req_header("accept", "application/rss+xml")
+ |> get("/users/#{user.nickname}/feed.rss", %{"max_id" => note_activity2.id})
+ |> response(200)
+
+ activity_titles =
+ resp
+ |> SweetXml.parse()
+ |> SweetXml.xpath(~x"//item/title/text()"l)
+
+ assert activity_titles == ['This is...']
+ end
+
+ test "returns 404 for a missing feed", %{conn: conn} do
+ conn =
+ conn
+ |> put_req_header("accept", "application/atom+xml")
+ |> get(user_feed_path(conn, :feed, "nonexisting"))
+
+ assert response(conn, 404)
+ end
+
+ test "returns feed with public and unlisted activities", %{conn: conn} do
+ user = insert(:user)
+
+ {:ok, _} = CommonAPI.post(user, %{status: "public", visibility: "public"})
+ {:ok, _} = CommonAPI.post(user, %{status: "direct", visibility: "direct"})
+ {:ok, _} = CommonAPI.post(user, %{status: "unlisted", visibility: "unlisted"})
+ {:ok, _} = CommonAPI.post(user, %{status: "private", visibility: "private"})
+
+ resp =
+ conn
+ |> put_req_header("accept", "application/atom+xml")
+ |> get(user_feed_path(conn, :feed, user.nickname))
+ |> response(200)
+
+ activity_titles =
+ resp
+ |> SweetXml.parse()
+ |> SweetXml.xpath(~x"//entry/title/text()"l)
+ |> Enum.sort()
+
+ assert activity_titles == ['public', 'unlisted']
+ end
+
+ test "returns 404 when the user is remote", %{conn: conn} do
+ user = insert(:user, local: false)
+
+ {:ok, _} = CommonAPI.post(user, %{status: "test"})
+
+ assert conn
+ |> put_req_header("accept", "application/atom+xml")
+ |> get(user_feed_path(conn, :feed, user.nickname))
+ |> response(404)
+ end
+ end
+
+ # Note: see ActivityPubControllerTest for JSON format tests
+ describe "feed_redirect" do
+ test "with html format, it redirects to user feed", %{conn: conn} do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ response =
+ conn
+ |> get("/users/#{user.nickname}")
+ |> response(200)
+
+ assert response ==
+ Pleroma.Web.Fallback.RedirectController.redirector_with_meta(
+ conn,
+ %{user: user}
+ ).resp_body
+ end
+
+ test "with html format, it returns error when user is not found", %{conn: conn} do
+ response =
+ conn
+ |> get("/users/jimm")
+ |> json_response(404)
+
+ assert response == %{"error" => "Not found"}
+ end
+
+ test "with non-html / non-json format, it redirects to user feed in atom format", %{
+ conn: conn
+ } do
+ note_activity = insert(:note_activity)
+ user = User.get_cached_by_ap_id(note_activity.data["actor"])
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get("/users/#{user.nickname}")
+
+ assert conn.status == 302
+ assert redirected_to(conn) == "#{Pleroma.Web.base_url()}/users/#{user.nickname}/feed.atom"
+ end
+
+ test "with non-html / non-json format, it returns error when user is not found", %{conn: conn} do
+ response =
+ conn
+ |> put_req_header("accept", "application/xml")
+ |> get(user_feed_path(conn, :feed, "jimm"))
+ |> response(404)
+
+ assert response == ~S({"error":"Not found"})
+ end
+ end
+
+ describe "private instance" do
+ setup do: clear_config([:instance, :public])
+
+ test "returns 404 for user feed", %{conn: conn} do
+ Config.put([:instance, :public], false)
+ user = insert(:user)
+
+ {:ok, _} = CommonAPI.post(user, %{status: "test"})
+
+ assert conn
+ |> put_req_header("accept", "application/atom+xml")
+ |> get(user_feed_path(conn, :feed, user.nickname))
+ |> response(404)
+ end
+ end
+end
diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/app_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/app_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/auth_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/auth_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/conversation_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/conversation_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/custom_emoji_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/custom_emoji_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/domain_block_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/domain_block_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/filter_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/follow_request_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/list_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/marker_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/poll_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/poll_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/report_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/scheduled_activity_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/search_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/subscription_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/subscription_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/suggestion_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/suggestion_controller_test.exs
diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs
diff --git a/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs b/test/pleroma/web/mastodon_api/masto_fe_controller_test.exs
@@ -0,0 +1,85 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MastoFEControllerTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.Config
+ alias Pleroma.User
+
+ import Pleroma.Factory
+
+ setup do: clear_config([:instance, :public])
+
+ test "put settings", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:accounts"]))
+ |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
+
+ assert _result = json_response(conn, 200)
+
+ user = User.get_cached_by_ap_id(user.ap_id)
+ assert user.mastofe_settings == %{"programming" => "socks"}
+ end
+
+ describe "index/2 redirections" do
+ setup %{conn: conn} do
+ session_opts = [
+ store: :cookie,
+ key: "_test",
+ signing_salt: "cooldude"
+ ]
+
+ conn =
+ conn
+ |> Plug.Session.call(Plug.Session.init(session_opts))
+ |> fetch_session()
+
+ test_path = "/web/statuses/test"
+ %{conn: conn, path: test_path}
+ end
+
+ test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
+ conn = get(conn, path)
+
+ assert conn.status == 302
+ assert redirected_to(conn) == "/web/login"
+ end
+
+ test "redirects not logged-in users to the login page on private instances", %{
+ conn: conn,
+ path: path
+ } do
+ Config.put([:instance, :public], false)
+
+ conn = get(conn, path)
+
+ assert conn.status == 302
+ assert redirected_to(conn) == "/web/login"
+ end
+
+ test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
+ token = insert(:oauth_token, scopes: ["read"])
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> get(path)
+
+ assert conn.status == 200
+ end
+
+ test "saves referer path to session", %{conn: conn, path: path} do
+ conn = get(conn, path)
+ return_to = Plug.Conn.get_session(conn, :return_to)
+
+ assert return_to == path
+ end
+ end
+end
diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/pleroma/web/mastodon_api/mastodon_api_controller_test.exs
diff --git a/test/web/mastodon_api/mastodon_api_test.exs b/test/pleroma/web/mastodon_api/mastodon_api_test.exs
diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs
@@ -0,0 +1,529 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ use Pleroma.Web.ConnCase
+
+ import Mock
+ import Pleroma.Factory
+
+ setup do: clear_config([:instance, :max_account_fields])
+
+ describe "updating credentials" do
+ setup do: oauth_access(["write:accounts"])
+ setup :request_content_type
+
+ test "sets user settings in a generic way", %{conn: conn} do
+ res_conn =
+ patch(conn, "/api/v1/accounts/update_credentials", %{
+ "pleroma_settings_store" => %{
+ pleroma_fe: %{
+ theme: "bla"
+ }
+ }
+ })
+
+ assert user_data = json_response_and_validate_schema(res_conn, 200)
+ assert user_data["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}}
+
+ user = Repo.get(User, user_data["id"])
+
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{
+ "pleroma_settings_store" => %{
+ masto_fe: %{
+ theme: "bla"
+ }
+ }
+ })
+
+ assert user_data = json_response_and_validate_schema(res_conn, 200)
+
+ assert user_data["pleroma"]["settings_store"] ==
+ %{
+ "pleroma_fe" => %{"theme" => "bla"},
+ "masto_fe" => %{"theme" => "bla"}
+ }
+
+ user = Repo.get(User, user_data["id"])
+
+ clear_config([:instance, :federating], true)
+
+ with_mock Pleroma.Web.Federator,
+ publish: fn _activity -> :ok end do
+ res_conn =
+ conn
+ |> assign(:user, user)
+ |> patch("/api/v1/accounts/update_credentials", %{
+ "pleroma_settings_store" => %{
+ masto_fe: %{
+ theme: "blub"
+ }
+ }
+ })
+
+ assert user_data = json_response_and_validate_schema(res_conn, 200)
+
+ assert user_data["pleroma"]["settings_store"] ==
+ %{
+ "pleroma_fe" => %{"theme" => "bla"},
+ "masto_fe" => %{"theme" => "blub"}
+ }
+
+ assert_called(Pleroma.Web.Federator.publish(:_))
+ end
+ end
+
+ test "updates the user's bio", %{conn: conn} do
+ user2 = insert(:user)
+
+ raw_bio = "I drink #cofe with @#{user2.nickname}\n\nsuya.."
+
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{"note" => raw_bio})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+
+ assert user_data["note"] ==
+ ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
+ user2.id
+ }" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
+
+ assert user_data["source"]["note"] == raw_bio
+
+ user = Repo.get(User, user_data["id"])
+
+ assert user.raw_bio == raw_bio
+ end
+
+ test "updates the user's locking status", %{conn: conn} do
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{locked: "true"})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["locked"] == true
+ end
+
+ test "updates the user's chat acceptance status", %{conn: conn} do
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{accepts_chat_messages: "false"})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["pleroma"]["accepts_chat_messages"] == false
+ end
+
+ test "updates the user's allow_following_move", %{user: user, conn: conn} do
+ assert user.allow_following_move == true
+
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{allow_following_move: "false"})
+
+ assert refresh_record(user).allow_following_move == false
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["pleroma"]["allow_following_move"] == false
+ end
+
+ test "updates the user's default scope", %{conn: conn} do
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "unlisted"})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["source"]["privacy"] == "unlisted"
+ end
+
+ test "updates the user's privacy", %{conn: conn} do
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{source: %{privacy: "unlisted"}})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["source"]["privacy"] == "unlisted"
+ end
+
+ test "updates the user's hide_followers status", %{conn: conn} do
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_followers: "true"})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["pleroma"]["hide_followers"] == true
+ end
+
+ test "updates the user's discoverable status", %{conn: conn} do
+ assert %{"source" => %{"pleroma" => %{"discoverable" => true}}} =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{discoverable: "true"})
+ |> json_response_and_validate_schema(:ok)
+
+ assert %{"source" => %{"pleroma" => %{"discoverable" => false}}} =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{discoverable: "false"})
+ |> json_response_and_validate_schema(:ok)
+ end
+
+ test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do
+ conn =
+ patch(conn, "/api/v1/accounts/update_credentials", %{
+ hide_followers_count: "true",
+ hide_follows_count: "true"
+ })
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["pleroma"]["hide_followers_count"] == true
+ assert user_data["pleroma"]["hide_follows_count"] == true
+ end
+
+ test "updates the user's skip_thread_containment option", %{user: user, conn: conn} do
+ response =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"})
+ |> json_response_and_validate_schema(200)
+
+ assert response["pleroma"]["skip_thread_containment"] == true
+ assert refresh_record(user).skip_thread_containment
+ end
+
+ test "updates the user's hide_follows status", %{conn: conn} do
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_follows: "true"})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["pleroma"]["hide_follows"] == true
+ end
+
+ test "updates the user's hide_favorites status", %{conn: conn} do
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["pleroma"]["hide_favorites"] == true
+ end
+
+ test "updates the user's show_role status", %{conn: conn} do
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{show_role: "false"})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["source"]["pleroma"]["show_role"] == false
+ end
+
+ test "updates the user's no_rich_text status", %{conn: conn} do
+ conn = patch(conn, "/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["source"]["pleroma"]["no_rich_text"] == true
+ end
+
+ test "updates the user's name", %{conn: conn} do
+ conn =
+ patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+ assert user_data["display_name"] == "markorepairs"
+
+ update_activity = Repo.one(Pleroma.Activity)
+ assert update_activity.data["type"] == "Update"
+ assert update_activity.data["object"]["name"] == "markorepairs"
+ end
+
+ test "updates the user's avatar", %{user: user, conn: conn} do
+ new_avatar = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ assert user.avatar == %{}
+
+ res = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
+
+ assert user_response = json_response_and_validate_schema(res, 200)
+ assert user_response["avatar"] != User.avatar_url(user)
+
+ user = User.get_by_id(user.id)
+ refute user.avatar == %{}
+
+ # Also resets it
+ _res = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => ""})
+
+ user = User.get_by_id(user.id)
+ assert user.avatar == nil
+ end
+
+ test "updates the user's banner", %{user: user, conn: conn} do
+ new_header = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ res = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header})
+
+ assert user_response = json_response_and_validate_schema(res, 200)
+ assert user_response["header"] != User.banner_url(user)
+
+ # Also resets it
+ _res = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => ""})
+
+ user = User.get_by_id(user.id)
+ assert user.banner == nil
+ end
+
+ test "updates the user's background", %{conn: conn, user: user} do
+ new_header = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ res =
+ patch(conn, "/api/v1/accounts/update_credentials", %{
+ "pleroma_background_image" => new_header
+ })
+
+ assert user_response = json_response_and_validate_schema(res, 200)
+ assert user_response["pleroma"]["background_image"]
+ #
+ # Also resets it
+ _res =
+ patch(conn, "/api/v1/accounts/update_credentials", %{"pleroma_background_image" => ""})
+
+ user = User.get_by_id(user.id)
+ assert user.background == nil
+ end
+
+ test "requires 'write:accounts' permission" do
+ token1 = insert(:oauth_token, scopes: ["read"])
+ token2 = insert(:oauth_token, scopes: ["write", "follow"])
+
+ for token <- [token1, token2] do
+ conn =
+ build_conn()
+ |> put_req_header("content-type", "multipart/form-data")
+ |> put_req_header("authorization", "Bearer #{token.token}")
+ |> patch("/api/v1/accounts/update_credentials", %{})
+
+ if token == token1 do
+ assert %{"error" => "Insufficient permissions: write:accounts."} ==
+ json_response_and_validate_schema(conn, 403)
+ else
+ assert json_response_and_validate_schema(conn, 200)
+ end
+ end
+ end
+
+ test "updates profile emojos", %{user: user, conn: conn} do
+ note = "*sips :blank:*"
+ name = "I am :firefox:"
+
+ ret_conn =
+ patch(conn, "/api/v1/accounts/update_credentials", %{
+ "note" => note,
+ "display_name" => name
+ })
+
+ assert json_response_and_validate_schema(ret_conn, 200)
+
+ conn = get(conn, "/api/v1/accounts/#{user.id}")
+
+ assert user_data = json_response_and_validate_schema(conn, 200)
+
+ assert user_data["note"] == note
+ assert user_data["display_name"] == name
+ assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user_data["emojis"]
+ end
+
+ test "update fields", %{conn: conn} do
+ fields = [
+ %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"},
+ %{"name" => "link.io", "value" => "cofe.io"}
+ ]
+
+ account_data =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+ |> json_response_and_validate_schema(200)
+
+ assert account_data["fields"] == [
+ %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
+ %{
+ "name" => "link.io",
+ "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)
+ }
+ ]
+
+ assert account_data["source"]["fields"] == [
+ %{
+ "name" => "<a href=\"http://google.com\">foo</a>",
+ "value" => "<script>bar</script>"
+ },
+ %{"name" => "link.io", "value" => "cofe.io"}
+ ]
+ end
+
+ test "emojis in fields labels", %{conn: conn} do
+ fields = [
+ %{"name" => ":firefox:", "value" => "is best 2hu"},
+ %{"name" => "they wins", "value" => ":blank:"}
+ ]
+
+ account_data =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+ |> json_response_and_validate_schema(200)
+
+ assert account_data["fields"] == [
+ %{"name" => ":firefox:", "value" => "is best 2hu"},
+ %{"name" => "they wins", "value" => ":blank:"}
+ ]
+
+ assert account_data["source"]["fields"] == [
+ %{"name" => ":firefox:", "value" => "is best 2hu"},
+ %{"name" => "they wins", "value" => ":blank:"}
+ ]
+
+ assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = account_data["emojis"]
+ end
+
+ test "update fields via x-www-form-urlencoded", %{conn: conn} do
+ fields =
+ [
+ "fields_attributes[1][name]=link",
+ "fields_attributes[1][value]=http://cofe.io",
+ "fields_attributes[0][name]=foo",
+ "fields_attributes[0][value]=bar"
+ ]
+ |> Enum.join("&")
+
+ account =
+ conn
+ |> put_req_header("content-type", "application/x-www-form-urlencoded")
+ |> patch("/api/v1/accounts/update_credentials", fields)
+ |> json_response_and_validate_schema(200)
+
+ assert account["fields"] == [
+ %{"name" => "foo", "value" => "bar"},
+ %{
+ "name" => "link",
+ "value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>)
+ }
+ ]
+
+ assert account["source"]["fields"] == [
+ %{"name" => "foo", "value" => "bar"},
+ %{"name" => "link", "value" => "http://cofe.io"}
+ ]
+ end
+
+ test "update fields with empty name", %{conn: conn} do
+ fields = [
+ %{"name" => "foo", "value" => ""},
+ %{"name" => "", "value" => "bar"}
+ ]
+
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+ |> json_response_and_validate_schema(200)
+
+ assert account["fields"] == [
+ %{"name" => "foo", "value" => ""}
+ ]
+ end
+
+ test "update fields when invalid request", %{conn: conn} do
+ name_limit = Pleroma.Config.get([:instance, :account_field_name_length])
+ value_limit = Pleroma.Config.get([:instance, :account_field_value_length])
+
+ long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
+ long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join()
+
+ fields = [%{"name" => "foo", "value" => long_value}]
+
+ assert %{"error" => "Invalid request"} ==
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+ |> json_response_and_validate_schema(403)
+
+ fields = [%{"name" => long_name, "value" => "bar"}]
+
+ assert %{"error" => "Invalid request"} ==
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+ |> json_response_and_validate_schema(403)
+
+ Pleroma.Config.put([:instance, :max_account_fields], 1)
+
+ fields = [
+ %{"name" => "foo", "value" => "bar"},
+ %{"name" => "link", "value" => "cofe.io"}
+ ]
+
+ assert %{"error" => "Invalid request"} ==
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
+ |> json_response_and_validate_schema(403)
+ end
+ end
+
+ describe "Mark account as bot" do
+ setup do: oauth_access(["write:accounts"])
+ setup :request_content_type
+
+ test "changing actor_type to Service makes account a bot", %{conn: conn} do
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{actor_type: "Service"})
+ |> json_response_and_validate_schema(200)
+
+ assert account["bot"]
+ assert account["source"]["pleroma"]["actor_type"] == "Service"
+ end
+
+ test "changing actor_type to Person makes account a human", %{conn: conn} do
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{actor_type: "Person"})
+ |> json_response_and_validate_schema(200)
+
+ refute account["bot"]
+ assert account["source"]["pleroma"]["actor_type"] == "Person"
+ end
+
+ test "changing actor_type to Application causes error", %{conn: conn} do
+ response =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{actor_type: "Application"})
+ |> json_response_and_validate_schema(403)
+
+ assert %{"error" => "Invalid request"} == response
+ end
+
+ test "changing bot field to true changes actor_type to Service", %{conn: conn} do
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{bot: "true"})
+ |> json_response_and_validate_schema(200)
+
+ assert account["bot"]
+ assert account["source"]["pleroma"]["actor_type"] == "Service"
+ end
+
+ test "changing bot field to false changes actor_type to Person", %{conn: conn} do
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{bot: "false"})
+ |> json_response_and_validate_schema(200)
+
+ refute account["bot"]
+ assert account["source"]["pleroma"]["actor_type"] == "Person"
+ end
+
+ test "actor_type field has a higher priority than bot", %{conn: conn} do
+ account =
+ conn
+ |> patch("/api/v1/accounts/update_credentials", %{
+ actor_type: "Person",
+ bot: "true"
+ })
+ |> json_response_and_validate_schema(200)
+
+ refute account["bot"]
+ assert account["source"]["pleroma"]["actor_type"] == "Person"
+ end
+ end
+end
diff --git a/test/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs
diff --git a/test/web/mastodon_api/views/conversation_view_test.exs b/test/pleroma/web/mastodon_api/views/conversation_view_test.exs
diff --git a/test/web/mastodon_api/views/list_view_test.exs b/test/pleroma/web/mastodon_api/views/list_view_test.exs
diff --git a/test/web/mastodon_api/views/marker_view_test.exs b/test/pleroma/web/mastodon_api/views/marker_view_test.exs
diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs
diff --git a/test/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs
diff --git a/test/web/mastodon_api/views/scheduled_activity_view_test.exs b/test/pleroma/web/mastodon_api/views/scheduled_activity_view_test.exs
diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs
diff --git a/test/web/mastodon_api/views/subscription_view_test.exs b/test/pleroma/web/mastodon_api/views/subscription_view_test.exs
diff --git a/test/web/media_proxy/invalidations/http_test.exs b/test/pleroma/web/media_proxy/invalidation/http_test.exs
diff --git a/test/web/media_proxy/invalidations/script_test.exs b/test/pleroma/web/media_proxy/invalidation/script_test.exs
diff --git a/test/web/media_proxy/invalidation_test.exs b/test/pleroma/web/media_proxy/invalidation_test.exs
diff --git a/test/web/media_proxy/media_proxy_controller_test.exs b/test/pleroma/web/media_proxy/media_proxy_controller_test.exs
diff --git a/test/web/media_proxy/media_proxy_test.exs b/test/pleroma/web/media_proxy_test.exs
diff --git a/test/web/metadata/player_view_test.exs b/test/pleroma/web/metadata/player_view_test.exs
diff --git a/test/web/metadata/feed_test.exs b/test/pleroma/web/metadata/providers/feed_test.exs
diff --git a/test/web/metadata/opengraph_test.exs b/test/pleroma/web/metadata/providers/open_graph_test.exs
diff --git a/test/web/metadata/rel_me_test.exs b/test/pleroma/web/metadata/providers/rel_me_test.exs
diff --git a/test/web/metadata/restrict_indexing_test.exs b/test/pleroma/web/metadata/providers/restrict_indexing_test.exs
diff --git a/test/web/metadata/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs
diff --git a/test/web/metadata/utils_test.exs b/test/pleroma/web/metadata/utils_test.exs
diff --git a/test/web/metadata/metadata_test.exs b/test/pleroma/web/metadata_test.exs
diff --git a/test/pleroma/web/mongoose_im_controller_test.exs b/test/pleroma/web/mongoose_im_controller_test.exs
@@ -0,0 +1,81 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MongooseIMControllerTest do
+ use Pleroma.Web.ConnCase
+ import Pleroma.Factory
+
+ test "/user_exists", %{conn: conn} do
+ _user = insert(:user, nickname: "lain")
+ _remote_user = insert(:user, nickname: "alice", local: false)
+ _deactivated_user = insert(:user, nickname: "konata", deactivated: true)
+
+ res =
+ conn
+ |> get(mongoose_im_path(conn, :user_exists), user: "lain")
+ |> json_response(200)
+
+ assert res == true
+
+ res =
+ conn
+ |> get(mongoose_im_path(conn, :user_exists), user: "alice")
+ |> json_response(404)
+
+ assert res == false
+
+ res =
+ conn
+ |> get(mongoose_im_path(conn, :user_exists), user: "bob")
+ |> json_response(404)
+
+ assert res == false
+
+ res =
+ conn
+ |> get(mongoose_im_path(conn, :user_exists), user: "konata")
+ |> json_response(404)
+
+ assert res == false
+ end
+
+ test "/check_password", %{conn: conn} do
+ user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt("cool"))
+
+ _deactivated_user =
+ insert(:user,
+ nickname: "konata",
+ deactivated: true,
+ password_hash: Pbkdf2.hash_pwd_salt("cool")
+ )
+
+ res =
+ conn
+ |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "cool")
+ |> json_response(200)
+
+ assert res == true
+
+ res =
+ conn
+ |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "uncool")
+ |> json_response(403)
+
+ assert res == false
+
+ res =
+ conn
+ |> get(mongoose_im_path(conn, :check_password), user: "konata", pass: "cool")
+ |> json_response(404)
+
+ assert res == false
+
+ res =
+ conn
+ |> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool")
+ |> json_response(404)
+
+ assert res == false
+ end
+end
diff --git a/test/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs
diff --git a/test/web/oauth/app_test.exs b/test/pleroma/web/o_auth/app_test.exs
diff --git a/test/web/oauth/authorization_test.exs b/test/pleroma/web/o_auth/authorization_test.exs
diff --git a/test/web/oauth/ldap_authorization_test.exs b/test/pleroma/web/o_auth/ldap_authorization_test.exs
diff --git a/test/web/oauth/mfa_controller_test.exs b/test/pleroma/web/o_auth/mfa_controller_test.exs
diff --git a/test/web/oauth/oauth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs
diff --git a/test/web/oauth/token/utils_test.exs b/test/pleroma/web/o_auth/token/utils_test.exs
diff --git a/test/web/oauth/token_test.exs b/test/pleroma/web/o_auth/token_test.exs
diff --git a/test/web/ostatus/ostatus_controller_test.exs b/test/pleroma/web/o_status/o_status_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/account_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/account_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/chat_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/chat_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/conversation_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/conversation_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/emoji_file_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/emoji_pack_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/emoji_reaction_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_reaction_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/mascot_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/mascot_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/notification_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/notification_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/scrobble_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/scrobble_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs
diff --git a/test/web/pleroma_api/controllers/user_import_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs
diff --git a/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_message_reference_view_test.exs
@@ -0,0 +1,72 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.ChatMessageReferenceViewTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Chat
+ alias Pleroma.Chat.MessageReference
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
+
+ import Pleroma.Factory
+
+ test "it displays a chat message" do
+ user = insert(:user)
+ recipient = insert(:user)
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image.jpg"),
+ filename: "an_image.jpg"
+ }
+
+ {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
+ {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "kippis :firefox:")
+
+ chat = Chat.get(user.id, recipient.ap_id)
+
+ object = Object.normalize(activity)
+
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ chat_message = MessageReferenceView.render("show.json", chat_message_reference: cm_ref)
+
+ assert chat_message[:id] == cm_ref.id
+ assert chat_message[:content] == "kippis :firefox:"
+ assert chat_message[:account_id] == user.id
+ assert chat_message[:chat_id]
+ assert chat_message[:created_at]
+ assert chat_message[:unread] == false
+ assert match?([%{shortcode: "firefox"}], chat_message[:emojis])
+
+ clear_config([:rich_media, :enabled], true)
+
+ Tesla.Mock.mock(fn
+ %{url: "https://example.com/ogp"} ->
+ %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}
+ end)
+
+ {:ok, activity} =
+ CommonAPI.post_chat_message(recipient, user, "gkgkgk https://example.com/ogp",
+ media_id: upload.id
+ )
+
+ object = Object.normalize(activity)
+
+ cm_ref = MessageReference.for_chat_and_object(chat, object)
+
+ chat_message_two = MessageReferenceView.render("show.json", chat_message_reference: cm_ref)
+
+ assert chat_message_two[:id] == cm_ref.id
+ assert chat_message_two[:content] == object.data["content"]
+ assert chat_message_two[:account_id] == recipient.id
+ assert chat_message_two[:chat_id] == chat_message[:chat_id]
+ assert chat_message_two[:attachment]
+ assert chat_message_two[:unread] == true
+ assert chat_message_two[:card]
+ end
+end
diff --git a/test/web/pleroma_api/views/chat_view_test.exs b/test/pleroma/web/pleroma_api/views/chat_view_test.exs
diff --git a/test/pleroma/web/pleroma_api/views/scrobble_view_test.exs b/test/pleroma/web/pleroma_api/views/scrobble_view_test.exs
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.PleromaAPI.ScrobbleViewTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Web.PleromaAPI.ScrobbleView
+
+ import Pleroma.Factory
+
+ test "successfully renders a Listen activity (pleroma extension)" do
+ listen_activity = insert(:listen)
+
+ status = ScrobbleView.render("show.json", activity: listen_activity)
+
+ assert status.length == listen_activity.data["object"]["length"]
+ assert status.title == listen_activity.data["object"]["title"]
+ end
+end
diff --git a/test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs b/test/pleroma/web/plugs/admin_secret_authentication_plug_test.exs
@@ -0,0 +1,75 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.AdminSecretAuthenticationPlugTest do
+ use Pleroma.Web.ConnCase
+
+ import Mock
+ import Pleroma.Factory
+
+ alias Pleroma.Web.Plugs.AdminSecretAuthenticationPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.PlugHelper
+ alias Pleroma.Web.Plugs.RateLimiter
+
+ test "does nothing if a user is assigned", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+
+ ret_conn =
+ conn
+ |> AdminSecretAuthenticationPlug.call(%{})
+
+ assert conn == ret_conn
+ end
+
+ describe "when secret set it assigns an admin user" do
+ setup do: clear_config([:admin_token])
+
+ setup_with_mocks([{RateLimiter, [:passthrough], []}]) do
+ :ok
+ end
+
+ test "with `admin_token` query parameter", %{conn: conn} do
+ Pleroma.Config.put(:admin_token, "password123")
+
+ conn =
+ %{conn | params: %{"admin_token" => "wrong_password"}}
+ |> AdminSecretAuthenticationPlug.call(%{})
+
+ refute conn.assigns[:user]
+ assert called(RateLimiter.call(conn, name: :authentication))
+
+ conn =
+ %{conn | params: %{"admin_token" => "password123"}}
+ |> AdminSecretAuthenticationPlug.call(%{})
+
+ assert conn.assigns[:user].is_admin
+ assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
+ end
+
+ test "with `x-admin-token` HTTP header", %{conn: conn} do
+ Pleroma.Config.put(:admin_token, "☕️")
+
+ conn =
+ conn
+ |> put_req_header("x-admin-token", "🥛")
+ |> AdminSecretAuthenticationPlug.call(%{})
+
+ refute conn.assigns[:user]
+ assert called(RateLimiter.call(conn, name: :authentication))
+
+ conn =
+ conn
+ |> put_req_header("x-admin-token", "☕️")
+ |> AdminSecretAuthenticationPlug.call(%{})
+
+ assert conn.assigns[:user].is_admin
+ assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
+ end
+ end
+end
diff --git a/test/pleroma/web/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs
@@ -0,0 +1,125 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.AuthenticationPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.PlugHelper
+
+ import ExUnit.CaptureLog
+ import Pleroma.Factory
+
+ setup %{conn: conn} do
+ user = %User{
+ id: 1,
+ name: "dude",
+ password_hash: Pbkdf2.hash_pwd_salt("guy")
+ }
+
+ conn =
+ conn
+ |> assign(:auth_user, user)
+
+ %{user: user, conn: conn}
+ end
+
+ test "it does nothing if a user is assigned", %{conn: conn} do
+ conn =
+ conn
+ |> assign(:user, %User{})
+
+ ret_conn =
+ conn
+ |> AuthenticationPlug.call(%{})
+
+ assert ret_conn == conn
+ end
+
+ test "with a correct password in the credentials, " <>
+ "it assigns the auth_user and marks OAuthScopesPlug as skipped",
+ %{conn: conn} do
+ conn =
+ conn
+ |> assign(:auth_credentials, %{password: "guy"})
+ |> AuthenticationPlug.call(%{})
+
+ assert conn.assigns.user == conn.assigns.auth_user
+ assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
+ end
+
+ test "with a bcrypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
+ user = insert(:user, password_hash: Bcrypt.hash_pwd_salt("123"))
+ assert "$2" <> _ = user.password_hash
+
+ conn =
+ conn
+ |> assign(:auth_user, user)
+ |> assign(:auth_credentials, %{password: "123"})
+ |> AuthenticationPlug.call(%{})
+
+ assert conn.assigns.user.id == conn.assigns.auth_user.id
+ assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
+
+ user = User.get_by_id(user.id)
+ assert "$pbkdf2" <> _ = user.password_hash
+ end
+
+ @tag :skip_on_mac
+ test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
+ user =
+ insert(:user,
+ password_hash:
+ "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
+ )
+
+ conn =
+ conn
+ |> assign(:auth_user, user)
+ |> assign(:auth_credentials, %{password: "password"})
+ |> AuthenticationPlug.call(%{})
+
+ assert conn.assigns.user.id == conn.assigns.auth_user.id
+ assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
+
+ user = User.get_by_id(user.id)
+ assert "$pbkdf2" <> _ = user.password_hash
+ end
+
+ describe "checkpw/2" do
+ test "check pbkdf2 hash" do
+ hash =
+ "$pbkdf2-sha512$160000$loXqbp8GYls43F0i6lEfIw$AY.Ep.2pGe57j2hAPY635sI/6w7l9Q9u9Bp02PkPmF3OrClDtJAI8bCiivPr53OKMF7ph6iHhN68Rom5nEfC2A"
+
+ assert AuthenticationPlug.checkpw("test-password", hash)
+ refute AuthenticationPlug.checkpw("test-password1", hash)
+ end
+
+ @tag :skip_on_mac
+ test "check sha512-crypt hash" do
+ hash =
+ "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
+
+ assert AuthenticationPlug.checkpw("password", hash)
+ end
+
+ test "check bcrypt hash" do
+ hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS"
+
+ assert AuthenticationPlug.checkpw("password", hash)
+ refute AuthenticationPlug.checkpw("password1", hash)
+ end
+
+ test "it returns false when hash invalid" do
+ hash =
+ "psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
+
+ assert capture_log(fn ->
+ refute AuthenticationPlug.checkpw("password", hash)
+ end) =~ "[error] Password hash not recognized"
+ end
+ end
+end
diff --git a/test/pleroma/web/plugs/basic_auth_decoder_plug_test.exs b/test/pleroma/web/plugs/basic_auth_decoder_plug_test.exs
@@ -0,0 +1,35 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.BasicAuthDecoderPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Web.Plugs.BasicAuthDecoderPlug
+
+ defp basic_auth_enc(username, password) do
+ "Basic " <> Base.encode64("#{username}:#{password}")
+ end
+
+ test "it puts the decoded credentials into the assigns", %{conn: conn} do
+ header = basic_auth_enc("moonman", "iloverobek")
+
+ conn =
+ conn
+ |> put_req_header("authorization", header)
+ |> BasicAuthDecoderPlug.call(%{})
+
+ assert conn.assigns[:auth_credentials] == %{
+ username: "moonman",
+ password: "iloverobek"
+ }
+ end
+
+ test "without a authorization header it doesn't do anything", %{conn: conn} do
+ ret_conn =
+ conn
+ |> BasicAuthDecoderPlug.call(%{})
+
+ assert conn == ret_conn
+ end
+end
diff --git a/test/pleroma/web/plugs/cache_control_test.exs b/test/pleroma/web/plugs/cache_control_test.exs
@@ -0,0 +1,20 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.CacheControlTest do
+ use Pleroma.Web.ConnCase
+ alias Plug.Conn
+
+ test "Verify Cache-Control header on static assets", %{conn: conn} do
+ conn = get(conn, "/index.html")
+
+ assert Conn.get_resp_header(conn, "cache-control") == ["public, no-cache"]
+ end
+
+ test "Verify Cache-Control header on the API", %{conn: conn} do
+ conn = get(conn, "/api/v1/instance")
+
+ assert Conn.get_resp_header(conn, "cache-control") == ["max-age=0, private, must-revalidate"]
+ end
+end
diff --git a/test/pleroma/web/plugs/cache_test.exs b/test/pleroma/web/plugs/cache_test.exs
@@ -0,0 +1,186 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.CacheTest do
+ use ExUnit.Case, async: true
+ use Plug.Test
+
+ alias Pleroma.Web.Plugs.Cache
+
+ @miss_resp {200,
+ [
+ {"cache-control", "max-age=0, private, must-revalidate"},
+ {"content-type", "cofe/hot; charset=utf-8"},
+ {"x-cache", "MISS from Pleroma"}
+ ], "cofe"}
+
+ @hit_resp {200,
+ [
+ {"cache-control", "max-age=0, private, must-revalidate"},
+ {"content-type", "cofe/hot; charset=utf-8"},
+ {"x-cache", "HIT from Pleroma"}
+ ], "cofe"}
+
+ @ttl 5
+
+ setup do
+ Cachex.clear(:web_resp_cache)
+ :ok
+ end
+
+ test "caches a response" do
+ assert @miss_resp ==
+ conn(:get, "/")
+ |> Cache.call(%{query_params: false, ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+
+ assert_raise(Plug.Conn.AlreadySentError, fn ->
+ conn(:get, "/")
+ |> Cache.call(%{query_params: false, ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+ end)
+
+ assert @hit_resp ==
+ conn(:get, "/")
+ |> Cache.call(%{query_params: false, ttl: nil})
+ |> sent_resp()
+ end
+
+ test "ttl is set" do
+ assert @miss_resp ==
+ conn(:get, "/")
+ |> Cache.call(%{query_params: false, ttl: @ttl})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+
+ assert @hit_resp ==
+ conn(:get, "/")
+ |> Cache.call(%{query_params: false, ttl: @ttl})
+ |> sent_resp()
+
+ :timer.sleep(@ttl + 1)
+
+ assert @miss_resp ==
+ conn(:get, "/")
+ |> Cache.call(%{query_params: false, ttl: @ttl})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+ end
+
+ test "set ttl via conn.assigns" do
+ assert @miss_resp ==
+ conn(:get, "/")
+ |> Cache.call(%{query_params: false, ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> assign(:cache_ttl, @ttl)
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+
+ assert @hit_resp ==
+ conn(:get, "/")
+ |> Cache.call(%{query_params: false, ttl: nil})
+ |> sent_resp()
+
+ :timer.sleep(@ttl + 1)
+
+ assert @miss_resp ==
+ conn(:get, "/")
+ |> Cache.call(%{query_params: false, ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+ end
+
+ test "ignore query string when `query_params` is false" do
+ assert @miss_resp ==
+ conn(:get, "/?cofe")
+ |> Cache.call(%{query_params: false, ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+
+ assert @hit_resp ==
+ conn(:get, "/?cofefe")
+ |> Cache.call(%{query_params: false, ttl: nil})
+ |> sent_resp()
+ end
+
+ test "take query string into account when `query_params` is true" do
+ assert @miss_resp ==
+ conn(:get, "/?cofe")
+ |> Cache.call(%{query_params: true, ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+
+ assert @miss_resp ==
+ conn(:get, "/?cofefe")
+ |> Cache.call(%{query_params: true, ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+ end
+
+ test "take specific query params into account when `query_params` is list" do
+ assert @miss_resp ==
+ conn(:get, "/?a=1&b=2&c=3&foo=bar")
+ |> fetch_query_params()
+ |> Cache.call(%{query_params: ["a", "b", "c"], ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+
+ assert @hit_resp ==
+ conn(:get, "/?bar=foo&c=3&b=2&a=1")
+ |> fetch_query_params()
+ |> Cache.call(%{query_params: ["a", "b", "c"], ttl: nil})
+ |> sent_resp()
+
+ assert @miss_resp ==
+ conn(:get, "/?bar=foo&c=3&b=2&a=2")
+ |> fetch_query_params()
+ |> Cache.call(%{query_params: ["a", "b", "c"], ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+ end
+
+ test "ignore not GET requests" do
+ expected =
+ {200,
+ [
+ {"cache-control", "max-age=0, private, must-revalidate"},
+ {"content-type", "cofe/hot; charset=utf-8"}
+ ], "cofe"}
+
+ assert expected ==
+ conn(:post, "/")
+ |> Cache.call(%{query_params: true, ttl: nil})
+ |> put_resp_content_type("cofe/hot")
+ |> send_resp(:ok, "cofe")
+ |> sent_resp()
+ end
+
+ test "ignore non-successful responses" do
+ expected =
+ {418,
+ [
+ {"cache-control", "max-age=0, private, must-revalidate"},
+ {"content-type", "tea/iced; charset=utf-8"}
+ ], "🥤"}
+
+ assert expected ==
+ conn(:get, "/cofe")
+ |> Cache.call(%{query_params: true, ttl: nil})
+ |> put_resp_content_type("tea/iced")
+ |> send_resp(:im_a_teapot, "🥤")
+ |> sent_resp()
+ end
+end
diff --git a/test/pleroma/web/plugs/ensure_authenticated_plug_test.exs b/test/pleroma/web/plugs/ensure_authenticated_plug_test.exs
@@ -0,0 +1,96 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsureAuthenticatedPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.EnsureAuthenticatedPlug
+
+ describe "without :if_func / :unless_func options" do
+ test "it halts if user is NOT assigned", %{conn: conn} do
+ conn = EnsureAuthenticatedPlug.call(conn, %{})
+
+ assert conn.status == 403
+ assert conn.halted == true
+ end
+
+ test "it continues if a user is assigned", %{conn: conn} do
+ conn = assign(conn, :user, %User{})
+ ret_conn = EnsureAuthenticatedPlug.call(conn, %{})
+
+ refute ret_conn.halted
+ end
+ end
+
+ test "it halts if user is assigned and MFA enabled", %{conn: conn} do
+ conn =
+ conn
+ |> assign(:user, %User{multi_factor_authentication_settings: %{enabled: true}})
+ |> assign(:auth_credentials, %{password: "xd-42"})
+ |> EnsureAuthenticatedPlug.call(%{})
+
+ assert conn.status == 403
+ assert conn.halted == true
+
+ assert conn.resp_body ==
+ "{\"error\":\"Two-factor authentication enabled, you must use a access token.\"}"
+ end
+
+ test "it continues if user is assigned and MFA disabled", %{conn: conn} do
+ conn =
+ conn
+ |> assign(:user, %User{multi_factor_authentication_settings: %{enabled: false}})
+ |> assign(:auth_credentials, %{password: "xd-42"})
+ |> EnsureAuthenticatedPlug.call(%{})
+
+ refute conn.status == 403
+ refute conn.halted
+ end
+
+ describe "with :if_func / :unless_func options" do
+ setup do
+ %{
+ true_fn: fn _conn -> true end,
+ false_fn: fn _conn -> false end
+ }
+ end
+
+ test "it continues if a user is assigned", %{conn: conn, true_fn: true_fn, false_fn: false_fn} do
+ conn = assign(conn, :user, %User{})
+ refute EnsureAuthenticatedPlug.call(conn, if_func: true_fn).halted
+ refute EnsureAuthenticatedPlug.call(conn, if_func: false_fn).halted
+ refute EnsureAuthenticatedPlug.call(conn, unless_func: true_fn).halted
+ refute EnsureAuthenticatedPlug.call(conn, unless_func: false_fn).halted
+ end
+
+ test "it continues if a user is NOT assigned but :if_func evaluates to `false`",
+ %{conn: conn, false_fn: false_fn} do
+ ret_conn = EnsureAuthenticatedPlug.call(conn, if_func: false_fn)
+ refute ret_conn.halted
+ end
+
+ test "it continues if a user is NOT assigned but :unless_func evaluates to `true`",
+ %{conn: conn, true_fn: true_fn} do
+ ret_conn = EnsureAuthenticatedPlug.call(conn, unless_func: true_fn)
+ refute ret_conn.halted
+ end
+
+ test "it halts if a user is NOT assigned and :if_func evaluates to `true`",
+ %{conn: conn, true_fn: true_fn} do
+ conn = EnsureAuthenticatedPlug.call(conn, if_func: true_fn)
+
+ assert conn.status == 403
+ assert conn.halted == true
+ end
+
+ test "it halts if a user is NOT assigned and :unless_func evaluates to `false`",
+ %{conn: conn, false_fn: false_fn} do
+ conn = EnsureAuthenticatedPlug.call(conn, unless_func: false_fn)
+
+ assert conn.status == 403
+ assert conn.halted == true
+ end
+ end
+end
diff --git a/test/pleroma/web/plugs/ensure_public_or_authenticated_plug_test.exs b/test/pleroma/web/plugs/ensure_public_or_authenticated_plug_test.exs
@@ -0,0 +1,48 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug
+
+ setup do: clear_config([:instance, :public])
+
+ test "it halts if not public and no user is assigned", %{conn: conn} do
+ Config.put([:instance, :public], false)
+
+ conn =
+ conn
+ |> EnsurePublicOrAuthenticatedPlug.call(%{})
+
+ assert conn.status == 403
+ assert conn.halted == true
+ end
+
+ test "it continues if public", %{conn: conn} do
+ Config.put([:instance, :public], true)
+
+ ret_conn =
+ conn
+ |> EnsurePublicOrAuthenticatedPlug.call(%{})
+
+ refute ret_conn.halted
+ end
+
+ test "it continues if a user is assigned, even if not public", %{conn: conn} do
+ Config.put([:instance, :public], false)
+
+ conn =
+ conn
+ |> assign(:user, %User{})
+
+ ret_conn =
+ conn
+ |> EnsurePublicOrAuthenticatedPlug.call(%{})
+
+ refute ret_conn.halted
+ end
+end
diff --git a/test/pleroma/web/plugs/ensure_user_key_plug_test.exs b/test/pleroma/web/plugs/ensure_user_key_plug_test.exs
@@ -0,0 +1,29 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsureUserKeyPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Web.Plugs.EnsureUserKeyPlug
+
+ test "if the conn has a user key set, it does nothing", %{conn: conn} do
+ conn =
+ conn
+ |> assign(:user, 1)
+
+ ret_conn =
+ conn
+ |> EnsureUserKeyPlug.call(%{})
+
+ assert conn == ret_conn
+ end
+
+ test "if the conn has no key set, it sets it to nil", %{conn: conn} do
+ conn =
+ conn
+ |> EnsureUserKeyPlug.call(%{})
+
+ assert Map.has_key?(conn.assigns, :user)
+ end
+end
diff --git a/test/pleroma/web/plugs/federating_plug_test.exs b/test/pleroma/web/plugs/federating_plug_test.exs
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.FederatingPlugTest do
+ use Pleroma.Web.ConnCase
+
+ setup do: clear_config([:instance, :federating])
+
+ test "returns and halt the conn when federating is disabled" do
+ Pleroma.Config.put([:instance, :federating], false)
+
+ conn =
+ build_conn()
+ |> Pleroma.Web.Plugs.FederatingPlug.call(%{})
+
+ assert conn.status == 404
+ assert conn.halted
+ end
+
+ test "does nothing when federating is enabled" do
+ Pleroma.Config.put([:instance, :federating], true)
+
+ conn =
+ build_conn()
+ |> Pleroma.Web.Plugs.FederatingPlug.call(%{})
+
+ refute conn.status
+ refute conn.halted
+ end
+end
diff --git a/test/pleroma/web/plugs/frontend_static_plug_test.exs b/test/pleroma/web/plugs/frontend_static_plug_test.exs
@@ -0,0 +1,56 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do
+ use Pleroma.Web.ConnCase
+
+ @dir "test/tmp/instance_static"
+
+ setup do
+ File.mkdir_p!(@dir)
+ on_exit(fn -> File.rm_rf(@dir) end)
+ end
+
+ setup do: clear_config([:instance, :static_dir], @dir)
+
+ test "init will give a static plug config + the frontend type" do
+ opts =
+ [
+ at: "/admin",
+ frontend_type: :admin
+ ]
+ |> Pleroma.Web.Plugs.FrontendStatic.init()
+
+ assert opts[:at] == ["admin"]
+ assert opts[:frontend_type] == :admin
+ end
+
+ test "overrides existing static files", %{conn: conn} do
+ name = "pelmora"
+ ref = "uguu"
+
+ clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
+ path = "#{@dir}/frontends/#{name}/#{ref}"
+
+ File.mkdir_p!(path)
+ File.write!("#{path}/index.html", "from frontend plug")
+
+ index = get(conn, "/")
+ assert html_response(index, 200) == "from frontend plug"
+ end
+
+ test "overrides existing static files for the `pleroma/admin` path", %{conn: conn} do
+ name = "pelmora"
+ ref = "uguu"
+
+ clear_config([:frontends, :admin], %{"name" => name, "ref" => ref})
+ path = "#{@dir}/frontends/#{name}/#{ref}"
+
+ File.mkdir_p!(path)
+ File.write!("#{path}/index.html", "from frontend plug")
+
+ index = get(conn, "/pleroma/admin/")
+ assert html_response(index, 200) == "from frontend plug"
+ end
+end
diff --git a/test/plugs/http_security_plug_test.exs b/test/pleroma/web/plugs/http_security_plug_test.exs
diff --git a/test/plugs/http_signature_plug_test.exs b/test/pleroma/web/plugs/http_signature_plug_test.exs
diff --git a/test/pleroma/web/plugs/idempotency_plug_test.exs b/test/pleroma/web/plugs/idempotency_plug_test.exs
@@ -0,0 +1,110 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.IdempotencyPlugTest do
+ use ExUnit.Case, async: true
+ use Plug.Test
+
+ alias Pleroma.Web.Plugs.IdempotencyPlug
+ alias Plug.Conn
+
+ test "returns result from cache" do
+ key = "test1"
+ orig_request_id = "test1"
+ second_request_id = "test2"
+ body = "testing"
+ status = 200
+
+ :post
+ |> conn("/cofe")
+ |> put_req_header("idempotency-key", key)
+ |> Conn.put_resp_header("x-request-id", orig_request_id)
+ |> Conn.put_resp_content_type("application/json")
+ |> IdempotencyPlug.call([])
+ |> Conn.send_resp(status, body)
+
+ conn =
+ :post
+ |> conn("/cofe")
+ |> put_req_header("idempotency-key", key)
+ |> Conn.put_resp_header("x-request-id", second_request_id)
+ |> Conn.put_resp_content_type("application/json")
+ |> IdempotencyPlug.call([])
+
+ assert_raise Conn.AlreadySentError, fn ->
+ Conn.send_resp(conn, :im_a_teapot, "no cofe")
+ end
+
+ assert conn.resp_body == body
+ assert conn.status == status
+
+ assert [^second_request_id] = Conn.get_resp_header(conn, "x-request-id")
+ assert [^orig_request_id] = Conn.get_resp_header(conn, "x-original-request-id")
+ assert [^key] = Conn.get_resp_header(conn, "idempotency-key")
+ assert ["true"] = Conn.get_resp_header(conn, "idempotent-replayed")
+ assert ["application/json; charset=utf-8"] = Conn.get_resp_header(conn, "content-type")
+ end
+
+ test "pass conn downstream if the cache not found" do
+ key = "test2"
+ orig_request_id = "test3"
+ body = "testing"
+ status = 200
+
+ conn =
+ :post
+ |> conn("/cofe")
+ |> put_req_header("idempotency-key", key)
+ |> Conn.put_resp_header("x-request-id", orig_request_id)
+ |> Conn.put_resp_content_type("application/json")
+ |> IdempotencyPlug.call([])
+ |> Conn.send_resp(status, body)
+
+ assert conn.resp_body == body
+ assert conn.status == status
+
+ assert [] = Conn.get_resp_header(conn, "idempotent-replayed")
+ assert [^key] = Conn.get_resp_header(conn, "idempotency-key")
+ end
+
+ test "passes conn downstream if idempotency is not present in headers" do
+ orig_request_id = "test4"
+ body = "testing"
+ status = 200
+
+ conn =
+ :post
+ |> conn("/cofe")
+ |> Conn.put_resp_header("x-request-id", orig_request_id)
+ |> Conn.put_resp_content_type("application/json")
+ |> IdempotencyPlug.call([])
+ |> Conn.send_resp(status, body)
+
+ assert [] = Conn.get_resp_header(conn, "idempotency-key")
+ end
+
+ test "doesn't work with GET/DELETE" do
+ key = "test3"
+ body = "testing"
+ status = 200
+
+ conn =
+ :get
+ |> conn("/cofe")
+ |> put_req_header("idempotency-key", key)
+ |> IdempotencyPlug.call([])
+ |> Conn.send_resp(status, body)
+
+ assert [] = Conn.get_resp_header(conn, "idempotency-key")
+
+ conn =
+ :delete
+ |> conn("/cofe")
+ |> put_req_header("idempotency-key", key)
+ |> IdempotencyPlug.call([])
+ |> Conn.send_resp(status, body)
+
+ assert [] = Conn.get_resp_header(conn, "idempotency-key")
+ end
+end
diff --git a/test/pleroma/web/plugs/instance_static_test.exs b/test/pleroma/web/plugs/instance_static_test.exs
@@ -0,0 +1,65 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.InstanceStaticTest do
+ use Pleroma.Web.ConnCase
+
+ @dir "test/tmp/instance_static"
+
+ setup do
+ File.mkdir_p!(@dir)
+ on_exit(fn -> File.rm_rf(@dir) end)
+ end
+
+ setup do: clear_config([:instance, :static_dir], @dir)
+
+ test "overrides index" do
+ bundled_index = get(build_conn(), "/")
+ refute html_response(bundled_index, 200) == "hello world"
+
+ File.write!(@dir <> "/index.html", "hello world")
+
+ index = get(build_conn(), "/")
+ assert html_response(index, 200) == "hello world"
+ end
+
+ test "also overrides frontend files", %{conn: conn} do
+ name = "pelmora"
+ ref = "uguu"
+
+ clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
+
+ bundled_index = get(conn, "/")
+ refute html_response(bundled_index, 200) == "from frontend plug"
+
+ path = "#{@dir}/frontends/#{name}/#{ref}"
+ File.mkdir_p!(path)
+ File.write!("#{path}/index.html", "from frontend plug")
+
+ index = get(conn, "/")
+ assert html_response(index, 200) == "from frontend plug"
+
+ File.write!(@dir <> "/index.html", "from instance static")
+
+ index = get(conn, "/")
+ assert html_response(index, 200) == "from instance static"
+ end
+
+ test "overrides any file in static/static" do
+ bundled_index = get(build_conn(), "/static/terms-of-service.html")
+
+ assert html_response(bundled_index, 200) ==
+ File.read!("priv/static/static/terms-of-service.html")
+
+ File.mkdir!(@dir <> "/static")
+ File.write!(@dir <> "/static/terms-of-service.html", "plz be kind")
+
+ index = get(build_conn(), "/static/terms-of-service.html")
+ assert html_response(index, 200) == "plz be kind"
+
+ File.write!(@dir <> "/static/kaniini.html", "<h1>rabbit hugs as a service</h1>")
+ index = get(build_conn(), "/static/kaniini.html")
+ assert html_response(index, 200) == "<h1>rabbit hugs as a service</h1>"
+ end
+end
diff --git a/test/pleroma/web/plugs/legacy_authentication_plug_test.exs b/test/pleroma/web/plugs/legacy_authentication_plug_test.exs
@@ -0,0 +1,82 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.LegacyAuthenticationPlugTest do
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.LegacyAuthenticationPlug
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.Plugs.PlugHelper
+
+ setup do
+ user =
+ insert(:user,
+ password: "password",
+ password_hash:
+ "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
+ )
+
+ %{user: user}
+ end
+
+ test "it does nothing if a user is assigned", %{conn: conn, user: user} do
+ conn =
+ conn
+ |> assign(:auth_credentials, %{username: "dude", password: "password"})
+ |> assign(:auth_user, user)
+ |> assign(:user, %User{})
+
+ ret_conn =
+ conn
+ |> LegacyAuthenticationPlug.call(%{})
+
+ assert ret_conn == conn
+ end
+
+ @tag :skip_on_mac
+ test "if `auth_user` is present and password is correct, " <>
+ "it authenticates the user, resets the password, marks OAuthScopesPlug as skipped",
+ %{
+ conn: conn,
+ user: user
+ } do
+ conn =
+ conn
+ |> assign(:auth_credentials, %{username: "dude", password: "password"})
+ |> assign(:auth_user, user)
+
+ conn = LegacyAuthenticationPlug.call(conn, %{})
+
+ assert conn.assigns.user.id == user.id
+ assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
+ end
+
+ @tag :skip_on_mac
+ test "it does nothing if the password is wrong", %{
+ conn: conn,
+ user: user
+ } do
+ conn =
+ conn
+ |> assign(:auth_credentials, %{username: "dude", password: "wrong_password"})
+ |> assign(:auth_user, user)
+
+ ret_conn =
+ conn
+ |> LegacyAuthenticationPlug.call(%{})
+
+ assert conn == ret_conn
+ end
+
+ test "with no credentials or user it does nothing", %{conn: conn} do
+ ret_conn =
+ conn
+ |> LegacyAuthenticationPlug.call(%{})
+
+ assert ret_conn == conn
+ end
+end
diff --git a/test/plugs/mapped_identity_to_signature_plug_test.exs b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs
diff --git a/test/pleroma/web/plugs/o_auth_plug_test.exs b/test/pleroma/web/plugs/o_auth_plug_test.exs
@@ -0,0 +1,80 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.OAuthPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Web.Plugs.OAuthPlug
+ import Pleroma.Factory
+
+ @session_opts [
+ store: :cookie,
+ key: "_test",
+ signing_salt: "cooldude"
+ ]
+
+ setup %{conn: conn} do
+ user = insert(:user)
+ {:ok, %{token: token}} = Pleroma.Web.OAuth.Token.create(insert(:oauth_app), user)
+ %{user: user, token: token, conn: conn}
+ end
+
+ test "with valid token(uppercase), it assigns the user", %{conn: conn} = opts do
+ conn =
+ conn
+ |> put_req_header("authorization", "BEARER #{opts[:token]}")
+ |> OAuthPlug.call(%{})
+
+ assert conn.assigns[:user] == opts[:user]
+ end
+
+ test "with valid token(downcase), it assigns the user", %{conn: conn} = opts do
+ conn =
+ conn
+ |> put_req_header("authorization", "bearer #{opts[:token]}")
+ |> OAuthPlug.call(%{})
+
+ assert conn.assigns[:user] == opts[:user]
+ end
+
+ test "with valid token(downcase) in url parameters, it assigns the user", opts do
+ conn =
+ :get
+ |> build_conn("/?access_token=#{opts[:token]}")
+ |> put_req_header("content-type", "application/json")
+ |> fetch_query_params()
+ |> OAuthPlug.call(%{})
+
+ assert conn.assigns[:user] == opts[:user]
+ end
+
+ test "with valid token(downcase) in body parameters, it assigns the user", opts do
+ conn =
+ :post
+ |> build_conn("/api/v1/statuses", access_token: opts[:token], status: "test")
+ |> OAuthPlug.call(%{})
+
+ assert conn.assigns[:user] == opts[:user]
+ end
+
+ test "with invalid token, it not assigns the user", %{conn: conn} do
+ conn =
+ conn
+ |> put_req_header("authorization", "bearer TTTTT")
+ |> OAuthPlug.call(%{})
+
+ refute conn.assigns[:user]
+ end
+
+ test "when token is missed but token in session, it assigns the user", %{conn: conn} = opts do
+ conn =
+ conn
+ |> Plug.Session.call(Plug.Session.init(@session_opts))
+ |> fetch_session()
+ |> put_session(:oauth_token, opts[:token])
+ |> OAuthPlug.call(%{})
+
+ assert conn.assigns[:user] == opts[:user]
+ end
+end
diff --git a/test/pleroma/web/plugs/o_auth_scopes_plug_test.exs b/test/pleroma/web/plugs/o_auth_scopes_plug_test.exs
@@ -0,0 +1,210 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.OAuthScopesPlugTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.Repo
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ import Mock
+ import Pleroma.Factory
+
+ test "is not performed if marked as skipped", %{conn: conn} do
+ with_mock OAuthScopesPlug, [:passthrough], perform: &passthrough([&1, &2]) do
+ conn =
+ conn
+ |> OAuthScopesPlug.skip_plug()
+ |> OAuthScopesPlug.call(%{scopes: ["random_scope"]})
+
+ refute called(OAuthScopesPlug.perform(:_, :_))
+ refute conn.halted
+ end
+ end
+
+ test "if `token.scopes` fulfills specified 'any of' conditions, " <>
+ "proceeds with no op",
+ %{conn: conn} do
+ token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user)
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: ["read"]})
+
+ refute conn.halted
+ assert conn.assigns[:user]
+ end
+
+ test "if `token.scopes` fulfills specified 'all of' conditions, " <>
+ "proceeds with no op",
+ %{conn: conn} do
+ token = insert(:oauth_token, scopes: ["scope1", "scope2", "scope3"]) |> Repo.preload(:user)
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: ["scope2", "scope3"], op: :&})
+
+ refute conn.halted
+ assert conn.assigns[:user]
+ end
+
+ describe "with `fallback: :proceed_unauthenticated` option, " do
+ test "if `token.scopes` doesn't fulfill specified conditions, " <>
+ "clears :user and :token assigns",
+ %{conn: conn} do
+ user = insert(:user)
+ token1 = insert(:oauth_token, scopes: ["read", "write"], user: user)
+
+ for token <- [token1, nil], op <- [:|, :&] do
+ ret_conn =
+ conn
+ |> assign(:user, user)
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{
+ scopes: ["follow"],
+ op: op,
+ fallback: :proceed_unauthenticated
+ })
+
+ refute ret_conn.halted
+ refute ret_conn.assigns[:user]
+ refute ret_conn.assigns[:token]
+ end
+ end
+ end
+
+ describe "without :fallback option, " do
+ test "if `token.scopes` does not fulfill specified 'any of' conditions, " <>
+ "returns 403 and halts",
+ %{conn: conn} do
+ for token <- [insert(:oauth_token, scopes: ["read", "write"]), nil] do
+ any_of_scopes = ["follow", "push"]
+
+ ret_conn =
+ conn
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: any_of_scopes})
+
+ assert ret_conn.halted
+ assert 403 == ret_conn.status
+
+ expected_error = "Insufficient permissions: #{Enum.join(any_of_scopes, " | ")}."
+ assert Jason.encode!(%{error: expected_error}) == ret_conn.resp_body
+ end
+ end
+
+ test "if `token.scopes` does not fulfill specified 'all of' conditions, " <>
+ "returns 403 and halts",
+ %{conn: conn} do
+ for token <- [insert(:oauth_token, scopes: ["read", "write"]), nil] do
+ token_scopes = (token && token.scopes) || []
+ all_of_scopes = ["write", "follow"]
+
+ conn =
+ conn
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: all_of_scopes, op: :&})
+
+ assert conn.halted
+ assert 403 == conn.status
+
+ expected_error =
+ "Insufficient permissions: #{Enum.join(all_of_scopes -- token_scopes, " & ")}."
+
+ assert Jason.encode!(%{error: expected_error}) == conn.resp_body
+ end
+ end
+ end
+
+ describe "with hierarchical scopes, " do
+ test "if `token.scopes` fulfills specified 'any of' conditions, " <>
+ "proceeds with no op",
+ %{conn: conn} do
+ token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user)
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: ["read:something"]})
+
+ refute conn.halted
+ assert conn.assigns[:user]
+ end
+
+ test "if `token.scopes` fulfills specified 'all of' conditions, " <>
+ "proceeds with no op",
+ %{conn: conn} do
+ token = insert(:oauth_token, scopes: ["scope1", "scope2", "scope3"]) |> Repo.preload(:user)
+
+ conn =
+ conn
+ |> assign(:user, token.user)
+ |> assign(:token, token)
+ |> OAuthScopesPlug.call(%{scopes: ["scope1:subscope", "scope2:subscope"], op: :&})
+
+ refute conn.halted
+ assert conn.assigns[:user]
+ end
+ end
+
+ describe "filter_descendants/2" do
+ test "filters scopes which directly match or are ancestors of supported scopes" do
+ f = fn scopes, supported_scopes ->
+ OAuthScopesPlug.filter_descendants(scopes, supported_scopes)
+ end
+
+ assert f.(["read", "follow"], ["write", "read"]) == ["read"]
+
+ assert f.(["read", "write:something", "follow"], ["write", "read"]) ==
+ ["read", "write:something"]
+
+ assert f.(["admin:read"], ["write", "read"]) == []
+
+ assert f.(["admin:read"], ["write", "admin"]) == ["admin:read"]
+ end
+ end
+
+ describe "transform_scopes/2" do
+ setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage])
+
+ setup do
+ {:ok, %{f: &OAuthScopesPlug.transform_scopes/2}}
+ end
+
+ test "with :admin option, prefixes all requested scopes with `admin:` " <>
+ "and [optionally] keeps only prefixed scopes, " <>
+ "depending on `[:auth, :enforce_oauth_admin_scope_usage]` setting",
+ %{f: f} do
+ Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false)
+
+ assert f.(["read"], %{admin: true}) == ["admin:read", "read"]
+
+ assert f.(["read", "write"], %{admin: true}) == [
+ "admin:read",
+ "read",
+ "admin:write",
+ "write"
+ ]
+
+ Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], true)
+
+ assert f.(["read:accounts"], %{admin: true}) == ["admin:read:accounts"]
+
+ assert f.(["read", "write:reports"], %{admin: true}) == [
+ "admin:read",
+ "admin:write:reports"
+ ]
+ end
+
+ test "with no supported options, returns unmodified scopes", %{f: f} do
+ assert f.(["read"], %{}) == ["read"]
+ assert f.(["read", "write"], %{}) == ["read", "write"]
+ end
+ end
+end
diff --git a/test/pleroma/web/plugs/plug_helper_test.exs b/test/pleroma/web/plugs/plug_helper_test.exs
@@ -0,0 +1,91 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.PlugHelperTest do
+ @moduledoc "Tests for the functionality added via `use Pleroma.Web, :plug`"
+
+ alias Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug
+ alias Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug
+ alias Pleroma.Web.Plugs.PlugHelper
+
+ import Mock
+
+ use Pleroma.Web.ConnCase
+
+ describe "when plug is skipped, " do
+ setup_with_mocks(
+ [
+ {ExpectPublicOrAuthenticatedCheckPlug, [:passthrough], []}
+ ],
+ %{conn: conn}
+ ) do
+ conn = ExpectPublicOrAuthenticatedCheckPlug.skip_plug(conn)
+ %{conn: conn}
+ end
+
+ test "it neither adds plug to called plugs list nor calls `perform/2`, " <>
+ "regardless of :if_func / :unless_func options",
+ %{conn: conn} do
+ for opts <- [%{}, %{if_func: fn _ -> true end}, %{unless_func: fn _ -> false end}] do
+ ret_conn = ExpectPublicOrAuthenticatedCheckPlug.call(conn, opts)
+
+ refute called(ExpectPublicOrAuthenticatedCheckPlug.perform(:_, :_))
+ refute PlugHelper.plug_called?(ret_conn, ExpectPublicOrAuthenticatedCheckPlug)
+ end
+ end
+ end
+
+ describe "when plug is NOT skipped, " do
+ setup_with_mocks([{ExpectAuthenticatedCheckPlug, [:passthrough], []}]) do
+ :ok
+ end
+
+ test "with no pre-run checks, adds plug to called plugs list and calls `perform/2`", %{
+ conn: conn
+ } do
+ ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{})
+
+ assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_))
+ assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
+ end
+
+ test "when :if_func option is given, calls the plug only if provided function evals tru-ish",
+ %{conn: conn} do
+ ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{if_func: fn _ -> false end})
+
+ refute called(ExpectAuthenticatedCheckPlug.perform(:_, :_))
+ refute PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
+
+ ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{if_func: fn _ -> true end})
+
+ assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_))
+ assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
+ end
+
+ test "if :unless_func option is given, calls the plug only if provided function evals falsy",
+ %{conn: conn} do
+ ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{unless_func: fn _ -> true end})
+
+ refute called(ExpectAuthenticatedCheckPlug.perform(:_, :_))
+ refute PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
+
+ ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{unless_func: fn _ -> false end})
+
+ assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_))
+ assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
+ end
+
+ test "allows a plug to be called multiple times (even if it's in called plugs list)", %{
+ conn: conn
+ } do
+ conn = ExpectAuthenticatedCheckPlug.call(conn, %{an_option: :value1})
+ assert called(ExpectAuthenticatedCheckPlug.perform(conn, %{an_option: :value1}))
+
+ assert PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug)
+
+ conn = ExpectAuthenticatedCheckPlug.call(conn, %{an_option: :value2})
+ assert called(ExpectAuthenticatedCheckPlug.perform(conn, %{an_option: :value2}))
+ end
+ end
+end
diff --git a/test/pleroma/web/plugs/rate_limiter_test.exs b/test/pleroma/web/plugs/rate_limiter_test.exs
@@ -0,0 +1,263 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.RateLimiterTest do
+ use Pleroma.Web.ConnCase
+
+ alias Phoenix.ConnTest
+ alias Pleroma.Config
+ alias Pleroma.Web.Plugs.RateLimiter
+ alias Plug.Conn
+
+ import Pleroma.Factory
+ import Pleroma.Tests.Helpers, only: [clear_config: 1, clear_config: 2]
+
+ # Note: each example must work with separate buckets in order to prevent concurrency issues
+ setup do: clear_config([Pleroma.Web.Endpoint, :http, :ip])
+ setup do: clear_config(:rate_limit)
+
+ describe "config" do
+ @limiter_name :test_init
+ setup do: clear_config([Pleroma.Web.Plugs.RemoteIp, :enabled])
+
+ test "config is required for plug to work" do
+ Config.put([:rate_limit, @limiter_name], {1, 1})
+ Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
+
+ assert %{limits: {1, 1}, name: :test_init, opts: [name: :test_init]} ==
+ [name: @limiter_name]
+ |> RateLimiter.init()
+ |> RateLimiter.action_settings()
+
+ assert nil ==
+ [name: :nonexisting_limiter]
+ |> RateLimiter.init()
+ |> RateLimiter.action_settings()
+ end
+ end
+
+ test "it is disabled if it remote ip plug is enabled but no remote ip is found" do
+ assert RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, false))
+ end
+
+ test "it is enabled if remote ip found" do
+ refute RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, true))
+ end
+
+ test "it is enabled if remote_ip_found flag doesn't exist" do
+ refute RateLimiter.disabled?(build_conn())
+ end
+
+ test "it restricts based on config values" do
+ limiter_name = :test_plug_opts
+ scale = 80
+ limit = 5
+
+ Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
+ Config.put([:rate_limit, limiter_name], {scale, limit})
+
+ plug_opts = RateLimiter.init(name: limiter_name)
+ conn = build_conn(:get, "/")
+
+ for i <- 1..5 do
+ conn = RateLimiter.call(conn, plug_opts)
+ assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
+ Process.sleep(10)
+ end
+
+ conn = RateLimiter.call(conn, plug_opts)
+ assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
+ assert conn.halted
+
+ Process.sleep(50)
+
+ conn = build_conn(:get, "/")
+
+ conn = RateLimiter.call(conn, plug_opts)
+ assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
+
+ refute conn.status == Conn.Status.code(:too_many_requests)
+ refute conn.resp_body
+ refute conn.halted
+ end
+
+ describe "options" do
+ test "`bucket_name` option overrides default bucket name" do
+ limiter_name = :test_bucket_name
+
+ Config.put([:rate_limit, limiter_name], {1000, 5})
+ Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
+
+ base_bucket_name = "#{limiter_name}:group1"
+ plug_opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name)
+
+ conn = build_conn(:get, "/")
+
+ RateLimiter.call(conn, plug_opts)
+ assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, plug_opts)
+ assert {:error, :not_found} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
+ end
+
+ test "`params` option allows different queries to be tracked independently" do
+ limiter_name = :test_params
+ Config.put([:rate_limit, limiter_name], {1000, 5})
+ Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
+
+ plug_opts = RateLimiter.init(name: limiter_name, params: ["id"])
+
+ conn = build_conn(:get, "/?id=1")
+ conn = Conn.fetch_query_params(conn)
+ conn_2 = build_conn(:get, "/?id=2")
+
+ RateLimiter.call(conn, plug_opts)
+ assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
+ assert {0, 5} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
+ end
+
+ test "it supports combination of options modifying bucket name" do
+ limiter_name = :test_options_combo
+ Config.put([:rate_limit, limiter_name], {1000, 5})
+ Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
+
+ base_bucket_name = "#{limiter_name}:group1"
+
+ plug_opts =
+ RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name, params: ["id"])
+
+ id = "100"
+
+ conn = build_conn(:get, "/?id=#{id}")
+ conn = Conn.fetch_query_params(conn)
+ conn_2 = build_conn(:get, "/?id=#{101}")
+
+ RateLimiter.call(conn, plug_opts)
+ assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, plug_opts)
+ assert {0, 5} = RateLimiter.inspect_bucket(conn_2, base_bucket_name, plug_opts)
+ end
+ end
+
+ describe "unauthenticated users" do
+ test "are restricted based on remote IP" do
+ limiter_name = :test_unauthenticated
+ Config.put([:rate_limit, limiter_name], [{1000, 5}, {1, 10}])
+ Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
+
+ plug_opts = RateLimiter.init(name: limiter_name)
+
+ conn = %{build_conn(:get, "/") | remote_ip: {127, 0, 0, 2}}
+ conn_2 = %{build_conn(:get, "/") | remote_ip: {127, 0, 0, 3}}
+
+ for i <- 1..5 do
+ conn = RateLimiter.call(conn, plug_opts)
+ assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
+ refute conn.halted
+ end
+
+ conn = RateLimiter.call(conn, plug_opts)
+
+ assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
+ assert conn.halted
+
+ conn_2 = RateLimiter.call(conn_2, plug_opts)
+ assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
+
+ refute conn_2.status == Conn.Status.code(:too_many_requests)
+ refute conn_2.resp_body
+ refute conn_2.halted
+ end
+ end
+
+ describe "authenticated users" do
+ setup do
+ Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
+
+ :ok
+ end
+
+ test "can have limits separate from unauthenticated connections" do
+ limiter_name = :test_authenticated1
+
+ scale = 50
+ limit = 5
+ Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
+ Config.put([:rate_limit, limiter_name], [{1000, 1}, {scale, limit}])
+
+ plug_opts = RateLimiter.init(name: limiter_name)
+
+ user = insert(:user)
+ conn = build_conn(:get, "/") |> assign(:user, user)
+
+ for i <- 1..5 do
+ conn = RateLimiter.call(conn, plug_opts)
+ assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
+ refute conn.halted
+ end
+
+ conn = RateLimiter.call(conn, plug_opts)
+
+ assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
+ assert conn.halted
+ end
+
+ test "different users are counted independently" do
+ limiter_name = :test_authenticated2
+ Config.put([:rate_limit, limiter_name], [{1, 10}, {1000, 5}])
+ Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
+
+ plug_opts = RateLimiter.init(name: limiter_name)
+
+ user = insert(:user)
+ conn = build_conn(:get, "/") |> assign(:user, user)
+
+ user_2 = insert(:user)
+ conn_2 = build_conn(:get, "/") |> assign(:user, user_2)
+
+ for i <- 1..5 do
+ conn = RateLimiter.call(conn, plug_opts)
+ assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
+ end
+
+ conn = RateLimiter.call(conn, plug_opts)
+ assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
+ assert conn.halted
+
+ conn_2 = RateLimiter.call(conn_2, plug_opts)
+ assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
+ refute conn_2.status == Conn.Status.code(:too_many_requests)
+ refute conn_2.resp_body
+ refute conn_2.halted
+ end
+ end
+
+ test "doesn't crash due to a race condition when multiple requests are made at the same time and the bucket is not yet initialized" do
+ limiter_name = :test_race_condition
+ Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5})
+ Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
+
+ opts = RateLimiter.init(name: limiter_name)
+
+ conn = build_conn(:get, "/")
+ conn_2 = build_conn(:get, "/")
+
+ %Task{pid: pid1} =
+ task1 =
+ Task.async(fn ->
+ receive do
+ :process2_up ->
+ RateLimiter.call(conn, opts)
+ end
+ end)
+
+ task2 =
+ Task.async(fn ->
+ send(pid1, :process2_up)
+ RateLimiter.call(conn_2, opts)
+ end)
+
+ Task.await(task1)
+ Task.await(task2)
+
+ refute {:err, :not_found} == RateLimiter.inspect_bucket(conn, limiter_name, opts)
+ end
+end
diff --git a/test/pleroma/web/plugs/remote_ip_test.exs b/test/pleroma/web/plugs/remote_ip_test.exs
@@ -0,0 +1,108 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.RemoteIpTest do
+ use ExUnit.Case
+ use Plug.Test
+
+ alias Pleroma.Web.Plugs.RemoteIp
+
+ import Pleroma.Tests.Helpers, only: [clear_config: 2]
+
+ setup do:
+ clear_config(RemoteIp,
+ enabled: true,
+ headers: ["x-forwarded-for"],
+ proxies: [],
+ reserved: [
+ "127.0.0.0/8",
+ "::1/128",
+ "fc00::/7",
+ "10.0.0.0/8",
+ "172.16.0.0/12",
+ "192.168.0.0/16"
+ ]
+ )
+
+ test "disabled" do
+ Pleroma.Config.put(RemoteIp, enabled: false)
+
+ %{remote_ip: remote_ip} = conn(:get, "/")
+
+ conn =
+ conn(:get, "/")
+ |> put_req_header("x-forwarded-for", "1.1.1.1")
+ |> RemoteIp.call(nil)
+
+ assert conn.remote_ip == remote_ip
+ end
+
+ test "enabled" do
+ conn =
+ conn(:get, "/")
+ |> put_req_header("x-forwarded-for", "1.1.1.1")
+ |> RemoteIp.call(nil)
+
+ assert conn.remote_ip == {1, 1, 1, 1}
+ end
+
+ test "custom headers" do
+ Pleroma.Config.put(RemoteIp, enabled: true, headers: ["cf-connecting-ip"])
+
+ conn =
+ conn(:get, "/")
+ |> put_req_header("x-forwarded-for", "1.1.1.1")
+ |> RemoteIp.call(nil)
+
+ refute conn.remote_ip == {1, 1, 1, 1}
+
+ conn =
+ conn(:get, "/")
+ |> put_req_header("cf-connecting-ip", "1.1.1.1")
+ |> RemoteIp.call(nil)
+
+ assert conn.remote_ip == {1, 1, 1, 1}
+ end
+
+ test "custom proxies" do
+ conn =
+ conn(:get, "/")
+ |> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2")
+ |> RemoteIp.call(nil)
+
+ refute conn.remote_ip == {1, 1, 1, 1}
+
+ Pleroma.Config.put([RemoteIp, :proxies], ["173.245.48.0/20"])
+
+ conn =
+ conn(:get, "/")
+ |> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2")
+ |> RemoteIp.call(nil)
+
+ assert conn.remote_ip == {1, 1, 1, 1}
+ end
+
+ test "proxies set without CIDR format" do
+ Pleroma.Config.put([RemoteIp, :proxies], ["173.245.48.1"])
+
+ conn =
+ conn(:get, "/")
+ |> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1")
+ |> RemoteIp.call(nil)
+
+ assert conn.remote_ip == {1, 1, 1, 1}
+ end
+
+ test "proxies set `nonsensical` CIDR" do
+ Pleroma.Config.put([RemoteIp, :reserved], ["127.0.0.0/8"])
+ Pleroma.Config.put([RemoteIp, :proxies], ["10.0.0.3/24"])
+
+ conn =
+ conn(:get, "/")
+ |> put_req_header("x-forwarded-for", "10.0.0.3, 1.1.1.1")
+ |> RemoteIp.call(nil)
+
+ assert conn.remote_ip == {1, 1, 1, 1}
+ end
+end
diff --git a/test/pleroma/web/plugs/session_authentication_plug_test.exs b/test/pleroma/web/plugs/session_authentication_plug_test.exs
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.SessionAuthenticationPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.SessionAuthenticationPlug
+
+ setup %{conn: conn} do
+ session_opts = [
+ store: :cookie,
+ key: "_test",
+ signing_salt: "cooldude"
+ ]
+
+ conn =
+ conn
+ |> Plug.Session.call(Plug.Session.init(session_opts))
+ |> fetch_session
+ |> assign(:auth_user, %User{id: 1})
+
+ %{conn: conn}
+ end
+
+ test "it does nothing if a user is assigned", %{conn: conn} do
+ conn =
+ conn
+ |> assign(:user, %User{})
+
+ ret_conn =
+ conn
+ |> SessionAuthenticationPlug.call(%{})
+
+ assert ret_conn == conn
+ end
+
+ test "if the auth_user has the same id as the user_id in the session, it assigns the user", %{
+ conn: conn
+ } do
+ conn =
+ conn
+ |> put_session(:user_id, conn.assigns.auth_user.id)
+ |> SessionAuthenticationPlug.call(%{})
+
+ assert conn.assigns.user == conn.assigns.auth_user
+ end
+
+ test "if the auth_user has a different id as the user_id in the session, it does nothing", %{
+ conn: conn
+ } do
+ conn =
+ conn
+ |> put_session(:user_id, -1)
+
+ ret_conn =
+ conn
+ |> SessionAuthenticationPlug.call(%{})
+
+ assert ret_conn == conn
+ end
+end
diff --git a/test/pleroma/web/plugs/set_format_plug_test.exs b/test/pleroma/web/plugs/set_format_plug_test.exs
@@ -0,0 +1,38 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.SetFormatPlugTest do
+ use ExUnit.Case, async: true
+ use Plug.Test
+
+ alias Pleroma.Web.Plugs.SetFormatPlug
+
+ test "set format from params" do
+ conn =
+ :get
+ |> conn("/cofe?_format=json")
+ |> SetFormatPlug.call([])
+
+ assert %{format: "json"} == conn.assigns
+ end
+
+ test "set format from header" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> put_private(:phoenix_format, "xml")
+ |> SetFormatPlug.call([])
+
+ assert %{format: "xml"} == conn.assigns
+ end
+
+ test "doesn't set format" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> SetFormatPlug.call([])
+
+ refute conn.assigns[:format]
+ end
+end
diff --git a/test/pleroma/web/plugs/set_locale_plug_test.exs b/test/pleroma/web/plugs/set_locale_plug_test.exs
@@ -0,0 +1,46 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.SetLocalePlugTest do
+ use ExUnit.Case, async: true
+ use Plug.Test
+
+ alias Pleroma.Web.Plugs.SetLocalePlug
+ alias Plug.Conn
+
+ test "default locale is `en`" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> SetLocalePlug.call([])
+
+ assert "en" == Gettext.get_locale()
+ assert %{locale: "en"} == conn.assigns
+ end
+
+ test "use supported locale from `accept-language`" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> Conn.put_req_header(
+ "accept-language",
+ "ru, fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5"
+ )
+ |> SetLocalePlug.call([])
+
+ assert "ru" == Gettext.get_locale()
+ assert %{locale: "ru"} == conn.assigns
+ end
+
+ test "use default locale if locale from `accept-language` is not supported" do
+ conn =
+ :get
+ |> conn("/cofe")
+ |> Conn.put_req_header("accept-language", "tlh")
+ |> SetLocalePlug.call([])
+
+ assert "en" == Gettext.get_locale()
+ assert %{locale: "en"} == conn.assigns
+ end
+end
diff --git a/test/pleroma/web/plugs/set_user_session_id_plug_test.exs b/test/pleroma/web/plugs/set_user_session_id_plug_test.exs
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.SetUserSessionIdPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.User
+ alias Pleroma.Web.Plugs.SetUserSessionIdPlug
+
+ setup %{conn: conn} do
+ session_opts = [
+ store: :cookie,
+ key: "_test",
+ signing_salt: "cooldude"
+ ]
+
+ conn =
+ conn
+ |> Plug.Session.call(Plug.Session.init(session_opts))
+ |> fetch_session
+
+ %{conn: conn}
+ end
+
+ test "doesn't do anything if the user isn't set", %{conn: conn} do
+ ret_conn =
+ conn
+ |> SetUserSessionIdPlug.call(%{})
+
+ assert ret_conn == conn
+ end
+
+ test "sets the user_id in the session to the user id of the user assign", %{conn: conn} do
+ Code.ensure_compiled(Pleroma.User)
+
+ conn =
+ conn
+ |> assign(:user, %User{id: 1})
+ |> SetUserSessionIdPlug.call(%{})
+
+ id = get_session(conn, :user_id)
+ assert id == 1
+ end
+end
diff --git a/test/pleroma/web/plugs/uploaded_media_plug_test.exs b/test/pleroma/web/plugs/uploaded_media_plug_test.exs
@@ -0,0 +1,43 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UploadedMediaPlugTest do
+ use Pleroma.Web.ConnCase
+ alias Pleroma.Upload
+
+ defp upload_file(context) do
+ Pleroma.DataCase.ensure_local_uploader(context)
+ File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
+
+ file = %Plug.Upload{
+ content_type: "image/jpg",
+ path: Path.absname("test/fixtures/image_tmp.jpg"),
+ filename: "nice_tf.jpg"
+ }
+
+ {:ok, data} = Upload.store(file)
+ [%{"href" => attachment_url} | _] = data["url"]
+ [attachment_url: attachment_url]
+ end
+
+ setup_all :upload_file
+
+ test "does not send Content-Disposition header when name param is not set", %{
+ attachment_url: attachment_url
+ } do
+ conn = get(build_conn(), attachment_url)
+ refute Enum.any?(conn.resp_headers, &(elem(&1, 0) == "content-disposition"))
+ end
+
+ test "sends Content-Disposition header when name param is set", %{
+ attachment_url: attachment_url
+ } do
+ conn = get(build_conn(), attachment_url <> "?name=\"cofe\".gif")
+
+ assert Enum.any?(
+ conn.resp_headers,
+ &(&1 == {"content-disposition", "filename=\"\\\"cofe\\\".gif\""})
+ )
+ end
+end
diff --git a/test/pleroma/web/plugs/user_enabled_plug_test.exs b/test/pleroma/web/plugs/user_enabled_plug_test.exs
@@ -0,0 +1,59 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserEnabledPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Web.Plugs.UserEnabledPlug
+ import Pleroma.Factory
+
+ setup do: clear_config([:instance, :account_activation_required])
+
+ test "doesn't do anything if the user isn't set", %{conn: conn} do
+ ret_conn =
+ conn
+ |> UserEnabledPlug.call(%{})
+
+ assert ret_conn == conn
+ end
+
+ test "with a user that's not confirmed and a config requiring confirmation, it removes that user",
+ %{conn: conn} do
+ Pleroma.Config.put([:instance, :account_activation_required], true)
+
+ user = insert(:user, confirmation_pending: true)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> UserEnabledPlug.call(%{})
+
+ assert conn.assigns.user == nil
+ end
+
+ test "with a user that is deactivated, it removes that user", %{conn: conn} do
+ user = insert(:user, deactivated: true)
+
+ conn =
+ conn
+ |> assign(:user, user)
+ |> UserEnabledPlug.call(%{})
+
+ assert conn.assigns.user == nil
+ end
+
+ test "with a user that is not deactivated, it does nothing", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> assign(:user, user)
+
+ ret_conn =
+ conn
+ |> UserEnabledPlug.call(%{})
+
+ assert conn == ret_conn
+ end
+end
diff --git a/test/pleroma/web/plugs/user_fetcher_plug_test.exs b/test/pleroma/web/plugs/user_fetcher_plug_test.exs
@@ -0,0 +1,41 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserFetcherPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Web.Plugs.UserFetcherPlug
+ import Pleroma.Factory
+
+ setup do
+ user = insert(:user)
+ %{user: user}
+ end
+
+ test "if an auth_credentials assign is present, it tries to fetch the user and assigns it", %{
+ conn: conn,
+ user: user
+ } do
+ conn =
+ conn
+ |> assign(:auth_credentials, %{
+ username: user.nickname,
+ password: nil
+ })
+
+ conn =
+ conn
+ |> UserFetcherPlug.call(%{})
+
+ assert conn.assigns[:auth_user] == user
+ end
+
+ test "without a credential assign it doesn't do anything", %{conn: conn} do
+ ret_conn =
+ conn
+ |> UserFetcherPlug.call(%{})
+
+ assert conn == ret_conn
+ end
+end
diff --git a/test/pleroma/web/plugs/user_is_admin_plug_test.exs b/test/pleroma/web/plugs/user_is_admin_plug_test.exs
@@ -0,0 +1,37 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.UserIsAdminPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Web.Plugs.UserIsAdminPlug
+ import Pleroma.Factory
+
+ test "accepts a user that is an admin" do
+ user = insert(:user, is_admin: true)
+
+ conn = assign(build_conn(), :user, user)
+
+ ret_conn = UserIsAdminPlug.call(conn, %{})
+
+ assert conn == ret_conn
+ end
+
+ test "denies a user that isn't an admin" do
+ user = insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> UserIsAdminPlug.call(%{})
+
+ assert conn.status == 403
+ end
+
+ test "denies when a user isn't set" do
+ conn = UserIsAdminPlug.call(build_conn(), %{})
+
+ assert conn.status == 403
+ end
+end
diff --git a/test/web/preload/instance_test.exs b/test/pleroma/web/preload/providers/instance_test.exs
diff --git a/test/web/preload/timeline_test.exs b/test/pleroma/web/preload/providers/timeline_test.exs
diff --git a/test/web/preload/user_test.exs b/test/pleroma/web/preload/providers/user_test.exs
diff --git a/test/web/push/impl_test.exs b/test/pleroma/web/push/impl_test.exs
diff --git a/test/web/rel_me_test.exs b/test/pleroma/web/rel_me_test.exs
diff --git a/test/pleroma/web/rich_media/helpers_test.exs b/test/pleroma/web/rich_media/helpers_test.exs
@@ -0,0 +1,85 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.HelpersTest do
+ use Pleroma.DataCase
+
+ alias Pleroma.Config
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.RichMedia.Helpers
+
+ import Pleroma.Factory
+ import Tesla.Mock
+
+ setup do
+ mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ :ok
+ end
+
+ setup do: clear_config([:rich_media, :enabled])
+
+ test "refuses to crawl incomplete URLs" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ status: "[test](example.com/ogp)",
+ content_type: "text/markdown"
+ })
+
+ Config.put([:rich_media, :enabled], true)
+
+ assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+ end
+
+ test "refuses to crawl malformed URLs" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ status: "[test](example.com[]/ogp)",
+ content_type: "text/markdown"
+ })
+
+ Config.put([:rich_media, :enabled], true)
+
+ assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+ end
+
+ test "crawls valid, complete URLs" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{
+ status: "[test](https://example.com/ogp)",
+ content_type: "text/markdown"
+ })
+
+ Config.put([:rich_media, :enabled], true)
+
+ assert %{page_url: "https://example.com/ogp", rich_media: _} =
+ Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+ end
+
+ test "refuses to crawl URLs of private network from posts" do
+ user = insert(:user)
+
+ {:ok, activity} =
+ CommonAPI.post(user, %{status: "http://127.0.0.1:4000/notice/9kCP7VNyPJXFOXDrgO"})
+
+ {:ok, activity2} = CommonAPI.post(user, %{status: "https://10.111.10.1/notice/9kCP7V"})
+ {:ok, activity3} = CommonAPI.post(user, %{status: "https://172.16.32.40/notice/9kCP7V"})
+ {:ok, activity4} = CommonAPI.post(user, %{status: "https://192.168.10.40/notice/9kCP7V"})
+ {:ok, activity5} = CommonAPI.post(user, %{status: "https://pleroma.local/notice/9kCP7V"})
+
+ Config.put([:rich_media, :enabled], true)
+
+ assert %{} = Helpers.fetch_data_for_activity(activity)
+ assert %{} = Helpers.fetch_data_for_activity(activity2)
+ assert %{} = Helpers.fetch_data_for_activity(activity3)
+ assert %{} = Helpers.fetch_data_for_activity(activity4)
+ assert %{} = Helpers.fetch_data_for_activity(activity5)
+ end
+end
diff --git a/test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs b/test/pleroma/web/rich_media/parser/ttl/aws_signed_url_test.exs
@@ -0,0 +1,82 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrlTest do
+ use ExUnit.Case, async: true
+
+ test "s3 signed url is parsed correct for expiration time" do
+ url = "https://pleroma.social/amz"
+
+ {:ok, timestamp} =
+ Timex.now()
+ |> DateTime.truncate(:second)
+ |> Timex.format("{ISO:Basic:Z}")
+
+ # in seconds
+ valid_till = 30
+
+ metadata = construct_metadata(timestamp, valid_till, url)
+
+ expire_time =
+ Timex.parse!(timestamp, "{ISO:Basic:Z}") |> Timex.to_unix() |> Kernel.+(valid_till)
+
+ assert {:ok, expire_time} == Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl.ttl(metadata, url)
+ end
+
+ test "s3 signed url is parsed and correct ttl is set for rich media" do
+ url = "https://pleroma.social/amz"
+
+ {:ok, timestamp} =
+ Timex.now()
+ |> DateTime.truncate(:second)
+ |> Timex.format("{ISO:Basic:Z}")
+
+ # in seconds
+ valid_till = 30
+
+ metadata = construct_metadata(timestamp, valid_till, url)
+
+ body = """
+ <meta name="twitter:card" content="Pleroma" />
+ <meta name="twitter:site" content="Pleroma" />
+ <meta name="twitter:title" content="Pleroma" />
+ <meta name="twitter:description" content="Pleroma" />
+ <meta name="twitter:image" content="#{Map.get(metadata, :image)}" />
+ """
+
+ Tesla.Mock.mock(fn
+ %{
+ method: :get,
+ url: "https://pleroma.social/amz"
+ } ->
+ %Tesla.Env{status: 200, body: body}
+ end)
+
+ Cachex.put(:rich_media_cache, url, metadata)
+
+ Pleroma.Web.RichMedia.Parser.set_ttl_based_on_image(metadata, url)
+
+ {:ok, cache_ttl} = Cachex.ttl(:rich_media_cache, url)
+
+ # as there is delay in setting and pulling the data from cache we ignore 1 second
+ # make it 2 seconds for flakyness
+ assert_in_delta(valid_till * 1000, cache_ttl, 2000)
+ end
+
+ defp construct_s3_url(timestamp, valid_till) do
+ "https://pleroma.s3.ap-southeast-1.amazonaws.com/sachin%20%281%29%20_a%20-%25%2Aasdasd%20BNN%20bnnn%20.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIBLWWK6RGDQXDLJQ%2F20190716%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Date=#{
+ timestamp
+ }&X-Amz-Expires=#{valid_till}&X-Amz-Signature=04ffd6b98634f4b1bbabc62e0fac4879093cd54a6eed24fe8eb38e8369526bbf&X-Amz-SignedHeaders=host"
+ end
+
+ defp construct_metadata(timestamp, valid_till, url) do
+ %{
+ image: construct_s3_url(timestamp, valid_till),
+ site: "Pleroma",
+ title: "Pleroma",
+ description: "Pleroma",
+ url: url
+ }
+ end
+end
diff --git a/test/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs
diff --git a/test/web/rich_media/parsers/twitter_card_test.exs b/test/pleroma/web/rich_media/parsers/twitter_card_test.exs
diff --git a/test/web/static_fe/static_fe_controller_test.exs b/test/pleroma/web/static_fe/static_fe_controller_test.exs
diff --git a/test/web/streamer/streamer_test.exs b/test/pleroma/web/streamer_test.exs
diff --git a/test/web/twitter_api/twitter_api_controller_test.exs b/test/pleroma/web/twitter_api/controller_test.exs
diff --git a/test/web/twitter_api/password_controller_test.exs b/test/pleroma/web/twitter_api/password_controller_test.exs
diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/pleroma/web/twitter_api/twitter_api_test.exs
diff --git a/test/web/twitter_api/util_controller_test.exs b/test/pleroma/web/twitter_api/util_controller_test.exs
diff --git a/test/web/uploader_controller_test.exs b/test/pleroma/web/uploader_controller_test.exs
diff --git a/test/web/views/error_view_test.exs b/test/pleroma/web/views/error_view_test.exs
diff --git a/test/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs
diff --git a/test/web/web_finger/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs
diff --git a/test/workers/cron/digest_emails_worker_test.exs b/test/pleroma/workers/cron/digest_emails_worker_test.exs
diff --git a/test/workers/cron/new_users_digest_worker_test.exs b/test/pleroma/workers/cron/new_users_digest_worker_test.exs
diff --git a/test/workers/purge_expired_activity_test.exs b/test/pleroma/workers/purge_expired_activity_test.exs
diff --git a/test/workers/purge_expired_token_test.exs b/test/pleroma/workers/purge_expired_token_test.exs
diff --git a/test/workers/scheduled_activity_worker_test.exs b/test/pleroma/workers/scheduled_activity_worker_test.exs
diff --git a/test/xml_builder_test.exs b/test/pleroma/xml_builder_test.exs
diff --git a/test/plugs/admin_secret_authentication_plug_test.exs b/test/plugs/admin_secret_authentication_plug_test.exs
@@ -1,75 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.AdminSecretAuthenticationPlugTest do
- use Pleroma.Web.ConnCase
-
- import Mock
- import Pleroma.Factory
-
- alias Pleroma.Plugs.AdminSecretAuthenticationPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.PlugHelper
- alias Pleroma.Plugs.RateLimiter
-
- test "does nothing if a user is assigned", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
-
- ret_conn =
- conn
- |> AdminSecretAuthenticationPlug.call(%{})
-
- assert conn == ret_conn
- end
-
- describe "when secret set it assigns an admin user" do
- setup do: clear_config([:admin_token])
-
- setup_with_mocks([{RateLimiter, [:passthrough], []}]) do
- :ok
- end
-
- test "with `admin_token` query parameter", %{conn: conn} do
- Pleroma.Config.put(:admin_token, "password123")
-
- conn =
- %{conn | params: %{"admin_token" => "wrong_password"}}
- |> AdminSecretAuthenticationPlug.call(%{})
-
- refute conn.assigns[:user]
- assert called(RateLimiter.call(conn, name: :authentication))
-
- conn =
- %{conn | params: %{"admin_token" => "password123"}}
- |> AdminSecretAuthenticationPlug.call(%{})
-
- assert conn.assigns[:user].is_admin
- assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
- end
-
- test "with `x-admin-token` HTTP header", %{conn: conn} do
- Pleroma.Config.put(:admin_token, "☕️")
-
- conn =
- conn
- |> put_req_header("x-admin-token", "🥛")
- |> AdminSecretAuthenticationPlug.call(%{})
-
- refute conn.assigns[:user]
- assert called(RateLimiter.call(conn, name: :authentication))
-
- conn =
- conn
- |> put_req_header("x-admin-token", "☕️")
- |> AdminSecretAuthenticationPlug.call(%{})
-
- assert conn.assigns[:user].is_admin
- assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
- end
- end
-end
diff --git a/test/plugs/authentication_plug_test.exs b/test/plugs/authentication_plug_test.exs
@@ -1,125 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.AuthenticationPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Plugs.AuthenticationPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.PlugHelper
- alias Pleroma.User
-
- import ExUnit.CaptureLog
- import Pleroma.Factory
-
- setup %{conn: conn} do
- user = %User{
- id: 1,
- name: "dude",
- password_hash: Pbkdf2.hash_pwd_salt("guy")
- }
-
- conn =
- conn
- |> assign(:auth_user, user)
-
- %{user: user, conn: conn}
- end
-
- test "it does nothing if a user is assigned", %{conn: conn} do
- conn =
- conn
- |> assign(:user, %User{})
-
- ret_conn =
- conn
- |> AuthenticationPlug.call(%{})
-
- assert ret_conn == conn
- end
-
- test "with a correct password in the credentials, " <>
- "it assigns the auth_user and marks OAuthScopesPlug as skipped",
- %{conn: conn} do
- conn =
- conn
- |> assign(:auth_credentials, %{password: "guy"})
- |> AuthenticationPlug.call(%{})
-
- assert conn.assigns.user == conn.assigns.auth_user
- assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
- end
-
- test "with a bcrypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
- user = insert(:user, password_hash: Bcrypt.hash_pwd_salt("123"))
- assert "$2" <> _ = user.password_hash
-
- conn =
- conn
- |> assign(:auth_user, user)
- |> assign(:auth_credentials, %{password: "123"})
- |> AuthenticationPlug.call(%{})
-
- assert conn.assigns.user.id == conn.assigns.auth_user.id
- assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
-
- user = User.get_by_id(user.id)
- assert "$pbkdf2" <> _ = user.password_hash
- end
-
- @tag :skip_on_mac
- test "with a crypt hash, it updates to a pkbdf2 hash", %{conn: conn} do
- user =
- insert(:user,
- password_hash:
- "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
- )
-
- conn =
- conn
- |> assign(:auth_user, user)
- |> assign(:auth_credentials, %{password: "password"})
- |> AuthenticationPlug.call(%{})
-
- assert conn.assigns.user.id == conn.assigns.auth_user.id
- assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
-
- user = User.get_by_id(user.id)
- assert "$pbkdf2" <> _ = user.password_hash
- end
-
- describe "checkpw/2" do
- test "check pbkdf2 hash" do
- hash =
- "$pbkdf2-sha512$160000$loXqbp8GYls43F0i6lEfIw$AY.Ep.2pGe57j2hAPY635sI/6w7l9Q9u9Bp02PkPmF3OrClDtJAI8bCiivPr53OKMF7ph6iHhN68Rom5nEfC2A"
-
- assert AuthenticationPlug.checkpw("test-password", hash)
- refute AuthenticationPlug.checkpw("test-password1", hash)
- end
-
- @tag :skip_on_mac
- test "check sha512-crypt hash" do
- hash =
- "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
-
- assert AuthenticationPlug.checkpw("password", hash)
- end
-
- test "check bcrypt hash" do
- hash = "$2a$10$uyhC/R/zoE1ndwwCtMusK.TLVzkQ/Ugsbqp3uXI.CTTz0gBw.24jS"
-
- assert AuthenticationPlug.checkpw("password", hash)
- refute AuthenticationPlug.checkpw("password1", hash)
- end
-
- test "it returns false when hash invalid" do
- hash =
- "psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
-
- assert capture_log(fn ->
- refute Pleroma.Plugs.AuthenticationPlug.checkpw("password", hash)
- end) =~ "[error] Password hash not recognized"
- end
- end
-end
diff --git a/test/plugs/basic_auth_decoder_plug_test.exs b/test/plugs/basic_auth_decoder_plug_test.exs
@@ -1,35 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.BasicAuthDecoderPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Plugs.BasicAuthDecoderPlug
-
- defp basic_auth_enc(username, password) do
- "Basic " <> Base.encode64("#{username}:#{password}")
- end
-
- test "it puts the decoded credentials into the assigns", %{conn: conn} do
- header = basic_auth_enc("moonman", "iloverobek")
-
- conn =
- conn
- |> put_req_header("authorization", header)
- |> BasicAuthDecoderPlug.call(%{})
-
- assert conn.assigns[:auth_credentials] == %{
- username: "moonman",
- password: "iloverobek"
- }
- end
-
- test "without a authorization header it doesn't do anything", %{conn: conn} do
- ret_conn =
- conn
- |> BasicAuthDecoderPlug.call(%{})
-
- assert conn == ret_conn
- end
-end
diff --git a/test/plugs/cache_control_test.exs b/test/plugs/cache_control_test.exs
@@ -1,20 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.CacheControlTest do
- use Pleroma.Web.ConnCase
- alias Plug.Conn
-
- test "Verify Cache-Control header on static assets", %{conn: conn} do
- conn = get(conn, "/index.html")
-
- assert Conn.get_resp_header(conn, "cache-control") == ["public, no-cache"]
- end
-
- test "Verify Cache-Control header on the API", %{conn: conn} do
- conn = get(conn, "/api/v1/instance")
-
- assert Conn.get_resp_header(conn, "cache-control") == ["max-age=0, private, must-revalidate"]
- end
-end
diff --git a/test/plugs/cache_test.exs b/test/plugs/cache_test.exs
@@ -1,186 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.CacheTest do
- use ExUnit.Case, async: true
- use Plug.Test
-
- alias Pleroma.Plugs.Cache
-
- @miss_resp {200,
- [
- {"cache-control", "max-age=0, private, must-revalidate"},
- {"content-type", "cofe/hot; charset=utf-8"},
- {"x-cache", "MISS from Pleroma"}
- ], "cofe"}
-
- @hit_resp {200,
- [
- {"cache-control", "max-age=0, private, must-revalidate"},
- {"content-type", "cofe/hot; charset=utf-8"},
- {"x-cache", "HIT from Pleroma"}
- ], "cofe"}
-
- @ttl 5
-
- setup do
- Cachex.clear(:web_resp_cache)
- :ok
- end
-
- test "caches a response" do
- assert @miss_resp ==
- conn(:get, "/")
- |> Cache.call(%{query_params: false, ttl: nil})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
-
- assert_raise(Plug.Conn.AlreadySentError, fn ->
- conn(:get, "/")
- |> Cache.call(%{query_params: false, ttl: nil})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
- end)
-
- assert @hit_resp ==
- conn(:get, "/")
- |> Cache.call(%{query_params: false, ttl: nil})
- |> sent_resp()
- end
-
- test "ttl is set" do
- assert @miss_resp ==
- conn(:get, "/")
- |> Cache.call(%{query_params: false, ttl: @ttl})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
-
- assert @hit_resp ==
- conn(:get, "/")
- |> Cache.call(%{query_params: false, ttl: @ttl})
- |> sent_resp()
-
- :timer.sleep(@ttl + 1)
-
- assert @miss_resp ==
- conn(:get, "/")
- |> Cache.call(%{query_params: false, ttl: @ttl})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
- end
-
- test "set ttl via conn.assigns" do
- assert @miss_resp ==
- conn(:get, "/")
- |> Cache.call(%{query_params: false, ttl: nil})
- |> put_resp_content_type("cofe/hot")
- |> assign(:cache_ttl, @ttl)
- |> send_resp(:ok, "cofe")
- |> sent_resp()
-
- assert @hit_resp ==
- conn(:get, "/")
- |> Cache.call(%{query_params: false, ttl: nil})
- |> sent_resp()
-
- :timer.sleep(@ttl + 1)
-
- assert @miss_resp ==
- conn(:get, "/")
- |> Cache.call(%{query_params: false, ttl: nil})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
- end
-
- test "ignore query string when `query_params` is false" do
- assert @miss_resp ==
- conn(:get, "/?cofe")
- |> Cache.call(%{query_params: false, ttl: nil})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
-
- assert @hit_resp ==
- conn(:get, "/?cofefe")
- |> Cache.call(%{query_params: false, ttl: nil})
- |> sent_resp()
- end
-
- test "take query string into account when `query_params` is true" do
- assert @miss_resp ==
- conn(:get, "/?cofe")
- |> Cache.call(%{query_params: true, ttl: nil})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
-
- assert @miss_resp ==
- conn(:get, "/?cofefe")
- |> Cache.call(%{query_params: true, ttl: nil})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
- end
-
- test "take specific query params into account when `query_params` is list" do
- assert @miss_resp ==
- conn(:get, "/?a=1&b=2&c=3&foo=bar")
- |> fetch_query_params()
- |> Cache.call(%{query_params: ["a", "b", "c"], ttl: nil})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
-
- assert @hit_resp ==
- conn(:get, "/?bar=foo&c=3&b=2&a=1")
- |> fetch_query_params()
- |> Cache.call(%{query_params: ["a", "b", "c"], ttl: nil})
- |> sent_resp()
-
- assert @miss_resp ==
- conn(:get, "/?bar=foo&c=3&b=2&a=2")
- |> fetch_query_params()
- |> Cache.call(%{query_params: ["a", "b", "c"], ttl: nil})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
- end
-
- test "ignore not GET requests" do
- expected =
- {200,
- [
- {"cache-control", "max-age=0, private, must-revalidate"},
- {"content-type", "cofe/hot; charset=utf-8"}
- ], "cofe"}
-
- assert expected ==
- conn(:post, "/")
- |> Cache.call(%{query_params: true, ttl: nil})
- |> put_resp_content_type("cofe/hot")
- |> send_resp(:ok, "cofe")
- |> sent_resp()
- end
-
- test "ignore non-successful responses" do
- expected =
- {418,
- [
- {"cache-control", "max-age=0, private, must-revalidate"},
- {"content-type", "tea/iced; charset=utf-8"}
- ], "🥤"}
-
- assert expected ==
- conn(:get, "/cofe")
- |> Cache.call(%{query_params: true, ttl: nil})
- |> put_resp_content_type("tea/iced")
- |> send_resp(:im_a_teapot, "🥤")
- |> sent_resp()
- end
-end
diff --git a/test/plugs/ensure_authenticated_plug_test.exs b/test/plugs/ensure_authenticated_plug_test.exs
@@ -1,96 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.EnsureAuthenticatedPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Plugs.EnsureAuthenticatedPlug
- alias Pleroma.User
-
- describe "without :if_func / :unless_func options" do
- test "it halts if user is NOT assigned", %{conn: conn} do
- conn = EnsureAuthenticatedPlug.call(conn, %{})
-
- assert conn.status == 403
- assert conn.halted == true
- end
-
- test "it continues if a user is assigned", %{conn: conn} do
- conn = assign(conn, :user, %User{})
- ret_conn = EnsureAuthenticatedPlug.call(conn, %{})
-
- refute ret_conn.halted
- end
- end
-
- test "it halts if user is assigned and MFA enabled", %{conn: conn} do
- conn =
- conn
- |> assign(:user, %User{multi_factor_authentication_settings: %{enabled: true}})
- |> assign(:auth_credentials, %{password: "xd-42"})
- |> EnsureAuthenticatedPlug.call(%{})
-
- assert conn.status == 403
- assert conn.halted == true
-
- assert conn.resp_body ==
- "{\"error\":\"Two-factor authentication enabled, you must use a access token.\"}"
- end
-
- test "it continues if user is assigned and MFA disabled", %{conn: conn} do
- conn =
- conn
- |> assign(:user, %User{multi_factor_authentication_settings: %{enabled: false}})
- |> assign(:auth_credentials, %{password: "xd-42"})
- |> EnsureAuthenticatedPlug.call(%{})
-
- refute conn.status == 403
- refute conn.halted
- end
-
- describe "with :if_func / :unless_func options" do
- setup do
- %{
- true_fn: fn _conn -> true end,
- false_fn: fn _conn -> false end
- }
- end
-
- test "it continues if a user is assigned", %{conn: conn, true_fn: true_fn, false_fn: false_fn} do
- conn = assign(conn, :user, %User{})
- refute EnsureAuthenticatedPlug.call(conn, if_func: true_fn).halted
- refute EnsureAuthenticatedPlug.call(conn, if_func: false_fn).halted
- refute EnsureAuthenticatedPlug.call(conn, unless_func: true_fn).halted
- refute EnsureAuthenticatedPlug.call(conn, unless_func: false_fn).halted
- end
-
- test "it continues if a user is NOT assigned but :if_func evaluates to `false`",
- %{conn: conn, false_fn: false_fn} do
- ret_conn = EnsureAuthenticatedPlug.call(conn, if_func: false_fn)
- refute ret_conn.halted
- end
-
- test "it continues if a user is NOT assigned but :unless_func evaluates to `true`",
- %{conn: conn, true_fn: true_fn} do
- ret_conn = EnsureAuthenticatedPlug.call(conn, unless_func: true_fn)
- refute ret_conn.halted
- end
-
- test "it halts if a user is NOT assigned and :if_func evaluates to `true`",
- %{conn: conn, true_fn: true_fn} do
- conn = EnsureAuthenticatedPlug.call(conn, if_func: true_fn)
-
- assert conn.status == 403
- assert conn.halted == true
- end
-
- test "it halts if a user is NOT assigned and :unless_func evaluates to `false`",
- %{conn: conn, false_fn: false_fn} do
- conn = EnsureAuthenticatedPlug.call(conn, unless_func: false_fn)
-
- assert conn.status == 403
- assert conn.halted == true
- end
- end
-end
diff --git a/test/plugs/ensure_public_or_authenticated_plug_test.exs b/test/plugs/ensure_public_or_authenticated_plug_test.exs
@@ -1,48 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Config
- alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
- alias Pleroma.User
-
- setup do: clear_config([:instance, :public])
-
- test "it halts if not public and no user is assigned", %{conn: conn} do
- Config.put([:instance, :public], false)
-
- conn =
- conn
- |> EnsurePublicOrAuthenticatedPlug.call(%{})
-
- assert conn.status == 403
- assert conn.halted == true
- end
-
- test "it continues if public", %{conn: conn} do
- Config.put([:instance, :public], true)
-
- ret_conn =
- conn
- |> EnsurePublicOrAuthenticatedPlug.call(%{})
-
- refute ret_conn.halted
- end
-
- test "it continues if a user is assigned, even if not public", %{conn: conn} do
- Config.put([:instance, :public], false)
-
- conn =
- conn
- |> assign(:user, %User{})
-
- ret_conn =
- conn
- |> EnsurePublicOrAuthenticatedPlug.call(%{})
-
- refute ret_conn.halted
- end
-end
diff --git a/test/plugs/ensure_user_key_plug_test.exs b/test/plugs/ensure_user_key_plug_test.exs
@@ -1,29 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.EnsureUserKeyPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Plugs.EnsureUserKeyPlug
-
- test "if the conn has a user key set, it does nothing", %{conn: conn} do
- conn =
- conn
- |> assign(:user, 1)
-
- ret_conn =
- conn
- |> EnsureUserKeyPlug.call(%{})
-
- assert conn == ret_conn
- end
-
- test "if the conn has no key set, it sets it to nil", %{conn: conn} do
- conn =
- conn
- |> EnsureUserKeyPlug.call(%{})
-
- assert Map.has_key?(conn.assigns, :user)
- end
-end
diff --git a/test/plugs/frontend_static_test.exs b/test/plugs/frontend_static_test.exs
@@ -1,57 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FrontendStaticPlugTest do
- alias Pleroma.Plugs.FrontendStatic
- use Pleroma.Web.ConnCase
-
- @dir "test/tmp/instance_static"
-
- setup do
- File.mkdir_p!(@dir)
- on_exit(fn -> File.rm_rf(@dir) end)
- end
-
- setup do: clear_config([:instance, :static_dir], @dir)
-
- test "init will give a static plug config + the frontend type" do
- opts =
- [
- at: "/admin",
- frontend_type: :admin
- ]
- |> FrontendStatic.init()
-
- assert opts[:at] == ["admin"]
- assert opts[:frontend_type] == :admin
- end
-
- test "overrides existing static files", %{conn: conn} do
- name = "pelmora"
- ref = "uguu"
-
- clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
- path = "#{@dir}/frontends/#{name}/#{ref}"
-
- File.mkdir_p!(path)
- File.write!("#{path}/index.html", "from frontend plug")
-
- index = get(conn, "/")
- assert html_response(index, 200) == "from frontend plug"
- end
-
- test "overrides existing static files for the `pleroma/admin` path", %{conn: conn} do
- name = "pelmora"
- ref = "uguu"
-
- clear_config([:frontends, :admin], %{"name" => name, "ref" => ref})
- path = "#{@dir}/frontends/#{name}/#{ref}"
-
- File.mkdir_p!(path)
- File.write!("#{path}/index.html", "from frontend plug")
-
- index = get(conn, "/pleroma/admin/")
- assert html_response(index, 200) == "from frontend plug"
- end
-end
diff --git a/test/plugs/idempotency_plug_test.exs b/test/plugs/idempotency_plug_test.exs
@@ -1,110 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.IdempotencyPlugTest do
- use ExUnit.Case, async: true
- use Plug.Test
-
- alias Pleroma.Plugs.IdempotencyPlug
- alias Plug.Conn
-
- test "returns result from cache" do
- key = "test1"
- orig_request_id = "test1"
- second_request_id = "test2"
- body = "testing"
- status = 200
-
- :post
- |> conn("/cofe")
- |> put_req_header("idempotency-key", key)
- |> Conn.put_resp_header("x-request-id", orig_request_id)
- |> Conn.put_resp_content_type("application/json")
- |> IdempotencyPlug.call([])
- |> Conn.send_resp(status, body)
-
- conn =
- :post
- |> conn("/cofe")
- |> put_req_header("idempotency-key", key)
- |> Conn.put_resp_header("x-request-id", second_request_id)
- |> Conn.put_resp_content_type("application/json")
- |> IdempotencyPlug.call([])
-
- assert_raise Conn.AlreadySentError, fn ->
- Conn.send_resp(conn, :im_a_teapot, "no cofe")
- end
-
- assert conn.resp_body == body
- assert conn.status == status
-
- assert [^second_request_id] = Conn.get_resp_header(conn, "x-request-id")
- assert [^orig_request_id] = Conn.get_resp_header(conn, "x-original-request-id")
- assert [^key] = Conn.get_resp_header(conn, "idempotency-key")
- assert ["true"] = Conn.get_resp_header(conn, "idempotent-replayed")
- assert ["application/json; charset=utf-8"] = Conn.get_resp_header(conn, "content-type")
- end
-
- test "pass conn downstream if the cache not found" do
- key = "test2"
- orig_request_id = "test3"
- body = "testing"
- status = 200
-
- conn =
- :post
- |> conn("/cofe")
- |> put_req_header("idempotency-key", key)
- |> Conn.put_resp_header("x-request-id", orig_request_id)
- |> Conn.put_resp_content_type("application/json")
- |> IdempotencyPlug.call([])
- |> Conn.send_resp(status, body)
-
- assert conn.resp_body == body
- assert conn.status == status
-
- assert [] = Conn.get_resp_header(conn, "idempotent-replayed")
- assert [^key] = Conn.get_resp_header(conn, "idempotency-key")
- end
-
- test "passes conn downstream if idempotency is not present in headers" do
- orig_request_id = "test4"
- body = "testing"
- status = 200
-
- conn =
- :post
- |> conn("/cofe")
- |> Conn.put_resp_header("x-request-id", orig_request_id)
- |> Conn.put_resp_content_type("application/json")
- |> IdempotencyPlug.call([])
- |> Conn.send_resp(status, body)
-
- assert [] = Conn.get_resp_header(conn, "idempotency-key")
- end
-
- test "doesn't work with GET/DELETE" do
- key = "test3"
- body = "testing"
- status = 200
-
- conn =
- :get
- |> conn("/cofe")
- |> put_req_header("idempotency-key", key)
- |> IdempotencyPlug.call([])
- |> Conn.send_resp(status, body)
-
- assert [] = Conn.get_resp_header(conn, "idempotency-key")
-
- conn =
- :delete
- |> conn("/cofe")
- |> put_req_header("idempotency-key", key)
- |> IdempotencyPlug.call([])
- |> Conn.send_resp(status, body)
-
- assert [] = Conn.get_resp_header(conn, "idempotency-key")
- end
-end
diff --git a/test/plugs/instance_static_test.exs b/test/plugs/instance_static_test.exs
@@ -1,65 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.InstanceStaticPlugTest do
- use Pleroma.Web.ConnCase
-
- @dir "test/tmp/instance_static"
-
- setup do
- File.mkdir_p!(@dir)
- on_exit(fn -> File.rm_rf(@dir) end)
- end
-
- setup do: clear_config([:instance, :static_dir], @dir)
-
- test "overrides index" do
- bundled_index = get(build_conn(), "/")
- refute html_response(bundled_index, 200) == "hello world"
-
- File.write!(@dir <> "/index.html", "hello world")
-
- index = get(build_conn(), "/")
- assert html_response(index, 200) == "hello world"
- end
-
- test "also overrides frontend files", %{conn: conn} do
- name = "pelmora"
- ref = "uguu"
-
- clear_config([:frontends, :primary], %{"name" => name, "ref" => ref})
-
- bundled_index = get(conn, "/")
- refute html_response(bundled_index, 200) == "from frontend plug"
-
- path = "#{@dir}/frontends/#{name}/#{ref}"
- File.mkdir_p!(path)
- File.write!("#{path}/index.html", "from frontend plug")
-
- index = get(conn, "/")
- assert html_response(index, 200) == "from frontend plug"
-
- File.write!(@dir <> "/index.html", "from instance static")
-
- index = get(conn, "/")
- assert html_response(index, 200) == "from instance static"
- end
-
- test "overrides any file in static/static" do
- bundled_index = get(build_conn(), "/static/terms-of-service.html")
-
- assert html_response(bundled_index, 200) ==
- File.read!("priv/static/static/terms-of-service.html")
-
- File.mkdir!(@dir <> "/static")
- File.write!(@dir <> "/static/terms-of-service.html", "plz be kind")
-
- index = get(build_conn(), "/static/terms-of-service.html")
- assert html_response(index, 200) == "plz be kind"
-
- File.write!(@dir <> "/static/kaniini.html", "<h1>rabbit hugs as a service</h1>")
- index = get(build_conn(), "/static/kaniini.html")
- assert html_response(index, 200) == "<h1>rabbit hugs as a service</h1>"
- end
-end
diff --git a/test/plugs/legacy_authentication_plug_test.exs b/test/plugs/legacy_authentication_plug_test.exs
@@ -1,82 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
- use Pleroma.Web.ConnCase
-
- import Pleroma.Factory
-
- alias Pleroma.Plugs.LegacyAuthenticationPlug
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.PlugHelper
- alias Pleroma.User
-
- setup do
- user =
- insert(:user,
- password: "password",
- password_hash:
- "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
- )
-
- %{user: user}
- end
-
- test "it does nothing if a user is assigned", %{conn: conn, user: user} do
- conn =
- conn
- |> assign(:auth_credentials, %{username: "dude", password: "password"})
- |> assign(:auth_user, user)
- |> assign(:user, %User{})
-
- ret_conn =
- conn
- |> LegacyAuthenticationPlug.call(%{})
-
- assert ret_conn == conn
- end
-
- @tag :skip_on_mac
- test "if `auth_user` is present and password is correct, " <>
- "it authenticates the user, resets the password, marks OAuthScopesPlug as skipped",
- %{
- conn: conn,
- user: user
- } do
- conn =
- conn
- |> assign(:auth_credentials, %{username: "dude", password: "password"})
- |> assign(:auth_user, user)
-
- conn = LegacyAuthenticationPlug.call(conn, %{})
-
- assert conn.assigns.user.id == user.id
- assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug)
- end
-
- @tag :skip_on_mac
- test "it does nothing if the password is wrong", %{
- conn: conn,
- user: user
- } do
- conn =
- conn
- |> assign(:auth_credentials, %{username: "dude", password: "wrong_password"})
- |> assign(:auth_user, user)
-
- ret_conn =
- conn
- |> LegacyAuthenticationPlug.call(%{})
-
- assert conn == ret_conn
- end
-
- test "with no credentials or user it does nothing", %{conn: conn} do
- ret_conn =
- conn
- |> LegacyAuthenticationPlug.call(%{})
-
- assert ret_conn == conn
- end
-end
diff --git a/test/plugs/oauth_plug_test.exs b/test/plugs/oauth_plug_test.exs
@@ -1,80 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.OAuthPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Plugs.OAuthPlug
- import Pleroma.Factory
-
- @session_opts [
- store: :cookie,
- key: "_test",
- signing_salt: "cooldude"
- ]
-
- setup %{conn: conn} do
- user = insert(:user)
- {:ok, %{token: token}} = Pleroma.Web.OAuth.Token.create(insert(:oauth_app), user)
- %{user: user, token: token, conn: conn}
- end
-
- test "with valid token(uppercase), it assigns the user", %{conn: conn} = opts do
- conn =
- conn
- |> put_req_header("authorization", "BEARER #{opts[:token]}")
- |> OAuthPlug.call(%{})
-
- assert conn.assigns[:user] == opts[:user]
- end
-
- test "with valid token(downcase), it assigns the user", %{conn: conn} = opts do
- conn =
- conn
- |> put_req_header("authorization", "bearer #{opts[:token]}")
- |> OAuthPlug.call(%{})
-
- assert conn.assigns[:user] == opts[:user]
- end
-
- test "with valid token(downcase) in url parameters, it assigns the user", opts do
- conn =
- :get
- |> build_conn("/?access_token=#{opts[:token]}")
- |> put_req_header("content-type", "application/json")
- |> fetch_query_params()
- |> OAuthPlug.call(%{})
-
- assert conn.assigns[:user] == opts[:user]
- end
-
- test "with valid token(downcase) in body parameters, it assigns the user", opts do
- conn =
- :post
- |> build_conn("/api/v1/statuses", access_token: opts[:token], status: "test")
- |> OAuthPlug.call(%{})
-
- assert conn.assigns[:user] == opts[:user]
- end
-
- test "with invalid token, it not assigns the user", %{conn: conn} do
- conn =
- conn
- |> put_req_header("authorization", "bearer TTTTT")
- |> OAuthPlug.call(%{})
-
- refute conn.assigns[:user]
- end
-
- test "when token is missed but token in session, it assigns the user", %{conn: conn} = opts do
- conn =
- conn
- |> Plug.Session.call(Plug.Session.init(@session_opts))
- |> fetch_session()
- |> put_session(:oauth_token, opts[:token])
- |> OAuthPlug.call(%{})
-
- assert conn.assigns[:user] == opts[:user]
- end
-end
diff --git a/test/plugs/oauth_scopes_plug_test.exs b/test/plugs/oauth_scopes_plug_test.exs
@@ -1,210 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.OAuthScopesPlugTest do
- use Pleroma.Web.ConnCase
-
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Repo
-
- import Mock
- import Pleroma.Factory
-
- test "is not performed if marked as skipped", %{conn: conn} do
- with_mock OAuthScopesPlug, [:passthrough], perform: &passthrough([&1, &2]) do
- conn =
- conn
- |> OAuthScopesPlug.skip_plug()
- |> OAuthScopesPlug.call(%{scopes: ["random_scope"]})
-
- refute called(OAuthScopesPlug.perform(:_, :_))
- refute conn.halted
- end
- end
-
- test "if `token.scopes` fulfills specified 'any of' conditions, " <>
- "proceeds with no op",
- %{conn: conn} do
- token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user)
-
- conn =
- conn
- |> assign(:user, token.user)
- |> assign(:token, token)
- |> OAuthScopesPlug.call(%{scopes: ["read"]})
-
- refute conn.halted
- assert conn.assigns[:user]
- end
-
- test "if `token.scopes` fulfills specified 'all of' conditions, " <>
- "proceeds with no op",
- %{conn: conn} do
- token = insert(:oauth_token, scopes: ["scope1", "scope2", "scope3"]) |> Repo.preload(:user)
-
- conn =
- conn
- |> assign(:user, token.user)
- |> assign(:token, token)
- |> OAuthScopesPlug.call(%{scopes: ["scope2", "scope3"], op: :&})
-
- refute conn.halted
- assert conn.assigns[:user]
- end
-
- describe "with `fallback: :proceed_unauthenticated` option, " do
- test "if `token.scopes` doesn't fulfill specified conditions, " <>
- "clears :user and :token assigns",
- %{conn: conn} do
- user = insert(:user)
- token1 = insert(:oauth_token, scopes: ["read", "write"], user: user)
-
- for token <- [token1, nil], op <- [:|, :&] do
- ret_conn =
- conn
- |> assign(:user, user)
- |> assign(:token, token)
- |> OAuthScopesPlug.call(%{
- scopes: ["follow"],
- op: op,
- fallback: :proceed_unauthenticated
- })
-
- refute ret_conn.halted
- refute ret_conn.assigns[:user]
- refute ret_conn.assigns[:token]
- end
- end
- end
-
- describe "without :fallback option, " do
- test "if `token.scopes` does not fulfill specified 'any of' conditions, " <>
- "returns 403 and halts",
- %{conn: conn} do
- for token <- [insert(:oauth_token, scopes: ["read", "write"]), nil] do
- any_of_scopes = ["follow", "push"]
-
- ret_conn =
- conn
- |> assign(:token, token)
- |> OAuthScopesPlug.call(%{scopes: any_of_scopes})
-
- assert ret_conn.halted
- assert 403 == ret_conn.status
-
- expected_error = "Insufficient permissions: #{Enum.join(any_of_scopes, " | ")}."
- assert Jason.encode!(%{error: expected_error}) == ret_conn.resp_body
- end
- end
-
- test "if `token.scopes` does not fulfill specified 'all of' conditions, " <>
- "returns 403 and halts",
- %{conn: conn} do
- for token <- [insert(:oauth_token, scopes: ["read", "write"]), nil] do
- token_scopes = (token && token.scopes) || []
- all_of_scopes = ["write", "follow"]
-
- conn =
- conn
- |> assign(:token, token)
- |> OAuthScopesPlug.call(%{scopes: all_of_scopes, op: :&})
-
- assert conn.halted
- assert 403 == conn.status
-
- expected_error =
- "Insufficient permissions: #{Enum.join(all_of_scopes -- token_scopes, " & ")}."
-
- assert Jason.encode!(%{error: expected_error}) == conn.resp_body
- end
- end
- end
-
- describe "with hierarchical scopes, " do
- test "if `token.scopes` fulfills specified 'any of' conditions, " <>
- "proceeds with no op",
- %{conn: conn} do
- token = insert(:oauth_token, scopes: ["read", "write"]) |> Repo.preload(:user)
-
- conn =
- conn
- |> assign(:user, token.user)
- |> assign(:token, token)
- |> OAuthScopesPlug.call(%{scopes: ["read:something"]})
-
- refute conn.halted
- assert conn.assigns[:user]
- end
-
- test "if `token.scopes` fulfills specified 'all of' conditions, " <>
- "proceeds with no op",
- %{conn: conn} do
- token = insert(:oauth_token, scopes: ["scope1", "scope2", "scope3"]) |> Repo.preload(:user)
-
- conn =
- conn
- |> assign(:user, token.user)
- |> assign(:token, token)
- |> OAuthScopesPlug.call(%{scopes: ["scope1:subscope", "scope2:subscope"], op: :&})
-
- refute conn.halted
- assert conn.assigns[:user]
- end
- end
-
- describe "filter_descendants/2" do
- test "filters scopes which directly match or are ancestors of supported scopes" do
- f = fn scopes, supported_scopes ->
- OAuthScopesPlug.filter_descendants(scopes, supported_scopes)
- end
-
- assert f.(["read", "follow"], ["write", "read"]) == ["read"]
-
- assert f.(["read", "write:something", "follow"], ["write", "read"]) ==
- ["read", "write:something"]
-
- assert f.(["admin:read"], ["write", "read"]) == []
-
- assert f.(["admin:read"], ["write", "admin"]) == ["admin:read"]
- end
- end
-
- describe "transform_scopes/2" do
- setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage])
-
- setup do
- {:ok, %{f: &OAuthScopesPlug.transform_scopes/2}}
- end
-
- test "with :admin option, prefixes all requested scopes with `admin:` " <>
- "and [optionally] keeps only prefixed scopes, " <>
- "depending on `[:auth, :enforce_oauth_admin_scope_usage]` setting",
- %{f: f} do
- Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], false)
-
- assert f.(["read"], %{admin: true}) == ["admin:read", "read"]
-
- assert f.(["read", "write"], %{admin: true}) == [
- "admin:read",
- "read",
- "admin:write",
- "write"
- ]
-
- Pleroma.Config.put([:auth, :enforce_oauth_admin_scope_usage], true)
-
- assert f.(["read:accounts"], %{admin: true}) == ["admin:read:accounts"]
-
- assert f.(["read", "write:reports"], %{admin: true}) == [
- "admin:read",
- "admin:write:reports"
- ]
- end
-
- test "with no supported options, returns unmodified scopes", %{f: f} do
- assert f.(["read"], %{}) == ["read"]
- assert f.(["read", "write"], %{}) == ["read", "write"]
- end
- end
-end
diff --git a/test/plugs/rate_limiter_test.exs b/test/plugs/rate_limiter_test.exs
@@ -1,263 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.RateLimiterTest do
- use Pleroma.Web.ConnCase
-
- alias Phoenix.ConnTest
- alias Pleroma.Config
- alias Pleroma.Plugs.RateLimiter
- alias Plug.Conn
-
- import Pleroma.Factory
- import Pleroma.Tests.Helpers, only: [clear_config: 1, clear_config: 2]
-
- # Note: each example must work with separate buckets in order to prevent concurrency issues
- setup do: clear_config([Pleroma.Web.Endpoint, :http, :ip])
- setup do: clear_config(:rate_limit)
-
- describe "config" do
- @limiter_name :test_init
- setup do: clear_config([Pleroma.Plugs.RemoteIp, :enabled])
-
- test "config is required for plug to work" do
- Config.put([:rate_limit, @limiter_name], {1, 1})
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
-
- assert %{limits: {1, 1}, name: :test_init, opts: [name: :test_init]} ==
- [name: @limiter_name]
- |> RateLimiter.init()
- |> RateLimiter.action_settings()
-
- assert nil ==
- [name: :nonexisting_limiter]
- |> RateLimiter.init()
- |> RateLimiter.action_settings()
- end
- end
-
- test "it is disabled if it remote ip plug is enabled but no remote ip is found" do
- assert RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, false))
- end
-
- test "it is enabled if remote ip found" do
- refute RateLimiter.disabled?(Conn.assign(build_conn(), :remote_ip_found, true))
- end
-
- test "it is enabled if remote_ip_found flag doesn't exist" do
- refute RateLimiter.disabled?(build_conn())
- end
-
- test "it restricts based on config values" do
- limiter_name = :test_plug_opts
- scale = 80
- limit = 5
-
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
- Config.put([:rate_limit, limiter_name], {scale, limit})
-
- plug_opts = RateLimiter.init(name: limiter_name)
- conn = build_conn(:get, "/")
-
- for i <- 1..5 do
- conn = RateLimiter.call(conn, plug_opts)
- assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
- Process.sleep(10)
- end
-
- conn = RateLimiter.call(conn, plug_opts)
- assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
- assert conn.halted
-
- Process.sleep(50)
-
- conn = build_conn(:get, "/")
-
- conn = RateLimiter.call(conn, plug_opts)
- assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
-
- refute conn.status == Conn.Status.code(:too_many_requests)
- refute conn.resp_body
- refute conn.halted
- end
-
- describe "options" do
- test "`bucket_name` option overrides default bucket name" do
- limiter_name = :test_bucket_name
-
- Config.put([:rate_limit, limiter_name], {1000, 5})
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
-
- base_bucket_name = "#{limiter_name}:group1"
- plug_opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name)
-
- conn = build_conn(:get, "/")
-
- RateLimiter.call(conn, plug_opts)
- assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, plug_opts)
- assert {:error, :not_found} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
- end
-
- test "`params` option allows different queries to be tracked independently" do
- limiter_name = :test_params
- Config.put([:rate_limit, limiter_name], {1000, 5})
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
-
- plug_opts = RateLimiter.init(name: limiter_name, params: ["id"])
-
- conn = build_conn(:get, "/?id=1")
- conn = Conn.fetch_query_params(conn)
- conn_2 = build_conn(:get, "/?id=2")
-
- RateLimiter.call(conn, plug_opts)
- assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
- assert {0, 5} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
- end
-
- test "it supports combination of options modifying bucket name" do
- limiter_name = :test_options_combo
- Config.put([:rate_limit, limiter_name], {1000, 5})
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
-
- base_bucket_name = "#{limiter_name}:group1"
-
- plug_opts =
- RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name, params: ["id"])
-
- id = "100"
-
- conn = build_conn(:get, "/?id=#{id}")
- conn = Conn.fetch_query_params(conn)
- conn_2 = build_conn(:get, "/?id=#{101}")
-
- RateLimiter.call(conn, plug_opts)
- assert {1, 4} = RateLimiter.inspect_bucket(conn, base_bucket_name, plug_opts)
- assert {0, 5} = RateLimiter.inspect_bucket(conn_2, base_bucket_name, plug_opts)
- end
- end
-
- describe "unauthenticated users" do
- test "are restricted based on remote IP" do
- limiter_name = :test_unauthenticated
- Config.put([:rate_limit, limiter_name], [{1000, 5}, {1, 10}])
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
-
- plug_opts = RateLimiter.init(name: limiter_name)
-
- conn = %{build_conn(:get, "/") | remote_ip: {127, 0, 0, 2}}
- conn_2 = %{build_conn(:get, "/") | remote_ip: {127, 0, 0, 3}}
-
- for i <- 1..5 do
- conn = RateLimiter.call(conn, plug_opts)
- assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
- refute conn.halted
- end
-
- conn = RateLimiter.call(conn, plug_opts)
-
- assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
- assert conn.halted
-
- conn_2 = RateLimiter.call(conn_2, plug_opts)
- assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
-
- refute conn_2.status == Conn.Status.code(:too_many_requests)
- refute conn_2.resp_body
- refute conn_2.halted
- end
- end
-
- describe "authenticated users" do
- setup do
- Ecto.Adapters.SQL.Sandbox.checkout(Pleroma.Repo)
-
- :ok
- end
-
- test "can have limits separate from unauthenticated connections" do
- limiter_name = :test_authenticated1
-
- scale = 50
- limit = 5
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
- Config.put([:rate_limit, limiter_name], [{1000, 1}, {scale, limit}])
-
- plug_opts = RateLimiter.init(name: limiter_name)
-
- user = insert(:user)
- conn = build_conn(:get, "/") |> assign(:user, user)
-
- for i <- 1..5 do
- conn = RateLimiter.call(conn, plug_opts)
- assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
- refute conn.halted
- end
-
- conn = RateLimiter.call(conn, plug_opts)
-
- assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
- assert conn.halted
- end
-
- test "different users are counted independently" do
- limiter_name = :test_authenticated2
- Config.put([:rate_limit, limiter_name], [{1, 10}, {1000, 5}])
- Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
-
- plug_opts = RateLimiter.init(name: limiter_name)
-
- user = insert(:user)
- conn = build_conn(:get, "/") |> assign(:user, user)
-
- user_2 = insert(:user)
- conn_2 = build_conn(:get, "/") |> assign(:user, user_2)
-
- for i <- 1..5 do
- conn = RateLimiter.call(conn, plug_opts)
- assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
- end
-
- conn = RateLimiter.call(conn, plug_opts)
- assert %{"error" => "Throttled"} = ConnTest.json_response(conn, :too_many_requests)
- assert conn.halted
-
- conn_2 = RateLimiter.call(conn_2, plug_opts)
- assert {1, 4} = RateLimiter.inspect_bucket(conn_2, limiter_name, plug_opts)
- refute conn_2.status == Conn.Status.code(:too_many_requests)
- refute conn_2.resp_body
- refute conn_2.halted
- end
- end
-
- test "doesn't crash due to a race condition when multiple requests are made at the same time and the bucket is not yet initialized" do
- limiter_name = :test_race_condition
- Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5})
- Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
-
- opts = RateLimiter.init(name: limiter_name)
-
- conn = build_conn(:get, "/")
- conn_2 = build_conn(:get, "/")
-
- %Task{pid: pid1} =
- task1 =
- Task.async(fn ->
- receive do
- :process2_up ->
- RateLimiter.call(conn, opts)
- end
- end)
-
- task2 =
- Task.async(fn ->
- send(pid1, :process2_up)
- RateLimiter.call(conn_2, opts)
- end)
-
- Task.await(task1)
- Task.await(task2)
-
- refute {:err, :not_found} == RateLimiter.inspect_bucket(conn, limiter_name, opts)
- end
-end
diff --git a/test/plugs/remote_ip_test.exs b/test/plugs/remote_ip_test.exs
@@ -1,108 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.RemoteIpTest do
- use ExUnit.Case
- use Plug.Test
-
- alias Pleroma.Plugs.RemoteIp
-
- import Pleroma.Tests.Helpers, only: [clear_config: 2]
-
- setup do:
- clear_config(RemoteIp,
- enabled: true,
- headers: ["x-forwarded-for"],
- proxies: [],
- reserved: [
- "127.0.0.0/8",
- "::1/128",
- "fc00::/7",
- "10.0.0.0/8",
- "172.16.0.0/12",
- "192.168.0.0/16"
- ]
- )
-
- test "disabled" do
- Pleroma.Config.put(RemoteIp, enabled: false)
-
- %{remote_ip: remote_ip} = conn(:get, "/")
-
- conn =
- conn(:get, "/")
- |> put_req_header("x-forwarded-for", "1.1.1.1")
- |> RemoteIp.call(nil)
-
- assert conn.remote_ip == remote_ip
- end
-
- test "enabled" do
- conn =
- conn(:get, "/")
- |> put_req_header("x-forwarded-for", "1.1.1.1")
- |> RemoteIp.call(nil)
-
- assert conn.remote_ip == {1, 1, 1, 1}
- end
-
- test "custom headers" do
- Pleroma.Config.put(RemoteIp, enabled: true, headers: ["cf-connecting-ip"])
-
- conn =
- conn(:get, "/")
- |> put_req_header("x-forwarded-for", "1.1.1.1")
- |> RemoteIp.call(nil)
-
- refute conn.remote_ip == {1, 1, 1, 1}
-
- conn =
- conn(:get, "/")
- |> put_req_header("cf-connecting-ip", "1.1.1.1")
- |> RemoteIp.call(nil)
-
- assert conn.remote_ip == {1, 1, 1, 1}
- end
-
- test "custom proxies" do
- conn =
- conn(:get, "/")
- |> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2")
- |> RemoteIp.call(nil)
-
- refute conn.remote_ip == {1, 1, 1, 1}
-
- Pleroma.Config.put([RemoteIp, :proxies], ["173.245.48.0/20"])
-
- conn =
- conn(:get, "/")
- |> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1, 173.245.48.2")
- |> RemoteIp.call(nil)
-
- assert conn.remote_ip == {1, 1, 1, 1}
- end
-
- test "proxies set without CIDR format" do
- Pleroma.Config.put([RemoteIp, :proxies], ["173.245.48.1"])
-
- conn =
- conn(:get, "/")
- |> put_req_header("x-forwarded-for", "173.245.48.1, 1.1.1.1")
- |> RemoteIp.call(nil)
-
- assert conn.remote_ip == {1, 1, 1, 1}
- end
-
- test "proxies set `nonsensical` CIDR" do
- Pleroma.Config.put([RemoteIp, :reserved], ["127.0.0.0/8"])
- Pleroma.Config.put([RemoteIp, :proxies], ["10.0.0.3/24"])
-
- conn =
- conn(:get, "/")
- |> put_req_header("x-forwarded-for", "10.0.0.3, 1.1.1.1")
- |> RemoteIp.call(nil)
-
- assert conn.remote_ip == {1, 1, 1, 1}
- end
-end
diff --git a/test/plugs/session_authentication_plug_test.exs b/test/plugs/session_authentication_plug_test.exs
@@ -1,63 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.SessionAuthenticationPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Plugs.SessionAuthenticationPlug
- alias Pleroma.User
-
- setup %{conn: conn} do
- session_opts = [
- store: :cookie,
- key: "_test",
- signing_salt: "cooldude"
- ]
-
- conn =
- conn
- |> Plug.Session.call(Plug.Session.init(session_opts))
- |> fetch_session
- |> assign(:auth_user, %User{id: 1})
-
- %{conn: conn}
- end
-
- test "it does nothing if a user is assigned", %{conn: conn} do
- conn =
- conn
- |> assign(:user, %User{})
-
- ret_conn =
- conn
- |> SessionAuthenticationPlug.call(%{})
-
- assert ret_conn == conn
- end
-
- test "if the auth_user has the same id as the user_id in the session, it assigns the user", %{
- conn: conn
- } do
- conn =
- conn
- |> put_session(:user_id, conn.assigns.auth_user.id)
- |> SessionAuthenticationPlug.call(%{})
-
- assert conn.assigns.user == conn.assigns.auth_user
- end
-
- test "if the auth_user has a different id as the user_id in the session, it does nothing", %{
- conn: conn
- } do
- conn =
- conn
- |> put_session(:user_id, -1)
-
- ret_conn =
- conn
- |> SessionAuthenticationPlug.call(%{})
-
- assert ret_conn == conn
- end
-end
diff --git a/test/plugs/set_format_plug_test.exs b/test/plugs/set_format_plug_test.exs
@@ -1,38 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.SetFormatPlugTest do
- use ExUnit.Case, async: true
- use Plug.Test
-
- alias Pleroma.Plugs.SetFormatPlug
-
- test "set format from params" do
- conn =
- :get
- |> conn("/cofe?_format=json")
- |> SetFormatPlug.call([])
-
- assert %{format: "json"} == conn.assigns
- end
-
- test "set format from header" do
- conn =
- :get
- |> conn("/cofe")
- |> put_private(:phoenix_format, "xml")
- |> SetFormatPlug.call([])
-
- assert %{format: "xml"} == conn.assigns
- end
-
- test "doesn't set format" do
- conn =
- :get
- |> conn("/cofe")
- |> SetFormatPlug.call([])
-
- refute conn.assigns[:format]
- end
-end
diff --git a/test/plugs/set_locale_plug_test.exs b/test/plugs/set_locale_plug_test.exs
@@ -1,46 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.SetLocalePlugTest do
- use ExUnit.Case, async: true
- use Plug.Test
-
- alias Pleroma.Plugs.SetLocalePlug
- alias Plug.Conn
-
- test "default locale is `en`" do
- conn =
- :get
- |> conn("/cofe")
- |> SetLocalePlug.call([])
-
- assert "en" == Gettext.get_locale()
- assert %{locale: "en"} == conn.assigns
- end
-
- test "use supported locale from `accept-language`" do
- conn =
- :get
- |> conn("/cofe")
- |> Conn.put_req_header(
- "accept-language",
- "ru, fr-CH, fr;q=0.9, en;q=0.8, *;q=0.5"
- )
- |> SetLocalePlug.call([])
-
- assert "ru" == Gettext.get_locale()
- assert %{locale: "ru"} == conn.assigns
- end
-
- test "use default locale if locale from `accept-language` is not supported" do
- conn =
- :get
- |> conn("/cofe")
- |> Conn.put_req_header("accept-language", "tlh")
- |> SetLocalePlug.call([])
-
- assert "en" == Gettext.get_locale()
- assert %{locale: "en"} == conn.assigns
- end
-end
diff --git a/test/plugs/set_user_session_id_plug_test.exs b/test/plugs/set_user_session_id_plug_test.exs
@@ -1,45 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.SetUserSessionIdPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Plugs.SetUserSessionIdPlug
- alias Pleroma.User
-
- setup %{conn: conn} do
- session_opts = [
- store: :cookie,
- key: "_test",
- signing_salt: "cooldude"
- ]
-
- conn =
- conn
- |> Plug.Session.call(Plug.Session.init(session_opts))
- |> fetch_session
-
- %{conn: conn}
- end
-
- test "doesn't do anything if the user isn't set", %{conn: conn} do
- ret_conn =
- conn
- |> SetUserSessionIdPlug.call(%{})
-
- assert ret_conn == conn
- end
-
- test "sets the user_id in the session to the user id of the user assign", %{conn: conn} do
- Code.ensure_compiled(Pleroma.User)
-
- conn =
- conn
- |> assign(:user, %User{id: 1})
- |> SetUserSessionIdPlug.call(%{})
-
- id = get_session(conn, :user_id)
- assert id == 1
- end
-end
diff --git a/test/plugs/uploaded_media_plug_test.exs b/test/plugs/uploaded_media_plug_test.exs
@@ -1,43 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.UploadedMediaPlugTest do
- use Pleroma.Web.ConnCase
- alias Pleroma.Upload
-
- defp upload_file(context) do
- Pleroma.DataCase.ensure_local_uploader(context)
- File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
-
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image_tmp.jpg"),
- filename: "nice_tf.jpg"
- }
-
- {:ok, data} = Upload.store(file)
- [%{"href" => attachment_url} | _] = data["url"]
- [attachment_url: attachment_url]
- end
-
- setup_all :upload_file
-
- test "does not send Content-Disposition header when name param is not set", %{
- attachment_url: attachment_url
- } do
- conn = get(build_conn(), attachment_url)
- refute Enum.any?(conn.resp_headers, &(elem(&1, 0) == "content-disposition"))
- end
-
- test "sends Content-Disposition header when name param is set", %{
- attachment_url: attachment_url
- } do
- conn = get(build_conn(), attachment_url <> "?name=\"cofe\".gif")
-
- assert Enum.any?(
- conn.resp_headers,
- &(&1 == {"content-disposition", "filename=\"\\\"cofe\\\".gif\""})
- )
- end
-end
diff --git a/test/plugs/user_enabled_plug_test.exs b/test/plugs/user_enabled_plug_test.exs
@@ -1,59 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.UserEnabledPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Plugs.UserEnabledPlug
- import Pleroma.Factory
-
- setup do: clear_config([:instance, :account_activation_required])
-
- test "doesn't do anything if the user isn't set", %{conn: conn} do
- ret_conn =
- conn
- |> UserEnabledPlug.call(%{})
-
- assert ret_conn == conn
- end
-
- test "with a user that's not confirmed and a config requiring confirmation, it removes that user",
- %{conn: conn} do
- Pleroma.Config.put([:instance, :account_activation_required], true)
-
- user = insert(:user, confirmation_pending: true)
-
- conn =
- conn
- |> assign(:user, user)
- |> UserEnabledPlug.call(%{})
-
- assert conn.assigns.user == nil
- end
-
- test "with a user that is deactivated, it removes that user", %{conn: conn} do
- user = insert(:user, deactivated: true)
-
- conn =
- conn
- |> assign(:user, user)
- |> UserEnabledPlug.call(%{})
-
- assert conn.assigns.user == nil
- end
-
- test "with a user that is not deactivated, it does nothing", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
-
- ret_conn =
- conn
- |> UserEnabledPlug.call(%{})
-
- assert conn == ret_conn
- end
-end
diff --git a/test/plugs/user_fetcher_plug_test.exs b/test/plugs/user_fetcher_plug_test.exs
@@ -1,41 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.UserFetcherPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Plugs.UserFetcherPlug
- import Pleroma.Factory
-
- setup do
- user = insert(:user)
- %{user: user}
- end
-
- test "if an auth_credentials assign is present, it tries to fetch the user and assigns it", %{
- conn: conn,
- user: user
- } do
- conn =
- conn
- |> assign(:auth_credentials, %{
- username: user.nickname,
- password: nil
- })
-
- conn =
- conn
- |> UserFetcherPlug.call(%{})
-
- assert conn.assigns[:auth_user] == user
- end
-
- test "without a credential assign it doesn't do anything", %{conn: conn} do
- ret_conn =
- conn
- |> UserFetcherPlug.call(%{})
-
- assert conn == ret_conn
- end
-end
diff --git a/test/plugs/user_is_admin_plug_test.exs b/test/plugs/user_is_admin_plug_test.exs
@@ -1,37 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Plugs.UserIsAdminPlugTest do
- use Pleroma.Web.ConnCase, async: true
-
- alias Pleroma.Plugs.UserIsAdminPlug
- import Pleroma.Factory
-
- test "accepts a user that is an admin" do
- user = insert(:user, is_admin: true)
-
- conn = assign(build_conn(), :user, user)
-
- ret_conn = UserIsAdminPlug.call(conn, %{})
-
- assert conn == ret_conn
- end
-
- test "denies a user that isn't an admin" do
- user = insert(:user)
-
- conn =
- build_conn()
- |> assign(:user, user)
- |> UserIsAdminPlug.call(%{})
-
- assert conn.status == 403
- end
-
- test "denies when a user isn't set" do
- conn = UserIsAdminPlug.call(build_conn(), %{})
-
- assert conn.status == 403
- end
-end
diff --git a/test/runtime_test.exs b/test/runtime_test.exs
@@ -1,11 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.RuntimeTest do
- use ExUnit.Case, async: true
-
- test "it loads custom runtime modules" do
- assert {:module, RuntimeModule} == Code.ensure_compiled(RuntimeModule)
- end
-end
diff --git a/test/support/captcha_mock.ex b/test/support/captcha/mock.ex
diff --git a/test/tasks/frontend_test.exs b/test/tasks/frontend_test.exs
@@ -1,85 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.FrontendTest do
- use Pleroma.DataCase
- alias Mix.Tasks.Pleroma.Frontend
-
- import ExUnit.CaptureIO, only: [capture_io: 1]
-
- @dir "test/frontend_static_test"
-
- setup do
- File.mkdir_p!(@dir)
- clear_config([:instance, :static_dir], @dir)
-
- on_exit(fn ->
- File.rm_rf(@dir)
- end)
- end
-
- test "it downloads and unzips a known frontend" do
- clear_config([:frontends, :available], %{
- "pleroma" => %{
- "ref" => "fantasy",
- "name" => "pleroma",
- "build_url" => "http://gensokyo.2hu/builds/${ref}"
- }
- })
-
- Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/builds/fantasy"} ->
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/frontend_dist.zip")}
- end)
-
- capture_io(fn ->
- Frontend.run(["install", "pleroma"])
- end)
-
- assert File.exists?(Path.join([@dir, "frontends", "pleroma", "fantasy", "test.txt"]))
- end
-
- test "it also works given a file" do
- clear_config([:frontends, :available], %{
- "pleroma" => %{
- "ref" => "fantasy",
- "name" => "pleroma",
- "build_dir" => ""
- }
- })
-
- folder = Path.join([@dir, "frontends", "pleroma", "fantasy"])
- previously_existing = Path.join([folder, "temp"])
- File.mkdir_p!(folder)
- File.write!(previously_existing, "yey")
- assert File.exists?(previously_existing)
-
- capture_io(fn ->
- Frontend.run(["install", "pleroma", "--file", "test/fixtures/tesla_mock/frontend.zip"])
- end)
-
- assert File.exists?(Path.join([folder, "test.txt"]))
- refute File.exists?(previously_existing)
- end
-
- test "it downloads and unzips unknown frontends" do
- Tesla.Mock.mock(fn %{url: "http://gensokyo.2hu/madeup.zip"} ->
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/frontend.zip")}
- end)
-
- capture_io(fn ->
- Frontend.run([
- "install",
- "unknown",
- "--ref",
- "baka",
- "--build-url",
- "http://gensokyo.2hu/madeup.zip",
- "--build-dir",
- ""
- ])
- end)
-
- assert File.exists?(Path.join([@dir, "frontends", "unknown", "baka", "test.txt"]))
- end
-end
diff --git a/test/tasks/instance_test.exs b/test/tasks/instance_test.exs
@@ -1,99 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.InstanceTest do
- use ExUnit.Case
-
- setup do
- File.mkdir_p!(tmp_path())
-
- on_exit(fn ->
- File.rm_rf(tmp_path())
- static_dir = Pleroma.Config.get([:instance, :static_dir], "test/instance_static/")
-
- if File.exists?(static_dir) do
- File.rm_rf(Path.join(static_dir, "robots.txt"))
- end
-
- Pleroma.Config.put([:instance, :static_dir], static_dir)
- end)
-
- :ok
- end
-
- defp tmp_path do
- "/tmp/generated_files/"
- end
-
- test "running gen" do
- mix_task = fn ->
- Mix.Tasks.Pleroma.Instance.run([
- "gen",
- "--output",
- tmp_path() <> "generated_config.exs",
- "--output-psql",
- tmp_path() <> "setup.psql",
- "--domain",
- "test.pleroma.social",
- "--instance-name",
- "Pleroma",
- "--admin-email",
- "admin@example.com",
- "--notify-email",
- "notify@example.com",
- "--dbhost",
- "dbhost",
- "--dbname",
- "dbname",
- "--dbuser",
- "dbuser",
- "--dbpass",
- "dbpass",
- "--indexable",
- "y",
- "--db-configurable",
- "y",
- "--rum",
- "y",
- "--listen-port",
- "4000",
- "--listen-ip",
- "127.0.0.1",
- "--uploads-dir",
- "test/uploads",
- "--static-dir",
- "./test/../test/instance/static/",
- "--strip-uploads",
- "y",
- "--dedupe-uploads",
- "n",
- "--anonymize-uploads",
- "n"
- ])
- end
-
- ExUnit.CaptureIO.capture_io(fn ->
- mix_task.()
- end)
-
- generated_config = File.read!(tmp_path() <> "generated_config.exs")
- assert generated_config =~ "host: \"test.pleroma.social\""
- assert generated_config =~ "name: \"Pleroma\""
- assert generated_config =~ "email: \"admin@example.com\""
- assert generated_config =~ "notify_email: \"notify@example.com\""
- assert generated_config =~ "hostname: \"dbhost\""
- assert generated_config =~ "database: \"dbname\""
- assert generated_config =~ "username: \"dbuser\""
- assert generated_config =~ "password: \"dbpass\""
- assert generated_config =~ "configurable_from_database: true"
- assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]"
- assert generated_config =~ "filters: [Pleroma.Upload.Filter.ExifTool]"
- assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
- assert File.exists?(Path.expand("./test/instance/static/robots.txt"))
- end
-
- defp generated_setup_psql do
- ~s(CREATE USER dbuser WITH ENCRYPTED PASSWORD 'dbpass';\nCREATE DATABASE dbname OWNER dbuser;\n\\c dbname;\n--Extensions made by ecto.migrate that need superuser access\nCREATE EXTENSION IF NOT EXISTS citext;\nCREATE EXTENSION IF NOT EXISTS pg_trgm;\nCREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\nCREATE EXTENSION IF NOT EXISTS rum;\n)
- end
-end
diff --git a/test/web/activity_pub/object_validators/announce_validation_test.exs b/test/web/activity_pub/object_validators/announce_validation_test.exs
@@ -1,106 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnouncValidationTest do
- use Pleroma.DataCase
-
- alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.Builder
- alias Pleroma.Web.ActivityPub.ObjectValidator
- alias Pleroma.Web.CommonAPI
-
- import Pleroma.Factory
-
- describe "announces" do
- setup do
- user = insert(:user)
- announcer = insert(:user)
- {:ok, post_activity} = CommonAPI.post(user, %{status: "uguu"})
-
- object = Object.normalize(post_activity, false)
- {:ok, valid_announce, []} = Builder.announce(announcer, object)
-
- %{
- valid_announce: valid_announce,
- user: user,
- post_activity: post_activity,
- announcer: announcer
- }
- end
-
- test "returns ok for a valid announce", %{valid_announce: valid_announce} do
- assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
- end
-
- test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
- without_object =
- valid_announce
- |> Map.delete("object")
-
- {:error, cng} = ObjectValidator.validate(without_object, [])
-
- assert {:object, {"can't be blank", [validation: :required]}} in cng.errors
-
- nonexisting_object =
- valid_announce
- |> Map.put("object", "https://gensokyo.2hu/objects/99999999")
-
- {:error, cng} = ObjectValidator.validate(nonexisting_object, [])
-
- assert {:object, {"can't find object", []}} in cng.errors
- end
-
- test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do
- nonexisting_actor =
- valid_announce
- |> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
-
- {:error, cng} = ObjectValidator.validate(nonexisting_actor, [])
-
- assert {:actor, {"can't find user", []}} in cng.errors
- end
-
- test "returns an error if the actor already announced the object", %{
- valid_announce: valid_announce,
- announcer: announcer,
- post_activity: post_activity
- } do
- _announce = CommonAPI.repeat(post_activity.id, announcer)
-
- {:error, cng} = ObjectValidator.validate(valid_announce, [])
-
- assert {:actor, {"already announced this object", []}} in cng.errors
- assert {:object, {"already announced by this actor", []}} in cng.errors
- end
-
- test "returns an error if the actor can't announce the object", %{
- announcer: announcer,
- user: user
- } do
- {:ok, post_activity} =
- CommonAPI.post(user, %{status: "a secret post", visibility: "private"})
-
- object = Object.normalize(post_activity, false)
-
- # Another user can't announce it
- {:ok, announce, []} = Builder.announce(announcer, object, public: false)
-
- {:error, cng} = ObjectValidator.validate(announce, [])
-
- assert {:actor, {"can not announce this object", []}} in cng.errors
-
- # The actor of the object can announce it
- {:ok, announce, []} = Builder.announce(user, object, public: false)
-
- assert {:ok, _, _} = ObjectValidator.validate(announce, [])
-
- # The actor of the object can not announce it publicly
- {:ok, announce, []} = Builder.announce(user, object, public: true)
-
- {:error, cng} = ObjectValidator.validate(announce, [])
-
- assert {:actor, {"can not announce this object publicly", []}} in cng.errors
- end
- end
-end
diff --git a/test/web/activity_pub/object_validators/types/date_time_test.exs b/test/web/activity_pub/object_validators/types/date_time_test.exs
@@ -1,36 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTimeTest do
- alias Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime
- use Pleroma.DataCase
-
- test "it validates an xsd:Datetime" do
- valid_strings = [
- "2004-04-12T13:20:00",
- "2004-04-12T13:20:15.5",
- "2004-04-12T13:20:00-05:00",
- "2004-04-12T13:20:00Z"
- ]
-
- invalid_strings = [
- "2004-04-12T13:00",
- "2004-04-1213:20:00",
- "99-04-12T13:00",
- "2004-04-12"
- ]
-
- assert {:ok, "2004-04-01T12:00:00Z"} == DateTime.cast("2004-04-01T12:00:00Z")
-
- Enum.each(valid_strings, fn date_time ->
- result = DateTime.cast(date_time)
- assert {:ok, _} = result
- end)
-
- Enum.each(invalid_strings, fn date_time ->
- result = DateTime.cast(date_time)
- assert :error == result
- end)
- end
-end
diff --git a/test/web/activity_pub/object_validators/types/object_id_test.exs b/test/web/activity_pub/object_validators/types/object_id_test.exs
@@ -1,41 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do
- alias Pleroma.EctoType.ActivityPub.ObjectValidators.ObjectID
- use Pleroma.DataCase
-
- @uris [
- "http://lain.com/users/lain",
- "http://lain.com",
- "https://lain.com/object/1"
- ]
-
- @non_uris [
- "https://",
- "rin",
- 1,
- :x,
- %{"1" => 2}
- ]
-
- test "it accepts http uris" do
- Enum.each(@uris, fn uri ->
- assert {:ok, uri} == ObjectID.cast(uri)
- end)
- end
-
- test "it accepts an object with a nested uri id" do
- Enum.each(@uris, fn uri ->
- assert {:ok, uri} == ObjectID.cast(%{"id" => uri})
- end)
- end
-
- test "it rejects non-uri strings" do
- Enum.each(@non_uris, fn non_uri ->
- assert :error == ObjectID.cast(non_uri)
- assert :error == ObjectID.cast(%{"id" => non_uri})
- end)
- end
-end
diff --git a/test/web/activity_pub/object_validators/types/recipients_test.exs b/test/web/activity_pub/object_validators/types/recipients_test.exs
@@ -1,31 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ObjectValidators.Types.RecipientsTest do
- alias Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients
- use Pleroma.DataCase
-
- test "it asserts that all elements of the list are object ids" do
- list = ["https://lain.com/users/lain", "invalid"]
-
- assert :error == Recipients.cast(list)
- end
-
- test "it works with a list" do
- list = ["https://lain.com/users/lain"]
- assert {:ok, list} == Recipients.cast(list)
- end
-
- test "it works with a list with whole objects" do
- list = ["https://lain.com/users/lain", %{"id" => "https://gensokyo.2hu/users/raymoo"}]
- resulting_list = ["https://gensokyo.2hu/users/raymoo", "https://lain.com/users/lain"]
- assert {:ok, resulting_list} == Recipients.cast(list)
- end
-
- test "it turns a single string into a list" do
- recipient = "https://lain.com/users/lain"
-
- assert {:ok, [recipient]} == Recipients.cast(recipient)
- end
-end
diff --git a/test/web/activity_pub/object_validators/types/safe_text_test.exs b/test/web/activity_pub/object_validators/types/safe_text_test.exs
@@ -1,30 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.SafeTextTest do
- use Pleroma.DataCase
-
- alias Pleroma.EctoType.ActivityPub.ObjectValidators.SafeText
-
- test "it lets normal text go through" do
- text = "hey how are you"
- assert {:ok, text} == SafeText.cast(text)
- end
-
- test "it removes html tags from text" do
- text = "hey look xss <script>alert('foo')</script>"
- assert {:ok, "hey look xss alert('foo')"} == SafeText.cast(text)
- end
-
- test "it keeps basic html tags" do
- text = "hey <a href='http://gensokyo.2hu'>look</a> xss <script>alert('foo')</script>"
-
- assert {:ok, "hey <a href=\"http://gensokyo.2hu\">look</a> xss alert('foo')"} ==
- SafeText.cast(text)
- end
-
- test "errors for non-text" do
- assert :error == SafeText.cast(1)
- end
-end
diff --git a/test/web/auth/auth_test_controller_test.exs b/test/web/auth/auth_test_controller_test.exs
@@ -1,242 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Tests.AuthTestControllerTest do
- use Pleroma.Web.ConnCase
-
- import Pleroma.Factory
-
- describe "do_oauth_check" do
- test "serves with proper OAuth token (fulfilling requested scopes)" do
- %{conn: good_token_conn, user: user} = oauth_access(["read"])
-
- assert %{"user_id" => user.id} ==
- good_token_conn
- |> get("/test/authenticated_api/do_oauth_check")
- |> json_response(200)
-
- # Unintended usage (:api) — use with :authenticated_api instead
- assert %{"user_id" => user.id} ==
- good_token_conn
- |> get("/test/api/do_oauth_check")
- |> json_response(200)
- end
-
- test "fails on no token / missing scope(s)" do
- %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"])
-
- bad_token_conn
- |> get("/test/authenticated_api/do_oauth_check")
- |> json_response(403)
-
- bad_token_conn
- |> assign(:token, nil)
- |> get("/test/api/do_oauth_check")
- |> json_response(403)
- end
- end
-
- describe "fallback_oauth_check" do
- test "serves with proper OAuth token (fulfilling requested scopes)" do
- %{conn: good_token_conn, user: user} = oauth_access(["read"])
-
- assert %{"user_id" => user.id} ==
- good_token_conn
- |> get("/test/api/fallback_oauth_check")
- |> json_response(200)
-
- # Unintended usage (:authenticated_api) — use with :api instead
- assert %{"user_id" => user.id} ==
- good_token_conn
- |> get("/test/authenticated_api/fallback_oauth_check")
- |> json_response(200)
- end
-
- test "for :api on public instance, drops :user and renders on no token / missing scope(s)" do
- clear_config([:instance, :public], true)
-
- %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"])
-
- assert %{"user_id" => nil} ==
- bad_token_conn
- |> get("/test/api/fallback_oauth_check")
- |> json_response(200)
-
- assert %{"user_id" => nil} ==
- bad_token_conn
- |> assign(:token, nil)
- |> get("/test/api/fallback_oauth_check")
- |> json_response(200)
- end
-
- test "for :api on private instance, fails on no token / missing scope(s)" do
- clear_config([:instance, :public], false)
-
- %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"])
-
- bad_token_conn
- |> get("/test/api/fallback_oauth_check")
- |> json_response(403)
-
- bad_token_conn
- |> assign(:token, nil)
- |> get("/test/api/fallback_oauth_check")
- |> json_response(403)
- end
- end
-
- describe "skip_oauth_check" do
- test "for :authenticated_api, serves if :user is set (regardless of token / token scopes)" do
- user = insert(:user)
-
- assert %{"user_id" => user.id} ==
- build_conn()
- |> assign(:user, user)
- |> get("/test/authenticated_api/skip_oauth_check")
- |> json_response(200)
-
- %{conn: bad_token_conn, user: user} = oauth_access(["irrelevant_scope"])
-
- assert %{"user_id" => user.id} ==
- bad_token_conn
- |> get("/test/authenticated_api/skip_oauth_check")
- |> json_response(200)
- end
-
- test "serves via :api on public instance if :user is not set" do
- clear_config([:instance, :public], true)
-
- assert %{"user_id" => nil} ==
- build_conn()
- |> get("/test/api/skip_oauth_check")
- |> json_response(200)
-
- build_conn()
- |> get("/test/authenticated_api/skip_oauth_check")
- |> json_response(403)
- end
-
- test "fails on private instance if :user is not set" do
- clear_config([:instance, :public], false)
-
- build_conn()
- |> get("/test/api/skip_oauth_check")
- |> json_response(403)
-
- build_conn()
- |> get("/test/authenticated_api/skip_oauth_check")
- |> json_response(403)
- end
- end
-
- describe "fallback_oauth_skip_publicity_check" do
- test "serves with proper OAuth token (fulfilling requested scopes)" do
- %{conn: good_token_conn, user: user} = oauth_access(["read"])
-
- assert %{"user_id" => user.id} ==
- good_token_conn
- |> get("/test/api/fallback_oauth_skip_publicity_check")
- |> json_response(200)
-
- # Unintended usage (:authenticated_api)
- assert %{"user_id" => user.id} ==
- good_token_conn
- |> get("/test/authenticated_api/fallback_oauth_skip_publicity_check")
- |> json_response(200)
- end
-
- test "for :api on private / public instance, drops :user and renders on token issue" do
- %{conn: bad_token_conn} = oauth_access(["irrelevant_scope"])
-
- for is_public <- [true, false] do
- clear_config([:instance, :public], is_public)
-
- assert %{"user_id" => nil} ==
- bad_token_conn
- |> get("/test/api/fallback_oauth_skip_publicity_check")
- |> json_response(200)
-
- assert %{"user_id" => nil} ==
- bad_token_conn
- |> assign(:token, nil)
- |> get("/test/api/fallback_oauth_skip_publicity_check")
- |> json_response(200)
- end
- end
- end
-
- describe "skip_oauth_skip_publicity_check" do
- test "for :authenticated_api, serves if :user is set (regardless of token / token scopes)" do
- user = insert(:user)
-
- assert %{"user_id" => user.id} ==
- build_conn()
- |> assign(:user, user)
- |> get("/test/authenticated_api/skip_oauth_skip_publicity_check")
- |> json_response(200)
-
- %{conn: bad_token_conn, user: user} = oauth_access(["irrelevant_scope"])
-
- assert %{"user_id" => user.id} ==
- bad_token_conn
- |> get("/test/authenticated_api/skip_oauth_skip_publicity_check")
- |> json_response(200)
- end
-
- test "for :api, serves on private and public instances regardless of whether :user is set" do
- user = insert(:user)
-
- for is_public <- [true, false] do
- clear_config([:instance, :public], is_public)
-
- assert %{"user_id" => nil} ==
- build_conn()
- |> get("/test/api/skip_oauth_skip_publicity_check")
- |> json_response(200)
-
- assert %{"user_id" => user.id} ==
- build_conn()
- |> assign(:user, user)
- |> get("/test/api/skip_oauth_skip_publicity_check")
- |> json_response(200)
- end
- end
- end
-
- describe "missing_oauth_check_definition" do
- def test_missing_oauth_check_definition_failure(endpoint, expected_error) do
- %{conn: conn} = oauth_access(["read", "write", "follow", "push", "admin"])
-
- assert %{"error" => expected_error} ==
- conn
- |> get(endpoint)
- |> json_response(403)
- end
-
- test "fails if served via :authenticated_api" do
- test_missing_oauth_check_definition_failure(
- "/test/authenticated_api/missing_oauth_check_definition",
- "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
- )
- end
-
- test "fails if served via :api and the instance is private" do
- clear_config([:instance, :public], false)
-
- test_missing_oauth_check_definition_failure(
- "/test/api/missing_oauth_check_definition",
- "This resource requires authentication."
- )
- end
-
- test "succeeds with dropped :user if served via :api on public instance" do
- %{conn: conn} = oauth_access(["read", "write", "follow", "push", "admin"])
-
- assert %{"user_id" => nil} ==
- conn
- |> get("/test/api/missing_oauth_check_definition")
- |> json_response(200)
- end
- end
-end
diff --git a/test/web/feed/user_controller_test.exs b/test/web/feed/user_controller_test.exs
@@ -1,265 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Feed.UserControllerTest do
- use Pleroma.Web.ConnCase
-
- import Pleroma.Factory
- import SweetXml
-
- alias Pleroma.Config
- alias Pleroma.Object
- alias Pleroma.User
- alias Pleroma.Web.CommonAPI
-
- setup do: clear_config([:instance, :federating], true)
-
- describe "feed" do
- setup do: clear_config([:feed])
-
- test "gets an atom feed", %{conn: conn} do
- Config.put(
- [:feed, :post_title],
- %{max_length: 10, omission: "..."}
- )
-
- activity = insert(:note_activity)
-
- note =
- insert(:note,
- data: %{
- "content" => "This is :moominmamma: note ",
- "attachment" => [
- %{
- "url" => [
- %{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}
- ]
- }
- ],
- "inReplyTo" => activity.data["id"]
- }
- )
-
- note_activity = insert(:note_activity, note: note)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
-
- note2 =
- insert(:note,
- user: user,
- data: %{
- "content" => "42 This is :moominmamma: note ",
- "inReplyTo" => activity.data["id"]
- }
- )
-
- note_activity2 = insert(:note_activity, note: note2)
- object = Object.normalize(note_activity)
-
- resp =
- conn
- |> put_req_header("accept", "application/atom+xml")
- |> get(user_feed_path(conn, :feed, user.nickname))
- |> response(200)
-
- activity_titles =
- resp
- |> SweetXml.parse()
- |> SweetXml.xpath(~x"//entry/title/text()"l)
-
- assert activity_titles == ['42 This...', 'This is...']
- assert resp =~ object.data["content"]
-
- resp =
- conn
- |> put_req_header("accept", "application/atom+xml")
- |> get("/users/#{user.nickname}/feed", %{"max_id" => note_activity2.id})
- |> response(200)
-
- activity_titles =
- resp
- |> SweetXml.parse()
- |> SweetXml.xpath(~x"//entry/title/text()"l)
-
- assert activity_titles == ['This is...']
- end
-
- test "gets a rss feed", %{conn: conn} do
- Pleroma.Config.put(
- [:feed, :post_title],
- %{max_length: 10, omission: "..."}
- )
-
- activity = insert(:note_activity)
-
- note =
- insert(:note,
- data: %{
- "content" => "This is :moominmamma: note ",
- "attachment" => [
- %{
- "url" => [
- %{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}
- ]
- }
- ],
- "inReplyTo" => activity.data["id"]
- }
- )
-
- note_activity = insert(:note_activity, note: note)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
-
- note2 =
- insert(:note,
- user: user,
- data: %{
- "content" => "42 This is :moominmamma: note ",
- "inReplyTo" => activity.data["id"]
- }
- )
-
- note_activity2 = insert(:note_activity, note: note2)
- object = Object.normalize(note_activity)
-
- resp =
- conn
- |> put_req_header("accept", "application/rss+xml")
- |> get("/users/#{user.nickname}/feed.rss")
- |> response(200)
-
- activity_titles =
- resp
- |> SweetXml.parse()
- |> SweetXml.xpath(~x"//item/title/text()"l)
-
- assert activity_titles == ['42 This...', 'This is...']
- assert resp =~ object.data["content"]
-
- resp =
- conn
- |> put_req_header("accept", "application/rss+xml")
- |> get("/users/#{user.nickname}/feed.rss", %{"max_id" => note_activity2.id})
- |> response(200)
-
- activity_titles =
- resp
- |> SweetXml.parse()
- |> SweetXml.xpath(~x"//item/title/text()"l)
-
- assert activity_titles == ['This is...']
- end
-
- test "returns 404 for a missing feed", %{conn: conn} do
- conn =
- conn
- |> put_req_header("accept", "application/atom+xml")
- |> get(user_feed_path(conn, :feed, "nonexisting"))
-
- assert response(conn, 404)
- end
-
- test "returns feed with public and unlisted activities", %{conn: conn} do
- user = insert(:user)
-
- {:ok, _} = CommonAPI.post(user, %{status: "public", visibility: "public"})
- {:ok, _} = CommonAPI.post(user, %{status: "direct", visibility: "direct"})
- {:ok, _} = CommonAPI.post(user, %{status: "unlisted", visibility: "unlisted"})
- {:ok, _} = CommonAPI.post(user, %{status: "private", visibility: "private"})
-
- resp =
- conn
- |> put_req_header("accept", "application/atom+xml")
- |> get(user_feed_path(conn, :feed, user.nickname))
- |> response(200)
-
- activity_titles =
- resp
- |> SweetXml.parse()
- |> SweetXml.xpath(~x"//entry/title/text()"l)
- |> Enum.sort()
-
- assert activity_titles == ['public', 'unlisted']
- end
-
- test "returns 404 when the user is remote", %{conn: conn} do
- user = insert(:user, local: false)
-
- {:ok, _} = CommonAPI.post(user, %{status: "test"})
-
- assert conn
- |> put_req_header("accept", "application/atom+xml")
- |> get(user_feed_path(conn, :feed, user.nickname))
- |> response(404)
- end
- end
-
- # Note: see ActivityPubControllerTest for JSON format tests
- describe "feed_redirect" do
- test "with html format, it redirects to user feed", %{conn: conn} do
- note_activity = insert(:note_activity)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
-
- response =
- conn
- |> get("/users/#{user.nickname}")
- |> response(200)
-
- assert response ==
- Fallback.RedirectController.redirector_with_meta(
- conn,
- %{user: user}
- ).resp_body
- end
-
- test "with html format, it returns error when user is not found", %{conn: conn} do
- response =
- conn
- |> get("/users/jimm")
- |> json_response(404)
-
- assert response == %{"error" => "Not found"}
- end
-
- test "with non-html / non-json format, it redirects to user feed in atom format", %{
- conn: conn
- } do
- note_activity = insert(:note_activity)
- user = User.get_cached_by_ap_id(note_activity.data["actor"])
-
- conn =
- conn
- |> put_req_header("accept", "application/xml")
- |> get("/users/#{user.nickname}")
-
- assert conn.status == 302
- assert redirected_to(conn) == "#{Pleroma.Web.base_url()}/users/#{user.nickname}/feed.atom"
- end
-
- test "with non-html / non-json format, it returns error when user is not found", %{conn: conn} do
- response =
- conn
- |> put_req_header("accept", "application/xml")
- |> get(user_feed_path(conn, :feed, "jimm"))
- |> response(404)
-
- assert response == ~S({"error":"Not found"})
- end
- end
-
- describe "private instance" do
- setup do: clear_config([:instance, :public])
-
- test "returns 404 for user feed", %{conn: conn} do
- Config.put([:instance, :public], false)
- user = insert(:user)
-
- {:ok, _} = CommonAPI.post(user, %{status: "test"})
-
- assert conn
- |> put_req_header("accept", "application/atom+xml")
- |> get(user_feed_path(conn, :feed, user.nickname))
- |> response(404)
- end
- end
-end
diff --git a/test/web/masto_fe_controller_test.exs b/test/web/masto_fe_controller_test.exs
@@ -1,85 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.MastodonAPI.MastoFEController do
- use Pleroma.Web.ConnCase
-
- alias Pleroma.Config
- alias Pleroma.User
-
- import Pleroma.Factory
-
- setup do: clear_config([:instance, :public])
-
- test "put settings", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> assign(:user, user)
- |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:accounts"]))
- |> put("/api/web/settings", %{"data" => %{"programming" => "socks"}})
-
- assert _result = json_response(conn, 200)
-
- user = User.get_cached_by_ap_id(user.ap_id)
- assert user.mastofe_settings == %{"programming" => "socks"}
- end
-
- describe "index/2 redirections" do
- setup %{conn: conn} do
- session_opts = [
- store: :cookie,
- key: "_test",
- signing_salt: "cooldude"
- ]
-
- conn =
- conn
- |> Plug.Session.call(Plug.Session.init(session_opts))
- |> fetch_session()
-
- test_path = "/web/statuses/test"
- %{conn: conn, path: test_path}
- end
-
- test "redirects not logged-in users to the login page", %{conn: conn, path: path} do
- conn = get(conn, path)
-
- assert conn.status == 302
- assert redirected_to(conn) == "/web/login"
- end
-
- test "redirects not logged-in users to the login page on private instances", %{
- conn: conn,
- path: path
- } do
- Config.put([:instance, :public], false)
-
- conn = get(conn, path)
-
- assert conn.status == 302
- assert redirected_to(conn) == "/web/login"
- end
-
- test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
- token = insert(:oauth_token, scopes: ["read"])
-
- conn =
- conn
- |> assign(:user, token.user)
- |> assign(:token, token)
- |> get(path)
-
- assert conn.status == 200
- end
-
- test "saves referer path to session", %{conn: conn, path: path} do
- conn = get(conn, path)
- return_to = Plug.Conn.get_session(conn, :return_to)
-
- assert return_to == path
- end
- end
-end
diff --git a/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs b/test/web/mastodon_api/controllers/account_controller/update_credentials_test.exs
@@ -1,529 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.MastodonAPI.MastodonAPIController.UpdateCredentialsTest do
- alias Pleroma.Repo
- alias Pleroma.User
-
- use Pleroma.Web.ConnCase
-
- import Mock
- import Pleroma.Factory
-
- setup do: clear_config([:instance, :max_account_fields])
-
- describe "updating credentials" do
- setup do: oauth_access(["write:accounts"])
- setup :request_content_type
-
- test "sets user settings in a generic way", %{conn: conn} do
- res_conn =
- patch(conn, "/api/v1/accounts/update_credentials", %{
- "pleroma_settings_store" => %{
- pleroma_fe: %{
- theme: "bla"
- }
- }
- })
-
- assert user_data = json_response_and_validate_schema(res_conn, 200)
- assert user_data["pleroma"]["settings_store"] == %{"pleroma_fe" => %{"theme" => "bla"}}
-
- user = Repo.get(User, user_data["id"])
-
- res_conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{
- "pleroma_settings_store" => %{
- masto_fe: %{
- theme: "bla"
- }
- }
- })
-
- assert user_data = json_response_and_validate_schema(res_conn, 200)
-
- assert user_data["pleroma"]["settings_store"] ==
- %{
- "pleroma_fe" => %{"theme" => "bla"},
- "masto_fe" => %{"theme" => "bla"}
- }
-
- user = Repo.get(User, user_data["id"])
-
- clear_config([:instance, :federating], true)
-
- with_mock Pleroma.Web.Federator,
- publish: fn _activity -> :ok end do
- res_conn =
- conn
- |> assign(:user, user)
- |> patch("/api/v1/accounts/update_credentials", %{
- "pleroma_settings_store" => %{
- masto_fe: %{
- theme: "blub"
- }
- }
- })
-
- assert user_data = json_response_and_validate_schema(res_conn, 200)
-
- assert user_data["pleroma"]["settings_store"] ==
- %{
- "pleroma_fe" => %{"theme" => "bla"},
- "masto_fe" => %{"theme" => "blub"}
- }
-
- assert_called(Pleroma.Web.Federator.publish(:_))
- end
- end
-
- test "updates the user's bio", %{conn: conn} do
- user2 = insert(:user)
-
- raw_bio = "I drink #cofe with @#{user2.nickname}\n\nsuya.."
-
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{"note" => raw_bio})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
-
- assert user_data["note"] ==
- ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
- user2.id
- }" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
-
- assert user_data["source"]["note"] == raw_bio
-
- user = Repo.get(User, user_data["id"])
-
- assert user.raw_bio == raw_bio
- end
-
- test "updates the user's locking status", %{conn: conn} do
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{locked: "true"})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["locked"] == true
- end
-
- test "updates the user's chat acceptance status", %{conn: conn} do
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{accepts_chat_messages: "false"})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["pleroma"]["accepts_chat_messages"] == false
- end
-
- test "updates the user's allow_following_move", %{user: user, conn: conn} do
- assert user.allow_following_move == true
-
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{allow_following_move: "false"})
-
- assert refresh_record(user).allow_following_move == false
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["pleroma"]["allow_following_move"] == false
- end
-
- test "updates the user's default scope", %{conn: conn} do
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{default_scope: "unlisted"})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["source"]["privacy"] == "unlisted"
- end
-
- test "updates the user's privacy", %{conn: conn} do
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{source: %{privacy: "unlisted"}})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["source"]["privacy"] == "unlisted"
- end
-
- test "updates the user's hide_followers status", %{conn: conn} do
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_followers: "true"})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["pleroma"]["hide_followers"] == true
- end
-
- test "updates the user's discoverable status", %{conn: conn} do
- assert %{"source" => %{"pleroma" => %{"discoverable" => true}}} =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{discoverable: "true"})
- |> json_response_and_validate_schema(:ok)
-
- assert %{"source" => %{"pleroma" => %{"discoverable" => false}}} =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{discoverable: "false"})
- |> json_response_and_validate_schema(:ok)
- end
-
- test "updates the user's hide_followers_count and hide_follows_count", %{conn: conn} do
- conn =
- patch(conn, "/api/v1/accounts/update_credentials", %{
- hide_followers_count: "true",
- hide_follows_count: "true"
- })
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["pleroma"]["hide_followers_count"] == true
- assert user_data["pleroma"]["hide_follows_count"] == true
- end
-
- test "updates the user's skip_thread_containment option", %{user: user, conn: conn} do
- response =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{skip_thread_containment: "true"})
- |> json_response_and_validate_schema(200)
-
- assert response["pleroma"]["skip_thread_containment"] == true
- assert refresh_record(user).skip_thread_containment
- end
-
- test "updates the user's hide_follows status", %{conn: conn} do
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_follows: "true"})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["pleroma"]["hide_follows"] == true
- end
-
- test "updates the user's hide_favorites status", %{conn: conn} do
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{hide_favorites: "true"})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["pleroma"]["hide_favorites"] == true
- end
-
- test "updates the user's show_role status", %{conn: conn} do
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{show_role: "false"})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["source"]["pleroma"]["show_role"] == false
- end
-
- test "updates the user's no_rich_text status", %{conn: conn} do
- conn = patch(conn, "/api/v1/accounts/update_credentials", %{no_rich_text: "true"})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["source"]["pleroma"]["no_rich_text"] == true
- end
-
- test "updates the user's name", %{conn: conn} do
- conn =
- patch(conn, "/api/v1/accounts/update_credentials", %{"display_name" => "markorepairs"})
-
- assert user_data = json_response_and_validate_schema(conn, 200)
- assert user_data["display_name"] == "markorepairs"
-
- update_activity = Repo.one(Pleroma.Activity)
- assert update_activity.data["type"] == "Update"
- assert update_activity.data["object"]["name"] == "markorepairs"
- end
-
- test "updates the user's avatar", %{user: user, conn: conn} do
- new_avatar = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- assert user.avatar == %{}
-
- res = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar})
-
- assert user_response = json_response_and_validate_schema(res, 200)
- assert user_response["avatar"] != User.avatar_url(user)
-
- user = User.get_by_id(user.id)
- refute user.avatar == %{}
-
- # Also resets it
- _res = patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => ""})
-
- user = User.get_by_id(user.id)
- assert user.avatar == nil
- end
-
- test "updates the user's banner", %{user: user, conn: conn} do
- new_header = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- res = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header})
-
- assert user_response = json_response_and_validate_schema(res, 200)
- assert user_response["header"] != User.banner_url(user)
-
- # Also resets it
- _res = patch(conn, "/api/v1/accounts/update_credentials", %{"header" => ""})
-
- user = User.get_by_id(user.id)
- assert user.banner == nil
- end
-
- test "updates the user's background", %{conn: conn, user: user} do
- new_header = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- res =
- patch(conn, "/api/v1/accounts/update_credentials", %{
- "pleroma_background_image" => new_header
- })
-
- assert user_response = json_response_and_validate_schema(res, 200)
- assert user_response["pleroma"]["background_image"]
- #
- # Also resets it
- _res =
- patch(conn, "/api/v1/accounts/update_credentials", %{"pleroma_background_image" => ""})
-
- user = User.get_by_id(user.id)
- assert user.background == nil
- end
-
- test "requires 'write:accounts' permission" do
- token1 = insert(:oauth_token, scopes: ["read"])
- token2 = insert(:oauth_token, scopes: ["write", "follow"])
-
- for token <- [token1, token2] do
- conn =
- build_conn()
- |> put_req_header("content-type", "multipart/form-data")
- |> put_req_header("authorization", "Bearer #{token.token}")
- |> patch("/api/v1/accounts/update_credentials", %{})
-
- if token == token1 do
- assert %{"error" => "Insufficient permissions: write:accounts."} ==
- json_response_and_validate_schema(conn, 403)
- else
- assert json_response_and_validate_schema(conn, 200)
- end
- end
- end
-
- test "updates profile emojos", %{user: user, conn: conn} do
- note = "*sips :blank:*"
- name = "I am :firefox:"
-
- ret_conn =
- patch(conn, "/api/v1/accounts/update_credentials", %{
- "note" => note,
- "display_name" => name
- })
-
- assert json_response_and_validate_schema(ret_conn, 200)
-
- conn = get(conn, "/api/v1/accounts/#{user.id}")
-
- assert user_data = json_response_and_validate_schema(conn, 200)
-
- assert user_data["note"] == note
- assert user_data["display_name"] == name
- assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = user_data["emojis"]
- end
-
- test "update fields", %{conn: conn} do
- fields = [
- %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"},
- %{"name" => "link.io", "value" => "cofe.io"}
- ]
-
- account_data =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
- |> json_response_and_validate_schema(200)
-
- assert account_data["fields"] == [
- %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
- %{
- "name" => "link.io",
- "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)
- }
- ]
-
- assert account_data["source"]["fields"] == [
- %{
- "name" => "<a href=\"http://google.com\">foo</a>",
- "value" => "<script>bar</script>"
- },
- %{"name" => "link.io", "value" => "cofe.io"}
- ]
- end
-
- test "emojis in fields labels", %{conn: conn} do
- fields = [
- %{"name" => ":firefox:", "value" => "is best 2hu"},
- %{"name" => "they wins", "value" => ":blank:"}
- ]
-
- account_data =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
- |> json_response_and_validate_schema(200)
-
- assert account_data["fields"] == [
- %{"name" => ":firefox:", "value" => "is best 2hu"},
- %{"name" => "they wins", "value" => ":blank:"}
- ]
-
- assert account_data["source"]["fields"] == [
- %{"name" => ":firefox:", "value" => "is best 2hu"},
- %{"name" => "they wins", "value" => ":blank:"}
- ]
-
- assert [%{"shortcode" => "blank"}, %{"shortcode" => "firefox"}] = account_data["emojis"]
- end
-
- test "update fields via x-www-form-urlencoded", %{conn: conn} do
- fields =
- [
- "fields_attributes[1][name]=link",
- "fields_attributes[1][value]=http://cofe.io",
- "fields_attributes[0][name]=foo",
- "fields_attributes[0][value]=bar"
- ]
- |> Enum.join("&")
-
- account =
- conn
- |> put_req_header("content-type", "application/x-www-form-urlencoded")
- |> patch("/api/v1/accounts/update_credentials", fields)
- |> json_response_and_validate_schema(200)
-
- assert account["fields"] == [
- %{"name" => "foo", "value" => "bar"},
- %{
- "name" => "link",
- "value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>)
- }
- ]
-
- assert account["source"]["fields"] == [
- %{"name" => "foo", "value" => "bar"},
- %{"name" => "link", "value" => "http://cofe.io"}
- ]
- end
-
- test "update fields with empty name", %{conn: conn} do
- fields = [
- %{"name" => "foo", "value" => ""},
- %{"name" => "", "value" => "bar"}
- ]
-
- account =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
- |> json_response_and_validate_schema(200)
-
- assert account["fields"] == [
- %{"name" => "foo", "value" => ""}
- ]
- end
-
- test "update fields when invalid request", %{conn: conn} do
- name_limit = Pleroma.Config.get([:instance, :account_field_name_length])
- value_limit = Pleroma.Config.get([:instance, :account_field_value_length])
-
- long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
- long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join()
-
- fields = [%{"name" => "foo", "value" => long_value}]
-
- assert %{"error" => "Invalid request"} ==
- conn
- |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
- |> json_response_and_validate_schema(403)
-
- fields = [%{"name" => long_name, "value" => "bar"}]
-
- assert %{"error" => "Invalid request"} ==
- conn
- |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
- |> json_response_and_validate_schema(403)
-
- Pleroma.Config.put([:instance, :max_account_fields], 1)
-
- fields = [
- %{"name" => "foo", "value" => "bar"},
- %{"name" => "link", "value" => "cofe.io"}
- ]
-
- assert %{"error" => "Invalid request"} ==
- conn
- |> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
- |> json_response_and_validate_schema(403)
- end
- end
-
- describe "Mark account as bot" do
- setup do: oauth_access(["write:accounts"])
- setup :request_content_type
-
- test "changing actor_type to Service makes account a bot", %{conn: conn} do
- account =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{actor_type: "Service"})
- |> json_response_and_validate_schema(200)
-
- assert account["bot"]
- assert account["source"]["pleroma"]["actor_type"] == "Service"
- end
-
- test "changing actor_type to Person makes account a human", %{conn: conn} do
- account =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{actor_type: "Person"})
- |> json_response_and_validate_schema(200)
-
- refute account["bot"]
- assert account["source"]["pleroma"]["actor_type"] == "Person"
- end
-
- test "changing actor_type to Application causes error", %{conn: conn} do
- response =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{actor_type: "Application"})
- |> json_response_and_validate_schema(403)
-
- assert %{"error" => "Invalid request"} == response
- end
-
- test "changing bot field to true changes actor_type to Service", %{conn: conn} do
- account =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{bot: "true"})
- |> json_response_and_validate_schema(200)
-
- assert account["bot"]
- assert account["source"]["pleroma"]["actor_type"] == "Service"
- end
-
- test "changing bot field to false changes actor_type to Person", %{conn: conn} do
- account =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{bot: "false"})
- |> json_response_and_validate_schema(200)
-
- refute account["bot"]
- assert account["source"]["pleroma"]["actor_type"] == "Person"
- end
-
- test "actor_type field has a higher priority than bot", %{conn: conn} do
- account =
- conn
- |> patch("/api/v1/accounts/update_credentials", %{
- actor_type: "Person",
- bot: "true"
- })
- |> json_response_and_validate_schema(200)
-
- refute account["bot"]
- assert account["source"]["pleroma"]["actor_type"] == "Person"
- end
- end
-end
diff --git a/test/web/mongooseim/mongoose_im_controller_test.exs b/test/web/mongooseim/mongoose_im_controller_test.exs
@@ -1,81 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.MongooseIMController do
- use Pleroma.Web.ConnCase
- import Pleroma.Factory
-
- test "/user_exists", %{conn: conn} do
- _user = insert(:user, nickname: "lain")
- _remote_user = insert(:user, nickname: "alice", local: false)
- _deactivated_user = insert(:user, nickname: "konata", deactivated: true)
-
- res =
- conn
- |> get(mongoose_im_path(conn, :user_exists), user: "lain")
- |> json_response(200)
-
- assert res == true
-
- res =
- conn
- |> get(mongoose_im_path(conn, :user_exists), user: "alice")
- |> json_response(404)
-
- assert res == false
-
- res =
- conn
- |> get(mongoose_im_path(conn, :user_exists), user: "bob")
- |> json_response(404)
-
- assert res == false
-
- res =
- conn
- |> get(mongoose_im_path(conn, :user_exists), user: "konata")
- |> json_response(404)
-
- assert res == false
- end
-
- test "/check_password", %{conn: conn} do
- user = insert(:user, password_hash: Pbkdf2.hash_pwd_salt("cool"))
-
- _deactivated_user =
- insert(:user,
- nickname: "konata",
- deactivated: true,
- password_hash: Pbkdf2.hash_pwd_salt("cool")
- )
-
- res =
- conn
- |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "cool")
- |> json_response(200)
-
- assert res == true
-
- res =
- conn
- |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "uncool")
- |> json_response(403)
-
- assert res == false
-
- res =
- conn
- |> get(mongoose_im_path(conn, :check_password), user: "konata", pass: "cool")
- |> json_response(404)
-
- assert res == false
-
- res =
- conn
- |> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool")
- |> json_response(404)
-
- assert res == false
- end
-end
diff --git a/test/web/pleroma_api/views/chat/message_reference_view_test.exs b/test/web/pleroma_api/views/chat/message_reference_view_test.exs
@@ -1,72 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.PleromaAPI.Chat.MessageReferenceViewTest do
- use Pleroma.DataCase
-
- alias Pleroma.Chat
- alias Pleroma.Chat.MessageReference
- alias Pleroma.Object
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
-
- import Pleroma.Factory
-
- test "it displays a chat message" do
- user = insert(:user)
- recipient = insert(:user)
-
- file = %Plug.Upload{
- content_type: "image/jpg",
- path: Path.absname("test/fixtures/image.jpg"),
- filename: "an_image.jpg"
- }
-
- {:ok, upload} = ActivityPub.upload(file, actor: user.ap_id)
- {:ok, activity} = CommonAPI.post_chat_message(user, recipient, "kippis :firefox:")
-
- chat = Chat.get(user.id, recipient.ap_id)
-
- object = Object.normalize(activity)
-
- cm_ref = MessageReference.for_chat_and_object(chat, object)
-
- chat_message = MessageReferenceView.render("show.json", chat_message_reference: cm_ref)
-
- assert chat_message[:id] == cm_ref.id
- assert chat_message[:content] == "kippis :firefox:"
- assert chat_message[:account_id] == user.id
- assert chat_message[:chat_id]
- assert chat_message[:created_at]
- assert chat_message[:unread] == false
- assert match?([%{shortcode: "firefox"}], chat_message[:emojis])
-
- clear_config([:rich_media, :enabled], true)
-
- Tesla.Mock.mock(fn
- %{url: "https://example.com/ogp"} ->
- %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}
- end)
-
- {:ok, activity} =
- CommonAPI.post_chat_message(recipient, user, "gkgkgk https://example.com/ogp",
- media_id: upload.id
- )
-
- object = Object.normalize(activity)
-
- cm_ref = MessageReference.for_chat_and_object(chat, object)
-
- chat_message_two = MessageReferenceView.render("show.json", chat_message_reference: cm_ref)
-
- assert chat_message_two[:id] == cm_ref.id
- assert chat_message_two[:content] == object.data["content"]
- assert chat_message_two[:account_id] == recipient.id
- assert chat_message_two[:chat_id] == chat_message[:chat_id]
- assert chat_message_two[:attachment]
- assert chat_message_two[:unread] == true
- assert chat_message_two[:card]
- end
-end
diff --git a/test/web/pleroma_api/views/scrobble_view_test.exs b/test/web/pleroma_api/views/scrobble_view_test.exs
@@ -1,20 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.PleromaAPI.StatusViewTest do
- use Pleroma.DataCase
-
- alias Pleroma.Web.PleromaAPI.ScrobbleView
-
- import Pleroma.Factory
-
- test "successfully renders a Listen activity (pleroma extension)" do
- listen_activity = insert(:listen)
-
- status = ScrobbleView.render("show.json", activity: listen_activity)
-
- assert status.length == listen_activity.data["object"]["length"]
- assert status.title == listen_activity.data["object"]["title"]
- end
-end
diff --git a/test/web/plugs/federating_plug_test.exs b/test/web/plugs/federating_plug_test.exs
@@ -1,31 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.FederatingPlugTest do
- use Pleroma.Web.ConnCase
-
- setup do: clear_config([:instance, :federating])
-
- test "returns and halt the conn when federating is disabled" do
- Pleroma.Config.put([:instance, :federating], false)
-
- conn =
- build_conn()
- |> Pleroma.Web.FederatingPlug.call(%{})
-
- assert conn.status == 404
- assert conn.halted
- end
-
- test "does nothing when federating is enabled" do
- Pleroma.Config.put([:instance, :federating], true)
-
- conn =
- build_conn()
- |> Pleroma.Web.FederatingPlug.call(%{})
-
- refute conn.status
- refute conn.halted
- end
-end
diff --git a/test/web/plugs/plug_test.exs b/test/web/plugs/plug_test.exs
@@ -1,91 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.PlugTest do
- @moduledoc "Tests for the functionality added via `use Pleroma.Web, :plug`"
-
- alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug
- alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug
- alias Pleroma.Plugs.PlugHelper
-
- import Mock
-
- use Pleroma.Web.ConnCase
-
- describe "when plug is skipped, " do
- setup_with_mocks(
- [
- {ExpectPublicOrAuthenticatedCheckPlug, [:passthrough], []}
- ],
- %{conn: conn}
- ) do
- conn = ExpectPublicOrAuthenticatedCheckPlug.skip_plug(conn)
- %{conn: conn}
- end
-
- test "it neither adds plug to called plugs list nor calls `perform/2`, " <>
- "regardless of :if_func / :unless_func options",
- %{conn: conn} do
- for opts <- [%{}, %{if_func: fn _ -> true end}, %{unless_func: fn _ -> false end}] do
- ret_conn = ExpectPublicOrAuthenticatedCheckPlug.call(conn, opts)
-
- refute called(ExpectPublicOrAuthenticatedCheckPlug.perform(:_, :_))
- refute PlugHelper.plug_called?(ret_conn, ExpectPublicOrAuthenticatedCheckPlug)
- end
- end
- end
-
- describe "when plug is NOT skipped, " do
- setup_with_mocks([{ExpectAuthenticatedCheckPlug, [:passthrough], []}]) do
- :ok
- end
-
- test "with no pre-run checks, adds plug to called plugs list and calls `perform/2`", %{
- conn: conn
- } do
- ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{})
-
- assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_))
- assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
- end
-
- test "when :if_func option is given, calls the plug only if provided function evals tru-ish",
- %{conn: conn} do
- ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{if_func: fn _ -> false end})
-
- refute called(ExpectAuthenticatedCheckPlug.perform(:_, :_))
- refute PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
-
- ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{if_func: fn _ -> true end})
-
- assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_))
- assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
- end
-
- test "if :unless_func option is given, calls the plug only if provided function evals falsy",
- %{conn: conn} do
- ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{unless_func: fn _ -> true end})
-
- refute called(ExpectAuthenticatedCheckPlug.perform(:_, :_))
- refute PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
-
- ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{unless_func: fn _ -> false end})
-
- assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_))
- assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug)
- end
-
- test "allows a plug to be called multiple times (even if it's in called plugs list)", %{
- conn: conn
- } do
- conn = ExpectAuthenticatedCheckPlug.call(conn, %{an_option: :value1})
- assert called(ExpectAuthenticatedCheckPlug.perform(conn, %{an_option: :value1}))
-
- assert PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug)
-
- conn = ExpectAuthenticatedCheckPlug.call(conn, %{an_option: :value2})
- assert called(ExpectAuthenticatedCheckPlug.perform(conn, %{an_option: :value2}))
- end
- end
-end
diff --git a/test/web/rich_media/aws_signed_url_test.exs b/test/web/rich_media/aws_signed_url_test.exs
@@ -1,82 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.RichMedia.TTL.AwsSignedUrlTest do
- use ExUnit.Case, async: true
-
- test "s3 signed url is parsed correct for expiration time" do
- url = "https://pleroma.social/amz"
-
- {:ok, timestamp} =
- Timex.now()
- |> DateTime.truncate(:second)
- |> Timex.format("{ISO:Basic:Z}")
-
- # in seconds
- valid_till = 30
-
- metadata = construct_metadata(timestamp, valid_till, url)
-
- expire_time =
- Timex.parse!(timestamp, "{ISO:Basic:Z}") |> Timex.to_unix() |> Kernel.+(valid_till)
-
- assert {:ok, expire_time} == Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl.ttl(metadata, url)
- end
-
- test "s3 signed url is parsed and correct ttl is set for rich media" do
- url = "https://pleroma.social/amz"
-
- {:ok, timestamp} =
- Timex.now()
- |> DateTime.truncate(:second)
- |> Timex.format("{ISO:Basic:Z}")
-
- # in seconds
- valid_till = 30
-
- metadata = construct_metadata(timestamp, valid_till, url)
-
- body = """
- <meta name="twitter:card" content="Pleroma" />
- <meta name="twitter:site" content="Pleroma" />
- <meta name="twitter:title" content="Pleroma" />
- <meta name="twitter:description" content="Pleroma" />
- <meta name="twitter:image" content="#{Map.get(metadata, :image)}" />
- """
-
- Tesla.Mock.mock(fn
- %{
- method: :get,
- url: "https://pleroma.social/amz"
- } ->
- %Tesla.Env{status: 200, body: body}
- end)
-
- Cachex.put(:rich_media_cache, url, metadata)
-
- Pleroma.Web.RichMedia.Parser.set_ttl_based_on_image(metadata, url)
-
- {:ok, cache_ttl} = Cachex.ttl(:rich_media_cache, url)
-
- # as there is delay in setting and pulling the data from cache we ignore 1 second
- # make it 2 seconds for flakyness
- assert_in_delta(valid_till * 1000, cache_ttl, 2000)
- end
-
- defp construct_s3_url(timestamp, valid_till) do
- "https://pleroma.s3.ap-southeast-1.amazonaws.com/sachin%20%281%29%20_a%20-%25%2Aasdasd%20BNN%20bnnn%20.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIBLWWK6RGDQXDLJQ%2F20190716%2Fap-southeast-1%2Fs3%2Faws4_request&X-Amz-Date=#{
- timestamp
- }&X-Amz-Expires=#{valid_till}&X-Amz-Signature=04ffd6b98634f4b1bbabc62e0fac4879093cd54a6eed24fe8eb38e8369526bbf&X-Amz-SignedHeaders=host"
- end
-
- defp construct_metadata(timestamp, valid_till, url) do
- %{
- image: construct_s3_url(timestamp, valid_till),
- site: "Pleroma",
- title: "Pleroma",
- description: "Pleroma",
- url: url
- }
- end
-end
diff --git a/test/web/rich_media/helpers_test.exs b/test/web/rich_media/helpers_test.exs
@@ -1,86 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.RichMedia.HelpersTest do
- use Pleroma.DataCase
-
- alias Pleroma.Config
- alias Pleroma.Object
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.RichMedia.Helpers
-
- import Pleroma.Factory
- import Tesla.Mock
-
- setup do
- mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
-
- :ok
- end
-
- setup do: clear_config([:rich_media, :enabled])
-
- test "refuses to crawl incomplete URLs" do
- user = insert(:user)
-
- {:ok, activity} =
- CommonAPI.post(user, %{
- status: "[test](example.com/ogp)",
- content_type: "text/markdown"
- })
-
- Config.put([:rich_media, :enabled], true)
-
- assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
- end
-
- test "refuses to crawl malformed URLs" do
- user = insert(:user)
-
- {:ok, activity} =
- CommonAPI.post(user, %{
- status: "[test](example.com[]/ogp)",
- content_type: "text/markdown"
- })
-
- Config.put([:rich_media, :enabled], true)
-
- assert %{} == Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
- end
-
- test "crawls valid, complete URLs" do
- user = insert(:user)
-
- {:ok, activity} =
- CommonAPI.post(user, %{
- status: "[test](https://example.com/ogp)",
- content_type: "text/markdown"
- })
-
- Config.put([:rich_media, :enabled], true)
-
- assert %{page_url: "https://example.com/ogp", rich_media: _} =
- Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
- end
-
- test "refuses to crawl URLs of private network from posts" do
- user = insert(:user)
-
- {:ok, activity} =
- CommonAPI.post(user, %{status: "http://127.0.0.1:4000/notice/9kCP7VNyPJXFOXDrgO"})
-
- {:ok, activity2} = CommonAPI.post(user, %{status: "https://10.111.10.1/notice/9kCP7V"})
- {:ok, activity3} = CommonAPI.post(user, %{status: "https://172.16.32.40/notice/9kCP7V"})
- {:ok, activity4} = CommonAPI.post(user, %{status: "https://192.168.10.40/notice/9kCP7V"})
- {:ok, activity5} = CommonAPI.post(user, %{status: "https://pleroma.local/notice/9kCP7V"})
-
- Config.put([:rich_media, :enabled], true)
-
- assert %{} = Helpers.fetch_data_for_activity(activity)
- assert %{} = Helpers.fetch_data_for_activity(activity2)
- assert %{} = Helpers.fetch_data_for_activity(activity3)
- assert %{} = Helpers.fetch_data_for_activity(activity4)
- assert %{} = Helpers.fetch_data_for_activity(activity5)
- end
-end