Merge branch 'develop' into issue/1276

This commit is contained in:
Maksim Pechnikov 2020-04-13 21:19:27 +03:00
commit a16b3dbcbf
66 changed files with 1102 additions and 291 deletions

View File

@ -4,9 +4,6 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [unreleased] ## [unreleased]
### Changed
- **Breaking:** BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines
### Removed ### Removed
- **Breaking:** removed `with_move` parameter from notifications timeline. - **Breaking:** removed `with_move` parameter from notifications timeline.
@ -18,11 +15,59 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
- Mastodon API: Support for `include_types` in `/api/v1/notifications`. - Mastodon API: Support for `include_types` in `/api/v1/notifications`.
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
</details> </details>
### Fixed
- Support pagination in conversations API
## [unreleased-patch]
## [2.0.2] - 2020-04-08
### Added
- Support for Funkwhale's `Audio` activity
- Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials`
### Fixed
- Blocked/muted users still generating push notifications
- Input textbox for bio ignoring newlines
- OTP: Inability to use PostgreSQL databases with SSL
- `user delete_activities` breaking when trying to delete already deleted posts
- Incorrect URL for Funkwhale channels
### Upgrade notes
1. Restart Pleroma
## [2.0.1] - 2020-03-15
### Security
- Static-FE: Fix remote posts not being sanitized
### Fixed
- 500 errors when no `Accept` header is present if Static-FE is enabled
- Instance panel not being updated immediately due to wrong `Cache-Control` headers
- Statuses posted with BBCode/Markdown having unncessary newlines in Pleroma-FE
- OTP: Fix some settings not being migrated to in-database config properly
- No `Cache-Control` headers on attachment/media proxy requests
- Character limit enforcement being off by 1
- Mastodon Streaming API: hashtag timelines not working
### Changed
- BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines
- Mastodon API: Allow registration without email if email verification is not enabled
### Upgrade notes
#### Nginx only
1. Remove `proxy_ignore_headers Cache-Control;` and `proxy_hide_header Cache-Control;` from your config.
#### Everyone
1. Run database migrations (inside Pleroma directory):
- OTP: `./bin/pleroma_ctl migrate`
- From Source: `mix ecto.migrate`
2. Restart Pleroma
## [2.0.0] - 2019-03-08 ## [2.0.0] - 2019-03-08
### Security ### Security
- Mastodon API: Fix being able to request enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request. - Mastodon API: Fix being able to request enormous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
### Removed ### Removed
- **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media` - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media`

View File

@ -386,47 +386,56 @@ defp render_timelines(user) do
favourites = ActivityPub.fetch_favourites(user) favourites = ActivityPub.fetch_favourites(user)
output_relationships =
!!Pleroma.Config.get([:extensions, :output_relationships_in_statuses_by_default])
Benchee.run( Benchee.run(
%{ %{
"Rendering home timeline" => fn -> "Rendering home timeline" => fn ->
StatusView.render("index.json", %{ StatusView.render("index.json", %{
activities: home_activities, activities: home_activities,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: !output_relationships
}) })
end, end,
"Rendering direct timeline" => fn -> "Rendering direct timeline" => fn ->
StatusView.render("index.json", %{ StatusView.render("index.json", %{
activities: direct_activities, activities: direct_activities,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: !output_relationships
}) })
end, end,
"Rendering public timeline" => fn -> "Rendering public timeline" => fn ->
StatusView.render("index.json", %{ StatusView.render("index.json", %{
activities: public_activities, activities: public_activities,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: !output_relationships
}) })
end, end,
"Rendering tag timeline" => fn -> "Rendering tag timeline" => fn ->
StatusView.render("index.json", %{ StatusView.render("index.json", %{
activities: tag_activities, activities: tag_activities,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: !output_relationships
}) })
end, end,
"Rendering notifications" => fn -> "Rendering notifications" => fn ->
Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{ Pleroma.Web.MastodonAPI.NotificationView.render("index.json", %{
notifications: notifications, notifications: notifications,
for: user for: user,
skip_relationships: !output_relationships
}) })
end, end,
"Rendering favourites timeline" => fn -> "Rendering favourites timeline" => fn ->
StatusView.render("index.json", %{ StatusView.render("index.json", %{
activities: favourites, activities: favourites,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: !output_relationships
}) })
end end
}, },

View File

@ -240,6 +240,8 @@
extended_nickname_format: true, extended_nickname_format: true,
cleanup_attachments: false cleanup_attachments: false
config :pleroma, :extensions, output_relationships_in_statuses_by_default: true
config :pleroma, :feed, config :pleroma, :feed,
post_title: %{ post_title: %{
max_length: 100, max_length: 100,

View File

@ -392,6 +392,19 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- `email` - `email`
- `name`, optional - `name`, optional
- Response:
- On success: `204`, empty response
- On failure:
- 400 Bad Request, JSON:
```json
[
{
"error": "Appropriate error message here"
}
]
```
## `GET /api/pleroma/admin/users/:nickname/password_reset` ## `GET /api/pleroma/admin/users/:nickname/password_reset`
### Get a password reset token for a given nickname ### Get a password reset token for a given nickname

View File

@ -5,7 +5,6 @@
defmodule Mix.Pleroma do defmodule Mix.Pleroma do
@doc "Common functions to be reused in mix tasks" @doc "Common functions to be reused in mix tasks"
def start_pleroma do def start_pleroma do
Mix.Task.run("app.start")
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
if Pleroma.Config.get(:env) != :test do if Pleroma.Config.get(:env) != :test do

View File

@ -67,7 +67,8 @@ def run(["render_timeline", nickname | _] = args) do
Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{
activities: activities, activities: activities,
for: user, for: user,
as: :activity as: :activity,
skip_relationships: true
}) })
end end
}, },

View File

@ -4,10 +4,16 @@
import EctoEnum import EctoEnum
defenum(UserRelationshipTypeEnum, defenum(Pleroma.UserRelationship.Type,
block: 1, block: 1,
mute: 2, mute: 2,
reblog_mute: 3, reblog_mute: 3,
notification_mute: 4, notification_mute: 4,
inverse_subscription: 5 inverse_subscription: 5
) )
defenum(Pleroma.FollowingRelationship.State,
follow_pending: 1,
follow_accept: 2,
follow_reject: 3
)

View File

@ -8,12 +8,13 @@ defmodule Pleroma.FollowingRelationship do
import Ecto.Changeset import Ecto.Changeset
import Ecto.Query import Ecto.Query
alias Ecto.Changeset
alias FlakeId.Ecto.CompatType alias FlakeId.Ecto.CompatType
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
schema "following_relationships" do schema "following_relationships" do
field(:state, :string, default: "accept") field(:state, Pleroma.FollowingRelationship.State, default: :follow_pending)
belongs_to(:follower, User, type: CompatType) belongs_to(:follower, User, type: CompatType)
belongs_to(:following, User, type: CompatType) belongs_to(:following, User, type: CompatType)
@ -27,6 +28,18 @@ def changeset(%__MODULE__{} = following_relationship, attrs) do
|> put_assoc(:follower, attrs.follower) |> put_assoc(:follower, attrs.follower)
|> put_assoc(:following, attrs.following) |> put_assoc(:following, attrs.following)
|> validate_required([:state, :follower, :following]) |> validate_required([:state, :follower, :following])
|> unique_constraint(:follower_id,
name: :following_relationships_follower_id_following_id_index
)
|> validate_not_self_relationship()
end
def state_to_enum(state) when state in ["pending", "accept", "reject"] do
String.to_existing_atom("follow_#{state}")
end
def state_to_enum(state) do
raise "State is not convertible to Pleroma.FollowingRelationship.State: #{state}"
end end
def get(%User{} = follower, %User{} = following) do def get(%User{} = follower, %User{} = following) do
@ -35,7 +48,7 @@ def get(%User{} = follower, %User{} = following) do
|> Repo.one() |> Repo.one()
end end
def update(follower, following, "reject"), do: unfollow(follower, following) def update(follower, following, :follow_reject), do: unfollow(follower, following)
def update(%User{} = follower, %User{} = following, state) do def update(%User{} = follower, %User{} = following, state) do
case get(follower, following) do case get(follower, following) do
@ -50,7 +63,7 @@ def update(%User{} = follower, %User{} = following, state) do
end end
end end
def follow(%User{} = follower, %User{} = following, state \\ "accept") do def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do
%__MODULE__{} %__MODULE__{}
|> changeset(%{follower: follower, following: following, state: state}) |> changeset(%{follower: follower, following: following, state: state})
|> Repo.insert(on_conflict: :nothing) |> Repo.insert(on_conflict: :nothing)
@ -80,7 +93,7 @@ def following_count(%User{} = user) do
def get_follow_requests(%User{id: id}) do def get_follow_requests(%User{id: id}) do
__MODULE__ __MODULE__
|> join(:inner, [r], f in assoc(r, :follower)) |> join(:inner, [r], f in assoc(r, :follower))
|> where([r], r.state == "pending") |> where([r], r.state == ^:follow_pending)
|> where([r], r.following_id == ^id) |> where([r], r.following_id == ^id)
|> select([r, f], f) |> select([r, f], f)
|> Repo.all() |> Repo.all()
@ -88,7 +101,7 @@ def get_follow_requests(%User{id: id}) do
def following?(%User{id: follower_id}, %User{id: followed_id}) do def following?(%User{id: follower_id}, %User{id: followed_id}) do
__MODULE__ __MODULE__
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept") |> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept)
|> Repo.exists?() |> Repo.exists?()
end end
@ -97,7 +110,7 @@ def following(%User{} = user) do
__MODULE__ __MODULE__
|> join(:inner, [r], u in User, on: r.following_id == u.id) |> join(:inner, [r], u in User, on: r.following_id == u.id)
|> where([r], r.follower_id == ^user.id) |> where([r], r.follower_id == ^user.id)
|> where([r], r.state == "accept") |> where([r], r.state == ^:follow_accept)
|> select([r, u], u.follower_address) |> select([r, u], u.follower_address)
|> Repo.all() |> Repo.all()
@ -157,4 +170,30 @@ def find(following_relationships, follower, following) do
fr -> fr.follower_id == follower.id and fr.following_id == following.id fr -> fr.follower_id == follower.id and fr.following_id == following.id
end) end)
end end
defp validate_not_self_relationship(%Changeset{} = changeset) do
changeset
|> validate_follower_id_following_id_inequality()
|> validate_following_id_follower_id_inequality()
end
defp validate_follower_id_following_id_inequality(%Changeset{} = changeset) do
validate_change(changeset, :follower_id, fn _, follower_id ->
if follower_id == get_field(changeset, :following_id) do
[source_id: "can't be equal to following_id"]
else
[]
end
end)
end
defp validate_following_id_follower_id_inequality(%Changeset{} = changeset) do
validate_change(changeset, :following_id, fn _, following_id ->
if following_id == get_field(changeset, :follower_id) do
[target_id: "can't be equal to follower_id"]
else
[]
end
end)
end
end end

View File

@ -35,9 +35,19 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
nickname_text = get_nickname_text(nickname, opts) nickname_text = get_nickname_text(nickname, opts)
link = link =
~s(<span class="h-card"><a data-user="#{id}" class="u-url mention" href="#{ap_id}" rel="ugc">@<span>#{ Phoenix.HTML.Tag.content_tag(
nickname_text :span,
}</span></a></span>) Phoenix.HTML.Tag.content_tag(
:a,
["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
"data-user": id,
class: "u-url mention",
href: ap_id,
rel: "ugc"
),
class: "h-card"
)
|> Phoenix.HTML.safe_to_string()
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}} {link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
@ -49,7 +59,15 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
tag = String.downcase(tag) tag = String.downcase(tag)
url = "#{Pleroma.Web.base_url()}/tag/#{tag}" url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
link = ~s(<a class="hashtag" data-tag="#{tag}" href="#{url}" rel="tag ugc">#{tag_text}</a>)
link =
Phoenix.HTML.Tag.content_tag(:a, tag_text,
class: "hashtag",
"data-tag": tag,
href: url,
rel: "tag ugc"
)
|> Phoenix.HTML.safe_to_string()
{link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}} {link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}
end end

View File

@ -49,8 +49,10 @@ def open(%URI{} = uri, name, opts) do
key = "#{uri.scheme}:#{uri.host}:#{uri.port}" key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
max_connections = pool_opts[:max_connections] || 250
conn_pid = conn_pid =
if Connections.count(name) < opts[:max_connection] do if Connections.count(name) < max_connections do
do_open(uri, opts) do_open(uri, opts)
else else
close_least_used_and_do_open(name, uri, opts) close_least_used_and_do_open(name, uri, opts)

View File

@ -16,6 +16,7 @@ defmodule Pleroma.User do
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Delivery alias Pleroma.Delivery
alias Pleroma.FollowingRelationship alias Pleroma.FollowingRelationship
alias Pleroma.Formatter
alias Pleroma.HTML alias Pleroma.HTML
alias Pleroma.Keys alias Pleroma.Keys
alias Pleroma.Notification alias Pleroma.Notification
@ -452,7 +453,7 @@ defp put_fields(changeset) do
fields = fields =
raw_fields raw_fields
|> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end) |> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
changeset changeset
|> put_change(:raw_fields, raw_fields) |> put_change(:raw_fields, raw_fields)
@ -462,6 +463,12 @@ defp put_fields(changeset) do
end end
end end
defp parse_fields(value) do
value
|> Formatter.linkify(mentions_format: :full)
|> elem(0)
end
defp put_change_if_present(changeset, map_field, value_function) do defp put_change_if_present(changeset, map_field, value_function) do
if value = get_change(changeset, map_field) do if value = get_change(changeset, map_field) do
with {:ok, new_value} <- value_function.(value) do with {:ok, new_value} <- value_function.(value) do
@ -693,7 +700,7 @@ def needs_update?(_), do: true
@spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()} @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
follow(follower, followed, "pending") follow(follower, followed, :follow_pending)
end end
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
@ -713,14 +720,14 @@ def maybe_direct_follow(%User{} = follower, %User{} = followed) do
def follow_all(follower, followeds) do def follow_all(follower, followeds) do
followeds followeds
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end) |> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
|> Enum.each(&follow(follower, &1, "accept")) |> Enum.each(&follow(follower, &1, :follow_accept))
set_cache(follower) set_cache(follower)
end end
defdelegate following(user), to: FollowingRelationship defdelegate following(user), to: FollowingRelationship
def follow(%User{} = follower, %User{} = followed, state \\ "accept") do def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked]) deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
cond do cond do
@ -747,7 +754,7 @@ def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
def unfollow(%User{} = follower, %User{} = followed) do def unfollow(%User{} = follower, %User{} = followed) do
case get_follow_state(follower, followed) do case get_follow_state(follower, followed) do
state when state in ["accept", "pending"] -> state when state in [:follow_pending, :follow_accept] ->
FollowingRelationship.unfollow(follower, followed) FollowingRelationship.unfollow(follower, followed)
{:ok, followed} = update_follower_count(followed) {:ok, followed} = update_follower_count(followed)
@ -765,6 +772,7 @@ def unfollow(%User{} = follower, %User{} = followed) do
defdelegate following?(follower, followed), to: FollowingRelationship defdelegate following?(follower, followed), to: FollowingRelationship
@doc "Returns follow state as Pleroma.FollowingRelationship.State value"
def get_follow_state(%User{} = follower, %User{} = following) do def get_follow_state(%User{} = follower, %User{} = following) do
following_relationship = FollowingRelationship.get(follower, following) following_relationship = FollowingRelationship.get(follower, following)
get_follow_state(follower, following, following_relationship) get_follow_state(follower, following, following_relationship)
@ -778,8 +786,11 @@ def get_follow_state(
case {following_relationship, following.local} do case {following_relationship, following.local} do
{nil, false} -> {nil, false} ->
case Utils.fetch_latest_follow(follower, following) do case Utils.fetch_latest_follow(follower, following) do
%{data: %{"state" => state}} when state in ["pending", "accept"] -> state %Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
_ -> nil FollowingRelationship.state_to_enum(state)
_ ->
nil
end end
{%{state: state}, _} -> {%{state: state}, _} ->
@ -1278,7 +1289,7 @@ def blocks?(nil, _), do: false
def blocks?(%User{} = user, %User{} = target) do def blocks?(%User{} = user, %User{} = target) do
blocks_user?(user, target) || blocks_user?(user, target) ||
(!User.following?(user, target) && blocks_domain?(user, target)) (blocks_domain?(user, target) and not User.following?(user, target))
end end
def blocks_user?(%User{} = user, %User{} = target) do def blocks_user?(%User{} = user, %User{} = target) do
@ -1979,17 +1990,6 @@ def fields(%{fields: nil}), do: []
def fields(%{fields: fields}), do: fields def fields(%{fields: fields}), do: fields
def sanitized_fields(%User{} = user) do
user
|> User.fields()
|> Enum.map(fn %{"name" => name, "value" => value} ->
%{
"name" => name,
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
}
end)
end
def validate_fields(changeset, remote? \\ false) do def validate_fields(changeset, remote? \\ false) do
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
limit = Pleroma.Config.get([:instance, limit_name], 0) limit = Pleroma.Config.get([:instance, limit_name], 0)

View File

@ -148,7 +148,7 @@ defp compose_query({:followers, %User{id: id}}, query) do
as: :relationships, as: :relationships,
on: r.following_id == ^id and r.follower_id == u.id on: r.following_id == ^id and r.follower_id == u.id
) )
|> where([relationships: r], r.state == "accept") |> where([relationships: r], r.state == ^:follow_accept)
end end
defp compose_query({:friends, %User{id: id}}, query) do defp compose_query({:friends, %User{id: id}}, query) do
@ -158,7 +158,7 @@ defp compose_query({:friends, %User{id: id}}, query) do
as: :relationships, as: :relationships,
on: r.following_id == u.id and r.follower_id == ^id on: r.following_id == u.id and r.follower_id == ^id
) )
|> where([relationships: r], r.state == "accept") |> where([relationships: r], r.state == ^:follow_accept)
end end
defp compose_query({:recipients_from_activity, to}, query) do defp compose_query({:recipients_from_activity, to}, query) do
@ -173,7 +173,7 @@ defp compose_query({:recipients_from_activity, to}, query) do
) )
|> where( |> where(
[u, following: f, relationships: r], [u, following: f, relationships: r],
u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept") u.ap_id in ^to or (f.follower_address in ^to and r.state == ^:follow_accept)
) )
|> distinct(true) |> distinct(true)
end end

View File

@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do
import Ecto.Changeset import Ecto.Changeset
import Ecto.Query import Ecto.Query
alias Ecto.Changeset
alias Pleroma.FollowingRelationship alias Pleroma.FollowingRelationship
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
@ -16,12 +17,12 @@ defmodule Pleroma.UserRelationship do
schema "user_relationships" do schema "user_relationships" do
belongs_to(:source, User, type: FlakeId.Ecto.CompatType) belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
belongs_to(:target, User, type: FlakeId.Ecto.CompatType) belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
field(:relationship_type, UserRelationshipTypeEnum) field(:relationship_type, Pleroma.UserRelationship.Type)
timestamps(updated_at: false) timestamps(updated_at: false)
end end
for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do
# `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`, # `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
# `def create_notification_mute/2`, `def create_inverse_subscription/2` # `def create_notification_mute/2`, `def create_inverse_subscription/2`
def unquote(:"create_#{relationship_type}")(source, target), def unquote(:"create_#{relationship_type}")(source, target),
@ -40,7 +41,7 @@ def unquote(:"#{relationship_type}_exists?")(source, target),
def user_relationship_types, do: Keyword.keys(user_relationship_mappings()) def user_relationship_types, do: Keyword.keys(user_relationship_mappings())
def user_relationship_mappings, do: UserRelationshipTypeEnum.__enum_map__() def user_relationship_mappings, do: Pleroma.UserRelationship.Type.__enum_map__()
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
user_relationship user_relationship
@ -129,17 +130,27 @@ def exists?(dictionary, rel_type, source, target, func) do
end end
@doc ":relationships option for StatusView / AccountView / NotificationView" @doc ":relationships option for StatusView / AccountView / NotificationView"
def view_relationships_option(nil = _reading_user, _actors) do def view_relationships_option(reading_user, actors, opts \\ [])
def view_relationships_option(nil = _reading_user, _actors, _opts) do
%{user_relationships: [], following_relationships: []} %{user_relationships: [], following_relationships: []}
end end
def view_relationships_option(%User{} = reading_user, actors) do def view_relationships_option(%User{} = reading_user, actors, opts) do
{source_to_target_rel_types, target_to_source_rel_types} =
if opts[:source_mutes_only] do
# This option is used for rendering statuses (FE needs `muted` flag for each one anyways)
{[:mute], []}
else
{[:block, :mute, :notification_mute, :reblog_mute], [:block, :inverse_subscription]}
end
user_relationships = user_relationships =
UserRelationship.dictionary( UserRelationship.dictionary(
[reading_user], [reading_user],
actors, actors,
[:block, :mute, :notification_mute, :reblog_mute], source_to_target_rel_types,
[:block, :inverse_subscription] target_to_source_rel_types
) )
following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors) following_relationships = FollowingRelationship.all_between_user_sets([reading_user], actors)
@ -147,16 +158,14 @@ def view_relationships_option(%User{} = reading_user, actors) do
%{user_relationships: user_relationships, following_relationships: following_relationships} %{user_relationships: user_relationships, following_relationships: following_relationships}
end end
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do defp validate_not_self_relationship(%Changeset{} = changeset) do
changeset changeset
|> validate_change(:target_id, fn _, target_id -> |> validate_source_id_target_id_inequality()
if target_id == get_field(changeset, :source_id) do |> validate_target_id_source_id_inequality()
[target_id: "can't be equal to source_id"] end
else
[] defp validate_source_id_target_id_inequality(%Changeset{} = changeset) do
end validate_change(changeset, :source_id, fn _, source_id ->
end)
|> validate_change(:source_id, fn _, source_id ->
if source_id == get_field(changeset, :target_id) do if source_id == get_field(changeset, :target_id) do
[source_id: "can't be equal to target_id"] [source_id: "can't be equal to target_id"]
else else
@ -164,4 +173,14 @@ defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
end end
end) end)
end end
defp validate_target_id_source_id_inequality(%Changeset{} = changeset) do
validate_change(changeset, :target_id, fn _, target_id ->
if target_id == get_field(changeset, :source_id) do
[target_id: "can't be equal to source_id"]
else
[]
end
end)
end
end end

View File

@ -721,7 +721,7 @@ def move(%User{} = origin, %User{} = target, local \\ true) do
end end
end end
defp fetch_activities_for_context_query(context, opts) do def fetch_activities_for_context_query(context, opts) do
public = [Constants.as_public()] public = [Constants.as_public()]
recipients = recipients =

View File

@ -6,14 +6,10 @@ def type, do: :string
def cast(object) when is_binary(object) do def cast(object) when is_binary(object) do
# Host has to be present and scheme has to be an http scheme (for now) # Host has to be present and scheme has to be an http scheme (for now)
case URI.parse(object) do case URI.parse(object) do
%URI{host: nil} -> %URI{host: nil} -> :error
:error %URI{host: ""} -> :error
%URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, object}
%URI{scheme: scheme} when scheme in ["https", "http"] -> _ -> :error
{:ok, object}
_ ->
:error
end end
end end

View File

@ -205,16 +205,46 @@ def fix_context(object) do
|> Map.put("conversation", context) |> Map.put("conversation", context)
end end
defp add_if_present(map, _key, nil), do: map
defp add_if_present(map, key, value) do
Map.put(map, key, value)
end
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
attachments = attachments =
Enum.map(attachment, fn data -> Enum.map(attachment, fn data ->
media_type = data["mediaType"] || data["mimeType"] url =
href = data["url"] || data["href"] cond do
url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}] is_list(data["url"]) -> List.first(data["url"])
is_map(data["url"]) -> data["url"]
true -> nil
end
data media_type =
|> Map.put("mediaType", media_type) cond do
|> Map.put("url", url) is_map(url) && is_binary(url["mediaType"]) -> url["mediaType"]
is_binary(data["mediaType"]) -> data["mediaType"]
is_binary(data["mimeType"]) -> data["mimeType"]
true -> nil
end
href =
cond do
is_map(url) && is_binary(url["href"]) -> url["href"]
is_binary(data["url"]) -> data["url"]
is_binary(data["href"]) -> data["href"]
end
attachment_url =
%{"href" => href}
|> add_if_present("mediaType", media_type)
|> add_if_present("type", Map.get(url || %{}, "type"))
%{"url" => [attachment_url]}
|> add_if_present("mediaType", media_type)
|> add_if_present("type", data["type"])
|> add_if_present("name", data["name"])
end) end)
Map.put(object, "attachment", attachments) Map.put(object, "attachment", attachments)
@ -494,7 +524,8 @@ def handle_incoming(
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)}, {_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
{_, {:ok, _}} <- {_, {:ok, _}} <-
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")}, {:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do {:ok, _relationship} <-
FollowingRelationship.update(follower, followed, :follow_accept) do
ActivityPub.accept(%{ ActivityPub.accept(%{
to: [follower.ap_id], to: [follower.ap_id],
actor: followed, actor: followed,
@ -504,7 +535,7 @@ def handle_incoming(
else else
{:user_blocked, true} -> {:user_blocked, true} ->
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject") {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject") {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
ActivityPub.reject(%{ ActivityPub.reject(%{
to: [follower.ap_id], to: [follower.ap_id],
@ -515,7 +546,7 @@ def handle_incoming(
{:follow, {:error, _}} -> {:follow, {:error, _}} ->
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject") {:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject") {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
ActivityPub.reject(%{ ActivityPub.reject(%{
to: [follower.ap_id], to: [follower.ap_id],
@ -525,7 +556,7 @@ def handle_incoming(
}) })
{:user_locked, true} -> {:user_locked, true} ->
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending") {:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
:noop :noop
end end
@ -545,7 +576,7 @@ def handle_incoming(
{:ok, follow_activity} <- get_follow_activity(follow_object, followed), {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
ActivityPub.accept(%{ ActivityPub.accept(%{
to: follow_activity.data["to"], to: follow_activity.data["to"],
type: "Accept", type: "Accept",
@ -568,7 +599,7 @@ def handle_incoming(
{:ok, follow_activity} <- get_follow_activity(follow_object, followed), {:ok, follow_activity} <- get_follow_activity(follow_object, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]), %User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"), {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
{:ok, activity} <- {:ok, activity} <-
ActivityPub.reject(%{ ActivityPub.reject(%{
to: follow_activity.data["to"], to: follow_activity.data["to"],

View File

@ -258,7 +258,7 @@ def list_instance_statuses(conn, %{"instance" => instance} = params) do
conn conn
|> put_view(Pleroma.Web.AdminAPI.StatusView) |> put_view(Pleroma.Web.AdminAPI.StatusView)
|> render("index.json", %{activities: activities, as: :activity}) |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
end end
def list_user_statuses(conn, %{"nickname" => nickname} = params) do def list_user_statuses(conn, %{"nickname" => nickname} = params) do
@ -277,7 +277,7 @@ def list_user_statuses(conn, %{"nickname" => nickname} = params) do
conn conn
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", %{activities: activities, as: :activity}) |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
else else
_ -> {:error, :not_found} _ -> {:error, :not_found}
end end
@ -576,9 +576,8 @@ def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target})
@doc "Sends registration invite via email" @doc "Sends registration invite via email"
def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
with true <- with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
Config.get([:instance, :invites_enabled]) && {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
!Config.get([:instance, :registrations_open]),
{:ok, invite_token} <- UserInviteToken.create_invite(), {:ok, invite_token} <- UserInviteToken.create_invite(),
email <- email <-
Pleroma.Emails.UserEmail.user_invitation_email( Pleroma.Emails.UserEmail.user_invitation_email(
@ -589,6 +588,18 @@ def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params)
), ),
{:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
json_response(conn, :no_content, "") json_response(conn, :no_content, "")
else
{:registrations_open, _} ->
errors(
conn,
{:error, "To send invites you need to set the `registrations_open` option to false."}
)
{:invites_enabled, _} ->
errors(
conn,
{:error, "To send invites you need to set the `invites_enabled` option to true."}
)
end end
end end
@ -801,7 +812,7 @@ def list_statuses(%{assigns: %{user: _admin}} = conn, params) do
conn conn
|> put_view(Pleroma.Web.AdminAPI.StatusView) |> put_view(Pleroma.Web.AdminAPI.StatusView)
|> render("index.json", %{activities: activities, as: :activity}) |> render("index.json", %{activities: activities, as: :activity, skip_relationships: false})
end end
def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do

View File

@ -38,7 +38,12 @@ def render("show.json", %{report: report, user: user, account: account, statuses
actor: merge_account_views(user), actor: merge_account_views(user),
content: content, content: content,
created_at: created_at, created_at: created_at,
statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}), statuses:
StatusView.render("index.json", %{
activities: statuses,
as: :activity,
skip_relationships: false
}),
state: report.data["state"], state: report.data["state"],
notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}) notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes})
} }

View File

@ -0,0 +1,44 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec do
alias OpenApiSpex.OpenApi
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router
@behaviour OpenApi
@impl OpenApi
def spec do
%OpenApi{
servers: [
# Populate the Server info from a phoenix endpoint
OpenApiSpex.Server.from_endpoint(Endpoint)
],
info: %OpenApiSpex.Info{
title: "Pleroma",
description: Application.spec(:pleroma, :description) |> to_string(),
version: Application.spec(:pleroma, :vsn) |> to_string()
},
# populate the paths from a phoenix router
paths: OpenApiSpex.Paths.from_router(Router),
components: %OpenApiSpex.Components{
securitySchemes: %{
"oAuth" => %OpenApiSpex.SecurityScheme{
type: "oauth2",
flows: %OpenApiSpex.OAuthFlows{
password: %OpenApiSpex.OAuthFlow{
authorizationUrl: "/oauth/authorize",
tokenUrl: "/oauth/token",
scopes: %{"read" => "read"}
}
}
}
}
}
}
# discover request/response schemas from path specs
|> OpenApiSpex.resolve_schema_modules()
end
end

View File

@ -0,0 +1,27 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Helpers do
def request_body(description, schema_ref, opts \\ []) do
media_types = ["application/json", "multipart/form-data"]
content =
media_types
|> Enum.map(fn type ->
{type,
%OpenApiSpex.MediaType{
schema: schema_ref,
example: opts[:example],
examples: opts[:examples]
}}
end)
|> Enum.into(%{})
%OpenApiSpex.RequestBody{
description: description,
content: content,
required: opts[:required] || false
}
end
end

View File

@ -0,0 +1,96 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.AppOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Helpers
alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
@spec open_api_operation(atom) :: Operation.t()
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
@spec create_operation() :: Operation.t()
def create_operation do
%Operation{
tags: ["apps"],
summary: "Create an application",
description: "Create a new application to obtain OAuth2 credentials",
operationId: "AppController.create",
requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true),
responses: %{
200 => Operation.response("App", "application/json", AppCreateResponse),
422 =>
Operation.response(
"Unprocessable Entity",
"application/json",
%Schema{
type: :object,
description:
"If a required parameter is missing or improperly formatted, the request will fail.",
properties: %{
error: %Schema{type: :string}
},
example: %{
"error" => "Validation failed: Redirect URI must be an absolute URI."
}
}
)
}
}
end
def verify_credentials_operation do
%Operation{
tags: ["apps"],
summary: "Verify your app works",
description: "Confirm that the app's OAuth2 credentials work.",
operationId: "AppController.verify_credentials",
security: [
%{
"oAuth" => ["read"]
}
],
responses: %{
200 =>
Operation.response("App", "application/json", %Schema{
type: :object,
description:
"If the Authorization header was provided with a valid token, you should see your app returned as an Application entity.",
properties: %{
name: %Schema{type: :string},
vapid_key: %Schema{type: :string},
website: %Schema{type: :string, nullable: true}
},
example: %{
"name" => "My App",
"vapid_key" =>
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
"website" => "https://myapp.com/"
}
}),
422 =>
Operation.response(
"Unauthorized",
"application/json",
%Schema{
type: :object,
description:
"If the Authorization header contains an invalid token, is malformed, or is not present, an error will be returned indicating an authorization failure.",
properties: %{
error: %Schema{type: :string}
},
example: %{
"error" => "The access token is invalid."
}
}
)
}
}
end
end

View File

@ -0,0 +1,33 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateRequest do
alias OpenApiSpex.Schema
require OpenApiSpex
OpenApiSpex.schema(%{
title: "AppCreateRequest",
description: "POST body for creating an app",
type: :object,
properties: %{
client_name: %Schema{type: :string, description: "A name for your application."},
redirect_uris: %Schema{
type: :string,
description:
"Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
},
scopes: %Schema{
type: :string,
description: "Space separated list of scopes. If none is provided, defaults to `read`."
},
website: %Schema{type: :string, description: "A URL to the homepage of your app"}
},
required: [:client_name, :redirect_uris],
example: %{
"client_name" => "My App",
"redirect_uris" => "https://myapp.com/auth/callback",
"website" => "https://myapp.com/"
}
})
end

View File

@ -0,0 +1,33 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateResponse do
alias OpenApiSpex.Schema
require OpenApiSpex
OpenApiSpex.schema(%{
title: "AppCreateResponse",
description: "Response schema for an app",
type: :object,
properties: %{
id: %Schema{type: :string},
name: %Schema{type: :string},
client_id: %Schema{type: :string},
client_secret: %Schema{type: :string},
redirect_uri: %Schema{type: :string},
vapid_key: %Schema{type: :string},
website: %Schema{type: :string, nullable: true}
},
example: %{
"id" => "123",
"name" => "My App",
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
"vapid_key" =>
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
"website" => "https://myapp.com/"
}
})
end

View File

@ -187,7 +187,7 @@ defp object(draft) do
end end
defp preview?(draft) do defp preview?(draft) do
preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"]) || false preview? = Pleroma.Web.ControllerHelper.truthy_param?(draft.params["preview"])
%__MODULE__{draft | preview?: preview?} %__MODULE__{draft | preview?: preview?}
end end

View File

@ -45,7 +45,7 @@ def accept_follow_request(follower, followed) do
with {:ok, follower} <- User.follow(follower, followed), with {:ok, follower} <- User.follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"), {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept),
{:ok, _activity} <- {:ok, _activity} <-
ActivityPub.accept(%{ ActivityPub.accept(%{
to: [follower.ap_id], to: [follower.ap_id],
@ -60,7 +60,7 @@ def accept_follow_request(follower, followed) do
def reject_follow_request(follower, followed) do def reject_follow_request(follower, followed) do
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"), {:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"), {:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
{:ok, _activity} <- {:ok, _activity} <-
ActivityPub.reject(%{ ActivityPub.reject(%{
to: [follower.ap_id], to: [follower.ap_id],

View File

@ -5,10 +5,18 @@
defmodule Pleroma.Web.ControllerHelper do defmodule Pleroma.Web.ControllerHelper do
use Pleroma.Web, :controller use Pleroma.Web, :controller
# As in MastoAPI, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html alias Pleroma.Config
# As in Mastodon API, per https://api.rubyonrails.org/classes/ActiveModel/Type/Boolean.html
@falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"] @falsy_param_values [false, 0, "0", "f", "F", "false", "False", "FALSE", "off", "OFF"]
def truthy_param?(blank_value) when blank_value in [nil, ""], do: nil
def truthy_param?(value), do: value not in @falsy_param_values def explicitly_falsy_param?(value), do: value in @falsy_param_values
# Note: `nil` and `""` are considered falsy values in Pleroma
def falsy_param?(value),
do: explicitly_falsy_param?(value) or value in [nil, ""]
def truthy_param?(value), do: not falsy_param?(value)
def json_response(conn, status, json) do def json_response(conn, status, json) do
conn conn
@ -96,4 +104,14 @@ def try_render(conn, _, _) do
def put_if_exist(map, _key, nil), do: map def put_if_exist(map, _key, nil), do: map
def put_if_exist(map, key, value), do: Map.put(map, key, value) def put_if_exist(map, key, value), do: Map.put(map, key, value)
@doc "Whether to skip rendering `[:account][:pleroma][:relationship]`for statuses/notifications"
def skip_relationships?(params) do
if Config.get([:extensions, :output_relationships_in_statuses_by_default]) do
false
else
# BREAKING: older PleromaFE versions do not send this param but _do_ expect relationships.
not truthy_param?(params["with_relationships"])
end
end
end end

View File

@ -6,7 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, import Pleroma.Web.ControllerHelper,
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,
skip_relationships?: 1
]
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
@ -237,7 +243,12 @@ def statuses(%{assigns: %{user: reading_user}} = conn, params) do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", activities: activities, for: reading_user, as: :activity) |> render("index.json",
activities: activities,
for: reading_user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else else
_e -> render_error(conn, :not_found, "Can't find user") _e -> render_error(conn, :not_found, "Can't find user")
end end

View File

@ -14,17 +14,20 @@ 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) plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
plug(OpenApiSpex.Plug.CastAndValidate)
@local_mastodon_name "Mastodon-Local" @local_mastodon_name "Mastodon-Local"
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
@doc "POST /api/v1/apps" @doc "POST /api/v1/apps"
def create(conn, params) do def create(%{body_params: params} = conn, _params) do
scopes = Scopes.fetch_scopes(params, ["read"]) scopes = Scopes.fetch_scopes(params, ["read"])
app_attrs = app_attrs =
params params
|> Map.drop(["scope", "scopes"]) |> Map.take([:client_name, :redirect_uris, :website])
|> Map.put("scopes", scopes) |> Map.put(:scopes, scopes)
with cs <- App.register_changeset(%App{}, app_attrs), with cs <- App.register_changeset(%App{}, app_attrs),
false <- cs.changes[:client_name] == @local_mastodon_name, false <- cs.changes[:client_name] == @local_mastodon_name,

View File

@ -5,7 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.NotificationController do defmodule Pleroma.Web.MastodonAPI.NotificationController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
@ -45,7 +45,11 @@ def index(%{assigns: %{user: user}} = conn, params) do
conn conn
|> add_link_headers(notifications) |> add_link_headers(notifications)
|> render("index.json", notifications: notifications, for: user) |> render("index.json",
notifications: notifications,
for: user,
skip_relationships: skip_relationships?(params)
)
end end
# GET /api/v1/notifications/:id # GET /api/v1/notifications/:id
@ -66,7 +70,8 @@ def clear(%{assigns: %{user: user}} = conn, _params) do
json(conn, %{}) json(conn, %{})
end end
# POST /api/v1/notifications/dismiss # POST /api/v1/notifications/:id/dismiss
# POST /api/v1/notifications/dismiss (deprecated)
def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
with {:ok, _notif} <- Notification.dismiss(user, id) do with {:ok, _notif} <- Notification.dismiss(user, id) do
json(conn, %{}) json(conn, %{})

View File

@ -5,13 +5,14 @@
defmodule Pleroma.Web.MastodonAPI.SearchController do defmodule Pleroma.Web.MastodonAPI.SearchController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [fetch_integer_param: 2, skip_relationships?: 1]
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.ControllerHelper
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
@ -66,10 +67,11 @@ defp do_search(version, %{assigns: %{user: user}} = conn, %{"q" => query} = para
defp search_options(params, user) do defp search_options(params, user) do
[ [
skip_relationships: skip_relationships?(params),
resolve: params["resolve"] == "true", resolve: params["resolve"] == "true",
following: params["following"] == "true", following: params["following"] == "true",
limit: ControllerHelper.fetch_integer_param(params, "limit"), limit: fetch_integer_param(params, "limit"),
offset: ControllerHelper.fetch_integer_param(params, "offset"), offset: fetch_integer_param(params, "offset"),
type: params["type"], type: params["type"],
author: get_author(params), author: get_author(params),
for_user: user for_user: user
@ -79,12 +81,24 @@ defp search_options(params, user) do
defp resource_search(_, "accounts", query, options) do defp resource_search(_, "accounts", query, options) do
accounts = with_fallback(fn -> User.search(query, options) end) accounts = with_fallback(fn -> User.search(query, options) end)
AccountView.render("index.json", users: accounts, for: options[:for_user], as: :user)
AccountView.render("index.json",
users: accounts,
for: options[:for_user],
as: :user,
skip_relationships: false
)
end end
defp resource_search(_, "statuses", query, options) do defp resource_search(_, "statuses", query, options) do
statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end) statuses = with_fallback(fn -> Activity.search(options[:for_user], query, options) end)
StatusView.render("index.json", activities: statuses, for: options[:for_user], as: :activity)
StatusView.render("index.json",
activities: statuses,
for: options[:for_user],
as: :activity,
skip_relationships: options[:skip_relationships]
)
end end
defp resource_search(:v2, "hashtags", query, _options) do defp resource_search(:v2, "hashtags", query, _options) do

View File

@ -5,7 +5,8 @@
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, add_link_headers: 2] import Pleroma.Web.ControllerHelper,
only: [try_render: 3, add_link_headers: 2, skip_relationships?: 1]
require Ecto.Query require Ecto.Query
@ -101,7 +102,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
`ids` query param is required `ids` query param is required
""" """
def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do def index(%{assigns: %{user: user}} = conn, %{"ids" => ids} = params) do
limit = 100 limit = 100
activities = activities =
@ -110,7 +111,12 @@ def index(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do
|> Activity.all_by_ids_with_object() |> Activity.all_by_ids_with_object()
|> Enum.filter(&Visibility.visible_for_user?(&1, user)) |> Enum.filter(&Visibility.visible_for_user?(&1, user))
render(conn, "index.json", activities: activities, for: user, as: :activity) render(conn, "index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
@doc """ @doc """
@ -360,7 +366,12 @@ def favourites(%{assigns: %{user: user}} = conn, params) do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
@doc "GET /api/v1/bookmarks" @doc "GET /api/v1/bookmarks"
@ -378,6 +389,11 @@ def bookmarks(%{assigns: %{user: user}} = conn, params) do
conn conn
|> add_link_headers(bookmarks) |> add_link_headers(bookmarks)
|> render("index.json", %{activities: activities, for: user, as: :activity}) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
end end

View File

@ -6,7 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, import Pleroma.Web.ControllerHelper,
only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1] only: [add_link_headers: 2, add_link_headers: 3, truthy_param?: 1, skip_relationships?: 1]
alias Pleroma.Pagination alias Pleroma.Pagination
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
@ -14,9 +14,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
# TODO: Replace with a macro when there is a Phoenix release with # TODO: Replace with a macro when there is a Phoenix release with the following commit in it:
# https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e
# in it
plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct) plug(RateLimiter, [name: :timeline, bucket_name: :direct_timeline] when action == :direct)
plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public) plug(RateLimiter, [name: :timeline, bucket_name: :public_timeline] when action == :public)
@ -49,7 +48,12 @@ def home(%{assigns: %{user: user}} = conn, params) do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
# GET /api/v1/timelines/direct # GET /api/v1/timelines/direct
@ -68,7 +72,12 @@ def direct(%{assigns: %{user: user}} = conn, params) do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> render("index.json", activities: activities, for: user, as: :activity) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
# GET /api/v1/timelines/public # GET /api/v1/timelines/public
@ -95,7 +104,12 @@ def public(%{assigns: %{user: user}} = conn, params) do
conn conn
|> add_link_headers(activities, %{"local" => local_only}) |> add_link_headers(activities, %{"local" => local_only})
|> render("index.json", activities: activities, for: user, as: :activity) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else else
render_error(conn, :unauthorized, "authorization required for timeline view") render_error(conn, :unauthorized, "authorization required for timeline view")
end end
@ -140,7 +154,12 @@ def hashtag(%{assigns: %{user: user}} = conn, params) do
conn conn
|> add_link_headers(activities, %{"local" => local_only}) |> add_link_headers(activities, %{"local" => local_only})
|> render("index.json", activities: activities, for: user, as: :activity) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
# GET /api/v1/timelines/list/:list_id # GET /api/v1/timelines/list/:list_id
@ -164,7 +183,12 @@ def list(%{assigns: %{user: user}} = conn, %{"list_id" => id} = params) do
|> ActivityPub.fetch_activities_bounded(following, params) |> ActivityPub.fetch_activities_bounded(following, params)
|> Enum.reverse() |> Enum.reverse()
render(conn, "index.json", activities: activities, for: user, as: :activity) render(conn, "index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else else
_e -> render_error(conn, :forbidden, "Error.") _e -> render_error(conn, :forbidden, "Error.")
end end

View File

@ -15,6 +15,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
def render("index.json", %{users: users} = opts) do def render("index.json", %{users: users} = opts) do
reading_user = opts[:for] reading_user = opts[:for]
# Note: :skip_relationships option is currently intentionally not supported for accounts
relationships_opt = relationships_opt =
cond do cond do
Map.has_key?(opts, :relationships) -> Map.has_key?(opts, :relationships) ->
@ -73,7 +74,7 @@ def render(
followed_by = followed_by =
if following_relationships do if following_relationships do
case FollowingRelationship.find(following_relationships, target, reading_user) do case FollowingRelationship.find(following_relationships, target, reading_user) do
%{state: "accept"} -> true %{state: :follow_accept} -> true
_ -> false _ -> false
end end
else else
@ -83,7 +84,7 @@ def render(
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
%{ %{
id: to_string(target.id), id: to_string(target.id),
following: follow_state == "accept", following: follow_state == :follow_accept,
followed_by: followed_by, followed_by: followed_by,
blocking: blocking:
UserRelationship.exists?( UserRelationship.exists?(
@ -125,7 +126,7 @@ def render(
reading_user, reading_user,
&User.subscribed_to?(&2, &1) &User.subscribed_to?(&2, &1)
), ),
requested: follow_state == "pending", requested: follow_state == :follow_pending,
domain_blocking: User.blocks_domain?(reading_user, target), domain_blocking: User.blocks_domain?(reading_user, target),
showing_reblogs: showing_reblogs:
not UserRelationship.exists?( not UserRelationship.exists?(
@ -192,11 +193,15 @@ defp do_render("show.json", %{user: user} = opts) do
end) end)
relationship = relationship =
render("relationship.json", %{ if opts[:skip_relationships] do
user: opts[:for], %{}
target: user, else
relationships: opts[:relationships] render("relationship.json", %{
}) user: opts[:for],
target: user,
relationships: opts[:relationships]
})
end
%{ %{
id: to_string(user.id), id: to_string(user.id),

View File

@ -51,14 +51,15 @@ def render("index.json", %{notifications: notifications, for: reading_user} = op
|> Enum.filter(& &1) |> Enum.filter(& &1)
|> Kernel.++(move_activities_targets) |> Kernel.++(move_activities_targets)
UserRelationship.view_relationships_option(reading_user, actors) UserRelationship.view_relationships_option(reading_user, actors,
source_mutes_only: opts[:skip_relationships]
)
end end
opts = %{ opts =
for: reading_user, opts
parent_activities: parent_activities, |> Map.put(:parent_activities, parent_activities)
relationships: relationships_opt |> Map.put(:relationships, relationships_opt)
}
safe_render_many(notifications, NotificationView, "show.json", opts) safe_render_many(notifications, NotificationView, "show.json", opts)
end end
@ -82,12 +83,16 @@ def render(
mastodon_type = Activity.mastodon_notification_type(activity) mastodon_type = Activity.mastodon_notification_type(activity)
render_opts = %{
relationships: opts[:relationships],
skip_relationships: opts[:skip_relationships]
}
with %{id: _} = account <- with %{id: _} = account <-
AccountView.render("show.json", %{ AccountView.render(
user: actor, "show.json",
for: reading_user, Map.merge(render_opts, %{user: actor, for: reading_user})
relationships: opts[:relationships] ) do
}) do
response = %{ response = %{
id: to_string(notification.id), id: to_string(notification.id),
type: mastodon_type, type: mastodon_type,
@ -98,8 +103,6 @@ def render(
} }
} }
render_opts = %{relationships: opts[:relationships]}
case mastodon_type do case mastodon_type do
"mention" -> "mention" ->
put_status(response, activity, reading_user, render_opts) put_status(response, activity, reading_user, render_opts)
@ -111,6 +114,7 @@ def render(
put_status(response, parent_activity_fn.(), reading_user, render_opts) put_status(response, parent_activity_fn.(), reading_user, render_opts)
"move" -> "move" ->
# Note: :skip_relationships option being applied to _account_ rendering (here)
put_target(response, activity, reading_user, render_opts) put_target(response, activity, reading_user, render_opts)
"follow" -> "follow" ->

View File

@ -99,7 +99,9 @@ def render("index.json", opts) do
true -> true ->
actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"])) actors = Enum.map(activities ++ parent_activities, &get_user(&1.data["actor"]))
UserRelationship.view_relationships_option(reading_user, actors) UserRelationship.view_relationships_option(reading_user, actors,
source_mutes_only: opts[:skip_relationships]
)
end end
opts = opts =
@ -153,7 +155,8 @@ def render(
AccountView.render("show.json", %{ AccountView.render("show.json", %{
user: user, user: user,
for: opts[:for], for: opts[:for],
relationships: opts[:relationships] relationships: opts[:relationships],
skip_relationships: opts[:skip_relationships]
}), }),
in_reply_to_id: nil, in_reply_to_id: nil,
in_reply_to_account_id: nil, in_reply_to_account_id: nil,
@ -301,6 +304,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
_ -> [] _ -> []
end end
# Status muted state (would do 1 request per status unless user mutes are preloaded)
muted = muted =
thread_muted? || thread_muted? ||
UserRelationship.exists?( UserRelationship.exists?(
@ -319,7 +323,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
AccountView.render("show.json", %{ AccountView.render("show.json", %{
user: user, user: user,
for: opts[:for], for: opts[:for],
relationships: opts[:relationships] relationships: opts[:relationships],
skip_relationships: opts[:skip_relationships]
}), }),
in_reply_to_id: reply_to && to_string(reply_to.id), in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id), in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),

View File

@ -15,7 +15,12 @@ defmodule Pleroma.Web.OAuth.Scopes do
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
""" """
@spec fetch_scopes(map(), list()) :: list() @spec fetch_scopes(map() | struct(), list()) :: list()
def fetch_scopes(%Pleroma.Web.ApiSpec.Schemas.AppCreateRequest{scopes: scopes}, default) do
parse_scopes(scopes, default)
end
def fetch_scopes(params, default) do def fetch_scopes(params, default) do
parse_scopes(params["scope"] || params["scopes"], default) parse_scopes(params["scope"] || params["scopes"], default)
end end

View File

@ -6,7 +6,7 @@ defmodule Pleroma.Web.PleromaAPI.AccountController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, import Pleroma.Web.ControllerHelper,
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, skip_relationships?: 1]
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Plugs.OAuthScopesPlug alias Pleroma.Plugs.OAuthScopesPlug
@ -139,7 +139,12 @@ def favourites(%{assigns: %{user: for_user, account: user}} = conn, params) do
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", activities: activities, for: for_user, as: :activity) |> render("index.json",
activities: activities,
for: for_user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
end end
@doc "POST /api/v1/pleroma/accounts/:id/subscribe" @doc "POST /api/v1/pleroma/accounts/:id/subscribe"

View File

@ -5,7 +5,7 @@
defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2] import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2, skip_relationships?: 1]
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
@ -110,12 +110,11 @@ def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id})
end end
def conversation_statuses( def conversation_statuses(
%{assigns: %{user: user}} = conn, %{assigns: %{user: %{id: user_id} = user}} = conn,
%{"id" => participation_id} = params %{"id" => participation_id} = params
) do ) do
with %Participation{} = participation <- with %Participation{user_id: ^user_id} = participation <-
Participation.get(participation_id, preload: [:conversation]), Participation.get(participation_id, preload: [:conversation]) do
true <- user.id == participation.user_id do
params = params =
params params
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
@ -124,13 +123,19 @@ def conversation_statuses(
activities = activities =
participation.conversation.ap_id participation.conversation.ap_id
|> ActivityPub.fetch_activities_for_context(params) |> ActivityPub.fetch_activities_for_context_query(params)
|> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
|> Enum.reverse() |> Enum.reverse()
conn conn
|> add_link_headers(activities) |> add_link_headers(activities)
|> put_view(StatusView) |> put_view(StatusView)
|> render("index.json", %{activities: activities, for: user, as: :activity}) |> render("index.json",
activities: activities,
for: user,
as: :activity,
skip_relationships: skip_relationships?(params)
)
else else
_error -> _error ->
conn conn
@ -184,13 +189,17 @@ def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_i
end end
end end
def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id} = params) do
with notifications <- Notification.set_read_up_to(user, max_id) do with notifications <- Notification.set_read_up_to(user, max_id) do
notifications = Enum.take(notifications, 80) notifications = Enum.take(notifications, 80)
conn conn
|> put_view(NotificationView) |> put_view(NotificationView)
|> render("index.json", %{notifications: notifications, for: user}) |> render("index.json",
notifications: notifications,
for: user,
skip_relationships: skip_relationships?(params)
)
end end
end end
end end

View File

@ -29,6 +29,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.SetUserSessionIdPlug) plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureUserKeyPlug) plug(Pleroma.Plugs.EnsureUserKeyPlug)
plug(Pleroma.Plugs.IdempotencyPlug) plug(Pleroma.Plugs.IdempotencyPlug)
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end end
pipeline :authenticated_api do pipeline :authenticated_api do
@ -44,6 +45,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.SetUserSessionIdPlug) plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug) plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
plug(Pleroma.Plugs.IdempotencyPlug) plug(Pleroma.Plugs.IdempotencyPlug)
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end end
pipeline :admin_api do pipeline :admin_api do
@ -61,6 +63,7 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.EnsureAuthenticatedPlug) plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
plug(Pleroma.Plugs.UserIsAdminPlug) plug(Pleroma.Plugs.UserIsAdminPlug)
plug(Pleroma.Plugs.IdempotencyPlug) plug(Pleroma.Plugs.IdempotencyPlug)
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end end
pipeline :mastodon_html do pipeline :mastodon_html do
@ -94,10 +97,12 @@ defmodule Pleroma.Web.Router do
pipeline :config do pipeline :config do
plug(:accepts, ["json", "xml"]) plug(:accepts, ["json", "xml"])
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end end
pipeline :pleroma_api do pipeline :pleroma_api do
plug(:accepts, ["html", "json"]) plug(:accepts, ["html", "json"])
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end end
pipeline :mailbox_preview do pipeline :mailbox_preview do
@ -347,9 +352,11 @@ defmodule Pleroma.Web.Router do
get("/notifications", NotificationController, :index) get("/notifications", NotificationController, :index)
get("/notifications/:id", NotificationController, :show) get("/notifications/:id", NotificationController, :show)
post("/notifications/:id/dismiss", NotificationController, :dismiss)
post("/notifications/clear", NotificationController, :clear) post("/notifications/clear", NotificationController, :clear)
post("/notifications/dismiss", NotificationController, :dismiss)
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
# Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead
post("/notifications/dismiss", NotificationController, :dismiss)
get("/scheduled_statuses", ScheduledActivityController, :index) get("/scheduled_statuses", ScheduledActivityController, :index)
get("/scheduled_statuses/:id", ScheduledActivityController, :show) get("/scheduled_statuses/:id", ScheduledActivityController, :show)
@ -500,6 +507,12 @@ defmodule Pleroma.Web.Router do
) )
end end
scope "/api" do
pipe_through(:api)
get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
end
scope "/api", Pleroma.Web, as: :authenticated_twitter_api do scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)

16
mix.exs
View File

@ -37,12 +37,21 @@ def project do
pleroma: [ pleroma: [
include_executables_for: [:unix], include_executables_for: [:unix],
applications: [ex_syslogger: :load, syslog: :load], applications: [ex_syslogger: :load, syslog: :load],
steps: [:assemble, &copy_files/1, &copy_nginx_config/1] steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1]
] ]
] ]
] ]
end end
def put_otp_version(%{path: target_path} = release) do
File.write!(
Path.join([target_path, "OTP_VERSION"]),
Pleroma.OTPVersion.version()
)
release
end
def copy_files(%{path: target_path} = release) do def copy_files(%{path: target_path} = release) do
File.cp_r!("./rel/files", target_path) File.cp_r!("./rel/files", target_path)
release release
@ -108,7 +117,7 @@ defp deps do
{:ecto_enum, "~> 1.4"}, {:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.3.2"}, {:ecto_sql, "~> 3.3.2"},
{:postgrex, ">= 0.13.5"}, {:postgrex, ">= 0.13.5"},
{:oban, "~> 0.12.1"}, {:oban, "~> 1.2"},
{:gettext, "~> 0.15"}, {:gettext, "~> 0.15"},
{:comeonin, "~> 4.1.1"}, {:comeonin, "~> 4.1.1"},
{:pbkdf2_elixir, "~> 0.12.3"}, {:pbkdf2_elixir, "~> 0.12.3"},
@ -179,7 +188,8 @@ defp deps do
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"}, ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
{:mox, "~> 0.5", only: :test}, {:mox, "~> 0.5", only: :test},
{:restarter, path: "./restarter"} {:restarter, path: "./restarter"},
{:open_api_spex, "~> 3.6"}
] ++ oauth_deps() ] ++ oauth_deps()
end end

View File

@ -22,13 +22,13 @@
"crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "48e513299cd28b12c77266c0ed5b1c844368e5c1823724994ae84834f43d6bbe"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "48e513299cd28b12c77266c0ed5b1c844368e5c1823724994ae84834f43d6bbe"},
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
"db_connection": {:hex, :db_connection, "2.2.0", "e923e88887cd60f9891fd324ac5e0290954511d090553c415fbf54be4c57ee63", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "bdf196feedfa6b83071e808b2b086fb113f8a1c4c7761f6eff6fe4b96aba0086"}, "db_connection": {:hex, :db_connection, "2.2.1", "caee17725495f5129cb7faebde001dc4406796f12a62b8949f4ac69315080566", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "2b02ece62d9f983fcd40954e443b7d9e6589664380e5546b2b9b523cd0fb59e1"},
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"}, "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm", "5e8806285d8a3a8999bd38e4a73c58d28534c856bc38c44818e5ba85bbda16fb"}, "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm", "5e8806285d8a3a8999bd38e4a73c58d28534c856bc38c44818e5ba85bbda16fb"},
"ecto": {:hex, :ecto, "3.3.1", "82ab74298065bf0c64ca299f6c6785e68ea5d6b980883ee80b044499df35aba1", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "e6c614dfe3bcff2d575ce16d815dbd43f4ee1844599a83de1eea81976a31c174"}, "ecto": {:hex, :ecto, "3.4.2", "6890af71025769bd27ef62b1ed1925cfe23f7f0460bcb3041da4b705215ff23e", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3959b8a83e086202a4bd86b4b5e6e71f9f1840813de14a57d502d3fc2ef7132"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
"ecto_sql": {:hex, :ecto_sql, "3.3.2", "92804e0de69bb63e621273c3492252cb08a29475c05d40eeb6f41ad2d483cfd3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b82d89d4e6a9f7f7f04783b07e8b0af968e0be2f01ee4b39047fe727c5c07471"}, "ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm", "98d0f3c6f4b8a0333170df770c6fe772b3d04564fb514c1a09504cf5ab2f48a5"}, "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm", "98d0f3c6f4b8a0333170df770c6fe772b3d04564fb514c1a09504cf5ab2f48a5"},
"eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm", "b14f1dc204321429479c569cfbe8fb287541184ed040956c8862cb7a677b8406"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
@ -73,7 +73,8 @@
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]}, "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm", "00e3ebdc821fb3a36957320d49e8f4bfa310d73ea31c90e5f925dc75e030da8f"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm", "00e3ebdc821fb3a36957320d49e8f4bfa310d73ea31c90e5f925dc75e030da8f"},
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
"oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d58d69b8b5a86e7167abbb8cc92764a66f25f12f6172052595067fc6a30a17"}, "oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
"open_api_spex": {:hex, :open_api_spex, "3.6.0", "64205aba9f2607f71b08fd43e3351b9c5e9898ec5ef49fc0ae35890da502ade9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "126ba3473966277132079cb1d5bf1e3df9e36fe2acd00166e75fd125cecb59c5"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"},
"phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "256ad7a140efadc3f0290470369da5bd3de985ec7c706eba07c2641b228974be"}, "phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "256ad7a140efadc3f0290470369da5bd3de985ec7c706eba07c2641b228974be"},

View File

@ -3,7 +3,6 @@ defmodule Pleroma.Repo.Migrations.MigrateOldBookmarks do
import Ecto.Query import Ecto.Query
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Bookmark alias Pleroma.Bookmark
alias Pleroma.User
alias Pleroma.Repo alias Pleroma.Repo
def up do def up do

View File

@ -1,6 +1,5 @@
defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do defmodule Pleroma.Repo.Migrations.CreateSafeJsonbSet do
use Ecto.Migration use Ecto.Migration
alias Pleroma.User
def change do def change do
execute(""" execute("""

View File

@ -0,0 +1,29 @@
defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do
use Ecto.Migration
@alter_following_relationship_state "ALTER TABLE following_relationships ALTER COLUMN state"
def up do
execute("""
#{@alter_following_relationship_state} TYPE integer USING
CASE
WHEN state = 'pending' THEN 1
WHEN state = 'accept' THEN 2
WHEN state = 'reject' THEN 3
ELSE 0
END;
""")
end
def down do
execute("""
#{@alter_following_relationship_state} TYPE varchar(255) USING
CASE
WHEN state = 1 THEN 'pending'
WHEN state = 2 THEN 'accept'
WHEN state = 3 THEN 'reject'
ELSE ''
END;
""")
end
end

View File

@ -0,0 +1,11 @@
defmodule Pleroma.Repo.Migrations.AddFollowingRelationshipsFollowingIdIndex do
use Ecto.Migration
# [:follower_index] index is useless because of [:follower_id, :following_id] index
# [:following_id] index makes sense because of user's followers-targeted queries
def change do
drop_if_exists(index(:following_relationships, [:follower_id]))
create_if_not_exists(index(:following_relationships, [:following_id]))
end
end

View File

@ -0,0 +1,11 @@
defmodule Pleroma.Repo.Migrations.UpdateObanJobsTable do
use Ecto.Migration
def up do
Oban.Migrations.up(version: 8)
end
def down do
Oban.Migrations.down(version: 7)
end
end

View File

@ -15,28 +15,28 @@ defmodule Pleroma.FollowingRelationshipTest do
test "returns following addresses without internal.fetch" do test "returns following addresses without internal.fetch" do
user = insert(:user) user = insert(:user)
fetch_actor = InternalFetchActor.get_actor() fetch_actor = InternalFetchActor.get_actor()
FollowingRelationship.follow(fetch_actor, user, "accept") FollowingRelationship.follow(fetch_actor, user, :follow_accept)
assert FollowingRelationship.following(fetch_actor) == [user.follower_address] assert FollowingRelationship.following(fetch_actor) == [user.follower_address]
end end
test "returns following addresses without relay" do test "returns following addresses without relay" do
user = insert(:user) user = insert(:user)
relay_actor = Relay.get_actor() relay_actor = Relay.get_actor()
FollowingRelationship.follow(relay_actor, user, "accept") FollowingRelationship.follow(relay_actor, user, :follow_accept)
assert FollowingRelationship.following(relay_actor) == [user.follower_address] assert FollowingRelationship.following(relay_actor) == [user.follower_address]
end end
test "returns following addresses without remote user" do test "returns following addresses without remote user" do
user = insert(:user) user = insert(:user)
actor = insert(:user, local: false) actor = insert(:user, local: false)
FollowingRelationship.follow(actor, user, "accept") FollowingRelationship.follow(actor, user, :follow_accept)
assert FollowingRelationship.following(actor) == [user.follower_address] assert FollowingRelationship.following(actor) == [user.follower_address]
end end
test "returns following addresses with local user" do test "returns following addresses with local user" do
user = insert(:user) user = insert(:user)
actor = insert(:user, local: true) actor = insert(:user, local: true)
FollowingRelationship.follow(actor, user, "accept") FollowingRelationship.follow(actor, user, :follow_accept)
assert FollowingRelationship.following(actor) == [ assert FollowingRelationship.following(actor) == [
actor.follower_address, actor.follower_address,

View File

@ -150,13 +150,13 @@ test "gives a replacement for user links, using local nicknames in user links te
assert length(mentions) == 3 assert length(mentions) == 3
expected_text = expected_text =
~s(<span class="h-card"><a data-user="#{gsimg.id}" class="u-url mention" href="#{ ~s(<span class="h-card"><a class="u-url mention" data-user="#{gsimg.id}" href="#{
gsimg.ap_id gsimg.ap_id
}" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a data-user="#{ }" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a class="u-url mention" data-user="#{
archaeme.id archaeme.id
}" class="u-url mention" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a data-user="#{ }" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a class="u-url mention" data-user="#{
archaeme_remote.id archaeme_remote.id
}" class="u-url mention" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>) }" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>)
assert expected_text == text assert expected_text == text
end end
@ -171,7 +171,7 @@ test "gives a replacement for user links when the user is using Osada" do
assert length(mentions) == 1 assert length(mentions) == 1
expected_text = expected_text =
~s(<span class="h-card"><a data-user="#{mike.id}" class="u-url mention" href="#{ ~s(<span class="h-card"><a class="u-url mention" data-user="#{mike.id}" href="#{
mike.ap_id mike.ap_id
}" rel="ugc">@<span>mike</span></a></span> test) }" rel="ugc">@<span>mike</span></a></span> test)
@ -187,7 +187,7 @@ test "gives a replacement for single-character local nicknames" do
assert length(mentions) == 1 assert length(mentions) == 1
expected_text = expected_text =
~s(<span class="h-card"><a data-user="#{o.id}" class="u-url mention" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi) ~s(<span class="h-card"><a class="u-url mention" data-user="#{o.id}" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi)
assert expected_text == text assert expected_text == text
end end
@ -209,17 +209,13 @@ test "given the 'safe_mention' option, it will only mention people in the beginn
assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}] assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}]
assert expected_text == assert expected_text ==
~s(<span class="h-card"><a data-user="#{user.id}" class="u-url mention" href="#{ ~s(<span class="h-card"><a class="u-url mention" data-user="#{user.id}" href="#{
user.ap_id user.ap_id
}" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a data-user="#{ }" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{
other_user.id other_user.id
}" class="u-url mention" href="#{other_user.ap_id}" rel="ugc">@<span>#{ }" href="#{other_user.ap_id}" rel="ugc">@<span>#{other_user.nickname}</span></a></span> hey dudes i hate <span class="h-card"><a class="u-url mention" data-user="#{
other_user.nickname
}</span></a></span> hey dudes i hate <span class="h-card"><a data-user="#{
third_user.id third_user.id
}" class="u-url mention" href="#{third_user.ap_id}" rel="ugc">@<span>#{ }" href="#{third_user.ap_id}" rel="ugc">@<span>#{third_user.nickname}</span></a></span>)
third_user.nickname
}</span></a></span>)
end end
test "given the 'safe_mention' option, it will still work without any mention" do test "given the 'safe_mention' option, it will still work without any mention" do

View File

@ -140,7 +140,7 @@ test "no user to toggle" do
test "user is unsubscribed" do test "user is unsubscribed" do
followed = insert(:user) followed = insert(:user)
user = insert(:user) user = insert(:user)
User.follow(user, followed, "accept") User.follow(user, followed, :follow_accept)
Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname]) Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname])

View File

@ -194,7 +194,8 @@ test "doesn't return already accepted or duplicate follow requests" do
CommonAPI.follow(pending_follower, locked) CommonAPI.follow(pending_follower, locked)
CommonAPI.follow(pending_follower, locked) CommonAPI.follow(pending_follower, locked)
CommonAPI.follow(accepted_follower, locked) CommonAPI.follow(accepted_follower, locked)
Pleroma.FollowingRelationship.update(accepted_follower, locked, "accept")
Pleroma.FollowingRelationship.update(accepted_follower, locked, :follow_accept)
assert [^pending_follower] = User.get_follow_requests(locked) assert [^pending_follower] = User.get_follow_requests(locked)
end end
@ -319,7 +320,7 @@ test "unfollow with syncronizes external user" do
following_address: "http://localhost:4001/users/fuser2/following" following_address: "http://localhost:4001/users/fuser2/following"
}) })
{:ok, user} = User.follow(user, followed, "accept") {:ok, user} = User.follow(user, followed, :follow_accept)
{:ok, user, _activity} = User.unfollow(user, followed) {:ok, user, _activity} = User.unfollow(user, followed)
@ -332,7 +333,7 @@ test "unfollow takes a user and another user" do
followed = insert(:user) followed = insert(:user)
user = insert(:user) user = insert(:user)
{:ok, user} = User.follow(user, followed, "accept") {:ok, user} = User.follow(user, followed, :follow_accept)
assert User.following(user) == [user.follower_address, followed.follower_address] assert User.following(user) == [user.follower_address, followed.follower_address]
@ -353,7 +354,7 @@ test "unfollow doesn't unfollow yourself" do
test "test if a user is following another user" do test "test if a user is following another user" do
followed = insert(:user) followed = insert(:user)
user = insert(:user) user = insert(:user)
User.follow(user, followed, "accept") User.follow(user, followed, :follow_accept)
assert User.following?(user, followed) assert User.following?(user, followed)
refute User.following?(followed, user) refute User.following?(followed, user)
@ -1404,7 +1405,7 @@ test "preserves hosts in user links text" do
bio = "A.k.a. @nick@domain.com" bio = "A.k.a. @nick@domain.com"
expected_text = expected_text =
~s(A.k.a. <span class="h-card"><a data-user="#{remote_user.id}" class="u-url mention" href="#{ ~s(A.k.a. <span class="h-card"><a class="u-url mention" data-user="#{remote_user.id}" href="#{
remote_user.ap_id remote_user.ap_id
}" rel="ugc">@<span>nick@domain.com</span></a></span>) }" rel="ugc">@<span>nick@domain.com</span></a></span>)

View File

@ -1239,16 +1239,56 @@ test "POST /api/ap/upload_media", %{conn: conn} do
filename: "an_image.jpg" filename: "an_image.jpg"
} }
conn = object =
conn conn
|> assign(:user, user) |> assign(:user, user)
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc}) |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|> json_response(:created)
assert object = json_response(conn, :created)
assert object["name"] == desc assert object["name"] == desc
assert object["type"] == "Document" assert object["type"] == "Document"
assert object["actor"] == user.ap_id assert object["actor"] == user.ap_id
assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
assert is_binary(object_href)
assert object_mediatype == "image/jpeg"
activity_request = %{
"@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Create",
"object" => %{
"type" => "Note",
"content" => "AP C2S test, attachment",
"attachment" => [object]
},
"to" => "https://www.w3.org/ns/activitystreams#Public",
"cc" => []
}
activity_response =
conn
|> assign(:user, user)
|> post("/users/#{user.nickname}/outbox", activity_request)
|> json_response(:created)
assert activity_response["id"]
assert activity_response["object"]
assert activity_response["actor"] == user.ap_id
assert %Object{data: %{"attachment" => [attachment]}} =
Object.normalize(activity_response["object"])
assert attachment["type"] == "Document"
assert attachment["name"] == desc
assert [
%{
"href" => ^object_href,
"type" => "Link",
"mediaType" => ^object_mediatype
}
] = attachment["url"]
# Fails if unauthenticated
conn conn
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc}) |> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|> json_response(403) |> json_response(403)

View File

@ -1230,19 +1230,13 @@ test "it remaps video URLs as attachments if necessary" do
attachment = %{ attachment = %{
"type" => "Link", "type" => "Link",
"mediaType" => "video/mp4", "mediaType" => "video/mp4",
"href" =>
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
"mimeType" => "video/mp4",
"size" => 5_015_880,
"url" => [ "url" => [
%{ %{
"href" => "href" =>
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
"mediaType" => "video/mp4", "mediaType" => "video/mp4"
"type" => "Link"
} }
], ]
"width" => 480
} }
assert object.data["url"] == assert object.data["url"] ==
@ -1624,7 +1618,7 @@ test "it upgrades a user to activitypub" do
}) })
user_two = insert(:user) user_two = insert(:user)
Pleroma.FollowingRelationship.follow(user_two, user, "accept") Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test"}) {:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"}) {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
@ -2063,11 +2057,7 @@ test "returns modified object when attachment is map" do
%{ %{
"mediaType" => "video/mp4", "mediaType" => "video/mp4",
"url" => [ "url" => [
%{ %{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
"href" => "https://peertube.moe/stat-480.mp4",
"mediaType" => "video/mp4",
"type" => "Link"
}
] ]
} }
] ]
@ -2085,23 +2075,13 @@ test "returns modified object when attachment is list" do
%{ %{
"mediaType" => "video/mp4", "mediaType" => "video/mp4",
"url" => [ "url" => [
%{ %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
"href" => "https://pe.er/stat-480.mp4",
"mediaType" => "video/mp4",
"type" => "Link"
}
] ]
}, },
%{ %{
"href" => "https://pe.er/stat-480.mp4",
"mediaType" => "video/mp4", "mediaType" => "video/mp4",
"mimeType" => "video/mp4",
"url" => [ "url" => [
%{ %{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
"href" => "https://pe.er/stat-480.mp4",
"mediaType" => "video/mp4",
"type" => "Link"
}
] ]
} }
] ]

View File

@ -625,6 +625,39 @@ test "it returns 403 if requested by a non-admin" do
assert json_response(conn, :forbidden) assert json_response(conn, :forbidden)
end end
test "email with +", %{conn: conn, admin: admin} do
recipient_email = "foo+bar@baz.com"
conn
|> put_req_header("content-type", "application/json;charset=utf-8")
|> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email})
|> json_response(:no_content)
token_record =
Pleroma.UserInviteToken
|> Repo.all()
|> List.last()
assert token_record
refute token_record.used
notify_email = Config.get([:instance, :notify_email])
instance_name = Config.get([:instance, :name])
email =
Pleroma.Emails.UserEmail.user_invitation_email(
admin,
token_record,
recipient_email
)
Swoosh.TestAssertions.assert_email_sent(
from: {instance_name, notify_email},
to: recipient_email,
html_body: email.html_body
)
end
end end
describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
@ -637,7 +670,8 @@ test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do
conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
assert json_response(conn, :internal_server_error) assert json_response(conn, :bad_request) ==
"To send invites you need to set the `invites_enabled` option to true."
end end
test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
@ -646,7 +680,8 @@ test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD") conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
assert json_response(conn, :internal_server_error) assert json_response(conn, :bad_request) ==
"To send invites you need to set the `registrations_open` option to false."
end end
end end

View File

@ -0,0 +1,45 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.AppOperationTest do
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Web.ApiSpec
alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
import OpenApiSpex.TestAssertions
import Pleroma.Factory
test "AppCreateRequest example matches schema" do
api_spec = ApiSpec.spec()
schema = AppCreateRequest.schema()
assert_schema(schema.example, "AppCreateRequest", api_spec)
end
test "AppCreateResponse example matches schema" do
api_spec = ApiSpec.spec()
schema = AppCreateResponse.schema()
assert_schema(schema.example, "AppCreateResponse", api_spec)
end
test "AppController produces a AppCreateResponse", %{conn: conn} do
api_spec = ApiSpec.spec()
app_attrs = build(:oauth_app)
json =
conn
|> put_req_header("content-type", "application/json")
|> post(
"/api/v1/apps",
Jason.encode!(%{
client_name: app_attrs.client_name,
redirect_uris: app_attrs.redirect_uris
})
)
|> json_response(200)
assert_schema(json, "AppCreateResponse", api_spec)
end
end

View File

@ -565,7 +565,7 @@ test "cancels a pending follow for a local user" do
assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
CommonAPI.follow(follower, followed) CommonAPI.follow(follower, followed)
assert User.get_follow_state(follower, followed) == "pending" assert User.get_follow_state(follower, followed) == :follow_pending
assert {:ok, follower} = CommonAPI.unfollow(follower, followed) assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
assert User.get_follow_state(follower, followed) == nil assert User.get_follow_state(follower, followed) == nil
@ -587,7 +587,7 @@ test "cancels a pending follow for a remote user" do
assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
CommonAPI.follow(follower, followed) CommonAPI.follow(follower, followed)
assert User.get_follow_state(follower, followed) == "pending" assert User.get_follow_state(follower, followed) == :follow_pending
assert {:ok, follower} = CommonAPI.unfollow(follower, followed) assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
assert User.get_follow_state(follower, followed) == nil assert User.get_follow_state(follower, followed) == nil

View File

@ -159,11 +159,11 @@ test "works for text/markdown with mentions" do
{output, _, _} = Utils.format_input(text, "text/markdown") {output, _, _} = Utils.format_input(text, "text/markdown")
assert output == assert output ==
~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a data-user="#{ ~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a class="u-url mention" data-user="#{
user.id user.id
}" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a data-user="#{ }" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a class="u-url mention" data-user="#{
user.id user.id
}" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>) }" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>)
end end
end end

View File

@ -82,9 +82,9 @@ test "updates the user's bio", %{conn: conn} do
assert user_data = json_response(conn, 200) assert user_data = json_response(conn, 200)
assert user_data["note"] == assert user_data["note"] ==
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a data-user="#{ ~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
user2.id user2.id
}" class="u-url mention" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..) }" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
end end
test "updates the user's locking status", %{conn: conn} do test "updates the user's locking status", %{conn: conn} do
@ -273,7 +273,7 @@ test "updates profile emojos", %{user: user, conn: conn} do
test "update fields", %{conn: conn} do test "update fields", %{conn: conn} do
fields = [ fields = [
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"}, %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"},
%{"name" => "link", "value" => "cofe.io"} %{"name" => "link.io", "value" => "cofe.io"}
] ]
account_data = account_data =
@ -283,7 +283,10 @@ test "update fields", %{conn: conn} do
assert account_data["fields"] == [ assert account_data["fields"] == [
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"}, %{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
%{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)} %{
"name" => "link.io",
"value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)
}
] ]
assert account_data["source"]["fields"] == [ assert account_data["source"]["fields"] == [
@ -291,14 +294,16 @@ test "update fields", %{conn: conn} do
"name" => "<a href=\"http://google.com\">foo</a>", "name" => "<a href=\"http://google.com\">foo</a>",
"value" => "<script>bar</script>" "value" => "<script>bar</script>"
}, },
%{"name" => "link", "value" => "cofe.io"} %{"name" => "link.io", "value" => "cofe.io"}
] ]
end
test "update fields via x-www-form-urlencoded", %{conn: conn} do
fields = fields =
[ [
"fields_attributes[1][name]=link", "fields_attributes[1][name]=link",
"fields_attributes[1][value]=cofe.io", "fields_attributes[1][value]=http://cofe.io",
"fields_attributes[0][name]=<a href=\"http://google.com\">foo</a>", "fields_attributes[0][name]=foo",
"fields_attributes[0][value]=bar" "fields_attributes[0][value]=bar"
] ]
|> Enum.join("&") |> Enum.join("&")
@ -310,51 +315,20 @@ test "update fields", %{conn: conn} do
|> json_response(200) |> json_response(200)
assert account["fields"] == [ assert account["fields"] == [
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"}, %{"name" => "foo", "value" => "bar"},
%{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)} %{
"name" => "link",
"value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>)
}
] ]
assert account["source"]["fields"] == [ assert account["source"]["fields"] == [
%{ %{"name" => "foo", "value" => "bar"},
"name" => "<a href=\"http://google.com\">foo</a>", %{"name" => "link", "value" => "http://cofe.io"}
"value" => "bar"
},
%{"name" => "link", "value" => "cofe.io"}
] ]
end
name_limit = Pleroma.Config.get([:instance, :account_field_name_length]) test "update fields with empty name", %{conn: conn} do
value_limit = Pleroma.Config.get([:instance, :account_field_value_length])
long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join()
fields = [%{"name" => "<b>foo<b>", "value" => long_value}]
assert %{"error" => "Invalid request"} ==
conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(403)
long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
fields = [%{"name" => long_name, "value" => "bar"}]
assert %{"error" => "Invalid request"} ==
conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(403)
Pleroma.Config.put([:instance, :max_account_fields], 1)
fields = [
%{"name" => "<b>foo<b>", "value" => "<i>bar</i>"},
%{"name" => "link", "value" => "cofe.io"}
]
assert %{"error" => "Invalid request"} ==
conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(403)
fields = [ fields = [
%{"name" => "foo", "value" => ""}, %{"name" => "foo", "value" => ""},
%{"name" => "", "value" => "bar"} %{"name" => "", "value" => "bar"}
@ -369,5 +343,39 @@ test "update fields", %{conn: conn} do
%{"name" => "foo", "value" => ""} %{"name" => "foo", "value" => ""}
] ]
end end
test "update fields when invalid request", %{conn: conn} do
name_limit = Pleroma.Config.get([:instance, :account_field_name_length])
value_limit = Pleroma.Config.get([:instance, :account_field_value_length])
long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join()
fields = [%{"name" => "foo", "value" => long_value}]
assert %{"error" => "Invalid request"} ==
conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(403)
fields = [%{"name" => long_name, "value" => "bar"}]
assert %{"error" => "Invalid request"} ==
conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(403)
Pleroma.Config.put([:instance, :max_account_fields], 1)
fields = [
%{"name" => "foo", "value" => "bar"},
%{"name" => "link", "value" => "cofe.io"}
]
assert %{"error" => "Invalid request"} ==
conn
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|> json_response(403)
end
end end
end end

View File

@ -794,7 +794,9 @@ test "blocking / unblocking a user" do
test "Account registration via Application", %{conn: conn} do test "Account registration via Application", %{conn: conn} do
conn = conn =
post(conn, "/api/v1/apps", %{ conn
|> put_req_header("content-type", "application/json")
|> post("/api/v1/apps", %{
client_name: "client_name", client_name: "client_name",
redirect_uris: "urn:ietf:wg:oauth:2.0:oob", redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
scopes: "read, write, follow" scopes: "read, write, follow"

View File

@ -16,8 +16,7 @@ test "apps/verify_credentials", %{conn: conn} do
conn = conn =
conn conn
|> assign(:user, token.user) |> put_req_header("authorization", "Bearer #{token.token}")
|> assign(:token, token)
|> get("/api/v1/apps/verify_credentials") |> get("/api/v1/apps/verify_credentials")
app = Repo.preload(token, :app).app app = Repo.preload(token, :app).app
@ -37,6 +36,7 @@ test "creates an oauth app", %{conn: conn} do
conn = conn =
conn conn
|> put_req_header("content-type", "application/json")
|> assign(:user, user) |> assign(:user, user)
|> post("/api/v1/apps", %{ |> post("/api/v1/apps", %{
client_name: app_attrs.client_name, client_name: app_attrs.client_name,

View File

@ -21,7 +21,7 @@ test "/api/v1/follow_requests works", %{user: user, conn: conn} do
other_user = insert(:user) other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user) {:ok, _activity} = ActivityPub.follow(other_user, user)
{:ok, other_user} = User.follow(other_user, user, "pending") {:ok, other_user} = User.follow(other_user, user, :follow_pending)
assert User.following?(other_user, user) == false assert User.following?(other_user, user) == false
@ -35,7 +35,7 @@ test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do
other_user = insert(:user) other_user = insert(:user)
{:ok, _activity} = ActivityPub.follow(other_user, user) {:ok, _activity} = ActivityPub.follow(other_user, user)
{:ok, other_user} = User.follow(other_user, user, "pending") {:ok, other_user} = User.follow(other_user, user, :follow_pending)
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id) other_user = User.get_cached_by_id(other_user.id)

View File

@ -12,6 +12,26 @@ defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
import Pleroma.Factory import Pleroma.Factory
test "does NOT render account/pleroma/relationship if this is disabled by default" do
clear_config([:extensions, :output_relationships_in_statuses_by_default], false)
%{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [_notification]} = Notification.create_notifications(activity)
response =
conn
|> assign(:user, user)
|> get("/api/v1/notifications")
|> json_response(200)
assert Enum.all?(response, fn n ->
get_in(n, ["account", "pleroma", "relationship"]) == %{}
end)
end
test "list of notifications" do test "list of notifications" do
%{user: user, conn: conn} = oauth_access(["read:notifications"]) %{user: user, conn: conn} = oauth_access(["read:notifications"])
other_user = insert(:user) other_user = insert(:user)
@ -26,7 +46,7 @@ test "list of notifications" do
|> get("/api/v1/notifications") |> get("/api/v1/notifications")
expected_response = expected_response =
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
user.ap_id user.ap_id
}\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>" }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
@ -45,7 +65,7 @@ test "getting a single notification" do
conn = get(conn, "/api/v1/notifications/#{notification.id}") conn = get(conn, "/api/v1/notifications/#{notification.id}")
expected_response = expected_response =
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{ "hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
user.ap_id user.ap_id
}\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>" }\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
@ -53,6 +73,22 @@ test "getting a single notification" do
assert response == expected_response assert response == expected_response
end end
test "dismissing a single notification (deprecated endpoint)" do
%{user: user, conn: conn} = oauth_access(["write:notifications"])
other_user = insert(:user)
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
{:ok, [notification]} = Notification.create_notifications(activity)
conn =
conn
|> assign(:user, user)
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
assert %{} = json_response(conn, 200)
end
test "dismissing a single notification" do test "dismissing a single notification" do
%{user: user, conn: conn} = oauth_access(["write:notifications"]) %{user: user, conn: conn} = oauth_access(["write:notifications"])
other_user = insert(:user) other_user = insert(:user)
@ -64,7 +100,7 @@ test "dismissing a single notification" do
conn = conn =
conn conn
|> assign(:user, user) |> assign(:user, user)
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id}) |> post("/api/v1/notifications/#{notification.id}/dismiss")
assert %{} = json_response(conn, 200) assert %{} = json_response(conn, 200)
end end

View File

@ -1047,6 +1047,8 @@ test "replaces missing description with an empty string", %{conn: conn, user: us
end end
test "bookmarks" do test "bookmarks" do
bookmarks_uri = "/api/v1/bookmarks?with_relationships=true"
%{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"]) %{conn: conn} = oauth_access(["write:bookmarks", "read:bookmarks"])
author = insert(:user) author = insert(:user)
@ -1068,7 +1070,7 @@ test "bookmarks" do
assert json_response(response2, 200)["bookmarked"] == true assert json_response(response2, 200)["bookmarked"] == true
bookmarks = get(conn, "/api/v1/bookmarks") bookmarks = get(conn, bookmarks_uri)
assert [json_response(response2, 200), json_response(response1, 200)] == assert [json_response(response2, 200), json_response(response1, 200)] ==
json_response(bookmarks, 200) json_response(bookmarks, 200)
@ -1077,7 +1079,7 @@ test "bookmarks" do
assert json_response(response1, 200)["bookmarked"] == false assert json_response(response1, 200)["bookmarked"] == false
bookmarks = get(conn, "/api/v1/bookmarks") bookmarks = get(conn, bookmarks_uri)
assert [json_response(response2, 200)] == json_response(bookmarks, 200) assert [json_response(response2, 200)] == json_response(bookmarks, 200)
end end

View File

@ -20,7 +20,30 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do
describe "home" do describe "home" do
setup do: oauth_access(["read:statuses"]) setup do: oauth_access(["read:statuses"])
test "does NOT render account/pleroma/relationship if this is disabled by default", %{
user: user,
conn: conn
} do
clear_config([:extensions, :output_relationships_in_statuses_by_default], false)
other_user = insert(:user)
{:ok, _} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
response =
conn
|> assign(:user, user)
|> get("/api/v1/timelines/home")
|> json_response(200)
assert Enum.all?(response, fn n ->
get_in(n, ["account", "pleroma", "relationship"]) == %{}
end)
end
test "the home timeline", %{user: user, conn: conn} do test "the home timeline", %{user: user, conn: conn} do
uri = "/api/v1/timelines/home?with_relationships=true"
following = insert(:user, nickname: "followed") following = insert(:user, nickname: "followed")
third_user = insert(:user, nickname: "repeated") third_user = insert(:user, nickname: "repeated")
@ -28,13 +51,13 @@ test "the home timeline", %{user: user, conn: conn} do
{:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"}) {:ok, activity} = CommonAPI.post(third_user, %{"status" => "repeated post"})
{:ok, _, _} = CommonAPI.repeat(activity.id, following) {:ok, _, _} = CommonAPI.repeat(activity.id, following)
ret_conn = get(conn, "/api/v1/timelines/home") ret_conn = get(conn, uri)
assert Enum.empty?(json_response(ret_conn, :ok)) assert Enum.empty?(json_response(ret_conn, :ok))
{:ok, _user} = User.follow(user, following) {:ok, _user} = User.follow(user, following)
ret_conn = get(conn, "/api/v1/timelines/home") ret_conn = get(conn, uri)
assert [ assert [
%{ %{
@ -59,7 +82,7 @@ test "the home timeline", %{user: user, conn: conn} do
{:ok, _user} = User.follow(third_user, user) {:ok, _user} = User.follow(third_user, user)
ret_conn = get(conn, "/api/v1/timelines/home") ret_conn = get(conn, uri)
assert [ assert [
%{ %{

View File

@ -169,6 +169,23 @@ test "/api/v1/pleroma/conversations/:id/statuses" do
id_one = activity.id id_one = activity.id
id_two = activity_two.id id_two = activity_two.id
assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result
{:ok, %{id: id_three}} =
CommonAPI.post(other_user, %{
"status" => "Bye!",
"in_reply_to_status_id" => activity.id,
"in_reply_to_conversation_id" => participation.id
})
assert [%{"id" => ^id_two}, %{"id" => ^id_three}] =
conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2")
|> json_response(:ok)
assert [%{"id" => ^id_three}] =
conn
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}")
|> json_response(:ok)
end end
test "PATCH /api/v1/pleroma/conversations/:id" do test "PATCH /api/v1/pleroma/conversations/:id" do

View File

@ -209,7 +209,7 @@ test "it doesn't send to user if recipients invalid and thread containment is en
Pleroma.Config.put([:instance, :skip_thread_containment], false) Pleroma.Config.put([:instance, :skip_thread_containment], false)
author = insert(:user) author = insert(:user)
user = insert(:user) user = insert(:user)
User.follow(user, author, "accept") User.follow(user, author, :follow_accept)
activity = activity =
insert(:note_activity, insert(:note_activity,
@ -232,7 +232,7 @@ test "it sends message if recipients invalid and thread containment is disabled"
Pleroma.Config.put([:instance, :skip_thread_containment], true) Pleroma.Config.put([:instance, :skip_thread_containment], true)
author = insert(:user) author = insert(:user)
user = insert(:user) user = insert(:user)
User.follow(user, author, "accept") User.follow(user, author, :follow_accept)
activity = activity =
insert(:note_activity, insert(:note_activity,
@ -255,7 +255,7 @@ test "it sends message if recipients invalid and thread containment is enabled b
Pleroma.Config.put([:instance, :skip_thread_containment], false) Pleroma.Config.put([:instance, :skip_thread_containment], false)
author = insert(:user) author = insert(:user)
user = insert(:user, skip_thread_containment: true) user = insert(:user, skip_thread_containment: true)
User.follow(user, author, "accept") User.follow(user, author, :follow_accept)
activity = activity =
insert(:note_activity, insert(:note_activity,

View File

@ -109,7 +109,7 @@ test "it registers a new user and parses mentions in the bio" do
{:ok, user2} = TwitterAPI.register_user(data2) {:ok, user2} = TwitterAPI.register_user(data2)
expected_text = expected_text =
~s(<span class="h-card"><a data-user="#{user1.id}" class="u-url mention" href="#{ ~s(<span class="h-card"><a class="u-url mention" data-user="#{user1.id}" href="#{
user1.ap_id user1.ap_id
}" rel="ugc">@<span>john</span></a></span> test) }" rel="ugc">@<span>john</span></a></span> test)