Merge branch 'automatic-authentication-and-instance-publicity-checks' into 'develop'

Automatic checks of authentication / authorization / instance publicity

See merge request 
This commit is contained in:
lain 2020-04-29 11:09:30 +00:00
commit 58fded9858
55 changed files with 829 additions and 408 deletions
docs
lib/pleroma
test

23
docs/dev.md Normal file
View file

@ -0,0 +1,23 @@
This document contains notes and guidelines for Pleroma developers.
# Authentication & Authorization
## OAuth token-based authentication & authorization
* 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 ...>)`.
* 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.
For routes with `:authenticated_api` pipeline, authentication & authorization are expected, thus `OAuthScopesPlug` will be run unless explicitly skipped (also `EnsureAuthenticatedPlug` will be executed immediately before action even if there was an early run to give an early error, since `OAuthScopesPlug` supports `:proceed_unauthenticated` option, and other plugs may support similar options as well).
For `:api` pipeline routes, it'll be verified whether `OAuthScopesPlug` was called or explicitly skipped, and if it was not then auth information will be dropped for request. Then `EnsurePublicOrAuthenticatedPlug` will be called to ensure that either the instance is not private or user is authenticated (unless explicitly skipped). Such automated checks help to prevent human errors and result in higher security / privacy for users.
## [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.
## Auth-related configuration, OAuth consumer mode etc.
See `Authentication` section of [`docs/configuration/cheatsheet.md`](docs/configuration/cheatsheet.md#authentication).

View file

@ -1,17 +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.AuthExpectedPlug do
import Plug.Conn
def init(options), do: options
def call(conn, _) do
put_private(conn, :auth_expected, true)
end
def auth_expected?(conn) do
conn.private[:auth_expected]
end
end

View file

@ -5,17 +5,21 @@
defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do defmodule Pleroma.Plugs.EnsureAuthenticatedPlug do
import Plug.Conn import Plug.Conn
import Pleroma.Web.TranslationHelpers import Pleroma.Web.TranslationHelpers
alias Pleroma.User alias Pleroma.User
use Pleroma.Web, :plug
def init(options) do def init(options) do
options options
end end
def call(%{assigns: %{user: %User{}}} = conn, _) do @impl true
def perform(%{assigns: %{user: %User{}}} = conn, _) do
conn conn
end end
def call(conn, options) do def perform(conn, options) do
perform = perform =
cond do cond do
options[:if_func] -> options[:if_func].() options[:if_func] -> options[:if_func].()

View file

@ -5,14 +5,18 @@
defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do defmodule Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug do
import Pleroma.Web.TranslationHelpers import Pleroma.Web.TranslationHelpers
import Plug.Conn import Plug.Conn
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.User alias Pleroma.User
use Pleroma.Web, :plug
def init(options) do def init(options) do
options options
end end
def call(conn, _) do @impl true
def perform(conn, _) do
public? = Config.get!([:instance, :public]) public? = Config.get!([:instance, :public])
case {public?, conn} do case {public?, conn} do

View file

@ -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.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

View file

@ -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.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

View file

@ -7,15 +7,12 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.PlugHelper
use Pleroma.Web, :plug use Pleroma.Web, :plug
@behaviour Plug
def init(%{scopes: _} = options), do: options def init(%{scopes: _} = options), do: options
@impl true
def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
op = options[:op] || :| op = options[:op] || :|
token = assigns[:token] token = assigns[:token]
@ -31,10 +28,7 @@ def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
conn conn
options[:fallback] == :proceed_unauthenticated -> options[:fallback] == :proceed_unauthenticated ->
conn drop_auth_info(conn)
|> assign(:user, nil)
|> assign(:token, nil)
|> maybe_perform_instance_privacy_check(options)
true -> true ->
missing_scopes = scopes -- matched_scopes missing_scopes = scopes -- matched_scopes
@ -50,6 +44,15 @@ def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
end end
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 "Filters descendants of supported scopes" @doc "Filters descendants of supported scopes"
def filter_descendants(scopes, supported_scopes) do def filter_descendants(scopes, supported_scopes) do
Enum.filter( Enum.filter(
@ -71,12 +74,4 @@ def transform_scopes(scopes, options) do
scopes scopes
end end
end end
defp maybe_perform_instance_privacy_check(%Plug.Conn{} = conn, options) do
if options[:skip_instance_privacy_check] do
conn
else
EnsurePublicOrAuthenticatedPlug.call(conn, [])
end
end
end end

View file

@ -0,0 +1,93 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
# A test controller reachable only in :test env.
defmodule Pleroma.Tests.AuthTestController do
@moduledoc false
use Pleroma.Web, :controller
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
# Serves only with proper OAuth token (:api and :authenticated_api)
# Skipping EnsurePublicOrAuthenticatedPlug has no effect in this case
#
# Suggested use case: all :authenticated_api endpoints (makes no sense for :api endpoints)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :do_oauth_check)
# Via :api, keeps :user if token has requested scopes (if :user is dropped, serves if public)
# Via :authenticated_api, serves if token is present and has requested scopes
#
# Suggested use case: vast majority of :api endpoints (no sense for :authenticated_api ones)
plug(
OAuthScopesPlug,
%{scopes: ["read"], fallback: :proceed_unauthenticated}
when action == :fallback_oauth_check
)
# Keeps :user if present, executes regardless of token / token scopes
# Fails with no :user for :authenticated_api / no user for :api on private instance
# Note: EnsurePublicOrAuthenticatedPlug is not skipped (private instance fails on no :user)
# Note: Basic Auth processing results in :skip_plug call for OAuthScopesPlug
#
# Suggested use: suppressing OAuth checks for other auth mechanisms (like Basic Auth)
# For controller-level use, see :skip_oauth_skip_publicity_check instead
plug(
:skip_plug,
OAuthScopesPlug when action == :skip_oauth_check
)
# (Shouldn't be executed since the plug is skipped)
plug(OAuthScopesPlug, %{scopes: ["admin"]} when action == :skip_oauth_check)
# Via :api, keeps :user if token has requested scopes, and continues with nil :user otherwise
# Via :authenticated_api, serves if token is present and has requested scopes
#
# Suggested use: as :fallback_oauth_check but open with nil :user for :api on private instances
plug(
:skip_plug,
EnsurePublicOrAuthenticatedPlug when action == :fallback_oauth_skip_publicity_check
)
plug(
OAuthScopesPlug,
%{scopes: ["read"], fallback: :proceed_unauthenticated}
when action == :fallback_oauth_skip_publicity_check
)
# Via :api, keeps :user if present, serves regardless of token presence / scopes / :user presence
# Via :authenticated_api, serves if :user is set (regardless of token presence and its scopes)
#
# Suggested use: making an :api endpoint always accessible (e.g. email confirmation endpoint)
plug(
:skip_plug,
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug]
when action == :skip_oauth_skip_publicity_check
)
# Via :authenticated_api, always fails with 403 (endpoint is insecure)
# Via :api, drops :user if present and serves if public (private instance rejects on no user)
#
# Suggested use: none; please define OAuth rules for all :api / :authenticated_api endpoints
plug(:skip_plug, [] when action == :missing_oauth_check_definition)
def do_oauth_check(conn, _params), do: conn_state(conn)
def fallback_oauth_check(conn, _params), do: conn_state(conn)
def skip_oauth_check(conn, _params), do: conn_state(conn)
def fallback_oauth_skip_publicity_check(conn, _params), do: conn_state(conn)
def skip_oauth_skip_publicity_check(conn, _params), do: conn_state(conn)
def missing_oauth_check_definition(conn, _params), do: conn_state(conn)
defp conn_state(%{assigns: %{user: %User{} = user}} = conn),
do: json(conn, %{user_id: user.id})
defp conn_state(conn), do: json(conn, %{user_id: nil})
end

View file

@ -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
# A test controller reachable only in :test env.
# Serves to test OAuth scopes check skipping / enforcement.
defmodule Pleroma.Tests.OAuthTestController do
@moduledoc false
use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
plug(:skip_plug, OAuthScopesPlug when action == :skipped_oauth)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action != :missed_oauth)
def skipped_oauth(conn, _params) do
noop(conn)
end
def performed_oauth(conn, _params) do
noop(conn)
end
def missed_oauth(conn, _params) do
noop(conn)
end
defp noop(conn), do: json(conn, %{})
end

View file

@ -48,6 +48,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
%{scopes: ["write:accounts"], admin: true} %{scopes: ["write:accounts"], admin: true}
when action in [ when action in [
:get_password_reset, :get_password_reset,
:force_password_reset,
:user_delete, :user_delete,
:users_create, :users_create,
:user_toggle_activation, :user_toggle_activation,
@ -56,7 +57,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
:tag_users, :tag_users,
:untag_users, :untag_users,
:right_add, :right_add,
:right_add_multiple,
:right_delete, :right_delete,
:right_delete_multiple,
:update_user_credentials :update_user_credentials
] ]
) )
@ -84,13 +87,13 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["write:reports"], admin: true} %{scopes: ["write:reports"], admin: true}
when action in [:reports_update] when action in [:reports_update, :report_notes_create, :report_notes_delete]
) )
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read:statuses"], admin: true} %{scopes: ["read:statuses"], admin: true}
when action == :list_user_statuses when action in [:list_statuses, :list_user_statuses, :list_instance_statuses]
) )
plug( plug(
@ -102,13 +105,30 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read"], admin: true} %{scopes: ["read"], admin: true}
when action in [:config_show, :list_log, :stats] when action in [
:config_show,
:list_log,
:stats,
:relay_list,
:config_descriptions,
:need_reboot
]
) )
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["write"], admin: true} %{scopes: ["write"], admin: true}
when action == :config_update when action in [
:restart,
:config_update,
:resend_confirmation_email,
:confirm_email,
:oauth_app_create,
:oauth_app_list,
:oauth_app_update,
:oauth_app_delete,
:reload_emoji
]
) )
action_fallback(:errors) action_fallback(:errors)
@ -1103,25 +1123,25 @@ def stats(conn, _) do
|> json(%{"status_visibility" => count}) |> json(%{"status_visibility" => count})
end end
def errors(conn, {:error, :not_found}) do defp errors(conn, {:error, :not_found}) do
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(dgettext("errors", "Not found")) |> json(dgettext("errors", "Not found"))
end end
def errors(conn, {:error, reason}) do defp errors(conn, {:error, reason}) do
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(reason) |> json(reason)
end end
def errors(conn, {:param_cast, _}) do defp errors(conn, {:param_cast, _}) do
conn conn
|> put_status(:bad_request) |> put_status(:bad_request)
|> json(dgettext("errors", "Invalid parameters")) |> json(dgettext("errors", "Invalid parameters"))
end end
def errors(conn, _) do defp errors(conn, _) do
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(dgettext("errors", "Something went wrong")) |> json(dgettext("errors", "Something went wrong"))

View file

@ -294,13 +294,13 @@ def unblock_operation do
} }
end end
def follows_operation do def follow_by_uri_operation do
%Operation{ %Operation{
tags: ["accounts"], tags: ["accounts"],
summary: "Follows", summary: "Follow by URI",
operationId: "AccountController.follows", operationId: "AccountController.follows",
security: [%{"oAuth" => ["follow", "write:follows"]}], security: [%{"oAuth" => ["follow", "write:follows"]}],
requestBody: request_body("Parameters", follows_request(), required: true), requestBody: request_body("Parameters", follow_by_uri_request(), required: true),
responses: %{ responses: %{
200 => Operation.response("Account", "application/json", AccountRelationship), 200 => Operation.response("Account", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError), 400 => Operation.response("Error", "application/json", ApiError),
@ -615,7 +615,7 @@ defp array_of_relationships do
} }
end end
defp follows_request do defp follow_by_uri_request do
%Schema{ %Schema{
title: "AccountFollowsRequest", title: "AccountFollowsRequest",
description: "POST body for muting an account", description: "POST body for muting an account",

View file

@ -4,7 +4,9 @@
defmodule Fallback.RedirectController do defmodule Fallback.RedirectController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
require Logger require Logger
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.Metadata alias Pleroma.Web.Metadata

View file

@ -5,19 +5,25 @@
defmodule Pleroma.Web.MastoFEController do defmodule Pleroma.Web.MastoFEController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
# Note: :index action handles attempt of unauthenticated access to private instance with redirect # Note: :index action handles attempt of unauthenticated access to private instance with redirect
plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action == :index)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read"], fallback: :proceed_unauthenticated, skip_instance_privacy_check: true} %{scopes: ["read"], fallback: :proceed_unauthenticated}
when action == :index when action == :index
) )
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action not in [:index, :manifest]) plug(
:skip_plug,
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :manifest
)
@doc "GET /web/*path" @doc "GET /web/*path"
def index(%{assigns: %{user: user, token: token}} = conn, _params) def index(%{assigns: %{user: user, token: token}} = conn, _params)

View file

@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
skip_relationships?: 1 skip_relationships?: 1
] ]
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
alias Pleroma.User alias Pleroma.User
@ -28,18 +29,26 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError)
plug(:skip_plug, OAuthScopesPlug when action == :identity_proofs) plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)
plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:show, :statuses])
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]} %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
when action == :show when action in [:show, :followers, :following]
)
plug(
OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["read:statuses"]}
when action == :statuses
) )
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["read:accounts"]} %{scopes: ["read:accounts"]}
when action in [:endorsements, :verify_credentials, :followers, :following] when action in [:verify_credentials, :endorsements, :identity_proofs]
) )
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials)
@ -58,21 +67,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships) plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships)
# Note: :follows (POST /api/v1/follows) is the same as :follow, consider removing :follows
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]} when action in [:follows, :follow, :unfollow] %{scopes: ["follow", "write:follows"]} when action in [:follow_by_uri, :follow, :unfollow]
) )
plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes) plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute]) plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
plug(
Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
when action not in [:create, :show, :statuses]
)
@relationship_actions [:follow, :unfollow] @relationship_actions [:follow, :unfollow]
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a @needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
@ -378,7 +381,7 @@ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
end end
@doc "POST /api/v1/follows" @doc "POST /api/v1/follows"
def follows(%{body_params: %{uri: uri}} = conn, _) do def follow_by_uri(%{body_params: %{uri: uri}} = conn, _) do
case User.get_cached_by_nickname(uri) do case User.get_cached_by_nickname(uri) do
%User{} = user -> %User{} = user ->
conn conn

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.AppController do defmodule Pleroma.Web.MastodonAPI.AppController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.App
@ -13,7 +14,14 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(
:skip_plug,
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug]
when action == :create
)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials) plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
plug(OpenApiSpex.Plug.CastAndValidate) plug(OpenApiSpex.Plug.CastAndValidate)
@local_mastodon_name "Mastodon-Local" @local_mastodon_name "Mastodon-Local"

View file

@ -13,10 +13,10 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@local_mastodon_name "Mastodon-Local"
plug(Pleroma.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset) plug(Pleroma.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)
@local_mastodon_name "Mastodon-Local"
@doc "GET /web/login" @doc "GET /web/login"
def login(%{assigns: %{user: %User{}}} = conn, _params) do def login(%{assigns: %{user: %User{}}} = conn, _params) do
redirect(conn, to: local_mastodon_root_path(conn)) redirect(conn, to: local_mastodon_root_path(conn))

View file

@ -14,9 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :index) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :index)
plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action == :read) plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action != :index)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/conversations" @doc "GET /api/v1/conversations"
def index(%{assigns: %{user: user}} = conn, params) do def index(%{assigns: %{user: user}} = conn, params) do
@ -28,7 +26,7 @@ def index(%{assigns: %{user: user}} = conn, params) do
end end
@doc "POST /api/v1/conversations/:id/read" @doc "POST /api/v1/conversations/:id/read"
def read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do def mark_as_read(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
with %Participation{} = participation <- with %Participation{} = participation <-
Repo.get_by(Participation, id: participation_id, user_id: user.id), Repo.get_by(Participation, id: participation_id, user_id: user.id),
{:ok, participation} <- Participation.mark_as_read(participation) do {:ok, participation} <- Participation.mark_as_read(participation) do

View file

@ -7,6 +7,12 @@ defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do
plug(OpenApiSpex.Plug.CastAndValidate) plug(OpenApiSpex.Plug.CastAndValidate)
plug(
:skip_plug,
[Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
when action == :index
)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.CustomEmojiOperation defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.CustomEmojiOperation
def index(conn, _params) do def index(conn, _params) do

View file

@ -21,8 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
%{scopes: ["follow", "write:blocks"]} when action != :index %{scopes: ["follow", "write:blocks"]} when action != :index
) )
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/domain_blocks" @doc "GET /api/v1/domain_blocks"
def index(%{assigns: %{user: user}} = conn, _) do def index(%{assigns: %{user: user}} = conn, _) do
json(conn, Map.get(user, :domain_blocks, [])) json(conn, Map.get(user, :domain_blocks, []))

View file

@ -17,8 +17,6 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
%{scopes: ["write:filters"]} when action not in @oauth_read_actions %{scopes: ["write:filters"]} when action not in @oauth_read_actions
) )
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/filters" @doc "GET /api/v1/filters"
def index(%{assigns: %{user: user}} = conn, _) do def index(%{assigns: %{user: user}} = conn, _) do
filters = Filter.get_filters(user) filters = Filter.get_filters(user)

View file

@ -21,8 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
%{scopes: ["follow", "write:follows"]} when action != :index %{scopes: ["follow", "write:follows"]} when action != :index
) )
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/follow_requests" @doc "GET /api/v1/follow_requests"
def index(%{assigns: %{user: followed}} = conn, _params) do def index(%{assigns: %{user: followed}} = conn, _params) do
follow_requests = User.get_follow_requests(followed) follow_requests = User.get_follow_requests(followed)

View file

@ -5,6 +5,12 @@
defmodule Pleroma.Web.MastodonAPI.InstanceController do defmodule Pleroma.Web.MastodonAPI.InstanceController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
plug(
:skip_plug,
[Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
when action in [:show, :peers]
)
@doc "GET /api/v1/instance" @doc "GET /api/v1/instance"
def show(conn, _params) do def show(conn, _params) do
render(conn, "show.json") render(conn, "show.json")

View file

@ -11,16 +11,16 @@ defmodule Pleroma.Web.MastodonAPI.ListController do
plug(:list_by_id_and_user when action not in [:index, :create]) plug(:list_by_id_and_user when action not in [:index, :create])
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in [:index, :show, :list_accounts]) @oauth_read_actions [:index, :show, :list_accounts]
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in @oauth_read_actions)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["write:lists"]} %{scopes: ["write:lists"]}
when action in [:create, :update, :delete, :add_to_list, :remove_from_list] when action not in @oauth_read_actions
) )
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
# GET /api/v1/lists # GET /api/v1/lists

View file

@ -13,7 +13,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do
) )
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :upsert) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :upsert)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
# GET /api/v1/markers # GET /api/v1/markers

View file

@ -15,9 +15,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
require Logger require Logger
plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug when action in [:empty_array, :empty_object]) plug(
:skip_plug,
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
when action in [:empty_array, :empty_object]
)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)

View file

@ -15,8 +15,6 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
plug(OAuthScopesPlug, %{scopes: ["write:media"]}) plug(OAuthScopesPlug, %{scopes: ["write:media"]})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "POST /api/v1/media" @doc "POST /api/v1/media"
def create(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do def create(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
with {:ok, object} <- with {:ok, object} <-

View file

@ -20,8 +20,6 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action not in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action not in @oauth_read_actions)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
# GET /api/v1/notifications # GET /api/v1/notifications
def index(conn, %{"account_id" => account_id} = params) do def index(conn, %{"account_id" => account_id} = params) do
case Pleroma.User.get_cached_by_id(account_id) do case Pleroma.User.get_cached_by_id(account_id) do

View file

@ -22,8 +22,6 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/polls/:id" @doc "GET /api/v1/polls/:id"
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),

View file

@ -11,8 +11,6 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do
plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create) plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "POST /api/v1/reports" @doc "POST /api/v1/reports"
def create(%{assigns: %{user: user}} = conn, params) do def create(%{assigns: %{user: user}} = conn, params) do
with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do

View file

@ -18,8 +18,6 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in @oauth_read_actions)
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions) plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action not in @oauth_read_actions)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@doc "GET /api/v1/scheduled_statuses" @doc "GET /api/v1/scheduled_statuses"

View file

@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
# Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search) # Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search)
plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated}) plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) # Note: on private instances auth is required (EnsurePublicOrAuthenticatedPlug is not skipped)
plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search]) plug(RateLimiter, [name: :search] when action in [:search, :search2, :account_search])

View file

@ -24,6 +24,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.ScheduledActivityView alias Pleroma.Web.MastodonAPI.ScheduledActivityView
plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show])
@unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
plug( plug(
@ -77,8 +79,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
%{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark] %{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark]
) )
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action not in [:index, :show])
@rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a
plug( plug(
@ -358,7 +358,7 @@ def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end end
@doc "GET /api/v1/favourites" @doc "GET /api/v1/favourites"
def favourites(%{assigns: %{user: user}} = conn, params) do def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do
activities = activities =
ActivityPub.fetch_favourites( ActivityPub.fetch_favourites(
user, user,

View file

@ -12,7 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
action_fallback(:errors) action_fallback(:errors)
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]}) plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
plug(:restrict_push_enabled) plug(:restrict_push_enabled)
# Creates PushSubscription # Creates PushSubscription

View file

@ -9,11 +9,14 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1] only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1]
alias Pleroma.Pagination alias Pleroma.Pagination
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action in [:public, :hashtag])
# TODO: Replace with a macro when there is a Phoenix release with the following commit in it: # TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
# https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
@ -26,7 +29,11 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct]) plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct])
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :public) plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated}
when action in [:public, :hashtag]
)
plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
@ -94,7 +101,9 @@ def public(%{assigns: %{user: user}} = conn, params) do
restrict? = Pleroma.Config.get([:restrict_unauthenticated, :timelines, cfg_key]) restrict? = Pleroma.Config.get([:restrict_unauthenticated, :timelines, cfg_key])
if not (restrict? and is_nil(user)) do if restrict? and is_nil(user) do
render_error(conn, :unauthorized, "authorization required for timeline view")
else
activities = activities =
params params
|> Map.put("type", ["Create", "Announce"]) |> Map.put("type", ["Create", "Announce"])
@ -112,12 +121,10 @@ def public(%{assigns: %{user: user}} = conn, params) do
as: :activity, as: :activity,
skip_relationships: skip_relationships?(params) skip_relationships: skip_relationships?(params)
) )
else
render_error(conn, :unauthorized, "authorization required for timeline view")
end end
end end
def hashtag_fetching(params, user, local_only) do defp hashtag_fetching(params, user, local_only) do
tags = tags =
[params["tag"], params["any"]] [params["tag"], params["any"]]
|> List.flatten() |> List.flatten()

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Web.MediaProxy.MediaProxyController do defmodule Pleroma.Web.MediaProxy.MediaProxyController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.ReverseProxy alias Pleroma.ReverseProxy
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy

View file

@ -25,9 +25,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
plug(:fetch_session) plug(:fetch_session)
plug(:fetch_flash) plug(:fetch_flash)
plug(RateLimiter, [name: :authentication] when action == :create_authorization)
plug(:skip_plug, Pleroma.Plugs.OAuthScopesPlug) plug(:skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug])
plug(RateLimiter, [name: :authentication] when action == :create_authorization)
action_fallback(Pleroma.Web.OAuth.FallbackController) action_fallback(Pleroma.Web.OAuth.FallbackController)

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1] only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2, skip_relationships?: 1]
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
alias Pleroma.User alias Pleroma.User
@ -17,6 +18,11 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
require Pleroma.Constants require Pleroma.Constants
plug(
:skip_plug,
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirmation_resend
)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe] %{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe]
@ -33,15 +39,13 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
] ]
) )
plug(OAuthScopesPlug, %{scopes: ["read:favourites"]} when action == :favourites)
# An extra safety measure for possible actions not guarded by OAuth permissions specification
plug( plug(
Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug OAuthScopesPlug,
when action != :confirmation_resend %{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
) )
plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend) plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe]) plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)

View file

@ -1,6 +1,7 @@
defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
require Logger require Logger
@ -11,17 +12,20 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
when action in [ when action in [
:create, :create,
:delete, :delete,
:download_from, :save_from,
:list_from,
:import_from_fs, :import_from_fs,
:update_file, :update_file,
:update_metadata :update_metadata
] ]
) )
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) plug(
:skip_plug,
[OAuthScopesPlug, ExpectPublicOrAuthenticatedCheckPlug]
when action in [:download_shared, :list_packs, :list_from]
)
def emoji_dir_path do defp emoji_dir_path do
Path.join( Path.join(
Pleroma.Config.get!([:instance, :static_dir]), Pleroma.Config.get!([:instance, :static_dir]),
"emoji" "emoji"
@ -212,13 +216,13 @@ defp shareable_packs_available(address) do
end end
@doc """ @doc """
An admin endpoint to request downloading a pack named `pack_name` from the instance An admin endpoint to request downloading and storing a pack named `pack_name` from the instance
`instance_address`. `instance_address`.
If the requested instance's admin chose to share the pack, it will be downloaded If the requested instance's admin chose to share the pack, it will be downloaded
from that instance, otherwise it will be downloaded from the fallback source, if there is one. from that instance, otherwise it will be downloaded from the fallback source, if there is one.
""" """
def download_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do def save_from(conn, %{"instance_address" => address, "pack_name" => name} = data) do
address = String.trim(address) address = String.trim(address)
if shareable_packs_available(address) do if shareable_packs_available(address) do

View file

@ -12,8 +12,6 @@ defmodule Pleroma.Web.PleromaAPI.MascotController do
plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show) plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action == :show)
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show) plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action != :show)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/pleroma/mascot" @doc "GET /api/v1/pleroma/mascot"
def show(%{assigns: %{user: user}} = conn, _params) do def show(%{assigns: %{user: user}} = conn, _params) do
json(conn, User.get_mascot(user)) json(conn, User.get_mascot(user))

View file

@ -26,6 +26,12 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
when action in [:conversation, :conversation_statuses] when action in [:conversation, :conversation_statuses]
) )
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated}
when action == :emoji_reactions_by
)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["write:statuses"]} %{scopes: ["write:statuses"]}
@ -34,12 +40,14 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["write:conversations"]} when action in [:update_conversation, :read_conversations] %{scopes: ["write:conversations"]}
when action in [:update_conversation, :mark_conversations_as_read]
) )
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :read_notification) plug(
OAuthScopesPlug,
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) %{scopes: ["write:notifications"]} when action == :mark_notifications_as_read
)
def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} = params) do def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id} = params) do
with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id), with %Activity{} = activity <- Activity.get_by_id_with_object(activity_id),
@ -167,7 +175,7 @@ def update_conversation(
end end
end end
def read_conversations(%{assigns: %{user: user}} = conn, _params) do def mark_conversations_as_read(%{assigns: %{user: user}} = conn, _params) do
with {:ok, _, participations} <- Participation.mark_all_as_read(user) do with {:ok, _, participations} <- Participation.mark_all_as_read(user) do
conn conn
|> add_link_headers(participations) |> add_link_headers(participations)
@ -176,7 +184,7 @@ def read_conversations(%{assigns: %{user: user}} = conn, _params) do
end end
end end
def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do
with {:ok, notification} <- Notification.read_one(user, notification_id) do with {:ok, notification} <- Notification.read_one(user, notification_id) do
conn conn
|> put_view(NotificationView) |> put_view(NotificationView)
@ -189,7 +197,7 @@ def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_i
end end
end end
def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do def mark_notifications_as_read(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do
with notifications <- Notification.set_read_up_to(user, max_id) do with notifications <- Notification.set_read_up_to(user, max_id) do
notifications = Enum.take(notifications, 80) notifications = Enum.take(notifications, 80)

View file

@ -13,10 +13,12 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :user_scrobbles) plug(
plug(OAuthScopesPlug, %{scopes: ["write"]} when action != :user_scrobbles) OAuthScopesPlug,
%{scopes: ["read"], fallback: :proceed_unauthenticated} when action == :user_scrobbles
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) plug(OAuthScopesPlug, %{scopes: ["write"]} when action != :user_scrobbles)
def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do
params = params =

View file

@ -16,6 +16,14 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.UserEnabledPlug) plug(Pleroma.Plugs.UserEnabledPlug)
end end
pipeline :expect_authentication do
plug(Pleroma.Plugs.ExpectAuthenticatedCheckPlug)
end
pipeline :expect_public_instance_or_authentication do
plug(Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug)
end
pipeline :authenticate do pipeline :authenticate do
plug(Pleroma.Plugs.OAuthPlug) plug(Pleroma.Plugs.OAuthPlug)
plug(Pleroma.Plugs.BasicAuthDecoderPlug) plug(Pleroma.Plugs.BasicAuthDecoderPlug)
@ -39,20 +47,22 @@ defmodule Pleroma.Web.Router do
end end
pipeline :api do pipeline :api do
plug(:expect_public_instance_or_authentication)
plug(:base_api) plug(:base_api)
plug(:after_auth) plug(:after_auth)
plug(Pleroma.Plugs.IdempotencyPlug) plug(Pleroma.Plugs.IdempotencyPlug)
end end
pipeline :authenticated_api do pipeline :authenticated_api do
plug(:expect_authentication)
plug(:base_api) plug(:base_api)
plug(Pleroma.Plugs.AuthExpectedPlug)
plug(:after_auth) plug(:after_auth)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug) plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
plug(Pleroma.Plugs.IdempotencyPlug) plug(Pleroma.Plugs.IdempotencyPlug)
end end
pipeline :admin_api do pipeline :admin_api do
plug(:expect_authentication)
plug(:base_api) plug(:base_api)
plug(Pleroma.Plugs.AdminSecretAuthenticationPlug) plug(Pleroma.Plugs.AdminSecretAuthenticationPlug)
plug(:after_auth) plug(:after_auth)
@ -200,24 +210,28 @@ defmodule Pleroma.Web.Router do
end end
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
# Modifying packs
scope "/packs" do scope "/packs" do
# Modifying packs
pipe_through(:admin_api) pipe_through(:admin_api)
post("/import_from_fs", EmojiAPIController, :import_from_fs) post("/import_from_fs", EmojiAPIController, :import_from_fs)
post("/:pack_name/update_file", EmojiAPIController, :update_file) post("/:pack_name/update_file", EmojiAPIController, :update_file)
post("/:pack_name/update_metadata", EmojiAPIController, :update_metadata) post("/:pack_name/update_metadata", EmojiAPIController, :update_metadata)
put("/:name", EmojiAPIController, :create) put("/:name", EmojiAPIController, :create)
delete("/:name", EmojiAPIController, :delete) delete("/:name", EmojiAPIController, :delete)
post("/download_from", EmojiAPIController, :download_from)
post("/list_from", EmojiAPIController, :list_from) # Note: /download_from downloads and saves to instance, not to requester
post("/download_from", EmojiAPIController, :save_from)
end end
# Pack info / downloading
scope "/packs" do scope "/packs" do
# Pack info / downloading
get("/", EmojiAPIController, :list_packs) get("/", EmojiAPIController, :list_packs)
get("/:name/download_shared/", EmojiAPIController, :download_shared) get("/:name/download_shared/", EmojiAPIController, :download_shared)
get("/list_from", EmojiAPIController, :list_from)
# Deprecated: POST /api/pleroma/emoji/packs/list_from (use GET instead)
post("/list_from", EmojiAPIController, :list_from)
end end
end end
@ -277,7 +291,7 @@ defmodule Pleroma.Web.Router do
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses) get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
get("/conversations/:id", PleromaAPIController, :conversation) get("/conversations/:id", PleromaAPIController, :conversation)
post("/conversations/read", PleromaAPIController, :read_conversations) post("/conversations/read", PleromaAPIController, :mark_conversations_as_read)
end end
scope [] do scope [] do
@ -286,7 +300,7 @@ defmodule Pleroma.Web.Router do
patch("/conversations/:id", PleromaAPIController, :update_conversation) patch("/conversations/:id", PleromaAPIController, :update_conversation)
put("/statuses/:id/reactions/:emoji", PleromaAPIController, :react_with_emoji) put("/statuses/:id/reactions/:emoji", PleromaAPIController, :react_with_emoji)
delete("/statuses/:id/reactions/:emoji", PleromaAPIController, :unreact_with_emoji) delete("/statuses/:id/reactions/:emoji", PleromaAPIController, :unreact_with_emoji)
post("/notifications/read", PleromaAPIController, :read_notification) post("/notifications/read", PleromaAPIController, :mark_notifications_as_read)
patch("/accounts/update_avatar", AccountController, :update_avatar) patch("/accounts/update_avatar", AccountController, :update_avatar)
patch("/accounts/update_banner", AccountController, :update_banner) patch("/accounts/update_banner", AccountController, :update_banner)
@ -322,53 +336,84 @@ defmodule Pleroma.Web.Router do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
get("/accounts/verify_credentials", AccountController, :verify_credentials) get("/accounts/verify_credentials", AccountController, :verify_credentials)
patch("/accounts/update_credentials", AccountController, :update_credentials)
get("/accounts/relationships", AccountController, :relationships) get("/accounts/relationships", AccountController, :relationships)
get("/accounts/:id/lists", AccountController, :lists) get("/accounts/:id/lists", AccountController, :lists)
get("/accounts/:id/identity_proofs", AccountController, :identity_proofs) get("/accounts/:id/identity_proofs", AccountController, :identity_proofs)
get("/endorsements", AccountController, :endorsements)
get("/follow_requests", FollowRequestController, :index)
get("/blocks", AccountController, :blocks) get("/blocks", AccountController, :blocks)
get("/mutes", AccountController, :mutes) get("/mutes", AccountController, :mutes)
get("/timelines/home", TimelineController, :home) post("/follows", AccountController, :follow_by_uri)
get("/timelines/direct", TimelineController, :direct) post("/accounts/:id/follow", AccountController, :follow)
post("/accounts/:id/unfollow", AccountController, :unfollow)
post("/accounts/:id/block", AccountController, :block)
post("/accounts/:id/unblock", AccountController, :unblock)
post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute)
get("/favourites", StatusController, :favourites) get("/apps/verify_credentials", AppController, :verify_credentials)
get("/bookmarks", StatusController, :bookmarks)
get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :mark_as_read)
get("/domain_blocks", DomainBlockController, :index)
post("/domain_blocks", DomainBlockController, :create)
delete("/domain_blocks", DomainBlockController, :delete)
get("/filters", FilterController, :index)
post("/filters", FilterController, :create)
get("/filters/:id", FilterController, :show)
put("/filters/:id", FilterController, :update)
delete("/filters/:id", FilterController, :delete)
get("/follow_requests", FollowRequestController, :index)
post("/follow_requests/:id/authorize", FollowRequestController, :authorize)
post("/follow_requests/:id/reject", FollowRequestController, :reject)
get("/lists", ListController, :index)
get("/lists/:id", ListController, :show)
get("/lists/:id/accounts", ListController, :list_accounts)
delete("/lists/:id", ListController, :delete)
post("/lists", ListController, :create)
put("/lists/:id", ListController, :update)
post("/lists/:id/accounts", ListController, :add_to_list)
delete("/lists/:id/accounts", ListController, :remove_from_list)
get("/markers", MarkerController, :index)
post("/markers", MarkerController, :upsert)
post("/media", MediaController, :create)
put("/media/:id", MediaController, :update)
get("/notifications", NotificationController, :index) get("/notifications", NotificationController, :index)
get("/notifications/:id", NotificationController, :show) get("/notifications/:id", NotificationController, :show)
post("/notifications/:id/dismiss", NotificationController, :dismiss) post("/notifications/:id/dismiss", NotificationController, :dismiss)
post("/notifications/clear", NotificationController, :clear) post("/notifications/clear", NotificationController, :clear)
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
# Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead # Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead
post("/notifications/dismiss", NotificationController, :dismiss) post("/notifications/dismiss", NotificationController, :dismiss)
post("/polls/:id/votes", PollController, :vote)
post("/reports", ReportController, :create)
get("/scheduled_statuses", ScheduledActivityController, :index) get("/scheduled_statuses", ScheduledActivityController, :index)
get("/scheduled_statuses/:id", ScheduledActivityController, :show) get("/scheduled_statuses/:id", ScheduledActivityController, :show)
get("/lists", ListController, :index) put("/scheduled_statuses/:id", ScheduledActivityController, :update)
get("/lists/:id", ListController, :show) delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
get("/lists/:id/accounts", ListController, :list_accounts)
get("/domain_blocks", DomainBlockController, :index) # Unlike `GET /api/v1/accounts/:id/favourites`, demands authentication
get("/favourites", StatusController, :favourites)
get("/filters", FilterController, :index) get("/bookmarks", StatusController, :bookmarks)
get("/suggestions", SuggestionController, :index)
get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :read)
get("/endorsements", AccountController, :endorsements)
patch("/accounts/update_credentials", AccountController, :update_credentials)
post("/statuses", StatusController, :create) post("/statuses", StatusController, :create)
delete("/statuses/:id", StatusController, :delete) delete("/statuses/:id", StatusController, :delete)
post("/statuses/:id/reblog", StatusController, :reblog) post("/statuses/:id/reblog", StatusController, :reblog)
post("/statuses/:id/unreblog", StatusController, :unreblog) post("/statuses/:id/unreblog", StatusController, :unreblog)
post("/statuses/:id/favourite", StatusController, :favourite) post("/statuses/:id/favourite", StatusController, :favourite)
@ -380,49 +425,16 @@ defmodule Pleroma.Web.Router do
post("/statuses/:id/mute", StatusController, :mute_conversation) post("/statuses/:id/mute", StatusController, :mute_conversation)
post("/statuses/:id/unmute", StatusController, :unmute_conversation) post("/statuses/:id/unmute", StatusController, :unmute_conversation)
put("/scheduled_statuses/:id", ScheduledActivityController, :update)
delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
post("/polls/:id/votes", PollController, :vote)
post("/media", MediaController, :create)
put("/media/:id", MediaController, :update)
delete("/lists/:id", ListController, :delete)
post("/lists", ListController, :create)
put("/lists/:id", ListController, :update)
post("/lists/:id/accounts", ListController, :add_to_list)
delete("/lists/:id/accounts", ListController, :remove_from_list)
post("/filters", FilterController, :create)
get("/filters/:id", FilterController, :show)
put("/filters/:id", FilterController, :update)
delete("/filters/:id", FilterController, :delete)
post("/reports", ReportController, :create)
post("/follows", AccountController, :follows)
post("/accounts/:id/follow", AccountController, :follow)
post("/accounts/:id/unfollow", AccountController, :unfollow)
post("/accounts/:id/block", AccountController, :block)
post("/accounts/:id/unblock", AccountController, :unblock)
post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute)
post("/follow_requests/:id/authorize", FollowRequestController, :authorize)
post("/follow_requests/:id/reject", FollowRequestController, :reject)
post("/domain_blocks", DomainBlockController, :create)
delete("/domain_blocks", DomainBlockController, :delete)
post("/push/subscription", SubscriptionController, :create) post("/push/subscription", SubscriptionController, :create)
get("/push/subscription", SubscriptionController, :get) get("/push/subscription", SubscriptionController, :get)
put("/push/subscription", SubscriptionController, :update) put("/push/subscription", SubscriptionController, :update)
delete("/push/subscription", SubscriptionController, :delete) delete("/push/subscription", SubscriptionController, :delete)
get("/markers", MarkerController, :index) get("/suggestions", SuggestionController, :index)
post("/markers", MarkerController, :upsert)
get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct)
get("/timelines/list/:list_id", TimelineController, :list)
end end
scope "/api/web", Pleroma.Web do scope "/api/web", Pleroma.Web do
@ -434,15 +446,24 @@ defmodule Pleroma.Web.Router do
scope "/api/v1", Pleroma.Web.MastodonAPI do scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:api) pipe_through(:api)
post("/accounts", AccountController, :create)
get("/accounts/search", SearchController, :account_search) get("/accounts/search", SearchController, :account_search)
get("/search", SearchController, :search)
get("/accounts/:id/statuses", AccountController, :statuses)
get("/accounts/:id/followers", AccountController, :followers)
get("/accounts/:id/following", AccountController, :following)
get("/accounts/:id", AccountController, :show)
post("/accounts", AccountController, :create)
get("/instance", InstanceController, :show) get("/instance", InstanceController, :show)
get("/instance/peers", InstanceController, :peers) get("/instance/peers", InstanceController, :peers)
post("/apps", AppController, :create) post("/apps", AppController, :create)
get("/apps/verify_credentials", AppController, :verify_credentials)
get("/statuses", StatusController, :index)
get("/statuses/:id", StatusController, :show)
get("/statuses/:id/context", StatusController, :context)
get("/statuses/:id/card", StatusController, :card) get("/statuses/:id/card", StatusController, :card)
get("/statuses/:id/favourited_by", StatusController, :favourited_by) get("/statuses/:id/favourited_by", StatusController, :favourited_by)
get("/statuses/:id/reblogged_by", StatusController, :reblogged_by) get("/statuses/:id/reblogged_by", StatusController, :reblogged_by)
@ -453,20 +474,8 @@ defmodule Pleroma.Web.Router do
get("/timelines/public", TimelineController, :public) get("/timelines/public", TimelineController, :public)
get("/timelines/tag/:tag", TimelineController, :hashtag) get("/timelines/tag/:tag", TimelineController, :hashtag)
get("/timelines/list/:list_id", TimelineController, :list)
get("/statuses", StatusController, :index)
get("/statuses/:id", StatusController, :show)
get("/statuses/:id/context", StatusController, :context)
get("/polls/:id", PollController, :show) get("/polls/:id", PollController, :show)
get("/accounts/:id/statuses", AccountController, :statuses)
get("/accounts/:id/followers", AccountController, :followers)
get("/accounts/:id/following", AccountController, :following)
get("/accounts/:id", AccountController, :show)
get("/search", SearchController, :search)
end end
scope "/api/v2", Pleroma.Web.MastodonAPI do scope "/api/v2", Pleroma.Web.MastodonAPI do
@ -507,7 +516,11 @@ defmodule Pleroma.Web.Router do
get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens) get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token)
post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read) post(
"/qvitter/statuses/notifications/read",
TwitterAPI.Controller,
:mark_notifications_as_read
)
end end
pipeline :ostatus do pipeline :ostatus do
@ -647,11 +660,28 @@ defmodule Pleroma.Web.Router do
# Test-only routes needed to test action dispatching and plug chain execution # Test-only routes needed to test action dispatching and plug chain execution
if Pleroma.Config.get(:env) == :test do if Pleroma.Config.get(:env) == :test do
@test_actions [
:do_oauth_check,
:fallback_oauth_check,
:skip_oauth_check,
:fallback_oauth_skip_publicity_check,
:skip_oauth_skip_publicity_check,
:missing_oauth_check_definition
]
scope "/test/api", Pleroma.Tests do
pipe_through(:api)
for action <- @test_actions do
get("/#{action}", AuthTestController, action)
end
end
scope "/test/authenticated_api", Pleroma.Tests do scope "/test/authenticated_api", Pleroma.Tests do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
for action <- [:skipped_oauth, :performed_oauth, :missed_oauth] do for action <- @test_actions do
get("/#{action}", OAuthTestController, action) get("/#{action}", AuthTestController, action)
end end
end end
end end

View file

@ -25,13 +25,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
when action == :follow_import when action == :follow_import
) )
# Note: follower can submit the form (with password auth) not being signed in (having no token)
plug(
OAuthScopesPlug,
%{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]}
when action == :do_remote_follow
)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import) plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import)
plug( plug(

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
@ -13,12 +14,18 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
require Logger require Logger
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read) 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]) plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token])
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(:errors) action_fallback(:errors)
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
@ -46,13 +53,13 @@ def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
json_reply(conn, 201, "") json_reply(conn, 201, "")
end end
def errors(conn, {:param_cast, _}) do defp errors(conn, {:param_cast, _}) do
conn conn
|> put_status(400) |> put_status(400)
|> json("Invalid parameters") |> json("Invalid parameters")
end end
def errors(conn, _) do defp errors(conn, _) do
conn conn
|> put_status(500) |> put_status(500)
|> json("Something went wrong") |> json("Something went wrong")
@ -64,7 +71,10 @@ defp json_reply(conn, status, json) do
|> send_resp(status, json) |> send_resp(status, json)
end end
def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do def mark_notifications_as_read(
%{assigns: %{user: user}} = conn,
%{"latest_id" => latest_id} = params
) do
Notification.set_read_up_to(user, latest_id) Notification.set_read_up_to(user, latest_id)
notifications = Notification.for_user(user, params) notifications = Notification.for_user(user, params)
@ -75,7 +85,7 @@ def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest
|> render("index.json", %{notifications: notifications, for: user}) |> render("index.json", %{notifications: notifications, for: user})
end end
def notifications_read(%{assigns: %{user: _user}} = conn, _) do def mark_notifications_as_read(%{assigns: %{user: _user}} = conn, _) do
bad_request_reply(conn, "You need to specify latest_id") bad_request_reply(conn, "You need to specify latest_id")
end end

View file

@ -2,6 +2,11 @@
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # 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 defmodule Pleroma.Web do
@moduledoc """ @moduledoc """
A module that keeps using definitions for controllers, A module that keeps using definitions for controllers,
@ -20,44 +25,91 @@ defmodule Pleroma.Web do
below. 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 def controller do
quote do quote do
use Phoenix.Controller, namespace: Pleroma.Web use Phoenix.Controller, namespace: Pleroma.Web
import Plug.Conn import Plug.Conn
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
import Pleroma.Web.Router.Helpers import Pleroma.Web.Router.Helpers
import Pleroma.Web.TranslationHelpers import Pleroma.Web.TranslationHelpers
alias Pleroma.Plugs.PlugHelper
plug(:set_put_layout) plug(:set_put_layout)
defp set_put_layout(conn, _) do defp set_put_layout(conn, _) do
put_layout(conn, Pleroma.Config.get(:app_layout, "app.html")) put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
end end
# Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain # Marks plugs intentionally skipped and blocks their execution if present in plugs chain
defp skip_plug(conn, plug_module) do defp skip_plug(conn, plug_modules) do
try do plug_modules
plug_module.skip_plug(conn) |> List.wrap()
rescue |> Enum.reduce(
UndefinedFunctionError -> conn,
raise "#{plug_module} is not skippable. Append `use Pleroma.Web, :plug` to its code." fn plug_module, conn ->
end 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 end
# Executed just before actual controller action, invokes before-action hooks (callbacks) # Executed just before actual controller action, invokes before-action hooks (callbacks)
defp action(conn, params) do defp action(conn, params) do
with %Plug.Conn{halted: false} <- maybe_halt_on_missing_oauth_scopes_check(conn) 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) super(conn, params)
end end
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 # Halts if authenticated API action neither performs nor explicitly skips OAuth scopes check
defp maybe_halt_on_missing_oauth_scopes_check(conn) do defp maybe_halt_on_missing_oauth_scopes_check(conn) do
if Pleroma.Plugs.AuthExpectedPlug.auth_expected?(conn) && if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) and
not PlugHelper.plug_called_or_skipped?(conn, Pleroma.Plugs.OAuthScopesPlug) do not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
conn conn
|> render_error( |> render_error(
:forbidden, :forbidden,
@ -132,7 +184,8 @@ def channel do
def plug do def plug do
quote do quote do
alias Pleroma.Plugs.PlugHelper @behaviour Pleroma.Web.Plug
@behaviour Plug
@doc """ @doc """
Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain. Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain.
@ -146,14 +199,22 @@ def skip_plug(conn) do
end end
@impl Plug @impl Plug
@doc "If marked as skipped, returns `conn`, and calls `perform/2` otherwise." @doc """
If marked as skipped, returns `conn`, otherwise calls `perform/2`.
Note: multiple invocations of the same plug (with different or same options) are allowed.
"""
def call(%Plug.Conn{} = conn, options) do def call(%Plug.Conn{} = conn, options) do
if PlugHelper.plug_skipped?(conn, __MODULE__) do if PlugHelper.plug_skipped?(conn, __MODULE__) do
conn conn
else else
conn conn =
|> PlugHelper.append_to_private_list(PlugHelper.called_plugs_list_id(), __MODULE__) PlugHelper.append_to_private_list(
|> perform(options) conn,
PlugHelper.called_plugs_list_id(),
__MODULE__
)
apply(__MODULE__, :perform, [conn, options])
end end
end end
end end

View file

@ -20,7 +20,7 @@ test "it continues if a user is assigned", %{conn: conn} do
conn = assign(conn, :user, %User{}) conn = assign(conn, :user, %User{})
ret_conn = EnsureAuthenticatedPlug.call(conn, %{}) ret_conn = EnsureAuthenticatedPlug.call(conn, %{})
assert ret_conn == conn refute ret_conn.halted
end end
end end
@ -34,20 +34,22 @@ test "it continues if a user is assigned", %{conn: conn} do
test "it continues if a user is assigned", %{conn: conn, true_fn: true_fn, false_fn: false_fn} do test "it continues if a user is assigned", %{conn: conn, true_fn: true_fn, false_fn: false_fn} do
conn = assign(conn, :user, %User{}) conn = assign(conn, :user, %User{})
assert EnsureAuthenticatedPlug.call(conn, if_func: true_fn) == conn refute EnsureAuthenticatedPlug.call(conn, if_func: true_fn).halted
assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn refute EnsureAuthenticatedPlug.call(conn, if_func: false_fn).halted
assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn refute EnsureAuthenticatedPlug.call(conn, unless_func: true_fn).halted
assert EnsureAuthenticatedPlug.call(conn, unless_func: false_fn) == conn refute EnsureAuthenticatedPlug.call(conn, unless_func: false_fn).halted
end end
test "it continues if a user is NOT assigned but :if_func evaluates to `false`", test "it continues if a user is NOT assigned but :if_func evaluates to `false`",
%{conn: conn, false_fn: false_fn} do %{conn: conn, false_fn: false_fn} do
assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn ret_conn = EnsureAuthenticatedPlug.call(conn, if_func: false_fn)
refute ret_conn.halted
end end
test "it continues if a user is NOT assigned but :unless_func evaluates to `true`", test "it continues if a user is NOT assigned but :unless_func evaluates to `true`",
%{conn: conn, true_fn: true_fn} do %{conn: conn, true_fn: true_fn} do
assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn ret_conn = EnsureAuthenticatedPlug.call(conn, unless_func: true_fn)
refute ret_conn.halted
end end
test "it halts if a user is NOT assigned and :if_func evaluates to `true`", test "it halts if a user is NOT assigned and :if_func evaluates to `true`",

View file

@ -29,7 +29,7 @@ test "it continues if public", %{conn: conn} do
conn conn
|> EnsurePublicOrAuthenticatedPlug.call(%{}) |> EnsurePublicOrAuthenticatedPlug.call(%{})
assert ret_conn == conn refute ret_conn.halted
end end
test "it continues if a user is assigned, even if not public", %{conn: conn} do test "it continues if a user is assigned, even if not public", %{conn: conn} do
@ -43,6 +43,6 @@ test "it continues if a user is assigned, even if not public", %{conn: conn} do
conn conn
|> EnsurePublicOrAuthenticatedPlug.call(%{}) |> EnsurePublicOrAuthenticatedPlug.call(%{})
assert ret_conn == conn refute ret_conn.halted
end end
end end

View file

@ -5,17 +5,12 @@
defmodule Pleroma.Plugs.OAuthScopesPlugTest do defmodule Pleroma.Plugs.OAuthScopesPlugTest do
use Pleroma.Web.ConnCase, async: true use Pleroma.Web.ConnCase, async: true
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo alias Pleroma.Repo
import Mock import Mock
import Pleroma.Factory import Pleroma.Factory
setup_with_mocks([{EnsurePublicOrAuthenticatedPlug, [], [call: fn conn, _ -> conn end]}]) do
:ok
end
test "is not performed if marked as skipped", %{conn: conn} do test "is not performed if marked as skipped", %{conn: conn} do
with_mock OAuthScopesPlug, [:passthrough], perform: &passthrough([&1, &2]) do with_mock OAuthScopesPlug, [:passthrough], perform: &passthrough([&1, &2]) do
conn = conn =
@ -60,7 +55,7 @@ test "if `token.scopes` fulfills specified 'all of' conditions, " <>
describe "with `fallback: :proceed_unauthenticated` option, " do describe "with `fallback: :proceed_unauthenticated` option, " do
test "if `token.scopes` doesn't fulfill specified conditions, " <> test "if `token.scopes` doesn't fulfill specified conditions, " <>
"clears :user and :token assigns and calls EnsurePublicOrAuthenticatedPlug", "clears :user and :token assigns",
%{conn: conn} do %{conn: conn} do
user = insert(:user) user = insert(:user)
token1 = insert(:oauth_token, scopes: ["read", "write"], user: user) token1 = insert(:oauth_token, scopes: ["read", "write"], user: user)
@ -79,35 +74,6 @@ test "if `token.scopes` doesn't fulfill specified conditions, " <>
refute ret_conn.halted refute ret_conn.halted
refute ret_conn.assigns[:user] refute ret_conn.assigns[:user]
refute ret_conn.assigns[:token] refute ret_conn.assigns[:token]
assert called(EnsurePublicOrAuthenticatedPlug.call(ret_conn, :_))
end
end
test "with :skip_instance_privacy_check option, " <>
"if `token.scopes` doesn't fulfill specified conditions, " <>
"clears :user and :token assigns and does NOT call EnsurePublicOrAuthenticatedPlug",
%{conn: conn} do
user = insert(:user)
token1 = insert(:oauth_token, scopes: ["read:statuses", "write"], user: user)
for token <- [token1, nil], op <- [:|, :&] do
ret_conn =
conn
|> assign(:user, user)
|> assign(:token, token)
|> OAuthScopesPlug.call(%{
scopes: ["read"],
op: op,
fallback: :proceed_unauthenticated,
skip_instance_privacy_check: true
})
refute ret_conn.halted
refute ret_conn.assigns[:user]
refute ret_conn.assigns[:token]
refute called(EnsurePublicOrAuthenticatedPlug.call(ret_conn, :_))
end end
end end
end end

View file

@ -766,7 +766,7 @@ test "it requires authentication if instance is NOT federating", %{
end end
describe "POST /users/:nickname/outbox" do describe "POST /users/:nickname/outbox" do
test "it rejects posts from other users / unauuthenticated users", %{conn: conn} do test "it rejects posts from other users / unauthenticated users", %{conn: conn} do
data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)

View file

@ -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.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

View file

@ -1,49 +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.OAuthTestControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
setup %{conn: conn} do
user = insert(:user)
conn = assign(conn, :user, user)
%{conn: conn, user: user}
end
test "missed_oauth", %{conn: conn} do
res =
conn
|> get("/test/authenticated_api/missed_oauth")
|> json_response(403)
assert res ==
%{
"error" =>
"Security violation: OAuth scopes check was neither handled nor explicitly skipped."
}
end
test "skipped_oauth", %{conn: conn} do
conn
|> assign(:token, nil)
|> get("/test/authenticated_api/skipped_oauth")
|> json_response(200)
end
test "performed_oauth", %{user: user} do
%{conn: good_token_conn} = oauth_access(["read"], user: user)
good_token_conn
|> get("/test/authenticated_api/performed_oauth")
|> json_response(200)
%{conn: bad_token_conn} = oauth_access(["follow"], user: user)
bad_token_conn
|> get("/test/authenticated_api/performed_oauth")
|> json_response(403)
end
end

View file

@ -7,35 +7,28 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
describe "empty_array/2 (stubs)" do describe "empty_array/2 (stubs)" do
test "GET /api/v1/accounts/:id/identity_proofs" do test "GET /api/v1/accounts/:id/identity_proofs" do
%{user: user, conn: conn} = oauth_access(["n/a"]) %{user: user, conn: conn} = oauth_access(["read:accounts"])
res = assert [] ==
conn conn
|> assign(:user, user) |> get("/api/v1/accounts/#{user.id}/identity_proofs")
|> get("/api/v1/accounts/#{user.id}/identity_proofs") |> json_response(200)
|> json_response(200)
assert res == []
end end
test "GET /api/v1/endorsements" do test "GET /api/v1/endorsements" do
%{conn: conn} = oauth_access(["read:accounts"]) %{conn: conn} = oauth_access(["read:accounts"])
res = assert [] ==
conn conn
|> get("/api/v1/endorsements") |> get("/api/v1/endorsements")
|> json_response(200) |> json_response(200)
assert res == []
end end
test "GET /api/v1/trends", %{conn: conn} do test "GET /api/v1/trends", %{conn: conn} do
res = assert [] ==
conn conn
|> get("/api/v1/trends") |> get("/api/v1/trends")
|> json_response(200) |> json_response(200)
assert res == []
end end
end end
end end

View file

@ -151,15 +151,18 @@ test "returns list of statuses favorited by specified user", %{
assert like["id"] == activity.id assert like["id"] == activity.id
end end
test "does not return favorites for specified user_id when user is not logged in", %{ test "returns favorites for specified user_id when requester is not logged in", %{
user: user user: user
} do } do
activity = insert(:note_activity) activity = insert(:note_activity)
CommonAPI.favorite(user, activity.id) CommonAPI.favorite(user, activity.id)
build_conn() response =
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites") build_conn()
|> json_response(403) |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(200)
assert length(response) == 1
end end
test "returns favorited DM only when user is logged in and he is one of recipients", %{ test "returns favorited DM only when user is logged in and he is one of recipients", %{
@ -185,9 +188,12 @@ test "returns favorited DM only when user is logged in and he is one of recipien
assert length(response) == 1 assert length(response) == 1
end end
build_conn() response =
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites") build_conn()
|> json_response(403) |> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(200)
assert length(response) == 0
end end
test "does not return others' favorited DM when user is not one of recipients", %{ test "does not return others' favorited DM when user is not one of recipients", %{

View file

@ -38,8 +38,7 @@ test "shared & non-shared pack information in list_packs is ok" do
end end
test "listing remote packs" do test "listing remote packs" do
admin = insert(:user, is_admin: true) conn = build_conn()
%{conn: conn} = oauth_access(["admin:write"], user: admin)
resp = resp =
build_conn() build_conn()
@ -76,7 +75,7 @@ test "downloading a shared pack from download_shared" do
assert Enum.find(arch, fn {n, _} -> n == 'blank.png' end) assert Enum.find(arch, fn {n, _} -> n == 'blank.png' end)
end end
test "downloading shared & unshared packs from another instance via download_from, deleting them" do test "downloading shared & unshared packs from another instance, deleting them" do
on_exit(fn -> on_exit(fn ->
File.rm_rf!("#{@emoji_dir_path}/test_pack2") File.rm_rf!("#{@emoji_dir_path}/test_pack2")
File.rm_rf!("#{@emoji_dir_path}/test_pack_nonshared2") File.rm_rf!("#{@emoji_dir_path}/test_pack_nonshared2")
@ -136,7 +135,7 @@ test "downloading shared & unshared packs from another instance via download_fro
|> post( |> post(
emoji_api_path( emoji_api_path(
conn, conn,
:download_from :save_from
), ),
%{ %{
instance_address: "https://old-instance", instance_address: "https://old-instance",
@ -152,7 +151,7 @@ test "downloading shared & unshared packs from another instance via download_fro
|> post( |> post(
emoji_api_path( emoji_api_path(
conn, conn,
:download_from :save_from
), ),
%{ %{
instance_address: "https://example.com", instance_address: "https://example.com",
@ -179,7 +178,7 @@ test "downloading shared & unshared packs from another instance via download_fro
|> post( |> post(
emoji_api_path( emoji_api_path(
conn, conn,
:download_from :save_from
), ),
%{ %{
instance_address: "https://example.com", instance_address: "https://example.com",

View file

@ -19,13 +19,9 @@ test "without valid credentials", %{conn: conn} do
end end
test "with credentials, without any params" do test "with credentials, without any params" do
%{user: current_user, conn: conn} = %{conn: conn} = oauth_access(["write:notifications"])
oauth_access(["read:notifications", "write:notifications"])
conn = conn = post(conn, "/api/qvitter/statuses/notifications/read")
conn
|> assign(:user, current_user)
|> post("/api/qvitter/statuses/notifications/read")
assert json_response(conn, 400) == %{ assert json_response(conn, 400) == %{
"error" => "You need to specify latest_id", "error" => "You need to specify latest_id",