Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop

This commit is contained in:
sadposter 2019-10-10 10:28:16 +01:00
commit 58f9ce0deb
209 changed files with 2743 additions and 1052 deletions

View file

@ -1,3 +1,3 @@
[ [
inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs"]
] ]

View file

@ -12,6 +12,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Pleroma API: `GET /api/v1/pleroma/accounts/:id/scrobbles` to get a list of recently scrobbled items - Pleroma API: `GET /api/v1/pleroma/accounts/:id/scrobbles` to get a list of recently scrobbled items
- Pleroma API: `POST /api/v1/pleroma/scrobble` to scrobble a media item - Pleroma API: `POST /api/v1/pleroma/scrobble` to scrobble a media item
- Mastodon API: Add `upload_limit`, `avatar_upload_limit`, `background_upload_limit`, and `banner_upload_limit` to `/api/v1/instance` - Mastodon API: Add `upload_limit`, `avatar_upload_limit`, `background_upload_limit`, and `banner_upload_limit` to `/api/v1/instance`
- Mastodon API: Add `pleroma.unread_conversation_count` to the Account entity
- OAuth: support for hierarchical permissions / [Mastodon 2.4.3 OAuth permissions](https://docs.joinmastodon.org/api/permissions/)
- Authentication: Added rate limit for password-authorized actions / login existence checks
- Metadata Link: Atom syndication Feed
- Mix task to re-count statuses for all users (`mix pleroma.count_statuses`)
### Changed ### Changed
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7) - **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
@ -21,11 +26,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Admin API: Return `total` when querying for reports - Admin API: Return `total` when querying for reports
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`) - Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
- Admin API: Return link alongside with token on password reset - Admin API: Return link alongside with token on password reset
- MRF (Simple Policy): Also use `:accept`/`:reject` on the actors rather than only their activities
- OStatus: Extract RSS functionality
- Mastodon API: Add `pleroma.direct_conversation_id` to the status endpoint (`GET /api/v1/statuses/:id`)
### Fixed ### Fixed
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`) - Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
- Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname` - Mastodon API: Inability to get some local users by nickname in `/api/v1/accounts/:id_or_nickname`
- Added `:instance, extended_nickname_format` setting to the default config - Added `:instance, extended_nickname_format` setting to the default config
- Report emails now include functional links to profiles of remote user accounts
## [1.1.0] - 2019-??-?? ## [1.1.0] - 2019-??-??
### Security ### Security
@ -78,6 +87,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- ActivityPub: Fix `/users/:nickname/inbox` crashing without an authenticated user - ActivityPub: Fix `/users/:nickname/inbox` crashing without an authenticated user
- MRF: fix ability to follow a relay when AntiFollowbotPolicy was enabled - MRF: fix ability to follow a relay when AntiFollowbotPolicy was enabled
- Mastodon API: Blocks are now treated consistently between the Streaming API and the Timeline APIs - Mastodon API: Blocks are now treated consistently between the Streaming API and the Timeline APIs
- Mastodon API: `exclude_replies` is correctly handled again.
### Added ### Added
- Expiring/ephemeral activites. All activities can have expires_at value set, which controls when they should be deleted automatically. - Expiring/ephemeral activites. All activities can have expires_at value set, which controls when they should be deleted automatically.

View file

@ -409,7 +409,8 @@
providers: [ providers: [
Pleroma.Web.Metadata.Providers.OpenGraph, Pleroma.Web.Metadata.Providers.OpenGraph,
Pleroma.Web.Metadata.Providers.TwitterCard, Pleroma.Web.Metadata.Providers.TwitterCard,
Pleroma.Web.Metadata.Providers.RelMe Pleroma.Web.Metadata.Providers.RelMe,
Pleroma.Web.Metadata.Providers.Feed
], ],
unfurl_nsfw: false unfurl_nsfw: false
@ -588,7 +589,7 @@
config :http_signatures, config :http_signatures,
adapter: Pleroma.Signature adapter: Pleroma.Signature
config :pleroma, :rate_limit, nil config :pleroma, :rate_limit, authentication: {60_000, 15}
config :pleroma, Pleroma.ActivityExpiration, enabled: true config :pleroma, Pleroma.ActivityExpiration, enabled: true

View file

@ -2290,7 +2290,8 @@
group: :pleroma, group: :pleroma,
key: :rate_limit, key: :rate_limit,
type: :group, type: :group,
description: "Rate limit settings. This is an advanced feature and disabled by default.", description:
"Rate limit settings. This is an advanced feature enabled only for :authentication by default.",
children: [ children: [
%{ %{
key: :search, key: :search,
@ -2329,6 +2330,12 @@
description: description:
"for fav / unfav or reblog / unreblog actions on the same status by the same user", "for fav / unfav or reblog / unreblog actions on the same status by the same user",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
},
%{
key: :authentication,
type: [:tuple, {:list, :tuple}],
description: "for authentication create / password check / user existence check requests",
suggestions: [{60_000, 15}]
} }
] ]
}, },

View file

@ -56,6 +56,7 @@ Has these additional fields under the `pleroma` object:
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials` - `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials` - `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
- `deactivated`: boolean, true when the user is deactivated - `deactivated`: boolean, true when the user is deactivated
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
### Source ### Source

View file

@ -475,6 +475,7 @@ config :pleroma, :workers,
* Pleroma.Web.Metadata.Providers.OpenGraph * Pleroma.Web.Metadata.Providers.OpenGraph
* Pleroma.Web.Metadata.Providers.TwitterCard * Pleroma.Web.Metadata.Providers.TwitterCard
* Pleroma.Web.Metadata.Providers.RelMe - add links from user bio with rel=me into the `<header>` as `<link rel=me>` * Pleroma.Web.Metadata.Providers.RelMe - add links from user bio with rel=me into the `<header>` as `<link rel=me>`
* Pleroma.Web.Metadata.Providers.Feed - add a link to a user's Atom feed into the `<header>` as `<link rel=alternate>`
* `unfurl_nsfw`: If set to `true` nsfw attachments will be shown in previews * `unfurl_nsfw`: If set to `true` nsfw attachments will be shown in previews
## :rich_media ## :rich_media

View file

@ -0,0 +1,22 @@
defmodule Mix.Tasks.Pleroma.CountStatuses do
@shortdoc "Re-counts statuses for all users"
use Mix.Task
alias Pleroma.User
import Ecto.Query
def run([]) do
Mix.Pleroma.start_pleroma()
stream =
User
|> where(local: true)
|> Pleroma.Repo.stream()
Pleroma.Repo.transaction(fn ->
Enum.each(stream, &User.update_note_count/1)
end)
Mix.Pleroma.shell_info("Done")
end
end

View file

@ -67,6 +67,8 @@ def create_or_bump_for(activity, opts \\ []) do
participations = participations =
Enum.map(users, fn user -> Enum.map(users, fn user ->
User.increment_unread_conversation_count(conversation, user)
{:ok, participation} = {:ok, participation} =
Participation.create_for_user_and_conversation(user, conversation, opts) Participation.create_for_user_and_conversation(user, conversation, opts)

View file

@ -52,6 +52,15 @@ def mark_as_read(participation) do
participation participation
|> read_cng(%{read: true}) |> read_cng(%{read: true})
|> Repo.update() |> Repo.update()
|> case do
{:ok, participation} ->
participation = Repo.preload(participation, :user)
User.set_unread_conversation_count(participation.user)
{:ok, participation}
error ->
error
end
end end
def mark_as_unread(participation) do def mark_as_unread(participation) do
@ -135,4 +144,12 @@ def set_recipients(participation, user_ids) do
{:ok, Repo.preload(participation, :recipients, force: true)} {:ok, Repo.preload(participation, :recipients, force: true)}
end end
def unread_conversation_count_for_user(user) do
from(p in __MODULE__,
where: p.user_id == ^user.id,
where: not p.read,
select: %{count: count(p.id)}
)
end
end end

View file

@ -17,7 +17,7 @@ defp instance_notify_email do
end end
defp user_url(user) do defp user_url(user) do
Helpers.o_status_url(Pleroma.Web.Endpoint, :feed_redirect, user.nickname) Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id)
end end
def report(to, reporter, account, statuses, comment) do def report(to, reporter, account, statuses, comment) do

View file

@ -6,6 +6,8 @@ defmodule Pleroma.Plugs.OAuthScopesPlug do
import Plug.Conn import Plug.Conn
import Pleroma.Web.Gettext import Pleroma.Web.Gettext
alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
@behaviour Plug @behaviour Plug
def init(%{scopes: _} = options), do: options def init(%{scopes: _} = options), do: options
@ -13,24 +15,26 @@ def init(%{scopes: _} = options), do: options
def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
op = options[:op] || :| op = options[:op] || :|
token = assigns[:token] token = assigns[:token]
matched_scopes = token && filter_descendants(scopes, token.scopes)
cond do cond do
is_nil(token) -> is_nil(token) ->
maybe_perform_instance_privacy_check(conn, options)
op == :| && Enum.any?(matched_scopes) ->
conn conn
op == :| && scopes -- token.scopes != scopes -> op == :& && matched_scopes == scopes ->
conn
op == :& && scopes -- token.scopes == [] ->
conn conn
options[:fallback] == :proceed_unauthenticated -> options[:fallback] == :proceed_unauthenticated ->
conn conn
|> assign(:user, nil) |> assign(:user, nil)
|> assign(:token, nil) |> assign(:token, nil)
|> maybe_perform_instance_privacy_check(options)
true -> true ->
missing_scopes = scopes -- token.scopes missing_scopes = scopes -- matched_scopes
permissions = Enum.join(missing_scopes, " #{op} ") permissions = Enum.join(missing_scopes, " #{op} ")
error_message = error_message =
@ -42,4 +46,25 @@ def call(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do
|> halt() |> halt()
end end
end end
@doc "Filters descendants of supported scopes"
def filter_descendants(scopes, supported_scopes) do
Enum.filter(
scopes,
fn scope ->
Enum.find(
supported_scopes,
&(scope == &1 || String.starts_with?(scope, &1 <> ":"))
)
end
)
end
defp maybe_perform_instance_privacy_check(%Plug.Conn{} = conn, options) do
if options[:skip_instance_privacy_check] do
conn
else
EnsurePublicOrAuthenticatedPlug.call(conn, [])
end
end
end end

View file

@ -48,7 +48,7 @@ def refetch_public_key(conn) do
end end
def sign(%User{} = user, headers) do def sign(%User{} = user, headers) do
with {:ok, %{info: %{keys: keys}}} <- User.ensure_keys_present(user), with {:ok, %{keys: keys}} <- User.ensure_keys_present(user),
{:ok, private_key, _} <- Keys.keys_from_pem(keys) do {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers) HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
end end

View file

@ -11,6 +11,7 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Ecto.Multi alias Ecto.Multi
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Conversation.Participation
alias Pleroma.Delivery alias Pleroma.Delivery
alias Pleroma.Keys alias Pleroma.Keys
alias Pleroma.Notification alias Pleroma.Notification
@ -50,6 +51,7 @@ defmodule Pleroma.User do
field(:password_hash, :string) field(:password_hash, :string)
field(:password, :string, virtual: true) field(:password, :string, virtual: true)
field(:password_confirmation, :string, virtual: true) field(:password_confirmation, :string, virtual: true)
field(:keys, :string)
field(:following, {:array, :string}, default: []) field(:following, {:array, :string}, default: [])
field(:ap_id, :string) field(:ap_id, :string)
field(:avatar, :map) field(:avatar, :map)
@ -842,6 +844,61 @@ def maybe_update_following_count(%User{local: false} = user) do
def maybe_update_following_count(user), do: user def maybe_update_following_count(user), do: user
def set_unread_conversation_count(%User{local: true} = user) do
unread_query = Participation.unread_conversation_count_for_user(user)
User
|> join(:inner, [u], p in subquery(unread_query))
|> update([u, p],
set: [
info:
fragment(
"jsonb_set(?, '{unread_conversation_count}', ?::varchar::jsonb, true)",
u.info,
p.count
)
]
)
|> where([u], u.id == ^user.id)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
end
def set_unread_conversation_count(_), do: :noop
def increment_unread_conversation_count(conversation, %User{local: true} = user) do
unread_query =
Participation.unread_conversation_count_for_user(user)
|> where([p], p.conversation_id == ^conversation.id)
User
|> join(:inner, [u], p in subquery(unread_query))
|> update([u, p],
set: [
info:
fragment(
"jsonb_set(?, '{unread_conversation_count}', (coalesce((?->>'unread_conversation_count')::int, 0) + 1)::varchar::jsonb, true)",
u.info,
u.info
)
]
)
|> where([u], u.id == ^user.id)
|> where([u, p], p.count == 0)
|> select([u], u)
|> Repo.update_all([])
|> case do
{1, [user]} -> set_cache(user)
_ -> {:error, user}
end
end
def increment_unread_conversation_count(_, _), do: :noop
def remove_duplicated_following(%User{following: following} = user) do def remove_duplicated_following(%User{following: following} = user) do
uniq_following = Enum.uniq(following) uniq_following = Enum.uniq(following)
@ -1498,11 +1555,14 @@ def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
} }
end end
def ensure_keys_present(%{info: %{keys: keys}} = user) when not is_nil(keys), do: {:ok, user} def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
def ensure_keys_present(%User{} = user) do def ensure_keys_present(%User{} = user) do
with {:ok, pem} <- Keys.generate_rsa_pem() do with {:ok, pem} <- Keys.generate_rsa_pem() do
update_info(user, &User.Info.set_keys(&1, pem)) user
|> cast(%{keys: pem}, [:keys])
|> validate_required([:keys])
|> update_and_set_cache()
end end
end end

View file

@ -47,6 +47,7 @@ defmodule Pleroma.User.Info do
field(:hide_followers, :boolean, default: false) field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false) field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true) field(:hide_favorites, :boolean, default: true)
field(:unread_conversation_count, :integer, default: 0)
field(:pinned_activities, {:array, :string}, default: []) field(:pinned_activities, {:array, :string}, default: [])
field(:email_notifications, :map, default: %{"digest" => false}) field(:email_notifications, :map, default: %{"digest" => false})
field(:mascot, :map, default: nil) field(:mascot, :map, default: nil)

View file

@ -780,8 +780,8 @@ defp restrict_media(query, _), do: query
defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
from( from(
activity in query, [_activity, object] in query,
where: fragment("?->'object'->>'inReplyTo' is null", activity.data) where: fragment("?->>'inReplyTo' is null", object.data)
) )
end end

View file

@ -168,7 +168,9 @@ def filter(%{"id" => actor, "type" => obj_type} = object)
when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do
actor_info = URI.parse(actor) actor_info = URI.parse(actor)
with {:ok, object} <- check_avatar_removal(actor_info, object), with {:ok, object} <- check_accept(actor_info, object),
{:ok, object} <- check_reject(actor_info, object),
{:ok, object} <- check_avatar_removal(actor_info, object),
{:ok, object} <- check_banner_removal(actor_info, object) do {:ok, object} <- check_banner_removal(actor_info, object) do
{:ok, object} {:ok, object}
else else

View file

@ -580,7 +580,7 @@ def handle_incoming(
) do ) do
with actor <- Containment.get_actor(data), with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor), {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- get_obj_helper(object_id), {:ok, object} <- get_embedded_obj_helper(object_id, actor),
public <- Visibility.is_public?(data), public <- Visibility.is_public?(data),
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
{:ok, activity} {:ok, activity}
@ -782,6 +782,29 @@ def get_obj_helper(id, options \\ []) do
end end
end end
@spec get_embedded_obj_helper(String.t() | Object.t(), User.t()) :: {:ok, Object.t()} | nil
def get_embedded_obj_helper(%{"attributedTo" => attributed_to, "id" => object_id} = data, %User{
ap_id: ap_id
})
when attributed_to == ap_id do
with {:ok, activity} <-
handle_incoming(%{
"type" => "Create",
"to" => data["to"],
"cc" => data["cc"],
"actor" => attributed_to,
"object" => data
}) do
{:ok, Object.normalize(activity)}
else
_ -> get_obj_helper(object_id)
end
end
def get_embedded_obj_helper(object_id, _) do
get_obj_helper(object_id)
end
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
with false <- String.starts_with?(in_reply_to, "http"), with false <- String.starts_with?(in_reply_to, "http"),
{:ok, %{data: replied_to_object}} <- get_obj_helper(in_reply_to) do {:ok, %{data: replied_to_object}} <- get_obj_helper(in_reply_to) do

View file

@ -33,7 +33,7 @@ def render("endpoints.json", _), do: %{}
def render("service.json", %{user: user}) do def render("service.json", %{user: user}) do
{:ok, user} = User.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Keys.keys_from_pem(user.info.keys) {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key]) public_key = :public_key.pem_encode([public_key])
@ -69,7 +69,7 @@ def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
def render("user.json", %{user: user}) do def render("user.json", %{user: user}) do
{:ok, user} = User.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Keys.keys_from_pem(user.info.keys) {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key]) public_key = :public_key.pem_encode([public_key])

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.ModerationLog alias Pleroma.ModerationLog
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserInviteToken alias Pleroma.UserInviteToken
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
@ -26,6 +27,67 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
require Logger require Logger
plug(
OAuthScopesPlug,
%{scopes: ["read:accounts"]}
when action in [:list_users, :user_show, :right_get, :invites]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]}
when action in [
:get_invite_token,
:revoke_invite,
:email_invite,
:get_password_reset,
:user_follow,
:user_unfollow,
:user_delete,
:users_create,
:user_toggle_activation,
:tag_users,
:untag_users,
:right_add,
:right_delete,
:set_activation_status
]
)
plug(
OAuthScopesPlug,
%{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
)
plug(
OAuthScopesPlug,
%{scopes: ["write:reports"]}
when action in [:report_update_state, :report_respond]
)
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"]} when action == :list_user_statuses
)
plug(
OAuthScopesPlug,
%{scopes: ["write:statuses"]}
when action in [:status_update, :status_delete]
)
plug(
OAuthScopesPlug,
%{scopes: ["read"]}
when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
)
plug(
OAuthScopesPlug,
%{scopes: ["write"]}
when action in [:relay_follow, :relay_unfollow, :config_update]
)
@users_page_size 50 @users_page_size 50
action_fallback(:errors) action_fallback(:errors)

View file

@ -0,0 +1,63 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.FeedController do
use Pleroma.Web, :controller
alias Fallback.RedirectController
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController
plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect])
action_fallback(:errors)
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
RedirectController.redirector_with_meta(conn, %{user: user})
end
end
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :user)
end
def feed_redirect(conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
redirect(conn, external: "#{feed_url(conn, :feed, user.nickname)}.atom")
end
end
def feed(conn, %{"nickname" => nickname} = params) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
query_params =
params
|> Map.take(["max_id"])
|> Map.put("type", ["Create"])
|> Map.put("whole_db", true)
|> Map.put("actor_id", user.ap_id)
activities =
query_params
|> ActivityPub.fetch_public_activities()
|> Enum.reverse()
conn
|> put_resp_content_type("application/atom+xml")
|> render("feed.xml", user: user, activities: activities)
end
end
def errors(conn, {:error, :not_found}) do
render_error(conn, :not_found, "Not found")
end
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
def errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong")
end
end

View file

@ -0,0 +1,77 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Feed.FeedView do
use Phoenix.HTML
use Pleroma.Web, :view
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.MediaProxy
require Pleroma.Constants
def most_recent_update(activities, user) do
(List.first(activities) || user).updated_at
|> NaiveDateTime.to_iso8601()
end
def logo(user) do
user
|> User.avatar_url()
|> MediaProxy.url()
end
def last_activity(activities) do
List.last(activities)
end
def activity_object(activity) do
Object.normalize(activity)
end
def activity_object_data(activity) do
activity
|> activity_object()
|> Map.get(:data)
end
def activity_content(activity) do
content = activity_object_data(activity)["content"]
content
|> String.replace(~r/[\n\r]/, "")
|> escape()
end
def activity_context(activity) do
activity.data["context"]
end
def attachment_href(attachment) do
attachment["url"]
|> hd()
|> Map.get("href")
end
def attachment_type(attachment) do
attachment["url"]
|> hd()
|> Map.get("mediaType")
end
def get_href(id) do
with %Object{data: %{"external_url" => external_url}} <- Object.get_cached_by_ap_id(id) do
external_url
else
_e -> id
end
end
def escape(html) do
html
|> html_escape()
|> safe_to_string()
end
end

View file

@ -5,8 +5,20 @@
defmodule Pleroma.Web.MastoFEController do defmodule Pleroma.Web.MastoFEController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User 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(
OAuthScopesPlug,
%{scopes: ["read"], fallback: :proceed_unauthenticated, skip_instance_privacy_check: true}
when action == :index
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :index)
@doc "GET /web/*path" @doc "GET /web/*path"
def index(%{assigns: %{user: user}} = conn, _params) do def index(%{assigns: %{user: user}} = conn, _params) do
token = get_session(conn, :oauth_token) token = get_session(conn, :oauth_token)

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3] only: [add_link_headers: 2, truthy_param?: 1, assign_account_by_id: 2, json_response: 3]
alias Pleroma.Emoji alias Pleroma.Emoji
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
@ -19,6 +20,49 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TwitterAPI 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", "read:blocks"]} when action == :blocks
)
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:blocks"]} when action in [:block, :unblock]
)
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]
)
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 != :create
)
@relations [:follow, :unfollow] @relations [:follow, :unfollow]
@needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a @needs_account ~W(followers following lists follow unfollow mute unmute block unblock)a
@ -105,6 +149,17 @@ def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
|> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text)) |> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
|> Enum.dedup() |> Enum.dedup()
params =
if Map.has_key?(params, "fields_attributes") do
Map.update!(params, "fields_attributes", fn fields ->
fields
|> normalize_fields_attributes()
|> Enum.filter(fn %{"name" => n} -> n != "" end)
end)
else
params
end
info_params = info_params =
[ [
:no_rich_text, :no_rich_text,
@ -122,12 +177,12 @@ def update_credentials(%{assigns: %{user: original_user}} = conn, params) do
add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)}) add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
end) end)
|> add_if_present(params, "default_scope", :default_scope) |> add_if_present(params, "default_scope", :default_scope)
|> add_if_present(params, "fields", :fields, fn fields -> |> add_if_present(params, "fields_attributes", :fields, fn fields ->
fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end) fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
{:ok, fields} {:ok, fields}
end) end)
|> add_if_present(params, "fields", :raw_fields) |> add_if_present(params, "fields_attributes", :raw_fields)
|> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value -> |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
{:ok, Map.merge(user.info.pleroma_settings_store, value)} {:ok, Map.merge(user.info.pleroma_settings_store, value)}
end) end)
@ -168,6 +223,14 @@ defp add_if_present(map, params, params_field, map_field, value_function \\ &{:o
end end
end end
defp normalize_fields_attributes(fields) do
if Enum.all?(fields, &is_tuple/1) do
Enum.map(fields, fn {_, v} -> v end)
else
fields
end
end
@doc "GET /api/v1/accounts/relationships" @doc "GET /api/v1/accounts/relationships"
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
targets = User.get_all_by_ids(List.wrap(id)) targets = User.get_all_by_ids(List.wrap(id))
@ -301,4 +364,30 @@ def unblock(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
{:error, message} -> json_response(conn, :forbidden, %{error: message}) {:error, message} -> json_response(conn, :forbidden, %{error: message})
end end
end end
@doc "POST /api/v1/follows"
def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
{_, true} <- {:followed, follower.id != followed.id},
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
render(conn, "show.json", user: followed, for: follower)
else
{:followed, _} -> {:error, :not_found}
{:error, message} -> json_response(conn, :forbidden, %{error: message})
end
end
@doc "GET /api/v1/mutes"
def mutes(%{assigns: %{user: user}} = conn, _) do
render(conn, "index.json", users: User.muted_users(user), for: user, as: :user)
end
@doc "GET /api/v1/blocks"
def blocks(%{assigns: %{user: user}} = conn, _) do
render(conn, "index.json", users: User.blocked_users(user), for: user, as: :user)
end
@doc "GET /api/v1/endorsements"
def endorsements(conn, params),
do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
end end

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.AppController do defmodule Pleroma.Web.MastodonAPI.AppController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Scopes alias Pleroma.Web.OAuth.Scopes
@ -12,6 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
@local_mastodon_name "Mastodon-Local" @local_mastodon_name "Mastodon-Local"
@doc "POST /api/v1/apps" @doc "POST /api/v1/apps"

View file

@ -8,10 +8,16 @@ defmodule Pleroma.Web.MastodonAPI.ConversationController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Repo alias Pleroma.Repo
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) 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" @doc "GET /api/v1/conversations"
def index(%{assigns: %{user: user}} = conn, params) do def index(%{assigns: %{user: user}} = conn, params) do
participations = Participation.for_user_with_last_activity_id(user, params) participations = Participation.for_user_with_last_activity_id(user, params)

View file

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

View file

@ -6,6 +6,18 @@ defmodule Pleroma.Web.MastodonAPI.FilterController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Filter 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" @doc "GET /api/v1/filters"
def index(%{assigns: %{user: user}} = conn, _) do def index(%{assigns: %{user: user}} = conn, _) do

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.FollowRequestController do defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
@ -13,6 +14,15 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestController do
action_fallback(:errors) action_fallback(:errors)
plug(OAuthScopesPlug, %{scopes: ["follow", "read:follows"]} when action == :index)
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]} when action != :index
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/follow_requests" @doc "GET /api/v1/follow_requests"
def index(%{assigns: %{user: followed}} = conn, _params) do def index(%{assigns: %{user: followed}} = conn, _params) do
follow_requests = User.get_follow_requests(followed) follow_requests = User.get_follow_requests(followed)

View file

@ -5,11 +5,22 @@
defmodule Pleroma.Web.MastodonAPI.ListController do defmodule Pleroma.Web.MastodonAPI.ListController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
plug(:list_by_id_and_user when action not in [:index, :create]) plug(:list_by_id_and_user when action not in [:index, :create])
plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action in [:index, :show, :list_accounts])
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) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
# GET /api/v1/lists # GET /api/v1/lists

View file

@ -5,86 +5,10 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Bookmark
alias Pleroma.Pagination
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView
require Logger require Logger
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
{_, true} <- {:followed, follower.id != followed.id},
{:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
conn
|> put_view(AccountView)
|> render("show.json", %{user: followed, for: follower})
else
{:followed, _} ->
{:error, :not_found}
{:error, message} ->
conn
|> put_status(:forbidden)
|> json(%{error: message})
end
end
def mutes(%{assigns: %{user: user}} = conn, _) do
with muted_accounts <- User.muted_users(user) do
res = AccountView.render("index.json", users: muted_accounts, for: user, as: :user)
json(conn, res)
end
end
def blocks(%{assigns: %{user: user}} = conn, _) do
with blocked_accounts <- User.blocked_users(user) do
res = AccountView.render("index.json", users: blocked_accounts, for: user, as: :user)
json(conn, res)
end
end
def favourites(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", "Create")
|> Map.put("favorited_by", user.ap_id)
|> Map.put("blocking_user", user)
activities =
ActivityPub.fetch_activities([], params)
|> Enum.reverse()
conn
|> add_link_headers(activities)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
def bookmarks(%{assigns: %{user: user}} = conn, params) do
user = User.get_cached_by_id(user.id)
bookmarks =
Bookmark.for_user_query(user.id)
|> Pagination.fetch_paginated(params)
activities =
bookmarks
|> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
conn
|> add_link_headers(bookmarks)
|> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
# Stubs for unimplemented mastodon api # Stubs for unimplemented mastodon api
# #
def empty_array(conn, _) do def empty_array(conn, _) do

View file

@ -6,12 +6,17 @@ defmodule Pleroma.Web.MastodonAPI.MediaController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
plug(OAuthScopesPlug, %{scopes: ["write:media"]})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "POST /api/v1/media" @doc "POST /api/v1/media"
def create(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do def create(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
with {:ok, object} <- with {:ok, object} <-

View file

@ -8,8 +8,20 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.MastodonAPI.MastodonAPI 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)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
# GET /api/v1/notifications # GET /api/v1/notifications
def index(%{assigns: %{user: user}} = conn, params) do def index(%{assigns: %{user: user}} = conn, params) do
notifications = MastodonAPI.get_notifications(user, params) notifications = MastodonAPI.get_notifications(user, params)

View file

@ -9,11 +9,21 @@ defmodule Pleroma.Web.MastodonAPI.PollController do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(
OAuthScopesPlug,
%{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} when action == :show
)
plug(OAuthScopesPlug, %{scopes: ["write:statuses"]} when action == :vote)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/polls/:id" @doc "GET /api/v1/polls/:id"
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60), with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),

View file

@ -3,10 +3,16 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.ReportController do defmodule Pleroma.Web.MastodonAPI.ReportController do
alias Pleroma.Plugs.OAuthScopesPlug
use Pleroma.Web, :controller use Pleroma.Web, :controller
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "POST /api/v1/reports" @doc "POST /api/v1/reports"
def create(%{assigns: %{user: user}} = conn, params) do def create(%{assigns: %{user: user}} = conn, params) do
with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do

View file

@ -7,11 +7,19 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.ScheduledActivity alias Pleroma.ScheduledActivity
alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonAPI
plug(:assign_scheduled_activity when action != :index) 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) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
@doc "GET /api/v1/scheduled_statuses" @doc "GET /api/v1/scheduled_statuses"

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
@ -15,6 +16,12 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
require Logger require Logger
# Note: Mastodon doesn't allow unauthenticated access (requires read:accounts / read:search)
plug(OAuthScopesPlug, %{scopes: ["read:search"], fallback: :proceed_unauthenticated})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
plug(RateLimiter, :search when action in [:search, :search2, :account_search]) plug(RateLimiter, :search when action in [:search, :search2, :account_search])
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do

View file

@ -5,13 +5,14 @@
defmodule Pleroma.Web.MastodonAPI.StatusController do defmodule Pleroma.Web.MastodonAPI.StatusController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [try_render: 3] import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2]
require Ecto.Query require Ecto.Query
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Bookmark alias Pleroma.Bookmark
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.ScheduledActivity alias Pleroma.ScheduledActivity
@ -22,6 +23,61 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.ScheduledActivityView alias Pleroma.Web.MastodonAPI.ScheduledActivityView
@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: ["read:favourites"]} when action == :favourites)
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: read:bookmarks
plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :bookmarks)
# 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 @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a
plug( plug(
@ -111,7 +167,11 @@ def create(%{assigns: %{user: _user}} = conn, %{"media_ids" => _} = params) do
def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id), with %Activity{} = activity <- Activity.get_by_id_with_object(id),
true <- Visibility.visible_for_user?(activity, user) do true <- Visibility.visible_for_user?(activity, user) do
try_render(conn, "show.json", activity: activity, for: user) try_render(conn, "show.json",
activity: activity,
for: user,
with_direct_conversation_id: true
)
end end
end end
@ -283,4 +343,39 @@ def context(%{assigns: %{user: user}} = conn, %{"id" => id}) do
render(conn, "context.json", activity: activity, activities: activities, user: user) render(conn, "context.json", activity: activity, activities: activities, user: user)
end end
end end
@doc "GET /api/v1/favourites"
def favourites(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", "Create")
|> Map.put("favorited_by", user.ap_id)
|> Map.put("blocking_user", user)
activities =
ActivityPub.fetch_activities([], params)
|> Enum.reverse()
conn
|> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity)
end
@doc "GET /api/v1/bookmarks"
def bookmarks(%{assigns: %{user: user}} = conn, params) do
user = User.get_cached_by_id(user.id)
bookmarks =
user.id
|> Bookmark.for_user_query()
|> Pleroma.Pagination.fetch_paginated(params)
activities =
bookmarks
|> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
conn
|> add_link_headers(bookmarks)
|> render("index.json", %{activities: activities, for: user, as: :activity})
end
end end

View file

@ -12,6 +12,10 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionController do
action_fallback(:errors) action_fallback(:errors)
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
# Creates PushSubscription # Creates PushSubscription
# POST /api/v1/push/subscription # POST /api/v1/push/subscription
# #

View file

@ -8,11 +8,16 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do
require Logger require Logger
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
action_fallback(Pleroma.Web.MastodonAPI.FallbackController) action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
@doc "GET /api/v1/suggestions" @doc "GET /api/v1/suggestions"
def index(%{assigns: %{user: user}} = conn, _) do def index(%{assigns: %{user: user}} = conn, _) do
if Config.get([:suggestions, :enabled], false) do if Config.get([:suggestions, :enabled], false) do

View file

@ -9,8 +9,14 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1] only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1]
alias Pleroma.Pagination alias Pleroma.Pagination
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub 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) plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
# GET /api/v1/timelines/home # GET /api/v1/timelines/home

View file

@ -167,6 +167,7 @@ defp do_render("show.json", %{user: user} = opts) do
|> maybe_put_chat_token(user, opts[:for], opts) |> maybe_put_chat_token(user, opts[:for], opts)
|> maybe_put_activation_status(user, opts[:for]) |> maybe_put_activation_status(user, opts[:for])
|> maybe_put_follow_requests_count(user, opts[:for]) |> maybe_put_follow_requests_count(user, opts[:for])
|> maybe_put_unread_conversation_count(user, opts[:for])
end end
defp username_from_nickname(string) when is_binary(string) do defp username_from_nickname(string) when is_binary(string) do
@ -248,6 +249,16 @@ defp maybe_put_activation_status(data, user, %User{info: %{is_admin: true}}) do
defp maybe_put_activation_status(data, _, _), do: data defp maybe_put_activation_status(data, _, _), do: data
defp maybe_put_unread_conversation_count(data, %User{id: user_id} = user, %User{id: user_id}) do
data
|> Kernel.put_in(
[:pleroma, :unread_conversation_count],
user.info.unread_conversation_count
)
end
defp maybe_put_unread_conversation_count(data, _, _), do: data
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil defp image_url(_), do: nil
end end

View file

@ -0,0 +1,23 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Metadata.Providers.Feed do
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Metadata.Providers.Provider
alias Pleroma.Web.Router.Helpers
@behaviour Provider
@impl Provider
def build_tags(%{user: user}) do
[
{:link,
[
rel: "alternate",
type: "application/atom+xml",
href: Helpers.feed_path(Endpoint, :feed, user.nickname) <> ".atom"
], []}
]
end
end

View file

@ -4,10 +4,15 @@
defmodule Pleroma.Web.MongooseIM.MongooseIMController do defmodule Pleroma.Web.MongooseIM.MongooseIMController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
plug(RateLimiter, :authentication when action in [:user_exists, :check_password])
plug(RateLimiter, {:authentication, params: ["user"]} when action == :check_password)
def user_exists(conn, %{"user" => username}) do def user_exists(conn, %{"user" => username}) do
with %User{} <- Repo.get_by(User, nickname: username, local: true) do with %User{} <- Repo.get_by(User, nickname: username, local: true) do
conn conn

View file

@ -24,6 +24,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
plug(:fetch_session) plug(:fetch_session)
plug(:fetch_flash) plug(:fetch_flash)
plug(Pleroma.Plugs.RateLimiter, :authentication when action == :create_authorization)
action_fallback(Pleroma.Web.OAuth.FallbackController) action_fallback(Pleroma.Web.OAuth.FallbackController)
@ -474,7 +475,7 @@ defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
defp validate_scopes(app, params) do defp validate_scopes(app, params) do
params params
|> Scopes.fetch_scopes(app.scopes) |> Scopes.fetch_scopes(app.scopes)
|> Scopes.validates(app.scopes) |> Scopes.validate(app.scopes)
end end
def default_redirect_uri(%App{} = app) do def default_redirect_uri(%App{} = app) do

View file

@ -8,7 +8,7 @@ defmodule Pleroma.Web.OAuth.Scopes do
""" """
@doc """ @doc """
Fetch scopes from requiest params. Fetch scopes from request params.
Note: `scopes` is used by Mastodon supporting it but sticking to Note: `scopes` is used by Mastodon supporting it but sticking to
OAuth's standard `scope` wherever we control it OAuth's standard `scope` wherever we control it
@ -53,14 +53,14 @@ def to_string(scopes), do: Enum.join(scopes, " ")
@doc """ @doc """
Validates scopes. Validates scopes.
""" """
@spec validates(list() | nil, list()) :: @spec validate(list() | nil, list()) ::
{:ok, list()} | {:error, :missing_scopes | :unsupported_scopes} {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
def validates([], _app_scopes), do: {:error, :missing_scopes} def validate([], _app_scopes), do: {:error, :missing_scopes}
def validates(nil, _app_scopes), do: {:error, :missing_scopes} def validate(nil, _app_scopes), do: {:error, :missing_scopes}
def validates(scopes, app_scopes) do def validate(scopes, app_scopes) do
case scopes -- app_scopes do case Pleroma.Plugs.OAuthScopesPlug.filter_descendants(scopes, app_scopes) do
[] -> {:ok, scopes} ^scopes -> {:ok, scopes}
_ -> {:error, :unsupported_scopes} _ -> {:error, :unsupported_scopes}
end end
end end

View file

@ -9,16 +9,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.ActivityPubController alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
alias Pleroma.Web.Metadata.PlayerView alias Pleroma.Web.Metadata.PlayerView
alias Pleroma.Web.OStatus
alias Pleroma.Web.OStatus.ActivityRepresenter alias Pleroma.Web.OStatus.ActivityRepresenter
alias Pleroma.Web.OStatus.FeedRepresenter
alias Pleroma.Web.Router alias Pleroma.Web.Router
alias Pleroma.Web.XML alias Pleroma.Web.XML
@ -31,49 +28,11 @@ defmodule Pleroma.Web.OStatus.OStatusController do
plug( plug(
Pleroma.Plugs.SetFormatPlug Pleroma.Plugs.SetFormatPlug
when action in [:feed_redirect, :object, :activity, :notice] when action in [:object, :activity, :notice]
) )
action_fallback(:errors) action_fallback(:errors)
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
RedirectController.redirector_with_meta(conn, %{user: user})
end
end
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :user)
end
def feed_redirect(conn, %{"nickname" => nickname}) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
redirect(conn, external: OStatus.feed_path(user))
end
end
def feed(conn, %{"nickname" => nickname} = params) do
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
activities =
params
|> Map.take(["max_id"])
|> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
|> ActivityPub.fetch_public_activities()
|> Enum.reverse()
response =
user
|> FeedRepresenter.to_simple_form(activities, [user])
|> :xmerl.export_simple(:xmerl_xml)
|> to_string
conn
|> put_resp_content_type("application/atom+xml")
|> send_resp(200, response)
end
end
defp decode_or_retry(body) do defp decode_or_retry(body) do
with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body), with {:ok, magic_key} <- Pleroma.Web.Salmon.fetch_magic_key(body),
{:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do {:ok, doc} <- Pleroma.Web.Salmon.decode_and_validate(magic_key, body) do

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] only: [json_response: 3, add_link_headers: 2, assign_account_by_id: 2]
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
@ -17,6 +18,30 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
require Pleroma.Constants 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(RateLimiter, :account_confirmation_resend when action == :confirmation_resend)
plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe]) plug(:assign_account_by_id when action in [:favourites, :subscribe, :unsubscribe])
plug(:put_view, Pleroma.Web.MastodonAPI.AccountView) plug(:put_view, Pleroma.Web.MastodonAPI.AccountView)

View file

@ -1,8 +1,26 @@
defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do defmodule Pleroma.Web.PleromaAPI.EmojiAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
require Logger require Logger
plug(
OAuthScopesPlug,
%{scopes: ["write"]}
when action in [
:create,
:delete,
:download_from,
:list_from,
:import_from_fs,
:update_file,
:update_metadata
]
)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
def emoji_dir_path do def emoji_dir_path do
Path.join( Path.join(
Pleroma.Config.get!([:instance, :static_dir]), Pleroma.Config.get!([:instance, :static_dir]),

View file

@ -5,9 +5,15 @@
defmodule Pleroma.Web.PleromaAPI.MascotController do defmodule Pleroma.Web.PleromaAPI.MascotController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub 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" @doc "GET /api/v1/pleroma/mascot"
def show(%{assigns: %{user: user}} = conn, _params) do def show(%{assigns: %{user: user}} = conn, _params) do
json(conn, User.get_mascot(user)) json(conn, User.get_mascot(user))

View file

@ -9,11 +9,26 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.MastodonAPI.ConversationView alias Pleroma.Web.MastodonAPI.ConversationView
alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.NotificationView
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
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 def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do
with %Participation{} = participation <- Participation.get(participation_id), with %Participation{} = participation <- Participation.get(participation_id),
true <- user.id == participation.user_id do true <- user.id == participation.user_id do

View file

@ -7,11 +7,17 @@ defmodule Pleroma.Web.PleromaAPI.ScrobbleController do
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, fetch_integer_param: 2] import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, fetch_integer_param: 2]
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :user_scrobbles)
plug(OAuthScopesPlug, %{scopes: ["write"]} when action != :user_scrobbles)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do def new_scrobble(%{assigns: %{user: user}} = conn, %{"title" => _} = params) do
params = params =
if !params["length"] do if !params["length"] do

View file

@ -87,31 +87,6 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.EnsureUserKeyPlug) plug(Pleroma.Plugs.EnsureUserKeyPlug)
end end
pipeline :oauth_read_or_public do
plug(Pleroma.Plugs.OAuthScopesPlug, %{
scopes: ["read"],
fallback: :proceed_unauthenticated
})
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
end
pipeline :oauth_read do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]})
end
pipeline :oauth_write do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["write"]})
end
pipeline :oauth_follow do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["follow"]})
end
pipeline :oauth_push do
plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
end
pipeline :well_known do pipeline :well_known do
plug(:accepts, ["json", "jrd+json", "xml", "xrd+xml"]) plug(:accepts, ["json", "jrd+json", "xml", "xrd+xml"])
end end
@ -154,7 +129,7 @@ defmodule Pleroma.Web.Router do
end end
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through([:admin_api, :oauth_write]) pipe_through(:admin_api)
post("/users/follow", AdminAPIController, :user_follow) post("/users/follow", AdminAPIController, :user_follow)
post("/users/unfollow", AdminAPIController, :user_unfollow) post("/users/unfollow", AdminAPIController, :user_unfollow)
@ -213,7 +188,7 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
scope "/packs" do scope "/packs" do
# Modifying packs # Modifying packs
pipe_through([:admin_api, :oauth_write]) pipe_through(:admin_api)
post("/import_from_fs", EmojiAPIController, :import_from_fs) post("/import_from_fs", EmojiAPIController, :import_from_fs)
@ -238,31 +213,20 @@ defmodule Pleroma.Web.Router do
post("/main/ostatus", UtilController, :remote_subscribe) post("/main/ostatus", UtilController, :remote_subscribe)
get("/ostatus_subscribe", UtilController, :remote_follow) get("/ostatus_subscribe", UtilController, :remote_follow)
scope [] do post("/ostatus_subscribe", UtilController, :do_remote_follow)
pipe_through(:oauth_follow)
post("/ostatus_subscribe", UtilController, :do_remote_follow)
end
end end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
scope [] do post("/change_email", UtilController, :change_email)
pipe_through(:oauth_write) post("/change_password", UtilController, :change_password)
post("/delete_account", UtilController, :delete_account)
put("/notification_settings", UtilController, :update_notificaton_settings)
post("/disable_account", UtilController, :disable_account)
post("/change_email", UtilController, :change_email) post("/blocks_import", UtilController, :blocks_import)
post("/change_password", UtilController, :change_password) post("/follow_import", UtilController, :follow_import)
post("/delete_account", UtilController, :delete_account)
put("/notification_settings", UtilController, :update_notificaton_settings)
post("/disable_account", UtilController, :disable_account)
end
scope [] do
pipe_through(:oauth_follow)
post("/blocks_import", UtilController, :blocks_import)
post("/follow_import", UtilController, :follow_import)
end
end end
scope "/oauth", Pleroma.Web.OAuth do scope "/oauth", Pleroma.Web.OAuth do
@ -289,14 +253,14 @@ defmodule Pleroma.Web.Router do
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
scope [] do scope [] do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
pipe_through(:oauth_read)
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses) get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
get("/conversations/:id", PleromaAPIController, :conversation) get("/conversations/:id", PleromaAPIController, :conversation)
end end
scope [] do scope [] do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
pipe_through(:oauth_write)
patch("/conversations/:id", PleromaAPIController, :update_conversation) patch("/conversations/:id", PleromaAPIController, :update_conversation)
post("/notifications/read", PleromaAPIController, :read_notification) post("/notifications/read", PleromaAPIController, :read_notification)
@ -312,13 +276,11 @@ defmodule Pleroma.Web.Router do
scope [] do scope [] do
pipe_through(:api) pipe_through(:api)
pipe_through(:oauth_read_or_public)
get("/accounts/:id/favourites", AccountController, :favourites) get("/accounts/:id/favourites", AccountController, :favourites)
end end
scope [] do scope [] do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
pipe_through(:oauth_follow)
post("/accounts/:id/subscribe", AccountController, :subscribe) post("/accounts/:id/subscribe", AccountController, :subscribe)
post("/accounts/:id/unsubscribe", AccountController, :unsubscribe) post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
@ -328,131 +290,114 @@ defmodule Pleroma.Web.Router do
end end
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do 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) get("/accounts/:id/scrobbles", ScrobbleController, :user_scrobbles)
end end
scope "/api/v1", Pleroma.Web.MastodonAPI do scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
scope [] do get("/accounts/verify_credentials", AccountController, :verify_credentials)
pipe_through(:oauth_read)
get("/accounts/verify_credentials", AccountController, :verify_credentials) get("/accounts/relationships", AccountController, :relationships)
get("/accounts/relationships", AccountController, :relationships) get("/accounts/:id/lists", AccountController, :lists)
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
get("/accounts/:id/lists", AccountController, :lists) get("/follow_requests", FollowRequestController, :index)
get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array) get("/blocks", AccountController, :blocks)
get("/mutes", AccountController, :mutes)
get("/follow_requests", FollowRequestController, :index) get("/timelines/home", TimelineController, :home)
get("/blocks", MastodonAPIController, :blocks) get("/timelines/direct", TimelineController, :direct)
get("/mutes", MastodonAPIController, :mutes)
get("/timelines/home", TimelineController, :home) get("/favourites", StatusController, :favourites)
get("/timelines/direct", TimelineController, :direct) get("/bookmarks", StatusController, :bookmarks)
get("/favourites", MastodonAPIController, :favourites) get("/notifications", NotificationController, :index)
get("/bookmarks", MastodonAPIController, :bookmarks) get("/notifications/:id", NotificationController, :show)
post("/notifications/clear", NotificationController, :clear)
post("/notifications/dismiss", NotificationController, :dismiss)
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
get("/notifications", NotificationController, :index) get("/scheduled_statuses", ScheduledActivityController, :index)
get("/notifications/:id", NotificationController, :show) get("/scheduled_statuses/:id", ScheduledActivityController, :show)
post("/notifications/clear", NotificationController, :clear)
post("/notifications/dismiss", NotificationController, :dismiss)
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
get("/scheduled_statuses", ScheduledActivityController, :index) get("/lists", ListController, :index)
get("/scheduled_statuses/:id", ScheduledActivityController, :show) get("/lists/:id", ListController, :show)
get("/lists/:id/accounts", ListController, :list_accounts)
get("/lists", ListController, :index) get("/domain_blocks", DomainBlockController, :index)
get("/lists/:id", ListController, :show)
get("/lists/:id/accounts", ListController, :list_accounts)
get("/domain_blocks", DomainBlockController, :index) get("/filters", FilterController, :index)
get("/filters", FilterController, :index) get("/suggestions", SuggestionController, :index)
get("/suggestions", SuggestionController, :index) get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :read)
get("/conversations", ConversationController, :index) get("/endorsements", AccountController, :endorsements)
post("/conversations/:id/read", ConversationController, :read)
get("/endorsements", MastodonAPIController, :empty_array) patch("/accounts/update_credentials", AccountController, :update_credentials)
end
scope [] do post("/statuses", StatusController, :create)
pipe_through(:oauth_write) delete("/statuses/:id", StatusController, :delete)
patch("/accounts/update_credentials", AccountController, :update_credentials) 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)
post("/statuses", StatusController, :create) put("/scheduled_statuses/:id", ScheduledActivityController, :update)
delete("/statuses/:id", StatusController, :delete) delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
post("/statuses/:id/reblog", StatusController, :reblog) post("/polls/:id/votes", PollController, :vote)
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) post("/media", MediaController, :create)
delete("/scheduled_statuses/:id", ScheduledActivityController, :delete) put("/media/:id", MediaController, :update)
post("/polls/:id/votes", PollController, :vote) delete("/lists/:id", ListController, :delete)
post("/lists", ListController, :create)
put("/lists/:id", ListController, :update)
post("/media", MediaController, :create) post("/lists/:id/accounts", ListController, :add_to_list)
put("/media/:id", MediaController, :update) delete("/lists/:id/accounts", ListController, :remove_from_list)
delete("/lists/:id", ListController, :delete) post("/filters", FilterController, :create)
post("/lists", ListController, :create) get("/filters/:id", FilterController, :show)
put("/lists/:id", ListController, :update) put("/filters/:id", FilterController, :update)
delete("/filters/:id", FilterController, :delete)
post("/lists/:id/accounts", ListController, :add_to_list) post("/reports", ReportController, :create)
delete("/lists/:id/accounts", ListController, :remove_from_list)
post("/filters", FilterController, :create) post("/follows", AccountController, :follows)
get("/filters/:id", FilterController, :show) post("/accounts/:id/follow", AccountController, :follow)
put("/filters/:id", FilterController, :update) post("/accounts/:id/unfollow", AccountController, :unfollow)
delete("/filters/:id", FilterController, :delete) 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("/reports", ReportController, :create) post("/follow_requests/:id/authorize", FollowRequestController, :authorize)
end post("/follow_requests/:id/reject", FollowRequestController, :reject)
scope [] do post("/domain_blocks", DomainBlockController, :create)
pipe_through(:oauth_follow) delete("/domain_blocks", DomainBlockController, :delete)
post("/follows", MastodonAPIController, :follows) post("/push/subscription", SubscriptionController, :create)
post("/accounts/:id/follow", AccountController, :follow) get("/push/subscription", SubscriptionController, :get)
post("/accounts/:id/unfollow", AccountController, :unfollow) put("/push/subscription", SubscriptionController, :update)
post("/accounts/:id/block", AccountController, :block) delete("/push/subscription", SubscriptionController, :delete)
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)
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
end end
scope "/api/web", Pleroma.Web do scope "/api/web", Pleroma.Web do
pipe_through([:authenticated_api, :oauth_write]) pipe_through(:authenticated_api)
put("/settings", MastoFEController, :put_settings) put("/settings", MastoFEController, :put_settings)
end end
@ -477,30 +422,26 @@ defmodule Pleroma.Web.Router do
get("/trends", MastodonAPIController, :empty_array) get("/trends", MastodonAPIController, :empty_array)
scope [] do get("/timelines/public", TimelineController, :public)
pipe_through(:oauth_read_or_public) get("/timelines/tag/:tag", TimelineController, :hashtag)
get("/timelines/list/:list_id", TimelineController, :list)
get("/timelines/public", TimelineController, :public) get("/statuses", StatusController, :index)
get("/timelines/tag/:tag", TimelineController, :hashtag) get("/statuses/:id", StatusController, :show)
get("/timelines/list/:list_id", TimelineController, :list) get("/statuses/:id/context", StatusController, :context)
get("/statuses", StatusController, :index) get("/polls/:id", PollController, :show)
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("/accounts/:id/statuses", AccountController, :statuses) get("/search", SearchController, :search)
get("/accounts/:id/followers", AccountController, :followers)
get("/accounts/:id/following", AccountController, :following)
get("/accounts/:id", AccountController, :show)
get("/search", SearchController, :search)
end
end end
scope "/api/v2", Pleroma.Web.MastodonAPI do scope "/api/v2", Pleroma.Web.MastodonAPI do
pipe_through([:api, :oauth_read_or_public]) pipe_through(:api)
get("/search", SearchController, :search2) get("/search", SearchController, :search2)
end end
@ -531,11 +472,7 @@ defmodule Pleroma.Web.Router do
get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens) get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token)
scope [] do post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
pipe_through(:oauth_read)
post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
end
end end
pipeline :ap_service_actor do pipeline :ap_service_actor do
@ -558,8 +495,9 @@ defmodule Pleroma.Web.Router do
get("/activities/:uuid", OStatus.OStatusController, :activity) get("/activities/:uuid", OStatus.OStatusController, :activity)
get("/notice/:id", OStatus.OStatusController, :notice) get("/notice/:id", OStatus.OStatusController, :notice)
get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player) get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player)
get("/users/:nickname/feed", OStatus.OStatusController, :feed)
get("/users/:nickname", OStatus.OStatusController, :feed_redirect) get("/users/:nickname/feed", Feed.FeedController, :feed)
get("/users/:nickname", Feed.FeedController, :feed_redirect)
post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming) post("/users/:nickname/salmon", OStatus.OStatusController, :salmon_incoming)
post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request) post("/push/hub/:nickname", Websub.WebsubController, :websub_subscription_request)
@ -599,23 +537,14 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do scope "/", Pleroma.Web.ActivityPub do
pipe_through([:activitypub_client]) pipe_through([:activitypub_client])
scope [] do get("/api/ap/whoami", ActivityPubController, :whoami)
pipe_through(:oauth_read) get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
get("/api/ap/whoami", ActivityPubController, :whoami)
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
end
scope [] do post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
pipe_through(:oauth_write) post("/api/ap/upload_media", ActivityPubController, :upload_media)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
post("/api/ap/upload_media", ActivityPubController, :upload_media)
end
scope [] do get("/users/:nickname/followers", ActivityPubController, :followers)
pipe_through(:oauth_read_or_public) get("/users/:nickname/following", ActivityPubController, :following)
get("/users/:nickname/followers", ActivityPubController, :followers)
get("/users/:nickname/following", ActivityPubController, :following)
end
end end
scope "/", Pleroma.Web.ActivityPub do scope "/", Pleroma.Web.ActivityPub do
@ -665,10 +594,7 @@ defmodule Pleroma.Web.Router do
post("/auth/password", MastodonAPI.AuthController, :password_reset) post("/auth/password", MastodonAPI.AuthController, :password_reset)
scope [] do get("/web/*path", MastoFEController, :index)
pipe_through(:oauth_read)
get("/web/*path", MastoFEController, :index)
end
end end
pipeline :remote_media do pipeline :remote_media do

View file

@ -202,7 +202,7 @@ def is_representable?(_), do: false
@spec publish(User.t(), Pleroma.Activity.t()) :: none @spec publish(User.t(), Pleroma.Activity.t()) :: none
def publish(user, activity) def publish(user, activity)
def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity) def publish(%{keys: keys} = user, %{data: %{"type" => type}} = activity)
when type in @supported_activities do when type in @supported_activities do
feed = ActivityRepresenter.to_simple_form(activity, user, true) feed = ActivityRepresenter.to_simple_form(activity, user, true)
@ -238,7 +238,7 @@ def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity
def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end) def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)
def gather_webfinger_links(%User{} = user) do def gather_webfinger_links(%User{} = user) do
{:ok, _private, public} = Keys.keys_from_pem(user.info.keys) {:ok, _private, public} = Keys.keys_from_pem(user.keys)
magic_key = encode_key(public) magic_key = encode_key(public)
[ [

View file

@ -0,0 +1,48 @@
<entry>
<activity:object-type>http://activitystrea.ms/schema/1.0/note</activity:object-type>
<activity:verb>http://activitystrea.ms/schema/1.0/post</activity:verb>
<id><%= @data["id"] %></id>
<title><%= "New note by #{@user.nickname}" %></title>
<content type="html"><%= activity_content(@activity) %></content>
<published><%= @data["published"] %></published>
<updated><%= @data["published"] %></updated>
<ostatus:conversation ref="<%= activity_context(@activity) %>"><%= activity_context(@activity) %></ostatus:conversation>
<link ref="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
<%= if @data["summary"] do %>
<summary><%= @data["summary"] %></summary>
<% end %>
<%= if @activity.local do %>
<link type="application/atom+xml" href='<%= @data["id"] %>' rel="self"/>
<link type="text/html" href='<%= @data["id"] %>' rel="alternate"/>
<% else %>
<link type="text/html" href='<%= @data["external_url"] %>' rel="alternate"/>
<% end %>
<%= for tag <- @data["tag"] || [] do %>
<category term="<%= tag %>"></category>
<% end %>
<%= for attachment <- @data["attachment"] || [] do %>
<link rel="enclosure" href="<%= attachment_href(attachment) %>" type="<%= attachment_type(attachment) %>"/>
<% end %>
<%= if @data["inReplyTo"] do %>
<thr:in-reply-to ref='<%= @data["inReplyTo"] %>' href='<%= get_href(@data["inReplyTo"]) %>'/>
<% end %>
<%= for id <- @activity.recipients do %>
<%= if id == Pleroma.Constants.as_public() do %>
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/collection" href="http://activityschema.org/collection/public"/>
<% else %>
<%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %>
<link rel="mentioned" ostatus:object-type="http://activitystrea.ms/schema/1.0/person" href="<%= id %>"/>
<% end %>
<% end %>
<% end %>
<%= for {emoji, file} <- @data["emoji"] || %{} do %>
<link name="<%= emoji %>" rel="emoji" href="<%= file %>"/>
<% end %>
</entry>

View file

@ -0,0 +1,17 @@
<author>
<id><%= @user.ap_id %></id>
<activity:object>http://activitystrea.ms/schema/1.0/person</activity:object>
<uri><%= @user.ap_id %></uri>
<poco:preferredUsername><%= @user.nickname %></poco:preferredUsername>
<poco:displayName><%= @user.name %></poco:displayName>
<poco:note><%= escape(@user.bio) %></poco:note>
<summary><%= escape(@user.bio) %></summary>
<name><%= @user.nickname %></name>
<link rel="avatar" href="<%= User.avatar_url(@user) %>"/>
<%= if User.banner_url(@user) do %>
<link rel="header" href="<%= User.banner_url(@user) %>"/>
<% end %>
<%= if @user.local do %>
<ap_enabled>true</ap_enabled>
<% end %>
</author>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<feed
xmlns="http://www.w3.org/2005/Atom"
xmlns:thr="http://purl.org/syndication/thread/1.0"
xmlns:activity="http://activitystrea.ms/spec/1.0/"
xmlns:poco="http://portablecontacts.net/spec/1.0"
xmlns:ostatus="http://ostatus.org/schema/1.0">
<id><%= feed_url(@conn, :feed, @user.nickname) <> ".atom" %></id>
<title><%= @user.nickname <> "'s timeline" %></title>
<updated><%= most_recent_update(@activities, @user) %></updated>
<logo><%= logo(@user) %></logo>
<link rel="hub" href="<%= websub_url(@conn, :websub_subscription_request, @user.nickname) %>"/>
<link rel="salmon" href="<%= o_status_url(@conn, :salmon_incoming, @user.nickname) %>"/>
<link rel="self" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom' %>" type="application/atom+xml"/>
<%= render @view_module, "_author.xml", assigns %>
<%= if last_activity(@activities) do %>
<link rel="next" href="<%= '#{feed_url(@conn, :feed, @user.nickname)}.atom?max_id=#{last_activity(@activities).id}' %>" type="application/atom+xml"/>
<% end %>
<%= for activity <- @activities do %>
<%= render @view_module, "_activity.xml", Map.merge(assigns, %{activity: activity, data: activity_object_data(activity)}) %>
<% end %>
</feed>

View file

@ -13,11 +13,34 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Healthcheck alias Pleroma.Healthcheck
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Plugs.AuthenticationPlug alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.WebFinger alias Pleroma.Web.WebFinger
plug(
OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]}
when action in [:do_remote_follow, :follow_import]
)
plug(OAuthScopesPlug, %{scopes: ["follow", "write:blocks"]} when action == :blocks_import)
plug(
OAuthScopesPlug,
%{scopes: ["write:accounts"]}
when action in [
:change_email,
:change_password,
:delete_account,
:update_notificaton_settings,
:disable_account
]
)
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version]) plug(Pleroma.Plugs.SetFormatPlug when action in [:config, :version])
def help_test(conn, _params) do def help_test(conn, _params) do

View file

@ -6,12 +6,17 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.TokenView alias Pleroma.Web.TwitterAPI.TokenView
require Logger require Logger
plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action == :notifications_read)
plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
action_fallback(:errors) action_fallback(:errors)
def confirm_email(conn, %{"user_id" => uid, "token" => token}) do def confirm_email(conn, %{"user_id" => uid, "token" => token}) do

View file

@ -3,14 +3,13 @@ defmodule Pleroma.Repo.Migrations.CreatePleroma.User do
def change do def change do
create_if_not_exists table(:users) do create_if_not_exists table(:users) do
add :email, :string add(:email, :string)
add :password_hash, :string add(:password_hash, :string)
add :name, :string add(:name, :string)
add :nickname, :string add(:nickname, :string)
add :bio, :string add(:bio, :string)
timestamps() timestamps()
end end
end end
end end

View file

@ -3,12 +3,11 @@ defmodule Pleroma.Repo.Migrations.CreatePleroma.Activity do
def change do def change do
create_if_not_exists table(:activities) do create_if_not_exists table(:activities) do
add :data, :map add(:data, :map)
timestamps() timestamps()
end end
create_if_not_exists index(:activities, [:data], using: :gin) create_if_not_exists(index(:activities, [:data], using: :gin))
end end
end end

View file

@ -3,10 +3,9 @@ defmodule Pleroma.Repo.Migrations.CreatePleroma.Object do
def change do def change do
create_if_not_exists table(:objects) do create_if_not_exists table(:objects) do
add :data, :map add(:data, :map)
timestamps() timestamps()
end end
end end
end end

View file

@ -3,7 +3,7 @@ defmodule Pleroma.Repo.Migrations.AddFollowingListToUsers do
def change do def change do
alter table(:users) do alter table(:users) do
add :following, :map add(:following, :map)
end end
end end
end end

View file

@ -3,7 +3,7 @@ defmodule Pleroma.Repo.Migrations.AddApIdToUsers do
def change do def change do
alter table(:users) do alter table(:users) do
add :ap_id, :string add(:ap_id, :string)
end end
end end
end end

View file

@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddIndexToObjects do
use Ecto.Migration use Ecto.Migration
def change do def change do
create_if_not_exists index(:objects, [:data], using: :gin) create_if_not_exists(index(:objects, [:data], using: :gin))
end end
end end

View file

@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.AddUniqueIndexToEmailAndNickname do
use Ecto.Migration use Ecto.Migration
def change do def change do
create_if_not_exists unique_index(:users, [:email]) create_if_not_exists(unique_index(:users, [:email]))
create_if_not_exists unique_index(:users, [:nickname]) create_if_not_exists(unique_index(:users, [:nickname]))
end end
end end

View file

@ -3,7 +3,7 @@ defmodule Pleroma.Repo.Migrations.AddAvatarObjectToUsers do
def change do def change do
alter table(:users) do alter table(:users) do
add :avatar, :map add(:avatar, :map)
end end
end end
end end

View file

@ -3,11 +3,11 @@ defmodule Pleroma.Repo.Migrations.CreateWebsubServerSubscription do
def change do def change do
create_if_not_exists table(:websub_server_subscriptions) do create_if_not_exists table(:websub_server_subscriptions) do
add :topic, :string add(:topic, :string)
add :callback, :string add(:callback, :string)
add :secret, :string add(:secret, :string)
add :valid_until, :naive_datetime add(:valid_until, :naive_datetime)
add :state, :string add(:state, :string)
timestamps() timestamps()
end end

View file

@ -3,8 +3,8 @@ defmodule Pleroma.Repo.Migrations.AddFieldsToUsers do
def change do def change do
alter table(:users) do alter table(:users) do
add :local, :boolean, default: true add(:local, :boolean, default: true)
add :info, :map add(:info, :map)
end end
end end
end end

View file

@ -3,11 +3,11 @@ defmodule Pleroma.Repo.Migrations.CreateWebsubClientSubscription do
def change do def change do
create_if_not_exists table(:websub_client_subscriptions) do create_if_not_exists table(:websub_client_subscriptions) do
add :topic, :string add(:topic, :string)
add :secret, :string add(:secret, :string)
add :valid_until, :naive_datetime_usec add(:valid_until, :naive_datetime_usec)
add :state, :string add(:state, :string)
add :subscribers, :map add(:subscribers, :map)
timestamps() timestamps()
end end

View file

@ -3,8 +3,8 @@ defmodule Pleroma.Repo.Migrations.AddUserAndHub do
def change do def change do
alter table(:websub_client_subscriptions) do alter table(:websub_client_subscriptions) do
add :hub, :string add(:hub, :string)
add :user_id, references(:users) add(:user_id, references(:users))
end end
end end
end end

View file

@ -2,10 +2,16 @@ defmodule Pleroma.Repo.Migrations.AddIdContraintsToActivitiesAndObjectsPartTwo d
use Ecto.Migration use Ecto.Migration
def up do def up do
drop_if_exists index(:objects, ["(data->>\"id\")"], name: :objects_unique_apid_index) drop_if_exists(index(:objects, ["(data->>\"id\")"], name: :objects_unique_apid_index))
drop_if_exists index(:activities, ["(data->>\"id\")"], name: :activities_unique_apid_index) drop_if_exists(index(:activities, ["(data->>\"id\")"], name: :activities_unique_apid_index))
create_if_not_exists unique_index(:objects, ["(data->>'id')"], name: :objects_unique_apid_index)
create_if_not_exists unique_index(:activities, ["(data->>'id')"], name: :activities_unique_apid_index) create_if_not_exists(
unique_index(:objects, ["(data->>'id')"], name: :objects_unique_apid_index)
)
create_if_not_exists(
unique_index(:activities, ["(data->>'id')"], name: :activities_unique_apid_index)
)
end end
def down, do: :ok def down, do: :ok

View file

@ -3,9 +3,9 @@ defmodule Pleroma.Repo.Migrations.AddLocalFieldToActivities do
def change do def change do
alter table(:activities) do alter table(:activities) do
add :local, :boolean, default: true add(:local, :boolean, default: true)
end end
create_if_not_exists index(:activities, [:local]) create_if_not_exists(index(:activities, [:local]))
end end
end end

View file

@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddUniqueIndexToAPID do
use Ecto.Migration use Ecto.Migration
def change do def change do
create_if_not_exists unique_index(:users, [:ap_id]) create_if_not_exists(unique_index(:users, [:ap_id]))
end end
end end

View file

@ -3,14 +3,13 @@ defmodule Pleroma.Repo.Migrations.LongerBios do
def up do def up do
alter table(:users) do alter table(:users) do
modify :bio, :text modify(:bio, :text)
end end
end end
def down do def down do
alter table(:users) do alter table(:users) do
modify :bio, :string modify(:bio, :string)
end end
end end
end end

View file

@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.RemoveActivitiesIndex do
use Ecto.Migration use Ecto.Migration
def change do def change do
drop_if_exists index(:activities, [:data]) drop_if_exists(index(:activities, [:data]))
end end
end end

View file

@ -2,7 +2,16 @@ defmodule Pleroma.Repo.Migrations.AddObjectActivityIndexPartTwo do
use Ecto.Migration use Ecto.Migration
def change do def change do
drop_if_exists index(:objects, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index) drop_if_exists(
create_if_not_exists index(:activities, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index) index(:objects, ["(data->'object'->>'id')", "(data->>'type')"],
name: :activities_create_objects_index
)
)
create_if_not_exists(
index(:activities, ["(data->'object'->>'id')", "(data->>'type')"],
name: :activities_create_objects_index
)
)
end end
end end

View file

@ -2,6 +2,8 @@ defmodule Pleroma.Repo.Migrations.AddActorIndexToActivity do
use Ecto.Migration use Ecto.Migration
def change do def change do
create_if_not_exists index(:activities, ["(data->>'actor')", "inserted_at desc"], name: :activities_actor_index) create_if_not_exists(
index(:activities, ["(data->>'actor')", "inserted_at desc"], name: :activities_actor_index)
)
end end
end end

View file

@ -3,13 +3,13 @@ defmodule Pleroma.Repo.Migrations.AddFollowerAddressToUser do
def up do def up do
alter table(:users) do alter table(:users) do
add :follower_address, :string, unique: true add(:follower_address, :string, unique: true)
end end
end end
def down do def down do
alter table(:users) do alter table(:users) do
remove :follower_address remove(:follower_address)
end end
end end
end end

View file

@ -3,12 +3,12 @@ defmodule Pleroma.Repo.Migrations.AddMastodonApps do
def change do def change do
create_if_not_exists table(:apps) do create_if_not_exists table(:apps) do
add :client_name, :string add(:client_name, :string)
add :redirect_uris, :string add(:redirect_uris, :string)
add :scopes, :string add(:scopes, :string)
add :website, :string add(:website, :string)
add :client_id, :string add(:client_id, :string)
add :client_secret, :string add(:client_secret, :string)
timestamps() timestamps()
end end

View file

@ -3,11 +3,11 @@ defmodule Pleroma.Repo.Migrations.CreateOAuthAuthorizations do
def change do def change do
create_if_not_exists table(:oauth_authorizations) do create_if_not_exists table(:oauth_authorizations) do
add :app_id, references(:apps) add(:app_id, references(:apps))
add :user_id, references(:users) add(:user_id, references(:users))
add :token, :string add(:token, :string)
add :valid_until, :naive_datetime_usec add(:valid_until, :naive_datetime_usec)
add :used, :boolean, default: false add(:used, :boolean, default: false)
timestamps() timestamps()
end end

View file

@ -3,11 +3,11 @@ defmodule Pleroma.Repo.Migrations.CreateOAuthToken do
def change do def change do
create_if_not_exists table(:oauth_tokens) do create_if_not_exists table(:oauth_tokens) do
add :app_id, references(:apps) add(:app_id, references(:apps))
add :user_id, references(:users) add(:user_id, references(:users))
add :token, :string add(:token, :string)
add :refresh_token, :string add(:refresh_token, :string)
add :valid_until, :naive_datetime_usec add(:valid_until, :naive_datetime_usec)
timestamps() timestamps()
end end

View file

@ -3,13 +3,13 @@ defmodule Pleroma.Repo.Migrations.CreateNotifications do
def change do def change do
create_if_not_exists table(:notifications) do create_if_not_exists table(:notifications) do
add :user_id, references(:users, on_delete: :delete_all) add(:user_id, references(:users, on_delete: :delete_all))
add :activity_id, references(:activities, on_delete: :delete_all) add(:activity_id, references(:activities, on_delete: :delete_all))
add :seen, :boolean, default: false add(:seen, :boolean, default: false)
timestamps() timestamps()
end end
create_if_not_exists index(:notifications, [:user_id]) create_if_not_exists(index(:notifications, [:user_id]))
end end
end end

View file

@ -3,6 +3,11 @@ defmodule Pleroma.Repo.Migrations.AddContextIndex do
@disable_ddl_transaction true @disable_ddl_transaction true
def change do def change do
create index(:activities, ["(data->>'type')", "(data->>'context')"], name: :activities_context_index, concurrently: true) create(
index(:activities, ["(data->>'type')", "(data->>'context')"],
name: :activities_context_index,
concurrently: true
)
)
end end
end end

View file

@ -3,6 +3,12 @@ defmodule Pleroma.Repo.Migrations.AddFTSIndexToActivities do
@disable_ddl_transaction true @disable_ddl_transaction true
def change do def change do
create index(:activities, ["(to_tsvector('english', data->'object'->>'content'))"], concurrently: true, using: :gin, name: :activities_fts) create(
index(:activities, ["(to_tsvector('english', data->'object'->>'content'))"],
concurrently: true,
using: :gin,
name: :activities_fts
)
)
end end
end end

View file

@ -4,6 +4,12 @@ defmodule Pleroma.Repo.Migrations.AddTagIndex do
@disable_ddl_transaction true @disable_ddl_transaction true
def change do def change do
create index(:activities, ["(data #> '{\"object\",\"tag\"}')"], concurrently: true, using: :gin, name: :activities_tags) create(
index(:activities, ["(data #> '{\"object\",\"tag\"}')"],
concurrently: true,
using: :gin,
name: :activities_tags
)
)
end end
end end

View file

@ -3,9 +3,9 @@ defmodule Pleroma.Repo.Migrations.CreatePasswordResetTokens do
def change do def change do
create_if_not_exists table(:password_reset_tokens) do create_if_not_exists table(:password_reset_tokens) do
add :token, :string add(:token, :string)
add :user_id, references(:users) add(:user_id, references(:users))
add :used, :boolean, default: false add(:used, :boolean, default: false)
timestamps() timestamps()
end end

View file

@ -4,7 +4,17 @@ defmodule Pleroma.Repo.Migrations.AddSecondObjectIndexToActivty do
@disable_ddl_transaction true @disable_ddl_transaction true
def change do def change do
drop_if_exists index(:activities, ["(data->'object'->>'id')", "(data->>'type')"], name: :activities_create_objects_index) drop_if_exists(
create index(:activities, ["(coalesce(data->'object'->>'id', data->>'object'))"], name: :activities_create_objects_index, concurrently: true) index(:activities, ["(data->'object'->>'id')", "(data->>'type')"],
name: :activities_create_objects_index
)
)
create(
index(:activities, ["(coalesce(data->'object'->>'id', data->>'object'))"],
name: :activities_create_objects_index,
concurrently: true
)
)
end end
end end

View file

@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.DropObjectIndex do
use Ecto.Migration use Ecto.Migration
def change do def change do
drop_if_exists index(:objects, [:data], using: :gin) drop_if_exists(index(:objects, [:data], using: :gin))
end end
end end

View file

@ -4,6 +4,11 @@ defmodule Pleroma.Repo.Migrations.AddObjectActorIndex do
@disable_ddl_transaction true @disable_ddl_transaction true
def change do def change do
create index(:objects, ["(data->>'actor')", "(data->>'type')"], concurrently: true, name: :objects_actor_type) create(
index(:objects, ["(data->>'actor')", "(data->>'type')"],
concurrently: true,
name: :objects_actor_type
)
)
end end
end end

View file

@ -5,16 +5,17 @@ defmodule Pleroma.Repo.Migrations.AddActorToActivity do
def up do def up do
alter table(:activities) do alter table(:activities) do
add :actor, :string add(:actor, :string)
end end
create index(:activities, [:actor, "id DESC NULLS LAST"], concurrently: true) create(index(:activities, [:actor, "id DESC NULLS LAST"], concurrently: true))
end end
def down do def down do
drop_if_exists index(:activities, [:actor, "id DESC NULLS LAST"]) drop_if_exists(index(:activities, [:actor, "id DESC NULLS LAST"]))
alter table(:activities) do alter table(:activities) do
remove :actor remove(:actor)
end end
end end
end end

View file

@ -5,17 +5,19 @@ defmodule Pleroma.Repo.Migrations.FillActorField do
def up do def up do
max = Repo.aggregate(Activity, :max, :id) max = Repo.aggregate(Activity, :max, :id)
if max do if max do
IO.puts("#{max} activities") IO.puts("#{max} activities")
chunks = 0..(round(max / 10_000)) chunks = 0..round(max / 10_000)
Enum.each(chunks, fn (i) -> Enum.each(chunks, fn i ->
min = i * 10_000 min = i * 10_000
max = min + 10_000 max = min + 10_000
execute(""" execute("""
update activities set actor = data->>'actor' where id > #{min} and id <= #{max}; update activities set actor = data->>'actor' where id > #{min} and id <= #{max};
""") """)
|> IO.inspect |> IO.inspect()
end) end)
end end
end end
@ -23,4 +25,3 @@ def up do
def down do def down do
end end
end end

View file

@ -3,6 +3,6 @@ defmodule Pleroma.Repo.Migrations.AddSortIndexToActivities do
@disable_ddl_transaction true @disable_ddl_transaction true
def change do def change do
create index(:activities, ["id desc nulls last"], concurrently: true) create(index(:activities, ["id desc nulls last"], concurrently: true))
end end
end end

View file

@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.AddLocalIndexToUser do
use Ecto.Migration use Ecto.Migration
def change do def change do
create_if_not_exists index(:users, [:local]) create_if_not_exists(index(:users, [:local]))
end end
end end

View file

@ -3,9 +3,9 @@ defmodule Pleroma.Repo.Migrations.AddRecipientsToActivities do
def change do def change do
alter table(:activities) do alter table(:activities) do
add :recipients, {:array, :string} add(:recipients, {:array, :string})
end end
create_if_not_exists index(:activities, [:recipients], using: :gin) create_if_not_exists(index(:activities, [:recipients], using: :gin))
end end
end end

View file

@ -4,17 +4,21 @@ defmodule Pleroma.Repo.Migrations.FillRecipientsInActivities do
def up do def up do
max = Repo.aggregate(Activity, :max, :id) max = Repo.aggregate(Activity, :max, :id)
if max do if max do
IO.puts("#{max} activities") IO.puts("#{max} activities")
chunks = 0..(round(max / 10_000)) chunks = 0..round(max / 10_000)
Enum.each(chunks, fn (i) -> Enum.each(chunks, fn i ->
min = i * 10_000 min = i * 10_000
max = min + 10_000 max = min + 10_000
execute(""" execute("""
update activities set recipients = array(select jsonb_array_elements_text(data->'to')) where id > #{min} and id <= #{max}; update activities set recipients = array(select jsonb_array_elements_text(data->'to')) where id > #{
min
} and id <= #{max};
""") """)
|> IO.inspect |> IO.inspect()
end) end)
end end
end end

View file

@ -3,17 +3,18 @@ defmodule Pleroma.Repo.Migrations.MakeFollowingPostgresArray do
def up do def up do
alter table(:users) do alter table(:users) do
add :following_temp, {:array, :string} add(:following_temp, {:array, :string})
end end
execute """ execute("""
update users set following_temp = array(select jsonb_array_elements_text(following)); update users set following_temp = array(select jsonb_array_elements_text(following));
""" """)
alter table(:users) do alter table(:users) do
remove :following remove(:following)
end end
rename table(:users), :following_temp, to: :following
rename(table(:users), :following_temp, to: :following)
end end
def down, do: :ok def down, do: :ok

View file

@ -3,7 +3,7 @@ defmodule Pleroma.Repo.Migrations.AddFollowerAddressIndexToUsers do
@disable_ddl_transaction true @disable_ddl_transaction true
def change do def change do
create index(:users, [:follower_address], concurrently: true) create(index(:users, [:follower_address], concurrently: true))
create index(:users, [:following], concurrently: true, using: :gin) create(index(:users, [:following], concurrently: true, using: :gin))
end end
end end

View file

@ -2,6 +2,6 @@ defmodule Pleroma.Repo.Migrations.DropLocalIndexOnActivities do
use Ecto.Migration use Ecto.Migration
def change do def change do
drop_if_exists index(:users, [:local]) drop_if_exists(index(:users, [:local]))
end end
end end

View file

@ -2,7 +2,7 @@ defmodule Pleroma.Repo.Migrations.ActuallyDropLocalIndex do
use Ecto.Migration use Ecto.Migration
def change do def change do
create_if_not_exists index(:users, [:local]) create_if_not_exists(index(:users, [:local]))
drop_if_exists index("activities", :local) drop_if_exists(index("activities", :local))
end end
end end

View file

@ -3,13 +3,13 @@ defmodule Pleroma.Repo.Migrations.CreateLists do
def change do def change do
create_if_not_exists table(:lists) do create_if_not_exists table(:lists) do
add :user_id, references(:users, on_delete: :delete_all) add(:user_id, references(:users, on_delete: :delete_all))
add :title, :string add(:title, :string)
add :following, {:array, :string} add(:following, {:array, :string})
timestamps() timestamps()
end end
create_if_not_exists index(:lists, [:user_id]) create_if_not_exists(index(:lists, [:user_id]))
end end
end end

Some files were not shown because too many files have changed in this diff Show more