Merge branch 'develop' into openapi/notifications

This commit is contained in:
Egor Kislitsyn 2020-04-30 13:52:07 +04:00
commit 9c1adb35de
No known key found for this signature in database
GPG key ID: 1B49CB15B71E7805
56 changed files with 842 additions and 408 deletions

View file

@ -120,6 +120,18 @@ Accepts additional parameters:
- `exclude_visibilities`: will exclude the notifications for activities with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`). Usage example: `GET /api/v1/notifications?exclude_visibilities[]=direct&exclude_visibilities[]=private`.
- `include_types`: will include the notifications for activities with the given types. The parameter accepts an array of types (`mention`, `follow`, `reblog`, `favourite`, `move`, `pleroma:emoji_reaction`). Usage example: `GET /api/v1/notifications?include_types[]=mention&include_types[]=reblog`.
## DELETE `/api/v1/notifications/destroy_multiple`
An endpoint to delete multiple statuses by IDs.
Required parameters:
- `ids`: array of activity ids
Usage example: `DELETE /api/v1/notifications/destroy_multiple/?ids[]=1&ids[]=2`.
Returns on success: 200 OK `{}`
## POST `/api/v1/statuses`
Additional parameters can be added to the JSON body/Form data:

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
import Plug.Conn
import Pleroma.Web.TranslationHelpers
alias Pleroma.User
use Pleroma.Web, :plug
def init(options) do
options
end
def call(%{assigns: %{user: %User{}}} = conn, _) do
@impl true
def perform(%{assigns: %{user: %User{}}} = conn, _) do
conn
end
def call(conn, options) do
def perform(conn, options) do
perform =
cond do
options[:if_func] -> options[:if_func].()

View file

@ -5,14 +5,18 @@
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
def call(conn, _) do
@impl true
def perform(conn, _) do
public? = Config.get!([:instance, :public])
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
alias Pleroma.Config
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.PlugHelper
use Pleroma.Web, :plug
@behaviour 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]
@ -31,10 +28,7 @@ def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
conn
options[:fallback] == :proceed_unauthenticated ->
conn
|> assign(:user, nil)
|> assign(:token, nil)
|> maybe_perform_instance_privacy_check(options)
drop_auth_info(conn)
true ->
missing_scopes = scopes -- matched_scopes
@ -50,6 +44,15 @@ def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
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"
def filter_descendants(scopes, supported_scopes) do
Enum.filter(
@ -71,12 +74,4 @@ def transform_scopes(scopes, options) do
scopes
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

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

View file

@ -131,6 +131,7 @@ def statuses_operation do
"Include statuses from muted acccounts."
),
Operation.parameter(:exclude_reblogs, :query, BooleanLike, "Exclude reblogs"),
Operation.parameter(:exclude_replies, :query, BooleanLike, "Exclude replies"),
Operation.parameter(
:exclude_visibilities,
:query,
@ -294,13 +295,13 @@ def unblock_operation do
}
end
def follows_operation do
def follow_by_uri_operation do
%Operation{
tags: ["accounts"],
summary: "Follows",
summary: "Follow by URI",
operationId: "AccountController.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: %{
200 => Operation.response("Account", "application/json", AccountRelationship),
400 => Operation.response("Error", "application/json", ApiError),
@ -615,7 +616,7 @@ defp array_of_relationships do
}
end
defp follows_request do
defp follow_by_uri_request do
%Schema{
title: "AccountFollowsRequest",
description: "POST body for muting an account",

View file

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

View file

@ -5,19 +5,25 @@
defmodule Pleroma.Web.MastoFEController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
# Note: :index action handles attempt of unauthenticated access to private instance with redirect
plug(:skip_plug, EnsurePublicOrAuthenticatedPlug when action == :index)
plug(
OAuthScopesPlug,
%{scopes: ["read"], fallback: :proceed_unauthenticated, skip_instance_privacy_check: true}
%{scopes: ["read"], fallback: :proceed_unauthenticated}
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"
def index(%{assigns: %{user: user, token: token}} = conn, _params)

View file

@ -14,6 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
skip_relationships?: 1
]
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
@ -28,18 +29,26 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
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(
OAuthScopesPlug,
%{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(
OAuthScopesPlug,
%{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)
@ -58,21 +67,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships)
# Note: :follows (POST /api/v1/follows) is the same as :follow, consider removing :follows
plug(
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", "write:mutes"]} when action in [:mute, :unmute])
plug(
Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
when action not in [:create, :show, :statuses]
)
@relationship_actions [:follow, :unfollow]
@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
@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
%User{} = user ->
conn

View file

@ -5,6 +5,7 @@
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
@ -13,7 +14,14 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(
:skip_plug,
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug]
when action == :create
)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
plug(OpenApiSpex.Plug.CastAndValidate)
@local_mastodon_name "Mastodon-Local"

View file

@ -13,10 +13,10 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@local_mastodon_name "Mastodon-Local"
plug(Pleroma.Plugs.RateLimiter, [name: :password_reset] when action == :password_reset)
@local_mastodon_name "Mastodon-Local"
@doc "GET /web/login"
def login(%{assigns: %{user: %User{}}} = conn, _params) do
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)
plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :index)
plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action == :read)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action != :index)
@doc "GET /api/v1/conversations"
def index(%{assigns: %{user: user}} = conn, params) do
@ -28,7 +26,7 @@ def index(%{assigns: %{user: user}} = conn, params) do
end
@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 <-
Repo.get_by(Participation, id: participation_id, user_id: user.id),
{: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(
:skip_plug,
[Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
when action == :index
)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.CustomEmojiOperation
def index(conn, _params) do

View file

@ -21,8 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
%{scopes: ["follow", "write:blocks"]} when action != :index
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/domain_blocks"
def index(%{assigns: %{user: user}} = conn, _) do
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
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/filters"
def index(%{assigns: %{user: user}} = conn, _) do
filters = Filter.get_filters(user)

View file

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

View file

@ -5,6 +5,12 @@
defmodule Pleroma.Web.MastodonAPI.InstanceController do
use Pleroma.Web, :controller
plug(
:skip_plug,
[Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug]
when action in [:show, :peers]
)
@doc "GET /api/v1/instance"
def show(conn, _params) do
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(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(
OAuthScopesPlug,
%{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)
# 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(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
# GET /api/v1/markers

View file

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

View file

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

View file

@ -22,8 +22,6 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action not in @oauth_read_actions)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.NotificationOperation
# GET /api/v1/notifications

View file

@ -22,8 +22,6 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/polls/:id"
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
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(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "POST /api/v1/reports"
def create(%{assigns: %{user: user}} = conn, 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: ["write:statuses"]} when action not in @oauth_read_actions)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@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)
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])

View file

@ -24,6 +24,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.ScheduledActivityView
plug(:skip_plug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action in [:index, :show])
@unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
plug(
@ -77,8 +79,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
%{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
plug(
@ -358,7 +358,7 @@ def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
end
@doc "GET /api/v1/favourites"
def favourites(%{assigns: %{user: user}} = conn, params) do
def favourites(%{assigns: %{user: %User{} = user}} = conn, params) do
activities =
ActivityPub.fetch_favourites(
user,

View file

@ -12,7 +12,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
action_fallback(:errors)
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
plug(:restrict_push_enabled)
# 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]
alias Pleroma.Pagination
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
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:
# 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: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)
@ -94,7 +101,9 @@ def public(%{assigns: %{user: user}} = conn, params) do
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 =
params
|> Map.put("type", ["Create", "Announce"])
@ -112,12 +121,10 @@ def public(%{assigns: %{user: user}} = conn, params) do
as: :activity,
skip_relationships: skip_relationships?(params)
)
else
render_error(conn, :unauthorized, "authorization required for timeline view")
end
end
def hashtag_fetching(params, user, local_only) do
defp hashtag_fetching(params, user, local_only) do
tags =
[params["tag"], params["any"]]
|> List.flatten()

View file

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

View file

@ -25,9 +25,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
plug(:fetch_session)
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)

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]
alias Ecto.Changeset
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
@ -17,6 +18,11 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
require Pleroma.Constants
plug(
:skip_plug,
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :confirmation_resend
)
plug(
OAuthScopesPlug,
%{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(
Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
when action != :confirmation_resend
OAuthScopesPlug,
%{scopes: ["read:favourites"], fallback: :proceed_unauthenticated} when action == :favourites
)
plug(RateLimiter, [name: :account_confirmation_resend] when action == :confirmation_resend)
plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)

View file

@ -1,6 +1,7 @@
defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
use Pleroma.Web, :controller
alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug
alias Pleroma.Plugs.OAuthScopesPlug
require Logger
@ -11,17 +12,20 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
when action in [
:create,
:delete,
:download_from,
:list_from,
:save_from,
:import_from_fs,
:update_file,
: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(
Pleroma.Config.get!([:instance, :static_dir]),
"emoji"
@ -212,13 +216,13 @@ defp shareable_packs_available(address) do
end
@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`.
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.
"""
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)
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: ["write:accounts"]} when action != :show)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/pleroma/mascot"
def show(%{assigns: %{user: user}} = conn, _params) do
json(conn, User.get_mascot(user))

View file

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

View file

@ -13,10 +13,12 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.StatusView
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :user_scrobbles)
plug(OAuthScopesPlug, %{scopes: ["write"]} when action != :user_scrobbles)
plug(
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
params =

View file

@ -16,6 +16,14 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.UserEnabledPlug)
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
plug(Pleroma.Plugs.OAuthPlug)
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
@ -39,20 +47,22 @@ defmodule Pleroma.Web.Router do
end
pipeline :api do
plug(:expect_public_instance_or_authentication)
plug(:base_api)
plug(:after_auth)
plug(Pleroma.Plugs.IdempotencyPlug)
end
pipeline :authenticated_api do
plug(:expect_authentication)
plug(:base_api)
plug(Pleroma.Plugs.AuthExpectedPlug)
plug(:after_auth)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
plug(Pleroma.Plugs.IdempotencyPlug)
end
pipeline :admin_api do
plug(:expect_authentication)
plug(:base_api)
plug(Pleroma.Plugs.AdminSecretAuthenticationPlug)
plug(:after_auth)
@ -200,24 +210,28 @@ defmodule Pleroma.Web.Router do
end
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
scope "/packs" do
# Modifying packs
scope "/packs" do
pipe_through(:admin_api)
post("/import_from_fs", EmojiAPIController, :import_from_fs)
post("/:pack_name/update_file", EmojiAPIController, :update_file)
post("/:pack_name/update_metadata", EmojiAPIController, :update_metadata)
put("/:name", EmojiAPIController, :create)
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
scope "/packs" do
# Pack info / downloading
scope "/packs" do
get("/", EmojiAPIController, :list_packs)
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
@ -277,7 +291,7 @@ defmodule Pleroma.Web.Router do
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
get("/conversations/:id", PleromaAPIController, :conversation)
post("/conversations/read", PleromaAPIController, :read_conversations)
post("/conversations/read", PleromaAPIController, :mark_conversations_as_read)
end
scope [] do
@ -286,7 +300,7 @@ defmodule Pleroma.Web.Router do
patch("/conversations/:id", PleromaAPIController, :update_conversation)
put("/statuses/:id/reactions/:emoji", PleromaAPIController, :react_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_banner", AccountController, :update_banner)
@ -322,53 +336,84 @@ defmodule Pleroma.Web.Router do
pipe_through(:authenticated_api)
get("/accounts/verify_credentials", AccountController, :verify_credentials)
patch("/accounts/update_credentials", AccountController, :update_credentials)
get("/accounts/relationships", AccountController, :relationships)
get("/accounts/:id/lists", AccountController, :lists)
get("/accounts/:id/identity_proofs", AccountController, :identity_proofs)
get("/follow_requests", FollowRequestController, :index)
get("/endorsements", AccountController, :endorsements)
get("/blocks", AccountController, :blocks)
get("/mutes", AccountController, :mutes)
get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct)
post("/follows", AccountController, :follow_by_uri)
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("/bookmarks", StatusController, :bookmarks)
get("/apps/verify_credentials", AppController, :verify_credentials)
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/:id", NotificationController, :show)
post("/notifications/:id/dismiss", NotificationController, :dismiss)
post("/notifications/clear", NotificationController, :clear)
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
# Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead
post("/notifications/dismiss", NotificationController, :dismiss_via_body)
post("/polls/:id/votes", PollController, :vote)
post("/reports", ReportController, :create)
get("/scheduled_statuses", ScheduledActivityController, :index)
get("/scheduled_statuses/:id", ScheduledActivityController, :show)
get("/lists", ListController, :index)
get("/lists/:id", ListController, :show)
get("/lists/:id/accounts", ListController, :list_accounts)
put("/scheduled_statuses/:id", ScheduledActivityController, :update)
delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
get("/domain_blocks", DomainBlockController, :index)
get("/filters", FilterController, :index)
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)
# Unlike `GET /api/v1/accounts/:id/favourites`, demands authentication
get("/favourites", StatusController, :favourites)
get("/bookmarks", StatusController, :bookmarks)
post("/statuses", StatusController, :create)
delete("/statuses/:id", StatusController, :delete)
post("/statuses/:id/reblog", StatusController, :reblog)
post("/statuses/:id/unreblog", StatusController, :unreblog)
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/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)
get("/push/subscription", SubscriptionController, :get)
put("/push/subscription", SubscriptionController, :update)
delete("/push/subscription", SubscriptionController, :delete)
get("/markers", MarkerController, :index)
post("/markers", MarkerController, :upsert)
get("/suggestions", SuggestionController, :index)
get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct)
get("/timelines/list/:list_id", TimelineController, :list)
end
scope "/api/web", Pleroma.Web do
@ -434,15 +446,24 @@ defmodule Pleroma.Web.Router do
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:api)
post("/accounts", AccountController, :create)
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/peers", InstanceController, :peers)
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/favourited_by", StatusController, :favourited_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/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("/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
scope "/api/v2", Pleroma.Web.MastodonAPI do
@ -507,7 +516,11 @@ defmodule Pleroma.Web.Router do
get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
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
pipeline :ostatus do
@ -647,11 +660,28 @@ defmodule Pleroma.Web.Router do
# Test-only routes needed to test action dispatching and plug chain execution
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
pipe_through(:authenticated_api)
for action <- [:skipped_oauth, :performed_oauth, :missed_oauth] do
get("/#{action}", OAuthTestController, action)
for action <- @test_actions do
get("/#{action}", AuthTestController, action)
end
end
end

View file

@ -25,13 +25,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
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(

View file

@ -6,6 +6,7 @@ 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
@ -13,12 +14,18 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
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(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(:errors)
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, "")
end
def errors(conn, {:param_cast, _}) do
defp errors(conn, {:param_cast, _}) do
conn
|> put_status(400)
|> json("Invalid parameters")
end
def errors(conn, _) do
defp errors(conn, _) do
conn
|> put_status(500)
|> json("Something went wrong")
@ -64,7 +71,10 @@ defp json_reply(conn, status, json) do
|> send_resp(status, json)
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)
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})
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")
end

View file

@ -2,6 +2,11 @@
# 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,
@ -20,44 +25,91 @@ defmodule Pleroma.Web do
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
alias Pleroma.Plugs.PlugHelper
plug(:set_put_layout)
defp set_put_layout(conn, _) do
put_layout(conn, Pleroma.Config.get(:app_layout, "app.html"))
end
# Marks a plug intentionally skipped and blocks its execution if it's present in plugs chain
defp skip_plug(conn, plug_module) do
# 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."
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 %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)
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 Pleroma.Plugs.AuthExpectedPlug.auth_expected?(conn) &&
not PlugHelper.plug_called_or_skipped?(conn, Pleroma.Plugs.OAuthScopesPlug) do
if PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) and
not PlugHelper.plug_called_or_skipped?(conn, OAuthScopesPlug) do
conn
|> render_error(
:forbidden,
@ -132,7 +184,8 @@ def channel do
def plug do
quote do
alias Pleroma.Plugs.PlugHelper
@behaviour Pleroma.Web.Plug
@behaviour Plug
@doc """
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
@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
if PlugHelper.plug_skipped?(conn, __MODULE__) do
conn
else
conn
|> PlugHelper.append_to_private_list(PlugHelper.called_plugs_list_id(), __MODULE__)
|> perform(options)
conn =
PlugHelper.append_to_private_list(
conn,
PlugHelper.called_plugs_list_id(),
__MODULE__
)
apply(__MODULE__, :perform, [conn, options])
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{})
ret_conn = EnsureAuthenticatedPlug.call(conn, %{})
assert ret_conn == conn
refute ret_conn.halted
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
conn = assign(conn, :user, %User{})
assert EnsureAuthenticatedPlug.call(conn, if_func: true_fn) == conn
assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn
assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn
assert EnsureAuthenticatedPlug.call(conn, unless_func: false_fn) == conn
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
assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn
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
assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn
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`",

View file

@ -29,7 +29,7 @@ test "it continues if public", %{conn: conn} do
conn
|> EnsurePublicOrAuthenticatedPlug.call(%{})
assert ret_conn == conn
refute ret_conn.halted
end
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
|> EnsurePublicOrAuthenticatedPlug.call(%{})
assert ret_conn == conn
refute ret_conn.halted
end
end

View file

@ -5,17 +5,12 @@
defmodule Pleroma.Plugs.OAuthScopesPlugTest do
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo
import Mock
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
with_mock OAuthScopesPlug, [:passthrough], perform: &passthrough([&1, &2]) do
conn =
@ -60,7 +55,7 @@ test "if `token.scopes` fulfills specified 'all of' conditions, " <>
describe "with `fallback: :proceed_unauthenticated` option, " do
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
user = insert(: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.assigns[:user]
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

View file

@ -766,7 +766,7 @@ test "it requires authentication if instance is NOT federating", %{
end
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!()
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
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
|> assign(:user, user)
|> get("/api/v1/accounts/#{user.id}/identity_proofs")
|> json_response(200)
assert res == []
end
test "GET /api/v1/endorsements" do
%{conn: conn} = oauth_access(["read:accounts"])
res =
assert [] ==
conn
|> get("/api/v1/endorsements")
|> json_response(200)
assert res == []
end
test "GET /api/v1/trends", %{conn: conn} do
res =
assert [] ==
conn
|> get("/api/v1/trends")
|> json_response(200)
assert res == []
end
end
end

View file

@ -151,15 +151,18 @@ test "returns list of statuses favorited by specified user", %{
assert like["id"] == activity.id
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
} do
activity = insert(:note_activity)
CommonAPI.favorite(user, activity.id)
response =
build_conn()
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(403)
|> json_response(200)
assert length(response) == 1
end
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
end
response =
build_conn()
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|> json_response(403)
|> json_response(200)
assert length(response) == 0
end
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
test "listing remote packs" do
admin = insert(:user, is_admin: true)
%{conn: conn} = oauth_access(["admin:write"], user: admin)
conn = build_conn()
resp =
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)
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 ->
File.rm_rf!("#{@emoji_dir_path}/test_pack2")
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(
emoji_api_path(
conn,
:download_from
:save_from
),
%{
instance_address: "https://old-instance",
@ -152,7 +151,7 @@ test "downloading shared & unshared packs from another instance via download_fro
|> post(
emoji_api_path(
conn,
:download_from
:save_from
),
%{
instance_address: "https://example.com",
@ -179,7 +178,7 @@ test "downloading shared & unshared packs from another instance via download_fro
|> post(
emoji_api_path(
conn,
:download_from
:save_from
),
%{
instance_address: "https://example.com",

View file

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