forked from AkkomaGang/akkoma
Merge branch 'develop' into arm
This commit is contained in:
commit
fa23098093
32 changed files with 566 additions and 87 deletions
|
@ -17,6 +17,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Deactivated users can no longer show up in the emoji reaction list
|
- Deactivated users can no longer show up in the emoji reaction list
|
||||||
- Embedded posts can no longer bypass `:restrict\_unauthenticated`
|
- Embedded posts can no longer bypass `:restrict\_unauthenticated`
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
- Add `no_new_privs` hardening to OpenRC and systemd service files
|
||||||
|
|
||||||
## 2023.05
|
## 2023.05
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
|
|
|
@ -25,6 +25,7 @@ Home, public, hashtag & list timelines accept these parameters:
|
||||||
## Statuses
|
## Statuses
|
||||||
|
|
||||||
- `visibility`: has additional possible values `list` and `local` (for local-only statuses)
|
- `visibility`: has additional possible values `list` and `local` (for local-only statuses)
|
||||||
|
- `emoji_reactions`: additional field since Akkoma 3.2.0; identical to `pleroma/emoji_reactions`
|
||||||
|
|
||||||
Has these additional fields under the `pleroma` object:
|
Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
|
@ -36,7 +37,9 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being its mimetype. Currently, the only alternate representation supported is `text/plain`
|
- `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being its mimetype. Currently, the only alternate representation supported is `text/plain`
|
||||||
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
||||||
- `thread_muted`: true if the thread the post belongs to is muted
|
- `thread_muted`: true if the thread the post belongs to is muted
|
||||||
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
|
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 2, me: true, account_ids: ["UserID1", "UserID2"]}`.
|
||||||
|
The `account_ids` property was added in Akkoma 3.2.0.
|
||||||
|
Further info about all reacting users at once, can be found using the `/statuses/:id/reactions` endpoint.
|
||||||
- `parent_visible`: If the parent of this post is visible to the user or not.
|
- `parent_visible`: If the parent of this post is visible to the user or not.
|
||||||
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
|
- `pinned_at`: a datetime (iso8601) when status was pinned, `null` otherwise.
|
||||||
|
|
||||||
|
@ -214,6 +217,11 @@ Returns: array of Status.
|
||||||
|
|
||||||
The maximum number of statuses is limited to 100 per request.
|
The maximum number of statuses is limited to 100 per request.
|
||||||
|
|
||||||
|
## PUT `/api/v1/statuses/:id/emoji_reactions/:emoji`
|
||||||
|
|
||||||
|
This endpoint is an extension of the Fedibird Mastodon fork.
|
||||||
|
It behaves identical to PUT `/api/v1/pleroma/statuses/:id/reactions/:emoji`.
|
||||||
|
|
||||||
## PATCH `/api/v1/accounts/update_credentials`
|
## PATCH `/api/v1/accounts/update_credentials`
|
||||||
|
|
||||||
Additional parameters can be added to the JSON body/Form data:
|
Additional parameters can be added to the JSON body/Form data:
|
||||||
|
|
|
@ -38,6 +38,8 @@ ProtectHome=true
|
||||||
ProtectSystem=full
|
ProtectSystem=full
|
||||||
; Sets up a new /dev mount for the process and only adds API pseudo devices like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled by default because it may not work on devices like the Raspberry Pi.
|
; Sets up a new /dev mount for the process and only adds API pseudo devices like /dev/null, /dev/zero or /dev/random but not physical devices. Disabled by default because it may not work on devices like the Raspberry Pi.
|
||||||
PrivateDevices=false
|
PrivateDevices=false
|
||||||
|
; Ensures that the service process and all its children can never gain new privileges through execve().
|
||||||
|
NoNewPrivileges=true
|
||||||
; Drops the sysadmin capability from the daemon.
|
; Drops the sysadmin capability from the daemon.
|
||||||
CapabilityBoundingSet=~CAP_SYS_ADMIN
|
CapabilityBoundingSet=~CAP_SYS_ADMIN
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ pidfile="/var/run/akkoma.pid"
|
||||||
directory=/opt/akkoma
|
directory=/opt/akkoma
|
||||||
healthcheck_delay=60
|
healthcheck_delay=60
|
||||||
healthcheck_timer=30
|
healthcheck_timer=30
|
||||||
|
no_new_privs="yes"
|
||||||
|
|
||||||
: ${akkoma_port:-4000}
|
: ${akkoma_port:-4000}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ defp generate_topics(object, activity) do
|
||||||
["user", "list"] ++ visibility_tags(object, activity)
|
["user", "list"] ++ visibility_tags(object, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp visibility_tags(object, activity) do
|
defp visibility_tags(object, %{data: %{"type" => type}} = activity) when type != "Announce" do
|
||||||
case Visibility.get_visibility(activity) do
|
case Visibility.get_visibility(activity) do
|
||||||
"public" ->
|
"public" ->
|
||||||
if activity.local do
|
if activity.local do
|
||||||
|
@ -31,6 +31,10 @@ defp visibility_tags(object, activity) do
|
||||||
end
|
end
|
||||||
|> item_creation_tags(object, activity)
|
|> item_creation_tags(object, activity)
|
||||||
|
|
||||||
|
"local" ->
|
||||||
|
["public:local"]
|
||||||
|
|> item_creation_tags(object, activity)
|
||||||
|
|
||||||
"direct" ->
|
"direct" ->
|
||||||
["direct"]
|
["direct"]
|
||||||
|
|
||||||
|
@ -39,6 +43,10 @@ defp visibility_tags(object, activity) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp visibility_tags(_object, _activity) do
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
|
||||||
defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
|
defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
|
||||||
tags ++
|
tags ++
|
||||||
remote_topics(activity) ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
|
remote_topics(activity) ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
|
||||||
|
@ -63,7 +71,18 @@ defp remote_topics(_), do: []
|
||||||
|
|
||||||
defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: []
|
defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: []
|
||||||
|
|
||||||
defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"]
|
defp attachment_topics(_object, %{local: true} = activity) do
|
||||||
|
case Visibility.get_visibility(activity) do
|
||||||
|
"public" ->
|
||||||
|
["public:media", "public:local:media"]
|
||||||
|
|
||||||
|
"local" ->
|
||||||
|
["public:local:media"]
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp attachment_topics(_object, %{actor: actor}) when is_binary(actor),
|
defp attachment_topics(_object, %{actor: actor}) when is_binary(actor),
|
||||||
do: ["public:media", "public:remote:media:" <> URI.parse(actor).host]
|
do: ["public:media", "public:remote:media:" <> URI.parse(actor).host]
|
||||||
|
|
|
@ -195,6 +195,7 @@ defp exclude_filtered(query, user) do
|
||||||
from([_n, a, o] in query,
|
from([_n, a, o] in query,
|
||||||
where:
|
where:
|
||||||
fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
|
fragment("not(?->>'content' ~* ?)", o.data, ^regex) or
|
||||||
|
fragment("?->>'content' is null", o.data) or
|
||||||
fragment("?->>'actor' = ?", o.data, ^user.ap_id)
|
fragment("?->>'actor' = ?", o.data, ^user.ap_id)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -695,7 +696,7 @@ def skip?(
|
||||||
cond do
|
cond do
|
||||||
opts[:type] == "poll" -> false
|
opts[:type] == "poll" -> false
|
||||||
user.ap_id == actor -> false
|
user.ap_id == actor -> false
|
||||||
!User.following?(follower, user) -> true
|
!User.following?(user, follower) -> true
|
||||||
true -> false
|
true -> false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -876,7 +876,7 @@ def post_register_action(%User{is_approved: true, is_confirmed: true} = user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp send_user_approval_email(user) do
|
defp send_user_approval_email(%User{email: email} = user) when is_binary(email) do
|
||||||
user
|
user
|
||||||
|> Pleroma.Emails.UserEmail.approval_pending_email()
|
|> Pleroma.Emails.UserEmail.approval_pending_email()
|
||||||
|> Pleroma.Emails.Mailer.deliver_async()
|
|> Pleroma.Emails.Mailer.deliver_async()
|
||||||
|
@ -884,6 +884,10 @@ defp send_user_approval_email(user) do
|
||||||
{:ok, :enqueued}
|
{:ok, :enqueued}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp send_user_approval_email(_user) do
|
||||||
|
{:ok, :skipped}
|
||||||
|
end
|
||||||
|
|
||||||
defp send_admin_approval_emails(user) do
|
defp send_admin_approval_emails(user) do
|
||||||
all_superusers()
|
all_superusers()
|
||||||
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
|> Enum.filter(fn user -> not is_nil(user.email) end)
|
||||||
|
|
|
@ -22,7 +22,10 @@ def cast_and_filter_recipients(message, field, follower_collection, field_fallba
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_object_defaults(data) do
|
def fix_object_defaults(data) do
|
||||||
context = Utils.maybe_create_context(data["context"] || data["conversation"])
|
context =
|
||||||
|
Utils.maybe_create_context(
|
||||||
|
data["context"] || data["conversation"] || data["inReplyTo"] || data["id"]
|
||||||
|
)
|
||||||
|
|
||||||
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
|
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
|
||||||
|
|
||||||
|
|
|
@ -410,7 +410,7 @@ def blocks_operation do
|
||||||
operationId: "AccountController.blocks",
|
operationId: "AccountController.blocks",
|
||||||
description: "View your blocks. See also accounts/:id/{block,unblock}",
|
description: "View your blocks. See also accounts/:id/{block,unblock}",
|
||||||
security: [%{"oAuth" => ["read:blocks"]}],
|
security: [%{"oAuth" => ["read:blocks"]}],
|
||||||
parameters: pagination_params(),
|
parameters: [with_relationships_param() | pagination_params()],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("Accounts", "application/json", array_of_accounts())
|
200 => Operation.response("Accounts", "application/json", array_of_accounts())
|
||||||
}
|
}
|
||||||
|
|
|
@ -143,7 +143,7 @@ def admin_account do
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tags: %Schema{type: :string},
|
tags: %Schema{type: :string},
|
||||||
is_confirmed: %Schema{type: :string}
|
is_confirmed: %Schema{type: :boolean}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -88,7 +88,7 @@ def reject_follow_request(follower, followed) do
|
||||||
|
|
||||||
def delete(activity_id, user) do
|
def delete(activity_id, user) do
|
||||||
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
|
with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
|
||||||
{:find_activity, Activity.get_by_id(activity_id)},
|
{:find_activity, Activity.get_by_id(activity_id, filter: [])},
|
||||||
{_, %Object{} = object, _} <-
|
{_, %Object{} = object, _} <-
|
||||||
{:find_object, Object.normalize(activity, fetch: false), activity},
|
{:find_object, Object.normalize(activity, fetch: false), activity},
|
||||||
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
|
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
|
||||||
|
|
|
@ -144,6 +144,8 @@ def make_poll_data(%{poll: %{options: options, expires_in: expires_in}} = data)
|
||||||
when is_list(options) do
|
when is_list(options) do
|
||||||
limits = Config.get([:instance, :poll_limits])
|
limits = Config.get([:instance, :poll_limits])
|
||||||
|
|
||||||
|
options = options |> Enum.uniq()
|
||||||
|
|
||||||
with :ok <- validate_poll_expiration(expires_in, limits),
|
with :ok <- validate_poll_expiration(expires_in, limits),
|
||||||
:ok <- validate_poll_options_amount(options, limits),
|
:ok <- validate_poll_options_amount(options, limits),
|
||||||
:ok <- validate_poll_options_length(options, limits) do
|
:ok <- validate_poll_options_length(options, limits) do
|
||||||
|
@ -179,9 +181,14 @@ def make_poll_data(_data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp validate_poll_options_amount(options, %{max_options: max_options}) do
|
defp validate_poll_options_amount(options, %{max_options: max_options}) do
|
||||||
if Enum.count(options) > max_options do
|
cond do
|
||||||
|
Enum.count(options) < 2 ->
|
||||||
|
{:error, "Poll must contain at least 2 options"}
|
||||||
|
|
||||||
|
Enum.count(options) > max_options ->
|
||||||
{:error, "Poll can't contain more than #{max_options} options"}
|
{:error, "Poll can't contain more than #{max_options} options"}
|
||||||
else
|
|
||||||
|
true ->
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -518,7 +518,12 @@ def blocks(%{assigns: %{user: user}} = conn, params) do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> add_link_headers(users)
|
|> add_link_headers(users)
|
||||||
|> render("index.json", users: users, for: user, as: :user)
|
|> render("index.json",
|
||||||
|
users: users,
|
||||||
|
for: user,
|
||||||
|
as: :user,
|
||||||
|
embed_relationships: embed_relationships?(params)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "GET /api/v1/accounts/lookup"
|
@doc "GET /api/v1/accounts/lookup"
|
||||||
|
|
|
@ -8,12 +8,20 @@ defmodule Pleroma.Web.Metadata.Providers.RelMe do
|
||||||
|
|
||||||
@impl Provider
|
@impl Provider
|
||||||
def build_tags(%{user: user}) do
|
def build_tags(%{user: user}) do
|
||||||
bio_tree = Floki.parse_fragment!(user.bio)
|
profile_tree =
|
||||||
|
user.bio
|
||||||
|
|> append_fields_tag(user.fields)
|
||||||
|
|> Floki.parse_fragment!()
|
||||||
|
|
||||||
(Floki.attribute(bio_tree, "link[rel~=me]", "href") ++
|
(Floki.attribute(profile_tree, "link[rel~=me]", "href") ++
|
||||||
Floki.attribute(bio_tree, "a[rel~=me]", "href"))
|
Floki.attribute(profile_tree, "a[rel~=me]", "href"))
|
||||||
|> Enum.map(fn link ->
|
|> Enum.map(fn link ->
|
||||||
{:link, [rel: "me", href: link], []}
|
{:link, [rel: "me", href: link], []}
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp append_fields_tag(bio, fields) do
|
||||||
|
fields
|
||||||
|
|> Enum.reduce(bio, fn %{"value" => v}, res -> res <> v end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,12 +20,12 @@ def build_tags(%{activity_id: id, object: object, user: user}) do
|
||||||
|
|
||||||
[
|
[
|
||||||
title_tag(user),
|
title_tag(user),
|
||||||
{:meta, [property: "twitter:description", content: scrubbed_content], []}
|
{:meta, [name: "twitter:description", content: scrubbed_content], []}
|
||||||
] ++
|
] ++
|
||||||
if attachments == [] or Metadata.activity_nsfw?(object) do
|
if attachments == [] or Metadata.activity_nsfw?(object) do
|
||||||
[
|
[
|
||||||
image_tag(user),
|
image_tag(user),
|
||||||
{:meta, [property: "twitter:card", content: "summary"], []}
|
{:meta, [name: "twitter:card", content: "summary"], []}
|
||||||
]
|
]
|
||||||
else
|
else
|
||||||
attachments
|
attachments
|
||||||
|
@ -37,20 +37,19 @@ def build_tags(%{user: user}) do
|
||||||
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
|
with truncated_bio = Utils.scrub_html_and_truncate(user.bio) do
|
||||||
[
|
[
|
||||||
title_tag(user),
|
title_tag(user),
|
||||||
{:meta, [property: "twitter:description", content: truncated_bio], []},
|
{:meta, [name: "twitter:description", content: truncated_bio], []},
|
||||||
image_tag(user),
|
image_tag(user),
|
||||||
{:meta, [property: "twitter:card", content: "summary"], []}
|
{:meta, [name: "twitter:card", content: "summary"], []}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp title_tag(user) do
|
defp title_tag(user) do
|
||||||
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}
|
{:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []}
|
||||||
end
|
end
|
||||||
|
|
||||||
def image_tag(user) do
|
def image_tag(user) do
|
||||||
{:meta, [property: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))],
|
{:meta, [name: "twitter:image", content: MediaProxy.preview_url(User.avatar_url(user))], []}
|
||||||
[]}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
|
@ -60,10 +59,10 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
case Utils.fetch_media_type(@media_types, url["mediaType"]) do
|
case Utils.fetch_media_type(@media_types, url["mediaType"]) do
|
||||||
"audio" ->
|
"audio" ->
|
||||||
[
|
[
|
||||||
{:meta, [property: "twitter:card", content: "player"], []},
|
{:meta, [name: "twitter:card", content: "player"], []},
|
||||||
{:meta, [property: "twitter:player:width", content: "480"], []},
|
{:meta, [name: "twitter:player:width", content: "480"], []},
|
||||||
{:meta, [property: "twitter:player:height", content: "80"], []},
|
{:meta, [name: "twitter:player:height", content: "80"], []},
|
||||||
{:meta, [property: "twitter:player", content: player_url(id)], []}
|
{:meta, [name: "twitter:player", content: player_url(id)], []}
|
||||||
| acc
|
| acc
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -74,10 +73,10 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
# workaround.
|
# workaround.
|
||||||
"image" ->
|
"image" ->
|
||||||
[
|
[
|
||||||
{:meta, [property: "twitter:card", content: "summary_large_image"], []},
|
{:meta, [name: "twitter:card", content: "summary_large_image"], []},
|
||||||
{:meta,
|
{:meta,
|
||||||
[
|
[
|
||||||
property: "twitter:player",
|
name: "twitter:player",
|
||||||
content: MediaProxy.url(url["href"])
|
content: MediaProxy.url(url["href"])
|
||||||
], []}
|
], []}
|
||||||
| acc
|
| acc
|
||||||
|
@ -90,14 +89,14 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do
|
||||||
width = url["width"] || 480
|
width = url["width"] || 480
|
||||||
|
|
||||||
[
|
[
|
||||||
{:meta, [property: "twitter:card", content: "player"], []},
|
{:meta, [name: "twitter:card", content: "player"], []},
|
||||||
{:meta, [property: "twitter:player", content: player_url(id)], []},
|
{:meta, [name: "twitter:player", content: player_url(id)], []},
|
||||||
{:meta, [property: "twitter:player:width", content: "#{width}"], []},
|
{:meta, [name: "twitter:player:width", content: "#{width}"], []},
|
||||||
{:meta, [property: "twitter:player:height", content: "#{height}"], []},
|
{:meta, [name: "twitter:player:height", content: "#{height}"], []},
|
||||||
{:meta, [property: "twitter:player:stream", content: MediaProxy.url(url["href"])],
|
{:meta, [name: "twitter:player:stream", content: MediaProxy.url(url["href"])],
|
||||||
[]},
|
[]},
|
||||||
{:meta,
|
{:meta, [name: "twitter:player:stream:content_type", content: url["mediaType"]],
|
||||||
[property: "twitter:player:stream:content_type", content: url["mediaType"]], []}
|
[]}
|
||||||
| acc
|
| acc
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -123,8 +122,8 @@ defp maybe_add_dimensions(metadata, url) do
|
||||||
!is_nil(url["height"]) && !is_nil(url["width"]) ->
|
!is_nil(url["height"]) && !is_nil(url["width"]) ->
|
||||||
metadata ++
|
metadata ++
|
||||||
[
|
[
|
||||||
{:meta, [property: "twitter:player:width", content: "#{url["width"]}"], []},
|
{:meta, [name: "twitter:player:width", content: "#{url["width"]}"], []},
|
||||||
{:meta, [property: "twitter:player:height", content: "#{url["height"]}"], []}
|
{:meta, [name: "twitter:player:height", content: "#{url["height"]}"], []}
|
||||||
]
|
]
|
||||||
|
|
||||||
true ->
|
true ->
|
||||||
|
|
|
@ -37,7 +37,7 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do
|
||||||
%{query_params: %{"name" => name}} = conn ->
|
%{query_params: %{"name" => name}} = conn ->
|
||||||
name = escape_header_value(name)
|
name = escape_header_value(name)
|
||||||
|
|
||||||
put_resp_header(conn, "content-disposition", "filename=\"#{name}\"")
|
put_resp_header(conn, "content-disposition", ~s[inline; filename="#{name}"])
|
||||||
|
|
||||||
conn ->
|
conn ->
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -25,6 +25,7 @@ defmodule Pleroma.Web.Streamer do
|
||||||
def registry, do: @registry
|
def registry, do: @registry
|
||||||
|
|
||||||
@public_streams ["public", "public:local", "public:media", "public:local:media"]
|
@public_streams ["public", "public:local", "public:media", "public:local:media"]
|
||||||
|
@local_streams ["public:local", "public:local:media"]
|
||||||
@user_streams ["user", "user:notification", "direct"]
|
@user_streams ["user", "user:notification", "direct"]
|
||||||
|
|
||||||
@doc "Expands and authorizes a stream, and registers the process for streaming."
|
@doc "Expands and authorizes a stream, and registers the process for streaming."
|
||||||
|
@ -41,14 +42,37 @@ def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp can_access_stream(user, oauth_token, kind) do
|
||||||
|
with {_, true} <- {:restrict?, Config.restrict_unauthenticated_access?(:timelines, kind)},
|
||||||
|
{_, %User{id: user_id}, %Token{user_id: user_id}} <- {:user, user, oauth_token},
|
||||||
|
{_, true} <-
|
||||||
|
{:scopes,
|
||||||
|
OAuthScopesPlug.filter_descendants(["read:statuses"], oauth_token.scopes) != []} do
|
||||||
|
true
|
||||||
|
else
|
||||||
|
{:restrict?, _} ->
|
||||||
|
true
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc "Expand and authorizes a stream"
|
@doc "Expand and authorizes a stream"
|
||||||
@spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) ::
|
@spec get_topic(stream :: String.t(), User.t() | nil, Token.t() | nil, Map.t()) ::
|
||||||
{:ok, topic :: String.t()} | {:error, :bad_topic}
|
{:ok, topic :: String.t()} | {:error, :bad_topic}
|
||||||
def get_topic(stream, user, oauth_token, params \\ %{})
|
def get_topic(stream, user, oauth_token, params \\ %{})
|
||||||
|
|
||||||
# Allow all public steams.
|
# Allow all public steams if the instance allows unauthenticated access.
|
||||||
def get_topic(stream, _user, _oauth_token, _params) when stream in @public_streams do
|
# Otherwise, only allow users with valid oauth tokens.
|
||||||
|
def get_topic(stream, user, oauth_token, _params) when stream in @public_streams do
|
||||||
|
kind = if stream in @local_streams, do: :local, else: :federated
|
||||||
|
|
||||||
|
if can_access_stream(user, oauth_token, kind) do
|
||||||
{:ok, stream}
|
{:ok, stream}
|
||||||
|
else
|
||||||
|
{:error, :unauthorized}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Allow all hashtags streams.
|
# Allow all hashtags streams.
|
||||||
|
@ -57,12 +81,20 @@ def get_topic("hashtag", _user, _oauth_token, %{"tag" => tag} = _params) do
|
||||||
end
|
end
|
||||||
|
|
||||||
# Allow remote instance streams.
|
# Allow remote instance streams.
|
||||||
def get_topic("public:remote", _user, _oauth_token, %{"instance" => instance} = _params) do
|
def get_topic("public:remote", user, oauth_token, %{"instance" => instance} = _params) do
|
||||||
|
if can_access_stream(user, oauth_token, :federated) do
|
||||||
{:ok, "public:remote:" <> instance}
|
{:ok, "public:remote:" <> instance}
|
||||||
|
else
|
||||||
|
{:error, :unauthorized}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_topic("public:remote:media", _user, _oauth_token, %{"instance" => instance} = _params) do
|
def get_topic("public:remote:media", user, oauth_token, %{"instance" => instance} = _params) do
|
||||||
|
if can_access_stream(user, oauth_token, :federated) do
|
||||||
{:ok, "public:remote:media:" <> instance}
|
{:ok, "public:remote:media:" <> instance}
|
||||||
|
else
|
||||||
|
{:error, :unauthorized}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Expand user streams.
|
# Expand user streams.
|
||||||
|
|
|
@ -9,6 +9,7 @@ command=/opt/akkoma/bin/pleroma
|
||||||
command_args="start"
|
command_args="start"
|
||||||
command_user=akkoma
|
command_user=akkoma
|
||||||
command_background=1
|
command_background=1
|
||||||
|
no_new_privs="yes"
|
||||||
|
|
||||||
# Ask process to terminate within 30 seconds, otherwise kill it
|
# Ask process to terminate within 30 seconds, otherwise kill it
|
||||||
retry="SIGTERM/30/SIGKILL/5"
|
retry="SIGTERM/30/SIGKILL/5"
|
||||||
|
|
1
test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json
vendored
Normal file
1
test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1",{"manuallyApprovesFollowers":"as:manuallyApprovesFollowers","sensitive":"as:sensitive","Hashtag":"as:Hashtag","quoteUrl":"as:quoteUrl","toot":"http://joinmastodon.org/ns#","Emoji":"toot:Emoji","featured":"toot:featured","discoverable":"toot:discoverable","schema":"http://schema.org#","PropertyValue":"schema:PropertyValue","value":"schema:value","misskey":"https://misskey-hub.net/ns#","_misskey_content":"misskey:_misskey_content","_misskey_quote":"misskey:_misskey_quote","_misskey_reaction":"misskey:_misskey_reaction","_misskey_votes":"misskey:_misskey_votes","_misskey_talk":"misskey:_misskey_talk","isCat":"misskey:isCat","vcard":"http://www.w3.org/2006/vcard/ns#"}],"id":"https://mk.absturztau.be/notes/93e7nm8wqg/activity","actor":"https://mk.absturztau.be/users/8ozbzjs3o8","type":"Create","published":"2022-08-01T11:06:49.568Z","object":{"id":"https://mk.absturztau.be/notes/93e7nm8wqg","type":"Note","attributedTo":"https://mk.absturztau.be/users/8ozbzjs3o8","summary":null,"content":"<p><span>meow</span></p>","_misskey_content":"meow","published":"2022-08-01T11:06:49.568Z","to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mk.absturztau.be/users/8ozbzjs3o8/followers"],"inReplyTo":null,"attachment":[],"sensitive":false,"tag":[]},"to":["https://www.w3.org/ns/activitystreams#Public"],"cc":["https://mk.absturztau.be/users/8ozbzjs3o8/followers"]}
|
|
@ -35,7 +35,7 @@ test "always add user and list topics" do
|
||||||
setup do
|
setup do
|
||||||
activity = %Activity{
|
activity = %Activity{
|
||||||
object: %Object{data: %{"type" => "Note"}},
|
object: %Object{data: %{"type" => "Note"}},
|
||||||
data: %{"to" => [Pleroma.Constants.as_public()]}
|
data: %{"to" => [Pleroma.Constants.as_public()], "type" => "Create"}
|
||||||
}
|
}
|
||||||
|
|
||||||
{:ok, activity: activity}
|
{:ok, activity: activity}
|
||||||
|
@ -114,6 +114,55 @@ test "local action doesn't produce public:remote topic", %{activity: activity} d
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "public visibility Announces" do
|
||||||
|
setup do
|
||||||
|
activity = %Activity{
|
||||||
|
object: %Object{data: %{"attachment" => []}},
|
||||||
|
data: %{"type" => "Announce", "to" => [Pleroma.Constants.as_public()]}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, activity: activity}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not generate public topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
refute "public" in topics
|
||||||
|
refute "public:remote" in topics
|
||||||
|
refute "public:local" in topics
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "local-public visibility create events" do
|
||||||
|
setup do
|
||||||
|
activity = %Activity{
|
||||||
|
object: %Object{data: %{"attachment" => []}},
|
||||||
|
data: %{"type" => "Create", "to" => [Pleroma.Web.ActivityPub.Utils.as_local_public()]}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, activity: activity}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't produce public topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
refute Enum.member?(topics, "public")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "produces public:local topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
assert Enum.member?(topics, "public:local")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with no attachments doesn't produce public:media topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
refute Enum.member?(topics, "public:media")
|
||||||
|
refute Enum.member?(topics, "public:local:media")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "public visibility create events with attachments" do
|
describe "public visibility create events with attachments" do
|
||||||
setup do
|
setup do
|
||||||
activity = %Activity{
|
activity = %Activity{
|
||||||
|
@ -152,9 +201,36 @@ test "non-local action produces public:remote:media topic", %{activity: activity
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "local-public visibility create events with attachments" do
|
||||||
|
setup do
|
||||||
|
activity = %Activity{
|
||||||
|
object: %Object{data: %{"attachment" => ["foo"]}},
|
||||||
|
data: %{"type" => "Create", "to" => [Pleroma.Web.ActivityPub.Utils.as_local_public()]}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, activity: activity}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "do not produce public:media topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
refute Enum.member?(topics, "public:media")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "produces public:local:media topics", %{activity: activity} do
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
assert Enum.member?(topics, "public:local:media")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "non-public visibility" do
|
describe "non-public visibility" do
|
||||||
test "produces direct topic" do
|
test "produces direct topic" do
|
||||||
activity = %Activity{object: %Object{data: %{"type" => "Note"}}, data: %{"to" => []}}
|
activity = %Activity{
|
||||||
|
object: %Object{data: %{"type" => "Note"}},
|
||||||
|
data: %{"to" => [], "type" => "Create"}
|
||||||
|
}
|
||||||
|
|
||||||
topics = Topics.get_activity_topics(activity)
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
assert Enum.member?(topics, "direct")
|
assert Enum.member?(topics, "direct")
|
||||||
|
|
|
@ -328,6 +328,32 @@ test "it disables notifications from strangers" do
|
||||||
refute Notification.create_notification(activity, followed)
|
refute Notification.create_notification(activity, followed)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it disables notifications from non-followees" do
|
||||||
|
follower = insert(:user)
|
||||||
|
|
||||||
|
followed =
|
||||||
|
insert(:user,
|
||||||
|
notification_settings: %Pleroma.User.NotificationSetting{block_from_strangers: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
CommonAPI.follow(follower, followed)
|
||||||
|
{:ok, activity} = CommonAPI.post(follower, %{status: "hey @#{followed.nickname}"})
|
||||||
|
refute Notification.create_notification(activity, followed)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it allows notifications from followees" do
|
||||||
|
poster = insert(:user)
|
||||||
|
|
||||||
|
receiver =
|
||||||
|
insert(:user,
|
||||||
|
notification_settings: %Pleroma.User.NotificationSetting{block_from_strangers: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
CommonAPI.follow(receiver, poster)
|
||||||
|
{:ok, activity} = CommonAPI.post(poster, %{status: "hey @#{receiver.nickname}"})
|
||||||
|
assert Notification.create_notification(activity, receiver)
|
||||||
|
end
|
||||||
|
|
||||||
test "it doesn't create a notification for user if he is the activity author" do
|
test "it doesn't create a notification for user if he is the activity author" do
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
author = User.get_cached_by_ap_id(activity.data["actor"])
|
author = User.get_cached_by_ap_id(activity.data["actor"])
|
||||||
|
@ -1225,5 +1251,32 @@ test "it returns notifications about favorites with filtered word", %{user: user
|
||||||
|
|
||||||
assert length(Notification.for_user(user)) == 1
|
assert length(Notification.for_user(user)) == 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it returns notifications when related object is without content and filters are defined",
|
||||||
|
%{user: user} do
|
||||||
|
followed_user = insert(:user, is_locked: true)
|
||||||
|
|
||||||
|
insert(:filter, user: followed_user, phrase: "test", hide: true)
|
||||||
|
|
||||||
|
{:ok, _, _, _activity} = CommonAPI.follow(user, followed_user)
|
||||||
|
refute FollowingRelationship.following?(user, followed_user)
|
||||||
|
assert [notification] = Notification.for_user(followed_user)
|
||||||
|
|
||||||
|
assert %{type: "follow_request"} =
|
||||||
|
NotificationView.render("show.json", %{
|
||||||
|
notification: notification,
|
||||||
|
for: followed_user
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:ok, _} = CommonAPI.accept_follow_request(user, followed_user)
|
||||||
|
|
||||||
|
assert [notification] = Notification.for_user(followed_user)
|
||||||
|
|
||||||
|
assert %{type: "follow"} =
|
||||||
|
NotificationView.render("show.json", %{
|
||||||
|
notification: notification,
|
||||||
|
for: followed_user
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -557,6 +557,21 @@ test "it fails gracefully with invalid email config" do
|
||||||
refute_email_sent()
|
refute_email_sent()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it works when the registering user does not provide an email" do
|
||||||
|
clear_config([Pleroma.Emails.Mailer, :enabled], false)
|
||||||
|
clear_config([:instance, :account_activation_required], false)
|
||||||
|
clear_config([:instance, :account_approval_required], true)
|
||||||
|
|
||||||
|
cng = User.register_changeset(%User{}, @full_user_data |> Map.put(:email, ""))
|
||||||
|
|
||||||
|
# The user is still created
|
||||||
|
assert {:ok, %User{nickname: "nick"}} = User.register(cng)
|
||||||
|
|
||||||
|
# No emails are sent
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
refute_email_sent()
|
||||||
|
end
|
||||||
|
|
||||||
test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do
|
test "it requires an email, name, nickname and password, bio is optional when account_activation_required is enabled" do
|
||||||
clear_config([:instance, :account_activation_required], true)
|
clear_config([:instance, :account_activation_required], true)
|
||||||
|
|
||||||
|
|
|
@ -732,9 +732,7 @@ test "it streams out the announce", %{announce: announce} do
|
||||||
]) do
|
]) do
|
||||||
{:ok, announce, _} = SideEffects.handle(announce)
|
{:ok, announce, _} = SideEffects.handle(announce)
|
||||||
|
|
||||||
assert called(
|
assert called(Pleroma.Web.Streamer.stream(["user", "list"], announce))
|
||||||
Pleroma.Web.Streamer.stream(["user", "list", "public", "public:local"], announce)
|
|
||||||
)
|
|
||||||
|
|
||||||
assert called(Pleroma.Web.Push.send(:_))
|
assert called(Pleroma.Web.Push.send(:_))
|
||||||
end
|
end
|
||||||
|
|
|
@ -783,4 +783,42 @@ test "quote fetching should stop after n levels", _ do
|
||||||
} = Transmogrifier.fix_quote_url(note)
|
} = Transmogrifier.fix_quote_url(note)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "the standalone note uses its own ID when context is missing" do
|
||||||
|
insert(:user, ap_id: "https://mk.absturztau.be/users/8ozbzjs3o8")
|
||||||
|
|
||||||
|
activity =
|
||||||
|
"test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> Jason.decode!()
|
||||||
|
|
||||||
|
{:ok, %Activity{} = modified} = Transmogrifier.handle_incoming(activity)
|
||||||
|
object = Object.normalize(modified, fetch: false)
|
||||||
|
|
||||||
|
assert object.data["context"] == object.data["id"]
|
||||||
|
assert modified.data["context"] == object.data["id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "the reply note uses its parent's ID when context is missing and reply is unreachable" do
|
||||||
|
insert(:user, ap_id: "https://mk.absturztau.be/users/8ozbzjs3o8")
|
||||||
|
|
||||||
|
activity =
|
||||||
|
"test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json"
|
||||||
|
|> File.read!()
|
||||||
|
|> Jason.decode!()
|
||||||
|
|
||||||
|
object =
|
||||||
|
activity["object"]
|
||||||
|
|> Map.put("inReplyTo", "https://404.site/object/went-to-buy-milk")
|
||||||
|
|
||||||
|
activity =
|
||||||
|
activity
|
||||||
|
|> Map.put("object", object)
|
||||||
|
|
||||||
|
{:ok, %Activity{} = modified} = Transmogrifier.handle_incoming(activity)
|
||||||
|
object = Object.normalize(modified, fetch: false)
|
||||||
|
|
||||||
|
assert object.data["context"] == object.data["inReplyTo"]
|
||||||
|
assert modified.data["context"] == object.data["inReplyTo"]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -225,6 +225,20 @@ test "superusers deleting non-local posts won't federate the delete" do
|
||||||
|
|
||||||
refute Activity.get_by_id(post.id)
|
refute Activity.get_by_id(post.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it allows privileged users to delete banned user's posts" do
|
||||||
|
clear_config([:instance, :moderator_privileges], [:messages_delete])
|
||||||
|
user = insert(:user)
|
||||||
|
moderator = insert(:user, is_moderator: true)
|
||||||
|
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
|
||||||
|
User.set_activation(user, false)
|
||||||
|
|
||||||
|
assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
|
||||||
|
assert delete.local
|
||||||
|
|
||||||
|
refute Activity.get_by_id(post.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "favoriting race condition" do
|
test "favoriting race condition" do
|
||||||
|
|
|
@ -1895,6 +1895,39 @@ test "getting a list of blocks" do
|
||||||
assert [%{"id" => ^id2}] = result
|
assert [%{"id" => ^id2}] = result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "list of blocks with with_relationships parameter" do
|
||||||
|
%{user: user, conn: conn} = oauth_access(["read:blocks"])
|
||||||
|
%{id: id1} = other_user1 = insert(:user)
|
||||||
|
%{id: id2} = other_user2 = insert(:user)
|
||||||
|
%{id: id3} = other_user3 = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _, _} = User.follow(other_user1, user)
|
||||||
|
{:ok, _, _} = User.follow(other_user2, user)
|
||||||
|
{:ok, _, _} = User.follow(other_user3, user)
|
||||||
|
|
||||||
|
{:ok, _} = User.block(user, other_user1)
|
||||||
|
{:ok, _} = User.block(user, other_user2)
|
||||||
|
{:ok, _} = User.block(user, other_user3)
|
||||||
|
|
||||||
|
assert [
|
||||||
|
%{
|
||||||
|
"id" => ^id1,
|
||||||
|
"pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"id" => ^id2,
|
||||||
|
"pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"id" => ^id3,
|
||||||
|
"pleroma" => %{"relationship" => %{"blocking" => true, "followed_by" => false}}
|
||||||
|
}
|
||||||
|
] =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/blocks?with_relationships=true")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
end
|
||||||
|
|
||||||
test "account lookup", %{conn: conn} do
|
test "account lookup", %{conn: conn} do
|
||||||
%{nickname: acct} = insert(:user, %{nickname: "nickname"})
|
%{nickname: acct} = insert(:user, %{nickname: "nickname"})
|
||||||
%{nickname: acct_two} = insert(:user, %{nickname: "nickname@notlocaldoma.in"})
|
%{nickname: acct_two} = insert(:user, %{nickname: "nickname@notlocaldoma.in"})
|
||||||
|
|
|
@ -674,7 +674,10 @@ test "option limit is enforced", %{conn: conn} do
|
||||||
|> put_req_header("content-type", "application/json")
|
|> put_req_header("content-type", "application/json")
|
||||||
|> post("/api/v1/statuses", %{
|
|> post("/api/v1/statuses", %{
|
||||||
"status" => "desu~",
|
"status" => "desu~",
|
||||||
"poll" => %{"options" => Enum.map(0..limit, fn _ -> "desu" end), "expires_in" => 1}
|
"poll" => %{
|
||||||
|
"options" => Enum.map(0..limit, fn num -> "desu #{num}" end),
|
||||||
|
"expires_in" => 1
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
||||||
|
@ -690,7 +693,7 @@ test "option character limit is enforced", %{conn: conn} do
|
||||||
|> post("/api/v1/statuses", %{
|
|> post("/api/v1/statuses", %{
|
||||||
"status" => "...",
|
"status" => "...",
|
||||||
"poll" => %{
|
"poll" => %{
|
||||||
"options" => [Enum.reduce(0..limit, "", fn _, acc -> acc <> "." end)],
|
"options" => [String.duplicate(".", limit + 1), "lol"],
|
||||||
"expires_in" => 1
|
"expires_in" => 1
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -772,6 +775,32 @@ test "scheduled poll", %{conn: conn} do
|
||||||
assert object.data["type"] == "Question"
|
assert object.data["type"] == "Question"
|
||||||
assert length(object.data["oneOf"]) == 3
|
assert length(object.data["oneOf"]) == 3
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "cannot have only one option", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "desu~",
|
||||||
|
"poll" => %{"options" => ["mew"], "expires_in" => 1}
|
||||||
|
})
|
||||||
|
|
||||||
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
||||||
|
assert error == "Poll must contain at least 2 options"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "cannot have only duplicated options", %{conn: conn} do
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> post("/api/v1/statuses", %{
|
||||||
|
"status" => "desu~",
|
||||||
|
"poll" => %{"options" => ["mew", "mew"], "expires_in" => 1}
|
||||||
|
})
|
||||||
|
|
||||||
|
%{"error" => error} = json_response_and_validate_schema(conn, 422)
|
||||||
|
assert error == "Poll must contain at least 2 options"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "get a status" do
|
test "get a status" do
|
||||||
|
@ -1044,6 +1073,27 @@ test "when you're an admin or moderator", %{conn: conn} do
|
||||||
refute Activity.get_by_id(activity1.id)
|
refute Activity.get_by_id(activity1.id)
|
||||||
refute Activity.get_by_id(activity2.id)
|
refute Activity.get_by_id(activity2.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "when you're privileged and the user is banned", %{conn: conn} do
|
||||||
|
clear_config([:instance, :moderator_privileges], [:messages_delete])
|
||||||
|
posting_user = insert(:user, is_active: false)
|
||||||
|
refute posting_user.is_active
|
||||||
|
activity = insert(:note_activity, user: posting_user)
|
||||||
|
user = insert(:user, is_moderator: true)
|
||||||
|
|
||||||
|
res_conn =
|
||||||
|
conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
|
||||||
|
|> delete("/api/v1/statuses/#{activity.id}")
|
||||||
|
|
||||||
|
assert %{} = json_response_and_validate_schema(res_conn, 200)
|
||||||
|
|
||||||
|
# assert ModerationLog |> Repo.one() |> ModerationLog.get_log_entry_message() ==
|
||||||
|
# "@#{user.nickname} deleted status ##{activity.id}"
|
||||||
|
|
||||||
|
refute Activity.get_by_id(activity.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "reblogging" do
|
describe "reblogging" do
|
||||||
|
|
|
@ -11,11 +11,24 @@ test "it renders all links with rel='me' from user bio" do
|
||||||
bio =
|
bio =
|
||||||
~s(<a href="https://some-link.com">https://some-link.com</a> <a rel="me" href="https://another-link.com">https://another-link.com</a> <link href="http://some.com"> <link rel="me" href="http://some3.com">)
|
~s(<a href="https://some-link.com">https://some-link.com</a> <a rel="me" href="https://another-link.com">https://another-link.com</a> <link href="http://some.com"> <link rel="me" href="http://some3.com">)
|
||||||
|
|
||||||
user = insert(:user, %{bio: bio})
|
fields = [
|
||||||
|
%{
|
||||||
|
"name" => "profile",
|
||||||
|
"value" => ~S(<a rel="me" href="http://profile.com">http://profile.com</a>)
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
"name" => "like",
|
||||||
|
"value" => ~S(<a href="http://cofe.io">http://cofe.io</a>)
|
||||||
|
},
|
||||||
|
%{"name" => "foo", "value" => "bar"}
|
||||||
|
]
|
||||||
|
|
||||||
|
user = insert(:user, %{bio: bio, fields: fields})
|
||||||
|
|
||||||
assert RelMe.build_tags(%{user: user}) == [
|
assert RelMe.build_tags(%{user: user}) == [
|
||||||
{:link, [rel: "me", href: "http://some3.com"], []},
|
{:link, [rel: "me", href: "http://some3.com"], []},
|
||||||
{:link, [rel: "me", href: "https://another-link.com"], []}
|
{:link, [rel: "me", href: "https://another-link.com"], []},
|
||||||
|
{:link, [rel: "me", href: "http://profile.com"], []}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,10 +22,10 @@ test "it renders twitter card for user info" do
|
||||||
res = TwitterCard.build_tags(%{user: user})
|
res = TwitterCard.build_tags(%{user: user})
|
||||||
|
|
||||||
assert res == [
|
assert res == [
|
||||||
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
|
{:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []},
|
||||||
{:meta, [property: "twitter:description", content: "born 19 March 1994"], []},
|
{:meta, [name: "twitter:description", content: "born 19 March 1994"], []},
|
||||||
{:meta, [property: "twitter:image", content: avatar_url], []},
|
{:meta, [name: "twitter:image", content: avatar_url], []},
|
||||||
{:meta, [property: "twitter:card", content: "summary"], []}
|
{:meta, [name: "twitter:card", content: "summary"], []}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -47,11 +47,11 @@ test "it uses summary twittercard if post has no attachment" do
|
||||||
result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
|
result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
|
||||||
|
|
||||||
assert [
|
assert [
|
||||||
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
|
{:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []},
|
||||||
{:meta, [property: "twitter:description", content: "pleroma in a nutshell"], []},
|
{:meta, [name: "twitter:description", content: "pleroma in a nutshell"], []},
|
||||||
{:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"],
|
{:meta, [name: "twitter:image", content: "http://localhost:4001/images/avi.png"],
|
||||||
[]},
|
[]},
|
||||||
{:meta, [property: "twitter:card", content: "summary"], []}
|
{:meta, [name: "twitter:card", content: "summary"], []}
|
||||||
] == result
|
] == result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -73,15 +73,15 @@ test "it uses summary as description if post has one" do
|
||||||
result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
|
result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
|
||||||
|
|
||||||
assert [
|
assert [
|
||||||
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
|
{:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []},
|
||||||
{:meta,
|
{:meta,
|
||||||
[
|
[
|
||||||
property: "twitter:description",
|
name: "twitter:description",
|
||||||
content: "Public service announcement on caffeine consumption"
|
content: "Public service announcement on caffeine consumption"
|
||||||
], []},
|
], []},
|
||||||
{:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"],
|
{:meta, [name: "twitter:image", content: "http://localhost:4001/images/avi.png"],
|
||||||
[]},
|
[]},
|
||||||
{:meta, [property: "twitter:card", content: "summary"], []}
|
{:meta, [name: "twitter:card", content: "summary"], []}
|
||||||
] == result
|
] == result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -123,11 +123,11 @@ test "it renders avatar not attachment if post is nsfw and unfurl_nsfw is disabl
|
||||||
result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
|
result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
|
||||||
|
|
||||||
assert [
|
assert [
|
||||||
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
|
{:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []},
|
||||||
{:meta, [property: "twitter:description", content: "pleroma in a nutshell"], []},
|
{:meta, [name: "twitter:description", content: "pleroma in a nutshell"], []},
|
||||||
{:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"],
|
{:meta, [name: "twitter:image", content: "http://localhost:4001/images/avi.png"],
|
||||||
[]},
|
[]},
|
||||||
{:meta, [property: "twitter:card", content: "summary"], []}
|
{:meta, [name: "twitter:card", content: "summary"], []}
|
||||||
] == result
|
] == result
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -179,26 +179,26 @@ test "it renders supported types of attachments and skips unknown types" do
|
||||||
result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
|
result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id})
|
||||||
|
|
||||||
assert [
|
assert [
|
||||||
{:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []},
|
{:meta, [name: "twitter:title", content: Utils.user_name_string(user)], []},
|
||||||
{:meta, [property: "twitter:description", content: "pleroma in a nutshell"], []},
|
{:meta, [name: "twitter:description", content: "pleroma in a nutshell"], []},
|
||||||
{:meta, [property: "twitter:card", content: "summary_large_image"], []},
|
{:meta, [name: "twitter:card", content: "summary_large_image"], []},
|
||||||
{:meta, [property: "twitter:player", content: "https://pleroma.gov/tenshi.png"], []},
|
{:meta, [name: "twitter:player", content: "https://pleroma.gov/tenshi.png"], []},
|
||||||
{:meta, [property: "twitter:player:width", content: "1280"], []},
|
{:meta, [name: "twitter:player:width", content: "1280"], []},
|
||||||
{:meta, [property: "twitter:player:height", content: "1024"], []},
|
{:meta, [name: "twitter:player:height", content: "1024"], []},
|
||||||
{:meta, [property: "twitter:card", content: "player"], []},
|
{:meta, [name: "twitter:card", content: "player"], []},
|
||||||
{:meta,
|
{:meta,
|
||||||
[
|
[
|
||||||
property: "twitter:player",
|
name: "twitter:player",
|
||||||
content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id)
|
content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id)
|
||||||
], []},
|
], []},
|
||||||
{:meta, [property: "twitter:player:width", content: "800"], []},
|
{:meta, [name: "twitter:player:width", content: "800"], []},
|
||||||
{:meta, [property: "twitter:player:height", content: "600"], []},
|
{:meta, [name: "twitter:player:height", content: "600"], []},
|
||||||
{:meta,
|
{:meta,
|
||||||
[
|
[
|
||||||
property: "twitter:player:stream",
|
name: "twitter:player:stream",
|
||||||
content: "https://pleroma.gov/about/juche.webm"
|
content: "https://pleroma.gov/about/juche.webm"
|
||||||
], []},
|
], []},
|
||||||
{:meta, [property: "twitter:player:stream:content_type", content: "video/webm"], []}
|
{:meta, [name: "twitter:player:stream:content_type", content: "video/webm"], []}
|
||||||
] == result
|
] == result
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,11 +33,11 @@ test "does not send Content-Disposition header when name param is not set", %{
|
||||||
test "sends Content-Disposition header when name param is set", %{
|
test "sends Content-Disposition header when name param is set", %{
|
||||||
attachment_url: attachment_url
|
attachment_url: attachment_url
|
||||||
} do
|
} do
|
||||||
conn = get(build_conn(), attachment_url <> "?name=\"cofe\".gif")
|
conn = get(build_conn(), attachment_url <> ~s[?name="cofe".gif])
|
||||||
|
|
||||||
assert Enum.any?(
|
assert Enum.any?(
|
||||||
conn.resp_headers,
|
conn.resp_headers,
|
||||||
&(&1 == {"content-disposition", "filename=\"\\\"cofe\\\".gif\""})
|
&(&1 == {"content-disposition", ~s[inline; filename="\\"cofe\\".gif"]})
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ test "removes control characters from the Content-Disposition header", %{
|
||||||
|
|
||||||
assert Enum.any?(
|
assert Enum.any?(
|
||||||
conn.resp_headers,
|
conn.resp_headers,
|
||||||
&(&1 == {"content-disposition", "filename=\"\\\"cofe\\\".gif\""})
|
&(&1 == {"content-disposition", ~s[inline; filename="\\"cofe\\".gif"]})
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,6 +25,26 @@ test "allows public" do
|
||||||
assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil, nil)
|
assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "rejects local public streams if restricted_unauthenticated is on" do
|
||||||
|
clear_config([:restrict_unauthenticated, :timelines, :local], true)
|
||||||
|
|
||||||
|
assert {:error, :unauthorized} = Streamer.get_topic("public:local", nil, nil)
|
||||||
|
assert {:error, :unauthorized} = Streamer.get_topic("public:local:media", nil, nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "rejects remote public streams if restricted_unauthenticated is on" do
|
||||||
|
clear_config([:restrict_unauthenticated, :timelines, :federated], true)
|
||||||
|
|
||||||
|
assert {:error, :unauthorized} = Streamer.get_topic("public", nil, nil)
|
||||||
|
assert {:error, :unauthorized} = Streamer.get_topic("public:media", nil, nil)
|
||||||
|
|
||||||
|
assert {:error, :unauthorized} =
|
||||||
|
Streamer.get_topic("public:remote", nil, nil, %{"instance" => "lain.com"})
|
||||||
|
|
||||||
|
assert {:error, :unauthorized} =
|
||||||
|
Streamer.get_topic("public:remote:media", nil, nil, %{"instance" => "lain.com"})
|
||||||
|
end
|
||||||
|
|
||||||
test "allows instance streams" do
|
test "allows instance streams" do
|
||||||
assert {:ok, "public:remote:lain.com"} =
|
assert {:ok, "public:remote:lain.com"} =
|
||||||
Streamer.get_topic("public:remote", nil, nil, %{"instance" => "lain.com"})
|
Streamer.get_topic("public:remote", nil, nil, %{"instance" => "lain.com"})
|
||||||
|
@ -65,6 +85,63 @@ test "allows public streams (regardless of OAuth token scopes)", %{
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "allows local public streams if restricted_unauthenticated is on", %{
|
||||||
|
user: user,
|
||||||
|
token: oauth_token
|
||||||
|
} do
|
||||||
|
clear_config([:restrict_unauthenticated, :timelines, :local], true)
|
||||||
|
|
||||||
|
%{token: read_notifications_token} = oauth_access(["read:notifications"], user: user)
|
||||||
|
%{token: badly_scoped_token} = oauth_access(["irrelevant:scope"], user: user)
|
||||||
|
|
||||||
|
assert {:ok, "public:local"} = Streamer.get_topic("public:local", user, oauth_token)
|
||||||
|
|
||||||
|
assert {:ok, "public:local:media"} =
|
||||||
|
Streamer.get_topic("public:local:media", user, oauth_token)
|
||||||
|
|
||||||
|
for token <- [read_notifications_token, badly_scoped_token] do
|
||||||
|
assert {:error, :unauthorized} = Streamer.get_topic("public:local", user, token)
|
||||||
|
|
||||||
|
assert {:error, :unauthorized} = Streamer.get_topic("public:local:media", user, token)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "allows remote public streams if restricted_unauthenticated is on", %{
|
||||||
|
user: user,
|
||||||
|
token: oauth_token
|
||||||
|
} do
|
||||||
|
clear_config([:restrict_unauthenticated, :timelines, :federated], true)
|
||||||
|
|
||||||
|
%{token: read_notifications_token} = oauth_access(["read:notifications"], user: user)
|
||||||
|
%{token: badly_scoped_token} = oauth_access(["irrelevant:scope"], user: user)
|
||||||
|
|
||||||
|
assert {:ok, "public"} = Streamer.get_topic("public", user, oauth_token)
|
||||||
|
assert {:ok, "public:media"} = Streamer.get_topic("public:media", user, oauth_token)
|
||||||
|
|
||||||
|
assert {:ok, "public:remote:lain.com"} =
|
||||||
|
Streamer.get_topic("public:remote", user, oauth_token, %{"instance" => "lain.com"})
|
||||||
|
|
||||||
|
assert {:ok, "public:remote:media:lain.com"} =
|
||||||
|
Streamer.get_topic("public:remote:media", user, oauth_token, %{
|
||||||
|
"instance" => "lain.com"
|
||||||
|
})
|
||||||
|
|
||||||
|
for token <- [read_notifications_token, badly_scoped_token] do
|
||||||
|
assert {:error, :unauthorized} = Streamer.get_topic("public", user, token)
|
||||||
|
assert {:error, :unauthorized} = Streamer.get_topic("public:media", user, token)
|
||||||
|
|
||||||
|
assert {:error, :unauthorized} =
|
||||||
|
Streamer.get_topic("public:remote", user, token, %{
|
||||||
|
"instance" => "lain.com"
|
||||||
|
})
|
||||||
|
|
||||||
|
assert {:error, :unauthorized} =
|
||||||
|
Streamer.get_topic("public:remote:media", user, token, %{
|
||||||
|
"instance" => "lain.com"
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "allows user streams (with proper OAuth token scopes)", %{
|
test "allows user streams (with proper OAuth token scopes)", %{
|
||||||
user: user,
|
user: user,
|
||||||
token: read_oauth_token
|
token: read_oauth_token
|
||||||
|
|
|
@ -1085,6 +1085,14 @@ def get("http://404.site" <> _, _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://404.site" <> _, _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 404,
|
||||||
|
body: ""
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
"https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=acct:lain@zetsubou.xn--q9jyb4c",
|
"https://zetsubou.xn--q9jyb4c/.well-known/webfinger?resource=acct:lain@zetsubou.xn--q9jyb4c",
|
||||||
_,
|
_,
|
||||||
|
@ -1399,6 +1407,15 @@ def get("https://mk.absturztau.be/notes/93e7nm8wqg", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://mk.absturztau.be/notes/93e7nm8wqg/activity", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/mk.absturztau.be-93e7nm8wqg-activity.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get("https://p.helene.moe/objects/fd5910ac-d9dc-412e-8d1d-914b203296c4", _, _, _) do
|
def get("https://p.helene.moe/objects/fd5910ac-d9dc-412e-8d1d-914b203296c4", _, _, _) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
|
|
Loading…
Reference in a new issue