use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper,
- only: [json_response: 3, add_link_headers: 5, add_link_headers: 4, add_link_headers: 3]
+ only: [json_response: 3, add_link_headers: 2, add_link_headers: 3]
alias Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Pagination
+ alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
alias Pleroma.ScheduledActivity
require Logger
require Pleroma.Constants
+ @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
+
+ # Note: :index action handles attempt of unauthenticated access to private instance with redirect
+ plug(
+ OAuthScopesPlug,
+ Map.merge(@unauthenticated_access, %{scopes: ["read"], skip_instance_privacy_check: true})
+ when action == :index
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read"]} when action in [:suggestions, :verify_app_credentials]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:accounts"]}
+ # Note: the following actions are not permission-secured in Mastodon:
+ when action in [
+ :put_settings,
+ :update_avatar,
+ :update_banner,
+ :update_background,
+ :set_mascot
+ ]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:accounts"]}
+ when action in [:pin_status, :unpin_status, :update_credentials]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:statuses"]}
+ when action in [
+ :conversations,
+ :scheduled_statuses,
+ :show_scheduled_status,
+ :home_timeline,
+ :dm_timeline
+ ]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{@unauthenticated_access | scopes: ["read:statuses"]}
+ when action in [
+ :user_statuses,
+ :get_statuses,
+ :get_status,
+ :get_context,
+ :status_card,
+ :get_poll
+ ]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:statuses"]}
+ when action in [
+ :update_scheduled_status,
+ :delete_scheduled_status,
+ :post_status,
+ :delete_status,
+ :reblog_status,
+ :unreblog_status,
+ :poll_vote
+ ]
+ )
+
+ plug(OAuthScopesPlug, %{scopes: ["write:conversations"]} when action == :conversation_read)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:accounts"]}
+ when action in [:endorsements, :verify_credentials, :followers, :following, :get_mascot]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{@unauthenticated_access | scopes: ["read:accounts"]}
+ when action in [:user, :favourited_by, :reblogged_by]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:favourites"]} when action in [:favourites, :user_favourites]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:favourites"]} when action in [:fav_status, :unfav_status]
+ )
+
+ plug(OAuthScopesPlug, %{scopes: ["read:filters"]} when action in [:get_filters, :get_filter])
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:filters"]} when action in [:create_filter, :update_filter, :delete_filter]
+ )
+
+ plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in [:account_lists, :list_timeline])
+
+ plug(OAuthScopesPlug, %{scopes: ["write:media"]} when action in [:upload, :update_media])
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:notifications"]} when action in [:notifications, :get_notification]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:notifications"]}
+ when action in [:clear_notifications, :dismiss_notification, :destroy_multiple_notifications]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:reports"]}
+ when action in [:create_report, :report_update_state, :report_respond]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["follow", "read:blocks"]} when action in [:blocks, :domain_blocks]
+ )
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["follow", "write:blocks"]}
+ when action in [:block, :unblock, :block_domain, :unblock_domain]
+ )
+
+ plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships)
+ plug(OAuthScopesPlug, %{scopes: ["follow", "read:follows"]} when action == :follow_requests)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["follow", "write:follows"]}
+ when action in [
+ :follow,
+ :unfollow,
+ :subscribe,
+ :unsubscribe,
+ :authorize_follow_request,
+ :reject_follow_request
+ ]
+ )
+
+ plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes)
+ plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:mutes"]} when action in [:mute_conversation, :unmute_conversation]
+ )
+
+ # Note: scopes not present in Mastodon: read:bookmarks, write:bookmarks
+ plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :bookmarks)
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:bookmarks"]} when action in [:bookmark_status, :unbookmark_status]
+ )
+
+ # An extra safety measure for possible actions not guarded by OAuth permissions specification
+ plug(
+ Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
+ when action not in [
+ :account_register,
+ :create_app,
+ :index,
+ :login,
+ :logout,
+ :password_reset,
+ :account_confirmation_resend,
+ :masto_instance,
+ :peers,
+ :custom_emojis
+ ]
+ )
+
@rate_limited_relations_actions ~w(follow unfollow)a
@rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status
[
:no_rich_text,
:locked,
+ :hide_followers_count,
+ :hide_follows_count,
:hide_followers,
:hide_follows,
:hide_favorites,
end
def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id),
+ with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do
account = AccountView.render("account.json", %{user: user, for: for_user})
json(conn, account)
|> Enum.reverse()
conn
- |> add_link_headers(:home_timeline, activities)
+ |> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
|> Enum.reverse()
conn
- |> add_link_headers(:public_timeline, activities, false, %{"local" => local_only})
+ |> add_link_headers(activities, %{"local" => local_only})
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do
- with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"]) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
params =
params
|> Map.put("tag", params["tagged"])
activities = ActivityPub.fetch_user_activities(user, reading_user, params)
conn
- |> add_link_headers(:user_statuses, activities, params["id"])
+ |> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", %{
activities: activities,
|> Pagination.fetch_paginated(params)
conn
- |> add_link_headers(:dm_timeline, activities)
+ |> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
+ def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
+ limit = 100
+
+ activities =
+ ids
+ |> Enum.take(limit)
+ |> Activity.all_by_ids_with_object()
+ |> Enum.filter(&Visibility.visible_for_user?(&1, user))
+
+ conn
+ |> put_view(StatusView)
+ |> render("index.json", activities: activities, for: user, as: :activity)
+ end
+
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.visible_for_user?(activity, user) do
end
def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with %Object{} = object <- Object.get_by_id(id),
+ with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
true <- Visibility.visible_for_user?(activity, user) do
conn
def scheduled_statuses(%{assigns: %{user: user}} = conn, params) do
with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
conn
- |> add_link_headers(:scheduled_statuses, scheduled_activities)
+ |> add_link_headers(scheduled_activities)
|> put_view(ScheduledActivityView)
|> render("index.json", %{scheduled_activities: scheduled_activities})
end
notifications = MastodonAPI.get_notifications(user, params)
conn
- |> add_link_headers(:notifications, notifications)
+ |> add_link_headers(notifications)
|> put_view(NotificationView)
|> render("index.json", %{notifications: notifications, for: user})
end
end
end
- def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do
+ def destroy_multiple_notifications(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do
Notification.destroy_multiple(user, ids)
json(conn, %{})
end
def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+ {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
%Object{data: %{"likes" => likes}} <- Object.normalize(activity) do
q = from(u in User, where: u.ap_id in ^likes)
|> put_view(AccountView)
|> render("accounts.json", %{for: user, users: users, as: :user})
else
+ {:visible, false} -> {:error, :not_found}
_ -> json(conn, [])
end
end
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+ {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
%Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
q = from(u in User, where: u.ap_id in ^announces)
|> put_view(AccountView)
|> render("accounts.json", %{for: user, users: users, as: :user})
else
+ {:visible, false} -> {:error, :not_found}
_ -> json(conn, [])
end
end
|> Enum.reverse()
conn
- |> add_link_headers(:hashtag_timeline, activities, params["tag"], %{"local" => local_only})
+ |> add_link_headers(activities, %{"local" => local_only})
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
end
conn
- |> add_link_headers(:followers, followers, user)
+ |> add_link_headers(followers)
|> put_view(AccountView)
|> render("accounts.json", %{for: for_user, users: followers, as: :user})
end
end
conn
- |> add_link_headers(:following, followers, user)
+ |> add_link_headers(followers)
|> put_view(AccountView)
|> render("accounts.json", %{for: for_user, users: followers, as: :user})
end
|> Enum.reverse()
conn
- |> add_link_headers(:favourites, activities)
+ |> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
|> Enum.reverse()
conn
- |> add_link_headers(:favourites, activities)
+ |> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: for_user, as: :activity})
else
|> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
conn
- |> add_link_headers(:bookmarks, bookmarks)
+ |> add_link_headers(bookmarks)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
json(conn, %{})
end
+ def endorsements(conn, params), do: empty_array(conn, params)
+
def get_filters(%{assigns: %{user: user}} = conn, _) do
filters = Filter.get_filters(user)
res = FilterView.render("filters.json", filters: filters)
end
end
- def reports(%{assigns: %{user: user}} = conn, params) do
+ def create_report(%{assigns: %{user: user}} = conn, params) do
case CommonAPI.report(user, params) do
{:ok, activity} ->
conn
end)
conn
- |> add_link_headers(:conversations, participations)
+ |> add_link_headers(participations)
|> json(conversations)
end