- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
- Pleroma API: Email change endpoint.
- Admin API: Added moderation log
+ - Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
- Web response cache (currently, enabled for ActivityPub)
- Mastodon API: Added an endpoint to get multiple statuses by IDs (`GET /api/v1/statuses/?ids[]=1&ids[]=2`)
+ - ActivityPub: Add ActivityPub actor's `discoverable` parameter.
+ - Admin API: Added moderation log filters (user/start date/end date/search/pagination)
+- OAuth: support for hierarchical permissions / [Mastodon 2.4.3 OAuth permissions](https://docs.joinmastodon.org/api/permissions/)
### Changed
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.MastodonAPI.AccountController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
+
+ alias Pleroma.Emoji
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Plugs.RateLimiter
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI.ListView
+ alias Pleroma.Web.MastodonAPI.MastodonAPI
+ alias Pleroma.Web.MastodonAPI.StatusView
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.TwitterAPI.TwitterAPI
+
++ plug(
++ OAuthScopesPlug,
++ %{fallback: :proceed_unauthenticated, scopes: ["read:accounts"]}
++ when action == :show
++ )
++
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["read:accounts"]}
++ when action in [:endorsements, :verify_credentials, :followers, :following]
++ )
++
++ plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :update_credentials)
++
++ plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :lists)
++
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["follow", "write:blocks"]} when action in [:block, :unblock]
++ )
++
++ plug(OAuthScopesPlug, %{scopes: ["read:follows"]} when action == :relationships)
++
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["follow", "write:follows"]} when action in [:follow, :unfollow]
++ )
++
++ plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
++
++ plug(
++ Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
++ when action != :create
++ )
++
+ @relations [:follow, :unfollow]
+ @needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
+
+ plug(RateLimiter, {:relations_id_action, params: ["id", "uri"]} when action in @relations)
+ plug(RateLimiter, :relations_actions when action in @relations)
+ plug(RateLimiter, :app_account_creation when action == :create)
+ plug(:assign_account_by_id when action in @needs_account)
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @doc "POST /api/v1/accounts"
+ def create(
+ %{assigns: %{app: app}} = conn,
+ %{"username" => nickname, "email" => _, "password" => _, "agreement" => true} = params
+ ) do
+ params =
+ params
+ |> Map.take([
+ "email",
+ "captcha_solution",
+ "captcha_token",
+ "captcha_answer_data",
+ "token",
+ "password"
+ ])
+ |> Map.put("nickname", nickname)
+ |> Map.put("fullname", params["fullname"] || nickname)
+ |> Map.put("bio", params["bio"] || "")
+ |> Map.put("confirm", params["password"])
+
+ with {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true),
+ {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do
+ json(conn, %{
+ token_type: "Bearer",
+ access_token: token.token,
+ scope: app.scopes,
+ created_at: Token.Utils.format_created_at(token)
+ })
+ else
+ {:error, errors} -> json_response(conn, :bad_request, errors)
+ end
+ end
+
+ def create(%{assigns: %{app: _app}} = conn, _) do
+ render_error(conn, :bad_request, "Missing parameters")
+ end
+
+ def create(conn, _) do
+ render_error(conn, :forbidden, "Invalid credentials")
+ end
+
+ @doc "GET /api/v1/accounts/verify_credentials"
+ def verify_credentials(%{assigns: %{user: user}} = conn, _) do
+ chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
+
+ render(conn, "show.json",
+ user: user,
+ for: user,
+ with_pleroma_settings: true,
+ with_chat_token: chat_token
+ )
+ end
+
+ @doc "PATCH /api/v1/accounts/update_credentials"
+ def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
+ user = original_user
+
+ user_params =
+ %{}
+ |> add_if_present(params, "display_name", :name)
+ |> add_if_present(params, "note", :bio, fn value -> {:ok, User.parse_bio(value, user)} end)
+ |> add_if_present(params, "avatar", :avatar, fn value ->
+ with %Plug.Upload{} <- value,
+ {:ok, object} <- ActivityPub.upload(value, type: :avatar) do
+ {:ok, object.data}
+ end
+ end)
+
+ emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "")
+
+ user_info_emojis =
+ user.info
+ |> Map.get(:emoji, [])
+ |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
+ |> Enum.dedup()
+
+ info_params =
+ [
+ :no_rich_text,
+ :locked,
+ :hide_followers_count,
+ :hide_follows_count,
+ :hide_followers,
+ :hide_follows,
+ :hide_favorites,
+ :show_role,
+ :skip_thread_containment,
+ :discoverable
+ ]
+ |> Enum.reduce(%{}, fn key, acc ->
+ add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
+ end)
+ |> add_if_present(params, "default_scope", :default_scope)
+ |> add_if_present(params, "fields", :fields, fn fields ->
+ fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
+
+ {:ok, fields}
+ end)
+ |> add_if_present(params, "fields", :raw_fields)
+ |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
+ {:ok, Map.merge(user.info.pleroma_settings_store, value)}
+ end)
+ |> add_if_present(params, "header", :banner, fn value ->
+ with %Plug.Upload{} <- value,
+ {:ok, object} <- ActivityPub.upload(value, type: :banner) do
+ {:ok, object.data}
+ end
+ end)
+ |> add_if_present(params, "pleroma_background_image", :background, fn value ->
+ with %Plug.Upload{} <- value,
+ {:ok, object} <- ActivityPub.upload(value, type: :background) do
+ {:ok, object.data}
+ end
+ end)
+ |> Map.put(:emoji, user_info_emojis)
+
+ changeset =
+ user
+ |> User.update_changeset(user_params)
+ |> User.change_info(&User.Info.profile_update(&1, info_params))
+
+ with {:ok, user} <- User.update_and_set_cache(changeset) do
+ if original_user != user, do: CommonAPI.update(user)
+
+ render(conn, "show.json", user: user, for: user, with_pleroma_settings: true)
+ else
+ _e -> render_error(conn, :forbidden, "Invalid request")
+ end
+ end
+
+ defp add_if_present(map, params, params_field, map_field, value_function \\ &{:ok, &1}) do
+ with true <- Map.has_key?(params, params_field),
+ {:ok, new_value} <- value_function.(params[params_field]) do
+ Map.put(map, map_field, new_value)
+ else
+ _ -> map
+ end
+ end
+
+ @doc "GET /api/v1/accounts/relationships"
+ def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ targets = User.get_all_by_ids(List.wrap(id))
+
+ render(conn, "relationships.json", user: user, targets: targets)
+ end
+
+ # Instead of returning a 400 when no "id" params is present, Mastodon returns an empty array.
+ def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
+
+ @doc "GET /api/v1/accounts/:id"
+ def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do
+ 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
+ render(conn, "show.json", user: user, for: for_user)
+ else
+ _e -> render_error(conn, :not_found, "Can't find user")
+ end
+ end
+
+ @doc "GET /api/v1/accounts/:id/statuses"
+ def statuses(%{assigns: %{user: reading_user}} = conn, params) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
+ params = Map.put(params, "tag", params["tagged"])
+ activities = ActivityPub.fetch_user_activities(user, reading_user, params)
+
+ conn
+ |> add_link_headers(activities)
+ |> put_view(StatusView)
+ |> render("index.json", activities: activities, for: reading_user, as: :activity)
+ end
+ end
+
+ @doc "GET /api/v1/accounts/:id/followers"
+ def followers(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ followers =
+ cond do
+ for_user && user.id == for_user.id -> MastodonAPI.get_followers(user, params)
+ user.info.hide_followers -> []
+ true -> MastodonAPI.get_followers(user, params)
+ end
+
+ conn
+ |> add_link_headers(followers)
+ |> render("index.json", for: for_user, users: followers, as: :user)
+ end
+
+ @doc "GET /api/v1/accounts/:id/following"
+ def following(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ followers =
+ cond do
+ for_user && user.id == for_user.id -> MastodonAPI.get_friends(user, params)
+ user.info.hide_follows -> []
+ true -> MastodonAPI.get_friends(user, params)
+ end
+
+ conn
+ |> add_link_headers(followers)
+ |> render("index.json", for: for_user, users: followers, as: :user)
+ end
+
+ @doc "GET /api/v1/accounts/:id/lists"
+ def lists(%{assigns: %{user: user, account: account}} = conn, _params) do
+ lists = Pleroma.List.get_lists_account_belongs(user, account)
+
+ conn
+ |> put_view(ListView)
+ |> render("index.json", lists: lists)
+ end
+
+ @doc "POST /api/v1/accounts/:id/follow"
+ def follow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
+ {:error, :not_found}
+ end
+
+ def follow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
+ with {:ok, follower} <- MastodonAPI.follow(follower, followed, conn.params) do
+ render(conn, "relationship.json", user: follower, target: followed)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/unfollow"
+ def unfollow(%{assigns: %{user: %{id: id}, account: %{id: id}}}, _params) do
+ {:error, :not_found}
+ end
+
+ def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) do
+ with {:ok, follower} <- CommonAPI.unfollow(follower, followed) do
+ render(conn, "relationship.json", user: follower, target: followed)
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/mute"
+ def mute(%{assigns: %{user: muter, account: muted}} = conn, params) do
+ notifications? = params |> Map.get("notifications", true) |> truthy_param?()
+
+ with {:ok, muter} <- User.mute(muter, muted, notifications?) do
+ render(conn, "relationship.json", user: muter, target: muted)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/unmute"
+ def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
+ with {:ok, muter} <- User.unmute(muter, muted) do
+ render(conn, "relationship.json", user: muter, target: muted)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/block"
+ def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
+ with {:ok, blocker} <- User.block(blocker, blocked),
+ {:ok, _activity} <- ActivityPub.block(blocker, blocked) do
+ render(conn, "relationship.json", user: blocker, target: blocked)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/accounts/:id/unblock"
+ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
+ with {:ok, blocker} <- User.unblock(blocker, blocked),
+ {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
+ render(conn, "relationship.json", user: blocker, target: blocked)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
++
++ @doc "GET /api/v1/endorsements"
++ def endorsements(conn, params),
++ do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
+ end
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.MastodonAPI.ConversationController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+
+ alias Pleroma.Conversation.Participation
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Repo
+
+ 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)
++
+ @doc "GET /api/v1/conversations"
+ def index(%{assigns: %{user: user}} = conn, params) do
+ participations = Participation.for_user_with_last_activity_id(user, params)
+
+ conn
+ |> add_link_headers(participations)
+ |> render("participations.json", participations: participations, for: user)
+ end
+
+ @doc "POST /api/v1/conversations/:id/read"
+ def 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
+ render(conn, "participation.json", participation: participation, for: user)
+ end
+ end
+ end
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.MastodonAPI.DomainBlockController do
+ use Pleroma.Web, :controller
+
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.User
+
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["follow", "read:blocks"]} when action == :index
++ )
++
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["follow", "write:blocks"]} when action != :index
++ )
++
+ @doc "GET /api/v1/domain_blocks"
+ def index(%{assigns: %{user: %{info: info}}} = conn, _) do
+ json(conn, Map.get(info, :domain_blocks, []))
+ end
+
+ @doc "POST /api/v1/domain_blocks"
+ def create(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
+ User.block_domain(blocker, domain)
+ json(conn, %{})
+ end
+
+ @doc "DELETE /api/v1/domain_blocks"
+ def delete(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
+ User.unblock_domain(blocker, domain)
+ json(conn, %{})
+ end
+ end
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.MastodonAPI.FilterController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Filter
++ alias Pleroma.Plugs.OAuthScopesPlug
++
++ @oauth_read_actions [:show, :index]
++
++ plug(OAuthScopesPlug, %{scopes: ["read:filters"]} when action in @oauth_read_actions)
++
++ plug(
++ OAuthScopesPlug,
++ %{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)
+
+ render(conn, "filters.json", filters: filters)
+ end
+
+ @doc "POST /api/v1/filters"
+ def create(
+ %{assigns: %{user: user}} = conn,
+ %{"phrase" => phrase, "context" => context} = params
+ ) do
+ query = %Filter{
+ user_id: user.id,
+ phrase: phrase,
+ context: context,
+ hide: Map.get(params, "irreversible", false),
+ whole_word: Map.get(params, "boolean", true)
+ # expires_at
+ }
+
+ {:ok, response} = Filter.create(query)
+
+ render(conn, "filter.json", filter: response)
+ end
+
+ @doc "GET /api/v1/filters/:id"
+ def show(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
+ filter = Filter.get(filter_id, user)
+
+ render(conn, "filter.json", filter: filter)
+ end
+
+ @doc "PUT /api/v1/filters/:id"
+ def update(
+ %{assigns: %{user: user}} = conn,
+ %{"phrase" => phrase, "context" => context, "id" => filter_id} = params
+ ) do
+ query = %Filter{
+ user_id: user.id,
+ filter_id: filter_id,
+ phrase: phrase,
+ context: context,
+ hide: Map.get(params, "irreversible", nil),
+ whole_word: Map.get(params, "boolean", true)
+ # expires_at
+ }
+
+ {:ok, response} = Filter.update(query)
+ render(conn, "filter.json", filter: response)
+ end
+
+ @doc "DELETE /api/v1/filters/:id"
+ def delete(%{assigns: %{user: user}} = conn, %{"id" => filter_id}) do
+ query = %Filter{
+ user_id: user.id,
+ filter_id: filter_id
+ }
+
+ {:ok, _} = Filter.delete(query)
+ json(conn, %{})
+ end
+ end
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
+ use Pleroma.Web, :controller
+
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+
+ plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)
+ plug(:assign_follower when action != :index)
+
+ action_fallback(:errors)
+
++ plug(OAuthScopesPlug, %{scopes: ["follow", "read:follows"]} when action == :index)
++
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["follow", "write:follows"]} when action != :index
++ )
++
+ @doc "GET /api/v1/follow_requests"
+ def index(%{assigns: %{user: followed}} = conn, _params) do
+ follow_requests = User.get_follow_requests(followed)
+
+ render(conn, "index.json", for: followed, users: follow_requests, as: :user)
+ end
+
+ @doc "POST /api/v1/follow_requests/:id/authorize"
+ def authorize(%{assigns: %{user: followed, follower: follower}} = conn, _params) do
+ with {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
+ render(conn, "relationship.json", user: followed, target: follower)
+ end
+ end
+
+ @doc "POST /api/v1/follow_requests/:id/reject"
+ def reject(%{assigns: %{user: followed, follower: follower}} = conn, _params) do
+ with {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
+ render(conn, "relationship.json", user: followed, target: follower)
+ end
+ end
+
+ defp assign_follower(%{params: %{"id" => id}} = conn, _) do
+ case User.get_cached_by_id(id) do
+ %User{} = follower -> assign(conn, :follower, follower)
+ nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
+ end
+ end
+
+ defp errors(conn, {:error, message}) do
+ conn
+ |> put_status(:forbidden)
+ |> json(%{error: message})
+ end
+ end
plug(:list_by_id_and_user when action not in [:index, :create])
+ plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in [:index, :show, :list_accounts])
+
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:lists"]}
+ when action in [:create, :update, :delete, :add_to_list, :remove_from_list]
+ )
+
++ plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
++
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
# GET /api/v1/lists
alias Pleroma.Activity
alias Pleroma.Bookmark
alias Pleroma.Config
- alias Pleroma.Conversation.Participation
- alias Pleroma.Filter
- alias Pleroma.Formatter
alias Pleroma.HTTP
- alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Pagination
+ alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo
- alias Pleroma.ScheduledActivity
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI
- alias Pleroma.Web.ControllerHelper
- import Ecto.Query
-
require Logger
- require Pleroma.Constants
- 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]
- )
+ @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]
+ )
+
- %{scopes: ["write:favourites"]} when action in [:fav_status, :unfav_status]
++ plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action == :put_settings)
+
+ plug(
+ OAuthScopesPlug,
- plug(OAuthScopesPlug, %{scopes: ["read:filters"]} when action in [:get_filters, :get_filter])
++ %{@unauthenticated_access | scopes: ["read:statuses"]} when action == :get_poll
+ )
+
- 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:statuses"]} when action == :poll_vote)
+
- %{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: ["read:favourites"]} when action == :favourites)
+
+ plug(OAuthScopesPlug, %{scopes: ["write:media"]} when action in [:upload, :update_media])
+
+ 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
- ]
++ %{scopes: ["follow", "read:blocks"]} when action == :blocks
+ )
+
++ # To do: POST /api/v1/follows is not present in Mastodon; consider removing the action
+ plug(
+ OAuthScopesPlug,
- plug(OAuthScopesPlug, %{scopes: ["follow", "write:mutes"]} when action in [:mute, :unmute])
-
- plug(
- OAuthScopesPlug,
- %{scopes: ["write:mutes"]} when action in [:mute_conversation, :unmute_conversation]
- )
++ %{scopes: ["follow", "write:follows"]} when action == :follows
+ )
+
+ plug(OAuthScopesPlug, %{scopes: ["follow", "read:mutes"]} when action == :mutes)
- # Note: scopes not present in Mastodon: read:bookmarks, write:bookmarks
+
- plug(
- OAuthScopesPlug,
- %{scopes: ["write:bookmarks"]} when action in [:bookmark_status, :unbookmark_status]
- )
-
++ # Note: scope not present in Mastodon: read:bookmarks
+ plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :bookmarks)
+
- :account_register,
+ # An extra safety measure for possible actions not guarded by OAuth permissions specification
+ plug(
+ Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
+ when action not in [
- :account_confirmation_resend,
+ :create_app,
+ :index,
+ :login,
+ :logout,
+ :password_reset,
- @rate_limited_relations_actions ~w(follow unfollow)a
-
- @rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status
- post_status delete_status)a
-
- plug(
- RateLimiter,
- {:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]}
- when action in ~w(reblog_status unreblog_status)a
- )
-
- plug(
- RateLimiter,
- {:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
- when action in ~w(fav_status unfav_status)a
- )
-
- plug(
- RateLimiter,
- {:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions
- )
-
- plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions)
- plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
- plug(RateLimiter, :app_account_creation when action == :account_register)
- plug(RateLimiter, :search when action in [:search, :search2, :account_search])
+ :masto_instance,
+ :peers,
+ :custom_emojis
+ ]
+ )
+
plug(RateLimiter, :password_reset when action == :password_reset)
- plug(RateLimiter, :account_confirmation_resend when action == :account_confirmation_resend)
@local_mastodon_name "Mastodon-Local"
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.MastodonAPI.NotificationController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+
+ alias Pleroma.Notification
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.MastodonAPI.MastodonAPI
+
++ @oauth_read_actions [:show, :index]
++
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["read:notifications"]} when action in @oauth_read_actions
++ )
++
++ plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action not in @oauth_read_actions)
++
+ # GET /api/v1/notifications
+ def index(%{assigns: %{user: user}} = conn, params) do
+ notifications = MastodonAPI.get_notifications(user, params)
+
+ conn
+ |> add_link_headers(notifications)
+ |> render("index.json", notifications: notifications, for: user)
+ end
+
+ # GET /api/v1/notifications/:id
+ def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with {:ok, notification} <- Notification.get(user, id) do
+ render(conn, "show.json", notification: notification, for: user)
+ else
+ {:error, reason} ->
+ conn
+ |> put_status(:forbidden)
+ |> json(%{"error" => reason})
+ end
+ end
+
+ # POST /api/v1/notifications/clear
+ def clear(%{assigns: %{user: user}} = conn, _params) do
+ Notification.clear(user)
+ json(conn, %{})
+ end
+
+ # POST /api/v1/notifications/dismiss
+ def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
+ with {:ok, _notif} <- Notification.dismiss(user, id) do
+ json(conn, %{})
+ else
+ {:error, reason} ->
+ conn
+ |> put_status(:forbidden)
+ |> json(%{"error" => reason})
+ end
+ end
+
+ # DELETE /api/v1/notifications/destroy_multiple
+ def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do
+ Notification.destroy_multiple(user, ids)
+ json(conn, %{})
+ end
+ end
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.MastodonAPI.ReportController do
++ alias Pleroma.Plugs.OAuthScopesPlug
++
+ use Pleroma.Web, :controller
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
++ plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
++
+ @doc "POST /api/v1/reports"
+ def create(%{assigns: %{user: user}} = conn, params) do
+ with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do
+ render(conn, "show.json", activity: activity)
+ end
+ end
+ end
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
+
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.ScheduledActivity
+ alias Pleroma.Web.MastodonAPI.MastodonAPI
+
+ plug(:assign_scheduled_activity when action != :index)
+
++ @oauth_read_actions [:show, :index]
++
++ 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"
+ def index(%{assigns: %{user: user}} = conn, params) do
+ with scheduled_activities <- MastodonAPI.get_scheduled_activities(user, params) do
+ conn
+ |> add_link_headers(scheduled_activities)
+ |> render("index.json", scheduled_activities: scheduled_activities)
+ end
+ end
+
+ @doc "GET /api/v1/scheduled_statuses/:id"
+ def show(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) do
+ render(conn, "show.json", scheduled_activity: scheduled_activity)
+ end
+
+ @doc "PUT /api/v1/scheduled_statuses/:id"
+ def update(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, params) do
+ with {:ok, scheduled_activity} <- ScheduledActivity.update(scheduled_activity, params) do
+ render(conn, "show.json", scheduled_activity: scheduled_activity)
+ end
+ end
+
+ @doc "DELETE /api/v1/scheduled_statuses/:id"
+ def delete(%{assigns: %{scheduled_activity: scheduled_activity}} = conn, _params) do
+ with {:ok, scheduled_activity} <- ScheduledActivity.delete(scheduled_activity) do
+ render(conn, "show.json", scheduled_activity: scheduled_activity)
+ end
+ end
+
+ defp assign_scheduled_activity(%{assigns: %{user: user}, params: %{"id" => id}} = conn, _) do
+ case ScheduledActivity.get(user, id) do
+ %ScheduledActivity{} = activity -> assign(conn, :scheduled_activity, activity)
+ nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
+ end
+ end
+ end
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.MastodonAPI.StatusController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.MastodonAPI.MastodonAPIController, only: [try_render: 3]
+
+ require Ecto.Query
+
+ alias Pleroma.Activity
+ alias Pleroma.Bookmark
+ alias Pleroma.Object
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Plugs.RateLimiter
+ alias Pleroma.Repo
+ alias Pleroma.ScheduledActivity
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.ScheduledActivityView
+
++ @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
++
++ plug(
++ OAuthScopesPlug,
++ %{@unauthenticated_access | scopes: ["read:statuses"]}
++ when action in [
++ :index,
++ :show,
++ :card,
++ :context
++ ]
++ )
++
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["write:statuses"]}
++ when action in [
++ :create,
++ :delete,
++ :reblog,
++ :unreblog
++ ]
++ )
++
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["write:favourites"]} when action in [:favourite, :unfavourite]
++ )
++
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["write:mutes"]} when action in [:mute_conversation, :unmute_conversation]
++ )
++
++ plug(
++ OAuthScopesPlug,
++ %{@unauthenticated_access | scopes: ["read:accounts"]}
++ when action in [:favourited_by, :reblogged_by]
++ )
++
++ plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action in [:pin, :unpin])
++
++ # Note: scope not present in Mastodon: write:bookmarks
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark]
++ )
++
++ plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
++
+ @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a
+
+ plug(
+ RateLimiter,
+ {:status_id_action, bucket_name: "status_id_action:reblog_unreblog", params: ["id"]}
+ when action in ~w(reblog unreblog)a
+ )
+
+ plug(
+ RateLimiter,
+ {:status_id_action, bucket_name: "status_id_action:fav_unfav", params: ["id"]}
+ when action in ~w(favourite unfavourite)a
+ )
+
+ plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @doc """
+ GET `/api/v1/statuses?ids[]=1&ids[]=2`
+
+ `ids` query param is required
+ """
+ def index(%{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))
+
+ render(conn, "index.json", activities: activities, for: user, as: :activity)
+ end
+
+ @doc """
+ POST /api/v1/statuses
+
+ Creates a scheduled status when `scheduled_at` param is present and it's far enough
+ """
+ def create(
+ %{assigns: %{user: user}} = conn,
+ %{"status" => _, "scheduled_at" => scheduled_at} = params
+ ) do
+ params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
+
+ if ScheduledActivity.far_enough?(scheduled_at) do
+ with {:ok, scheduled_activity} <-
+ ScheduledActivity.create(user, %{"params" => params, "scheduled_at" => scheduled_at}) do
+ conn
+ |> put_view(ScheduledActivityView)
+ |> render("show.json", scheduled_activity: scheduled_activity)
+ end
+ else
+ create(conn, Map.drop(params, ["scheduled_at"]))
+ end
+ end
+
+ @doc """
+ POST /api/v1/statuses
+
+ Creates a regular status
+ """
+ def create(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
+ params = Map.put(params, "in_reply_to_status_id", params["in_reply_to_id"])
+
+ with {:ok, activity} <- CommonAPI.post(user, params) do
+ try_render(conn, "show.json",
+ activity: activity,
+ for: user,
+ as: :activity,
+ with_direct_conversation_id: true
+ )
+ else
+ {:error, message} ->
+ conn
+ |> put_status(:unprocessable_entity)
+ |> json(%{error: message})
+ end
+ end
+
+ def create(%{assigns: %{user: _user}} = conn, %{"media_ids" => _} = params) do
+ create(conn, Map.put(params, "status", ""))
+ end
+
+ @doc "GET /api/v1/statuses/:id"
+ def show(%{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
+ try_render(conn, "show.json", activity: activity, for: user)
+ end
+ end
+
+ @doc "DELETE /api/v1/statuses/:id"
+ def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
+ json(conn, %{})
+ else
+ _e -> render_error(conn, :forbidden, "Can't delete this post")
+ end
+ end
+
+ @doc "POST /api/v1/statuses/:id/reblog"
+ def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+ with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
+ %Activity{} = announce <- Activity.normalize(announce.data) do
+ try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
+ end
+ end
+
+ @doc "POST /api/v1/statuses/:id/unreblog"
+ def unreblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+ with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
+ %Activity{} = activity <- Activity.get_create_by_object_ap_id_with_object(id) do
+ try_render(conn, "show.json", %{activity: activity, for: user, as: :activity})
+ end
+ end
+
+ @doc "POST /api/v1/statuses/:id/favourite"
+ def favourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+ with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
+ %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
+ try_render(conn, "show.json", activity: activity, for: user, as: :activity)
+ end
+ end
+
+ @doc "POST /api/v1/statuses/:id/unfavourite"
+ def unfavourite(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+ with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
+ %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
+ try_render(conn, "show.json", activity: activity, for: user, as: :activity)
+ end
+ end
+
+ @doc "POST /api/v1/statuses/:id/pin"
+ def pin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+ with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do
+ try_render(conn, "show.json", activity: activity, for: user, as: :activity)
+ end
+ end
+
+ @doc "POST /api/v1/statuses/:id/unpin"
+ def unpin(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
+ with {:ok, activity} <- CommonAPI.unpin(ap_id_or_id, user) do
+ try_render(conn, "show.json", activity: activity, for: user, as: :activity)
+ end
+ end
+
+ @doc "POST /api/v1/statuses/:id/bookmark"
+ def bookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+ %User{} = user <- User.get_cached_by_nickname(user.nickname),
+ true <- Visibility.visible_for_user?(activity, user),
+ {:ok, _bookmark} <- Bookmark.create(user.id, activity.id) do
+ try_render(conn, "show.json", activity: activity, for: user, as: :activity)
+ end
+ end
+
+ @doc "POST /api/v1/statuses/:id/unbookmark"
+ def unbookmark(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with %Activity{} = activity <- Activity.get_by_id_with_object(id),
+ %User{} = user <- User.get_cached_by_nickname(user.nickname),
+ true <- Visibility.visible_for_user?(activity, user),
+ {:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do
+ try_render(conn, "show.json", activity: activity, for: user, as: :activity)
+ end
+ end
+
+ @doc "POST /api/v1/statuses/:id/mute"
+ def mute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with %Activity{} = activity <- Activity.get_by_id(id),
+ {:ok, activity} <- CommonAPI.add_mute(user, activity) do
+ try_render(conn, "show.json", activity: activity, for: user, as: :activity)
+ end
+ end
+
+ @doc "POST /api/v1/statuses/:id/unmute"
+ def unmute_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with %Activity{} = activity <- Activity.get_by_id(id),
+ {:ok, activity} <- CommonAPI.remove_mute(user, activity) do
+ try_render(conn, "show.json", activity: activity, for: user, as: :activity)
+ end
+ end
+
+ @doc "GET /api/v1/statuses/:id/card"
+ @deprecated "https://github.com/tootsuite/mastodon/pull/11213"
+ def card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do
+ with %Activity{} = activity <- Activity.get_by_id(status_id),
+ true <- Visibility.visible_for_user?(activity, user) do
+ data = Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
+ render(conn, "card.json", data)
+ else
+ _ -> render_error(conn, :not_found, "Record not found")
+ end
+ end
+
+ @doc "GET /api/v1/statuses/:id/favourited_by"
+ 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
+ users =
+ User
+ |> Ecto.Query.where([u], u.ap_id in ^likes)
+ |> Repo.all()
+ |> Enum.filter(&(not User.blocks?(user, &1)))
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", for: user, users: users, as: :user)
+ else
+ {:visible, false} -> {:error, :not_found}
+ _ -> json(conn, [])
+ end
+ end
+
+ @doc "GET /api/v1/statuses/:id/reblogged_by"
+ 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
+ users =
+ User
+ |> Ecto.Query.where([u], u.ap_id in ^announces)
+ |> Repo.all()
+ |> Enum.filter(&(not User.blocks?(user, &1)))
+
+ conn
+ |> put_view(AccountView)
+ |> render("index.json", for: user, users: users, as: :user)
+ else
+ {:visible, false} -> {:error, :not_found}
+ _ -> json(conn, [])
+ end
+ end
+
+ @doc "GET /api/v1/statuses/:id/context"
+ def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with %Activity{} = activity <- Activity.get_by_id(id) do
+ activities =
+ ActivityPub.fetch_activities_for_context(activity.data["context"], %{
+ "blocking_user" => user,
+ "user" => user,
+ "exclude_id" => activity.id
+ })
+
+ render(conn, "context.json", activity: activity, activities: activities, user: user)
+ end
+ end
+ end
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.MastodonAPI.TimelineController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1]
+
+ alias Pleroma.Pagination
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Web.ActivityPub.ActivityPub
+
++ plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct])
++ plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list)
++
++ plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
++
+ plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
+
+ # GET /api/v1/timelines/home
+ def home(%{assigns: %{user: user}} = conn, params) do
+ params =
+ params
+ |> Map.put("type", ["Create", "Announce"])
+ |> Map.put("blocking_user", user)
+ |> Map.put("muting_user", user)
+ |> Map.put("user", user)
+
+ recipients = [user.ap_id | user.following]
+
+ activities =
+ recipients
+ |> ActivityPub.fetch_activities(params)
+ |> Enum.reverse()
+
+ conn
+ |> add_link_headers(activities)
+ |> render("index.json", activities: activities, for: user, as: :activity)
+ end
+
+ # GET /api/v1/timelines/direct
+ def direct(%{assigns: %{user: user}} = conn, params) do
+ params =
+ params
+ |> Map.put("type", "Create")
+ |> Map.put("blocking_user", user)
+ |> Map.put("user", user)
+ |> Map.put(:visibility, "direct")
+
+ activities =
+ [user.ap_id]
+ |> ActivityPub.fetch_activities_query(params)
+ |> Pagination.fetch_paginated(params)
+
+ conn
+ |> add_link_headers(activities)
+ |> render("index.json", activities: activities, for: user, as: :activity)
+ end
+
+ # GET /api/v1/timelines/public
+ def public(%{assigns: %{user: user}} = conn, params) do
+ local_only = truthy_param?(params["local"])
+
+ activities =
+ params
+ |> Map.put("type", ["Create", "Announce"])
+ |> Map.put("local_only", local_only)
+ |> Map.put("blocking_user", user)
+ |> Map.put("muting_user", user)
+ |> ActivityPub.fetch_public_activities()
+ |> Enum.reverse()
+
+ conn
+ |> add_link_headers(activities, %{"local" => local_only})
+ |> render("index.json", activities: activities, for: user, as: :activity)
+ end
+
+ # GET /api/v1/timelines/tag/:tag
+ def hashtag(%{assigns: %{user: user}} = conn, params) do
+ local_only = truthy_param?(params["local"])
+
+ tags =
+ [params["tag"], params["any"]]
+ |> List.flatten()
+ |> Enum.uniq()
+ |> Enum.filter(& &1)
+ |> Enum.map(&String.downcase(&1))
+
+ tag_all =
+ params
+ |> Map.get("all", [])
+ |> Enum.map(&String.downcase(&1))
+
+ tag_reject =
+ params
+ |> Map.get("none", [])
+ |> Enum.map(&String.downcase(&1))
+
+ activities =
+ params
+ |> Map.put("type", "Create")
+ |> Map.put("local_only", local_only)
+ |> Map.put("blocking_user", user)
+ |> Map.put("muting_user", user)
+ |> Map.put("user", user)
+ |> Map.put("tag", tags)
+ |> Map.put("tag_all", tag_all)
+ |> Map.put("tag_reject", tag_reject)
+ |> ActivityPub.fetch_public_activities()
+ |> Enum.reverse()
+
+ conn
+ |> add_link_headers(activities, %{"local" => local_only})
+ |> render("index.json", activities: activities, for: user, as: :activity)
+ end
+
+ # GET /api/v1/timelines/list/:list_id
+ def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
+ with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
+ params =
+ params
+ |> Map.put("type", "Create")
+ |> Map.put("blocking_user", user)
+ |> Map.put("user", user)
+ |> Map.put("muting_user", user)
+
+ # we must filter the following list for the user to avoid leaking statuses the user
+ # does not actually have permission to see (for more info, peruse security issue #270).
+ activities =
+ following
+ |> Enum.filter(fn x -> x in user.following end)
+ |> ActivityPub.fetch_activities_bounded(following, params)
+ |> Enum.reverse()
+
+ render(conn, "index.json", activities: activities, for: user, as: :activity)
+ else
+ _e -> render_error(conn, :forbidden, "Error.")
+ end
+ end
+ end
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.PleromaAPI.AccountController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
+
+ alias Ecto.Changeset
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.Plugs.RateLimiter
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MastodonAPI.StatusView
+
+ require Pleroma.Constants
+
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["follow", "write:follows"]} when action in [:subscribe, :unsubscribe]
++ )
++
++ plug(
++ OAuthScopesPlug,
++ %{scopes: ["write:accounts"]}
++ # Note: the following actions are not permission-secured in Mastodon:
++ when action in [
++ :update_avatar,
++ :update_banner,
++ :update_background
++ ]
++ )
++
++ 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
++ )
++
+ plug(RateLimiter, :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)
+
+ @doc "POST /api/v1/pleroma/accounts/confirmation_resend"
+ def confirmation_resend(conn, params) do
+ nickname_or_email = params["email"] || params["nickname"]
+
+ with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
+ {:ok, _} <- User.try_send_confirmation_email(user) do
+ json_response(conn, :no_content, "")
+ end
+ end
+
+ @doc "PATCH /api/v1/pleroma/accounts/update_avatar"
+ def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+ {:ok, user} =
+ user
+ |> Changeset.change(%{avatar: nil})
+ |> User.update_and_set_cache()
+
+ CommonAPI.update(user)
+
+ json(conn, %{url: nil})
+ end
+
+ def update_avatar(%{assigns: %{user: user}} = conn, params) do
+ {:ok, %{data: data}} = ActivityPub.upload(params, type: :avatar)
+ {:ok, user} = user |> Changeset.change(%{avatar: data}) |> User.update_and_set_cache()
+ %{"url" => [%{"href" => href} | _]} = data
+
+ CommonAPI.update(user)
+
+ json(conn, %{url: href})
+ end
+
+ @doc "PATCH /api/v1/pleroma/accounts/update_banner"
+ def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
+ new_info = %{"banner" => %{}}
+
+ with {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+ CommonAPI.update(user)
+ json(conn, %{url: nil})
+ end
+ end
+
+ def update_banner(%{assigns: %{user: user}} = conn, params) do
+ with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
+ new_info <- %{"banner" => object.data},
+ {:ok, user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+ CommonAPI.update(user)
+ %{"url" => [%{"href" => href} | _]} = object.data
+
+ json(conn, %{url: href})
+ end
+ end
+
+ @doc "PATCH /api/v1/pleroma/accounts/update_background"
+ def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
+ new_info = %{"background" => %{}}
+
+ with {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+ json(conn, %{url: nil})
+ end
+ end
+
+ def update_background(%{assigns: %{user: user}} = conn, params) do
+ with {:ok, object} <- ActivityPub.upload(params, type: :background),
+ new_info <- %{"background" => object.data},
+ {:ok, _user} <- User.update_info(user, &User.Info.profile_update(&1, new_info)) do
+ %{"url" => [%{"href" => href} | _]} = object.data
+
+ json(conn, %{url: href})
+ end
+ end
+
+ @doc "GET /api/v1/pleroma/accounts/:id/favourites"
+ def favourites(%{assigns: %{account: %{info: %{hide_favorites: true}}}} = conn, _params) do
+ render_error(conn, :forbidden, "Can't get favorites")
+ end
+
+ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
+ params =
+ params
+ |> Map.put("type", "Create")
+ |> Map.put("favorited_by", user.ap_id)
+ |> Map.put("blocking_user", for_user)
+
+ recipients =
+ if for_user do
+ [Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following]
+ else
+ [Pleroma.Constants.as_public()]
+ end
+
+ activities =
+ recipients
+ |> ActivityPub.fetch_activities(params)
+ |> Enum.reverse()
+
+ conn
+ |> add_link_headers(activities)
+ |> put_view(StatusView)
+ |> render("index.json", activities: activities, for: for_user, as: :activity)
+ end
+
+ @doc "POST /api/v1/pleroma/accounts/:id/subscribe"
+ def subscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
+ with {:ok, subscription_target} <- User.subscribe(user, subscription_target) do
+ render(conn, "relationship.json", user: user, target: subscription_target)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "POST /api/v1/pleroma/accounts/:id/unsubscribe"
+ def unsubscribe(%{assigns: %{user: user, account: subscription_target}} = conn, _params) do
+ with {:ok, subscription_target} <- User.unsubscribe(user, subscription_target) do
+ render(conn, "relationship.json", user: user, target: subscription_target)
+ else
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+ end
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.PleromaAPI.MascotController do
+ use Pleroma.Web, :controller
+
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+
++ 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))
+ end
+
+ @doc "PUT /api/v1/pleroma/mascot"
+ def update(%{assigns: %{user: user}} = conn, %{"file" => file}) do
+ with {:ok, object} <- ActivityPub.upload(file, actor: User.ap_id(user)),
+ # Reject if not an image
+ %{type: "image"} = attachment <- render_attachment(object) do
+ # Sure!
+ # Save to the user's info
+ {:ok, _user} = User.update_info(user, &User.Info.mascot_update(&1, attachment))
+
+ json(conn, attachment)
+ else
+ %{type: _} -> render_error(conn, :unsupported_media_type, "mascots can only be images")
+ end
+ end
+
+ defp render_attachment(object) do
+ attachment_data = Map.put(object.data, "id", object.id)
+ Pleroma.Web.MastodonAPI.StatusView.render("attachment.json", %{attachment: attachment_data})
+ end
+ end
alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView
- %{scopes: ["write:conversations"]} when action in [:conversations, :conversation_read]
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["read:statuses"]} when action in [:conversation, :conversation_statuses]
+ )
+
+ plug(
+ OAuthScopesPlug,
++ %{scopes: ["write:conversations"]} when action == :update_conversation
+ )
+
+ plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :read_notification)
+
++ plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
++
def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
with %Participation{} = participation <- Participation.get(participation_id),
true <- user.id == participation.user_id do
--- /dev/null
+ # Pleroma: A lightweight social networking server
+ # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+ # SPDX-License-Identifier: AGPL-3.0-only
+
+ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, fetch_integer_param: 2]
+
++ alias Pleroma.Plugs.OAuthScopesPlug
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+ 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(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
++
+ def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do
+ params =
+ if !params["length"] do
+ params
+ else
+ params
+ |> Map.put("length", fetch_integer_param(params, "length"))
+ end
+
+ with {:ok, activity} <- CommonAPI.listen(user, params) do
+ conn
+ |> put_view(StatusView)
+ |> render("listen.json", %{activity: activity, for: user})
+ else
+ {:error, message} ->
+ conn
+ |> put_status(:bad_request)
+ |> json(%{"error" => message})
+ end
+ end
+
+ def user_scrobbles(%{assigns: %{user: reading_user}} = conn, params) do
+ with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do
+ params = Map.put(params, "type", ["Listen"])
+
+ activities = ActivityPub.fetch_user_abstract_activities(user, reading_user, params)
+
+ conn
+ |> add_link_headers(activities)
+ |> put_view(StatusView)
+ |> render("listens.json", %{
+ activities: activities,
+ for: reading_user,
+ as: :activity
+ })
+ end
+ end
+ end
get("/config/migrate_from_db", AdminAPIController, :migrate_from_db)
get("/moderation_log", AdminAPIController, :list_log)
- pipe_through([:admin_api, :oauth_write])
+
+ post("/reload_emoji", AdminAPIController, :reload_emoji)
+ end
+
+ scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
+ scope "/packs" do
+ # Modifying packs
++ 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)
+ end
+
+ scope "/packs" do
+ # Pack info / downloading
+ get("/", EmojiAPIController, :list_packs)
+ get("/:name/download_shared/", EmojiAPIController, :download_shared)
+ end
end
scope "/", Pleroma.Web.TwitterAPI do
end
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
- pipe_through(:authenticated_api)
+ scope [] do
+ pipe_through(:authenticated_api)
- pipe_through(:oauth_read)
++
+ get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
+ get("/conversations/:id", PleromaAPIController, :conversation)
+ end
+
+ scope [] do
+ pipe_through(:authenticated_api)
- pipe_through(:oauth_write)
++
+ patch("/conversations/:id", PleromaAPIController, :update_conversation)
+ post("/notifications/read", PleromaAPIController, :read_notification)
+
+ patch("/accounts/update_avatar", AccountController, :update_avatar)
+ patch("/accounts/update_banner", AccountController, :update_banner)
+ patch("/accounts/update_background", AccountController, :update_background)
+
+ get("/mascot", MascotController, :show)
+ put("/mascot", MascotController, :update)
+
+ post("/scrobble", ScrobbleController, :new_scrobble)
+ end
+
+ scope [] do
+ pipe_through(:api)
- pipe_through(:oauth_read_or_public)
+ get("/accounts/:id/favourites", AccountController, :favourites)
+ end
+
+ scope [] do
+ pipe_through(:authenticated_api)
- pipe_through(:oauth_follow)
- get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
- get("/conversations/:id", PleromaAPIController, :conversation)
+ post("/accounts/:id/subscribe", AccountController, :subscribe)
+ post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
+ end
- patch("/conversations/:id", PleromaAPIController, :update_conversation)
- post("/notifications/read", PleromaAPIController, :read_notification)
+ post("/accounts/confirmation_resend", AccountController, :confirmation_resend)
+ end
+
+ scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
- pipe_through([:api, :oauth_read_or_public])
++ pipe_through(:api)
+
+ get("/accounts/:id/scrobbles", ScrobbleController, :user_scrobbles)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api)
- get("/accounts/verify_credentials", MastodonAPIController, :verify_credentials)
- scope [] do
- pipe_through(:oauth_read)
++ get("/accounts/verify_credentials", AccountController, :verify_credentials)
- get("/accounts/relationships", MastodonAPIController, :relationships)
- get("/accounts/verify_credentials", AccountController, :verify_credentials)
++ get("/accounts/relationships", AccountController, :relationships)
- get("/accounts/:id/lists", MastodonAPIController, :account_lists)
- get("/accounts/relationships", AccountController, :relationships)
++ get("/accounts/:id/lists", AccountController, :lists)
+ get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
- get("/follow_requests", MastodonAPIController, :follow_requests)
- get("/accounts/:id/lists", AccountController, :lists)
- get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
++ get("/follow_requests", FollowRequestController, :index)
+ get("/blocks", MastodonAPIController, :blocks)
+ get("/mutes", MastodonAPIController, :mutes)
- get("/timelines/home", MastodonAPIController, :home_timeline)
- get("/timelines/direct", MastodonAPIController, :dm_timeline)
- get("/follow_requests", FollowRequestController, :index)
- get("/blocks", MastodonAPIController, :blocks)
- get("/mutes", MastodonAPIController, :mutes)
++ get("/timelines/home", TimelineController, :home)
++ get("/timelines/direct", TimelineController, :direct)
- get("/timelines/home", TimelineController, :home)
- get("/timelines/direct", TimelineController, :direct)
+ get("/favourites", MastodonAPIController, :favourites)
- # Note: not present in Mastodon: bookmarks
+ get("/bookmarks", MastodonAPIController, :bookmarks)
- post("/notifications/clear", MastodonAPIController, :clear_notifications)
- post("/notifications/dismiss", MastodonAPIController, :dismiss_notification)
- get("/notifications", MastodonAPIController, :notifications)
- get("/notifications/:id", MastodonAPIController, :get_notification)
- get("/favourites", MastodonAPIController, :favourites)
- get("/bookmarks", MastodonAPIController, :bookmarks)
++ get("/notifications", NotificationController, :index)
++ get("/notifications/:id", NotificationController, :show)
++ post("/notifications/clear", NotificationController, :clear)
++ post("/notifications/dismiss", NotificationController, :dismiss)
++ delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
- delete(
- "/notifications/destroy_multiple",
- MastodonAPIController,
- :destroy_multiple_notifications
- )
-
- get("/scheduled_statuses", MastodonAPIController, :scheduled_statuses)
- get("/scheduled_statuses/:id", MastodonAPIController, :show_scheduled_status)
- get("/notifications", NotificationController, :index)
- get("/notifications/:id", NotificationController, :show)
- post("/notifications/clear", NotificationController, :clear)
- post("/notifications/dismiss", NotificationController, :dismiss)
- delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
++ get("/scheduled_statuses", ScheduledActivityController, :index)
++ get("/scheduled_statuses/:id", ScheduledActivityController, :show)
- 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)
- get("/domain_blocks", MastodonAPIController, :domain_blocks)
- get("/lists", ListController, :index)
- get("/lists/:id", ListController, :show)
- get("/lists/:id/accounts", ListController, :list_accounts)
++ get("/domain_blocks", DomainBlockController, :index)
- get("/filters", MastodonAPIController, :get_filters)
- get("/domain_blocks", DomainBlockController, :index)
++ get("/filters", FilterController, :index)
- get("/filters", FilterController, :index)
+ get("/suggestions", MastodonAPIController, :suggestions)
- get("/conversations", MastodonAPIController, :conversations)
- post("/conversations/:id/read", MastodonAPIController, :conversation_read)
- get("/suggestions", MastodonAPIController, :suggestions)
++ get("/conversations", ConversationController, :index)
++ post("/conversations/:id/read", ConversationController, :read)
- get("/endorsements", MastodonAPIController, :endorsements)
- get("/conversations", ConversationController, :index)
- post("/conversations/:id/read", ConversationController, :read)
++ get("/endorsements", AccountController, :endorsements)
- patch("/accounts/update_credentials", MastodonAPIController, :update_credentials)
- get("/endorsements", MastodonAPIController, :empty_array)
- end
++ patch("/accounts/update_credentials", AccountController, :update_credentials)
- post("/statuses", MastodonAPIController, :post_status)
- delete("/statuses/:id", MastodonAPIController, :delete_status)
- scope [] do
- pipe_through(:oauth_write)
-
- patch("/accounts/update_credentials", AccountController, :update_credentials)
++ post("/statuses", StatusController, :create)
++ delete("/statuses/:id", StatusController, :delete)
- post("/statuses/:id/reblog", MastodonAPIController, :reblog_status)
- post("/statuses/:id/unreblog", MastodonAPIController, :unreblog_status)
- post("/statuses/:id/favourite", MastodonAPIController, :fav_status)
- post("/statuses/:id/unfavourite", MastodonAPIController, :unfav_status)
- post("/statuses/:id/pin", MastodonAPIController, :pin_status)
- post("/statuses/:id/unpin", MastodonAPIController, :unpin_status)
- # Note: not present in Mastodon: bookmark
- post("/statuses/:id/bookmark", MastodonAPIController, :bookmark_status)
- # Note: not present in Mastodon: unbookmark
- post("/statuses/:id/unbookmark", MastodonAPIController, :unbookmark_status)
- post("/statuses/:id/mute", MastodonAPIController, :mute_conversation)
- post("/statuses/:id/unmute", MastodonAPIController, :unmute_conversation)
- 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)
++ post("/statuses/:id/unfavourite", StatusController, :unfavourite)
++ post("/statuses/:id/pin", StatusController, :pin)
++ post("/statuses/:id/unpin", StatusController, :unpin)
++ post("/statuses/:id/bookmark", StatusController, :bookmark)
++ post("/statuses/:id/unbookmark", StatusController, :unbookmark)
++ post("/statuses/:id/mute", StatusController, :mute_conversation)
++ post("/statuses/:id/unmute", StatusController, :unmute_conversation)
- put("/scheduled_statuses/:id", MastodonAPIController, :update_scheduled_status)
- delete("/scheduled_statuses/:id", MastodonAPIController, :delete_scheduled_status)
- post("/statuses/:id/reblog", StatusController, :reblog)
- post("/statuses/:id/unreblog", StatusController, :unreblog)
- post("/statuses/:id/favourite", StatusController, :favourite)
- post("/statuses/:id/unfavourite", StatusController, :unfavourite)
- post("/statuses/:id/pin", StatusController, :pin)
- post("/statuses/:id/unpin", StatusController, :unpin)
- post("/statuses/:id/bookmark", StatusController, :bookmark)
- post("/statuses/:id/unbookmark", StatusController, :unbookmark)
- 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)
- put("/scheduled_statuses/:id", ScheduledActivityController, :update)
- delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
+ post("/polls/:id/votes", MastodonAPIController, :poll_vote)
- post("/polls/:id/votes", MastodonAPIController, :poll_vote)
+ post("/media", MastodonAPIController, :upload)
+ put("/media/:id", MastodonAPIController, :update_media)
- post("/media", MastodonAPIController, :upload)
- put("/media/:id", MastodonAPIController, :update_media)
+ delete("/lists/:id", ListController, :delete)
+ post("/lists", ListController, :create)
+ put("/lists/:id", ListController, :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", MastodonAPIController, :create_filter)
- get("/filters/:id", MastodonAPIController, :get_filter)
- put("/filters/:id", MastodonAPIController, :update_filter)
- delete("/filters/:id", MastodonAPIController, :delete_filter)
-
- patch("/pleroma/accounts/update_avatar", MastodonAPIController, :update_avatar)
- patch("/pleroma/accounts/update_banner", MastodonAPIController, :update_banner)
- patch("/pleroma/accounts/update_background", MastodonAPIController, :update_background)
-
- get("/pleroma/mascot", MastodonAPIController, :get_mascot)
- put("/pleroma/mascot", MastodonAPIController, :set_mascot)
- 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", MastodonAPIController, :create_report)
- 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", MastodonAPIController, :follow)
- post("/accounts/:id/follow", MastodonAPIController, :follow)
- post("/reports", ReportController, :create)
- end
++ # To do: POST /api/v1/follows is not present in Mastodon - consider removing
++ post("/follows", MastodonAPIController, :follows)
- post("/accounts/:id/unfollow", MastodonAPIController, :unfollow)
- post("/accounts/:id/block", MastodonAPIController, :block)
- post("/accounts/:id/unblock", MastodonAPIController, :unblock)
- post("/accounts/:id/mute", MastodonAPIController, :mute)
- post("/accounts/:id/unmute", MastodonAPIController, :unmute)
- scope [] do
- pipe_through(:oauth_follow)
++ 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", MastodonAPIController, :authorize_follow_request)
- post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request)
- post("/follows", MastodonAPIController, :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", MastodonAPIController, :block_domain)
- delete("/domain_blocks", MastodonAPIController, :unblock_domain)
-
- post("/pleroma/accounts/:id/subscribe", MastodonAPIController, :subscribe)
- post("/pleroma/accounts/:id/unsubscribe", MastodonAPIController, :unsubscribe)
- 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("/domain_blocks", DomainBlockController, :create)
- delete("/domain_blocks", DomainBlockController, :delete)
- end
-
- scope [] do
- pipe_through(:oauth_push)
-
- post("/push/subscription", SubscriptionController, :create)
- get("/push/subscription", SubscriptionController, :get)
- put("/push/subscription", SubscriptionController, :update)
- delete("/push/subscription", SubscriptionController, :delete)
- end
+ post("/push/subscription", SubscriptionController, :create)
+ get("/push/subscription", SubscriptionController, :get)
+ put("/push/subscription", SubscriptionController, :update)
+ delete("/push/subscription", SubscriptionController, :delete)
end
scope "/api/web", Pleroma.Web.MastodonAPI do
get("/accounts/search", SearchController, :account_search)
- post(
- "/pleroma/accounts/confirmation_resend",
- MastodonAPIController,
- :account_confirmation_resend
- )
- scope [] do
- pipe_through(:oauth_read_or_public)
--
- get("/timelines/public", MastodonAPIController, :public_timeline)
- get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)
- get("/timelines/list/:list_id", MastodonAPIController, :list_timeline)
- get("/timelines/public", TimelineController, :public)
- get("/timelines/tag/:tag", TimelineController, :hashtag)
- get("/timelines/list/:list_id", TimelineController, :list)
++ get("/timelines/public", TimelineController, :public)
++ get("/timelines/tag/:tag", TimelineController, :hashtag)
++ get("/timelines/list/:list_id", TimelineController, :list)
- get("/statuses", MastodonAPIController, :get_statuses)
- get("/statuses/:id", MastodonAPIController, :get_status)
- get("/statuses/:id/context", MastodonAPIController, :get_context)
- get("/statuses", StatusController, :index)
- get("/statuses/:id", StatusController, :show)
- get("/statuses/:id/context", StatusController, :context)
++ get("/statuses", StatusController, :index)
++ get("/statuses/:id", StatusController, :show)
++ get("/statuses/:id/context", StatusController, :context)
- get("/polls/:id", MastodonAPIController, :get_poll)
+ get("/polls/:id", MastodonAPIController, :get_poll)
- get("/accounts/:id/statuses", MastodonAPIController, :user_statuses)
- get("/accounts/:id/followers", MastodonAPIController, :followers)
- get("/accounts/:id/following", MastodonAPIController, :following)
- get("/accounts/:id", MastodonAPIController, :user)
- get("/accounts/:id/statuses", AccountController, :statuses)
- get("/accounts/:id/followers", AccountController, :followers)
- get("/accounts/:id/following", AccountController, :following)
- get("/accounts/:id", AccountController, :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
+ get("/search", SearchController, :search)
-
- get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
end
scope "/api/v2", Pleroma.Web.MastodonAPI do
scope "/", Pleroma.Web.ActivityPub do
pipe_through([:activitypub_client])
- scope [] do
- pipe_through(:oauth_read)
- get("/api/ap/whoami", ActivityPubController, :whoami)
- get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
- end
+ get("/api/ap/whoami", ActivityPubController, :whoami)
+ get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
+
- scope [] do
- pipe_through(:oauth_write)
- post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
- post("/api/ap/upload_media", ActivityPubController, :upload_media)
- end
+ post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
++ post("/api/ap/upload_media", ActivityPubController, :upload_media)
+
- scope [] do
- pipe_through(:oauth_read_or_public)
- get("/users/:nickname/followers", ActivityPubController, :followers)
- get("/users/:nickname/following", ActivityPubController, :following)
- end
+ get("/users/:nickname/followers", ActivityPubController, :followers)
+ get("/users/:nickname/following", ActivityPubController, :following)
end
scope "/", Pleroma.Web.ActivityPub do
defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller
- alias Ecto.Changeset
alias Pleroma.Notification
+ alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TokenView
require Logger
+ plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
+
++ plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
++
action_fallback(:errors)
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do