Merge branch 'develop' into gun

This commit is contained in:
Alexander Strizhakov 2020-03-16 14:25:55 +03:00
commit f0651730bd
No known key found for this signature in database
GPG Key ID: 022896A53AEF1381
108 changed files with 1197 additions and 841 deletions

View File

@ -3,6 +3,10 @@ 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]
### Changed
- **Breaking:** BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines
## [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 enourmous amount of statuses in timelines leading to DoS. Now limited to 40 per request.

View File

@ -61,8 +61,6 @@
config :web_push_encryption, :http_client, Pleroma.Web.WebPushHttpClientMock config :web_push_encryption, :http_client, Pleroma.Web.WebPushHttpClientMock
config :pleroma_job_queue, disabled: true
config :pleroma, Pleroma.ScheduledActivity, config :pleroma, Pleroma.ScheduledActivity,
daily_user_limit: 2, daily_user_limit: 2,
total_user_limit: 3, total_user_limit: 3,

View File

@ -482,10 +482,6 @@
federator_outgoing: 5 federator_outgoing: 5
] ]
config :pleroma, :fetch_initial_posts,
enabled: false,
pages: 5
config :auto_linker, config :auto_linker,
opts: [ opts: [
extra: true, extra: true,

View File

@ -1780,25 +1780,6 @@
} }
] ]
}, },
%{
group: :pleroma_job_queue,
key: :queues,
type: :group,
description: "[Deprecated] Replaced with `Oban`/`:queues` (keeping the same format)"
},
%{
group: :pleroma,
key: Pleroma.Web.Federator.RetryQueue,
type: :group,
description: "[Deprecated] See `Oban` and `:workers` sections for configuration notes",
children: [
%{
key: :max_retries,
type: :integer,
description: "[Deprecated] Replaced as `Oban`/`:queues`/`:outgoing_federation` value"
}
]
},
%{ %{
group: :pleroma, group: :pleroma,
key: Oban, key: Oban,
@ -2007,25 +1988,6 @@
} }
] ]
}, },
%{
group: :pleroma,
key: :fetch_initial_posts,
type: :group,
description: "Fetching initial posts settings",
children: [
%{
key: :enabled,
type: :boolean,
description: "Fetch posts when a new user is federated with"
},
%{
key: :pages,
type: :integer,
description: "The amount of pages to fetch",
suggestions: [5]
}
]
},
%{ %{
group: :auto_linker, group: :auto_linker,
key: :opts, key: :opts,
@ -2596,19 +2558,6 @@
} }
] ]
}, },
%{
group: :tesla,
type: :group,
description: "Tesla settings",
children: [
%{
key: :adapter,
type: :module,
description: "Tesla adapter",
suggestions: [Tesla.Adapter.Hackney, Tesla.Adapter.Gun]
}
]
},
%{ %{
group: :pleroma, group: :pleroma,
key: :chat, key: :chat,

View File

@ -94,6 +94,8 @@
config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true config :pleroma, Pleroma.Emails.NewUsersDigestEmail, enabled: true
config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false
if File.exists?("./config/test.secret.exs") do if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs" import_config "test.secret.exs"
else else

View File

@ -180,7 +180,7 @@ Post here request with grant_type=refresh_token to obtain new access token. Retu
## Account Registration ## Account Registration
`POST /api/v1/accounts` `POST /api/v1/accounts`
Has theses additionnal parameters (which are the same as in Pleroma-API): Has theses additional parameters (which are the same as in Pleroma-API):
* `fullname`: optional * `fullname`: optional
* `bio`: optional * `bio`: optional
* `captcha_solution`: optional, contains provider-specific captcha solution, * `captcha_solution`: optional, contains provider-specific captcha solution,

View File

@ -288,10 +288,11 @@ Pleroma Conversations have the same general structure that Mastodon Conversation
2. Pleroma Conversations statuses can be requested by Conversation id. 2. Pleroma Conversations statuses can be requested by Conversation id.
3. Pleroma Conversations can be replied to. 3. Pleroma Conversations can be replied to.
Conversations have the additional field "recipients" under the "pleroma" key. This holds a list of all the accounts that will receive a message in this conversation. Conversations have the additional field `recipients` under the `pleroma` key. This holds a list of all the accounts that will receive a message in this conversation.
The status posting endpoint takes an additional parameter, `in_reply_to_conversation_id`, which, when set, will set the visiblity to direct and address only the people who are the recipients of that Conversation. The status posting endpoint takes an additional parameter, `in_reply_to_conversation_id`, which, when set, will set the visiblity to direct and address only the people who are the recipients of that Conversation.
⚠ Conversation IDs can be found in direct messages with the `pleroma.direct_conversation_id` key, do not confuse it with `pleroma.conversation_id`.
## `GET /api/v1/pleroma/conversations/:id/statuses` ## `GET /api/v1/pleroma/conversations/:id/statuses`
### Timeline for a given conversation ### Timeline for a given conversation

View File

@ -1,5 +1,5 @@
# Pleroma Clients # Pleroma Clients
Note: Additionnal clients may be working but theses are officially supporting Pleroma. Note: Additional clients may be working but theses are officially supporting Pleroma.
Feel free to contact us to be added to this list! Feel free to contact us to be added to this list!
## Desktop ## Desktop

View File

@ -151,14 +151,6 @@ config :pleroma, :mrf_user_allowlist,
* `sign_object_fetches`: Sign object fetches with HTTP signatures * `sign_object_fetches`: Sign object fetches with HTTP signatures
* `authorized_fetch_mode`: Require HTTP signatures for AP fetches * `authorized_fetch_mode`: Require HTTP signatures for AP fetches
### :fetch_initial_posts
!!! warning
Be careful with this setting, fetching posts may lead to new users being discovered whose posts will then also be fetched. This can lead to serious load on your instance and database.
* `enabled`: If enabled, when a new user is discovered by your instance, fetch some of their latest posts.
* `pages`: The amount of pages to fetch
## Pleroma.ScheduledActivity ## Pleroma.ScheduledActivity
* `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`) * `daily_user_limit`: the number of scheduled activities a user is allowed to create in a single day (Default: `25`)

View File

@ -90,8 +90,6 @@ server {
proxy_ignore_client_abort on; proxy_ignore_client_abort on;
proxy_buffering on; proxy_buffering on;
chunked_transfer_encoding on; chunked_transfer_encoding on;
proxy_ignore_headers Cache-Control;
proxy_hide_header Cache-Control;
proxy_pass http://127.0.0.1:4000; proxy_pass http://127.0.0.1:4000;
} }
} }

View File

@ -39,7 +39,7 @@ defp visibility_tags(object, activity) do
end end
end end
defp item_creation_tags(tags, %{data: %{"type" => "Create"}} = object, activity) do defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity) tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
end end

View File

@ -0,0 +1,256 @@
# Pleroma: A lightweight social networking server
# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
#
# This file is derived from Earmark, under the following copyright:
# Copyright © 2014 Dave Thomas, The Pragmatic Programmers
# SPDX-License-Identifier: Apache-2.0
# Upstream: https://github.com/pragdave/earmark/blob/master/lib/earmark/html_renderer.ex
defmodule Pleroma.EarmarkRenderer do
@moduledoc false
alias Earmark.Block
alias Earmark.Context
alias Earmark.HtmlRenderer
alias Earmark.Options
import Earmark.Inline, only: [convert: 3]
import Earmark.Helpers.HtmlHelpers
import Earmark.Message, only: [add_messages_from: 2, get_messages: 1, set_messages: 2]
import Earmark.Context, only: [append: 2, set_value: 2]
import Earmark.Options, only: [get_mapper: 1]
@doc false
def render(blocks, %Context{options: %Options{}} = context) do
messages = get_messages(context)
{contexts, html} =
get_mapper(context.options).(
blocks,
&render_block(&1, put_in(context.options.messages, []))
)
|> Enum.unzip()
all_messages =
contexts
|> Enum.reduce(messages, fn ctx, messages1 -> messages1 ++ get_messages(ctx) end)
{put_in(context.options.messages, all_messages), html |> IO.iodata_to_binary()}
end
#############
# Paragraph #
#############
defp render_block(%Block.Para{lnb: lnb, lines: lines, attrs: attrs}, context) do
lines = convert(lines, lnb, context)
add_attrs(lines, "<p>#{lines.value}</p>", attrs, [], lnb)
end
########
# Html #
########
defp render_block(%Block.Html{html: html}, context) do
{context, html}
end
defp render_block(%Block.HtmlComment{lines: lines}, context) do
{context, lines}
end
defp render_block(%Block.HtmlOneline{html: html}, context) do
{context, html}
end
#########
# Ruler #
#########
defp render_block(%Block.Ruler{lnb: lnb, attrs: attrs}, context) do
add_attrs(context, "<hr />", attrs, [], lnb)
end
###########
# Heading #
###########
defp render_block(
%Block.Heading{lnb: lnb, level: level, content: content, attrs: attrs},
context
) do
converted = convert(content, lnb, context)
html = "<h#{level}>#{converted.value}</h#{level}>"
add_attrs(converted, html, attrs, [], lnb)
end
##############
# Blockquote #
##############
defp render_block(%Block.BlockQuote{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
{context1, body} = render(blocks, context)
html = "<blockquote>#{body}</blockquote>"
add_attrs(context1, html, attrs, [], lnb)
end
#########
# Table #
#########
defp render_block(
%Block.Table{lnb: lnb, header: header, rows: rows, alignments: aligns, attrs: attrs},
context
) do
{context1, html} = add_attrs(context, "<table>", attrs, [], lnb)
context2 = set_value(context1, html)
context3 =
if header do
append(add_trs(append(context2, "<thead>"), [header], "th", aligns, lnb), "</thead>")
else
# Maybe an error, needed append(context, html)
context2
end
context4 = append(add_trs(append(context3, "<tbody>"), rows, "td", aligns, lnb), "</tbody>")
{context4, [context4.value, "</table>"]}
end
########
# Code #
########
defp render_block(
%Block.Code{lnb: lnb, language: language, attrs: attrs} = block,
%Context{options: options} = context
) do
class =
if language, do: ~s{ class="#{code_classes(language, options.code_class_prefix)}"}, else: ""
tag = ~s[<pre><code#{class}>]
lines = options.render_code.(block)
html = ~s[#{tag}#{lines}</code></pre>]
add_attrs(context, html, attrs, [], lnb)
end
#########
# Lists #
#########
defp render_block(
%Block.List{lnb: lnb, type: type, blocks: items, attrs: attrs, start: start},
context
) do
{context1, content} = render(items, context)
html = "<#{type}#{start}>#{content}</#{type}>"
add_attrs(context1, html, attrs, [], lnb)
end
# format a single paragraph list item, and remove the para tags
defp render_block(
%Block.ListItem{lnb: lnb, blocks: blocks, spaced: false, attrs: attrs},
context
)
when length(blocks) == 1 do
{context1, content} = render(blocks, context)
content = Regex.replace(~r{</?p>}, content, "")
html = "<li>#{content}</li>"
add_attrs(context1, html, attrs, [], lnb)
end
# format a spaced list item
defp render_block(%Block.ListItem{lnb: lnb, blocks: blocks, attrs: attrs}, context) do
{context1, content} = render(blocks, context)
html = "<li>#{content}</li>"
add_attrs(context1, html, attrs, [], lnb)
end
##################
# Footnote Block #
##################
defp render_block(%Block.FnList{blocks: footnotes}, context) do
items =
Enum.map(footnotes, fn note ->
blocks = append_footnote_link(note)
%Block.ListItem{attrs: "#fn:#{note.number}", type: :ol, blocks: blocks}
end)
{context1, html} = render_block(%Block.List{type: :ol, blocks: items}, context)
{context1, Enum.join([~s[<div class="footnotes">], "<hr />", html, "</div>"])}
end
#######################################
# Isolated IALs are rendered as paras #
#######################################
defp render_block(%Block.Ial{verbatim: verbatim}, context) do
{context, "<p>{:#{verbatim}}</p>"}
end
####################
# IDDef is ignored #
####################
defp render_block(%Block.IdDef{}, context), do: {context, ""}
#####################################
# And here are the inline renderers #
#####################################
defdelegate br, to: HtmlRenderer
defdelegate codespan(text), to: HtmlRenderer
defdelegate em(text), to: HtmlRenderer
defdelegate strong(text), to: HtmlRenderer
defdelegate strikethrough(text), to: HtmlRenderer
defdelegate link(url, text), to: HtmlRenderer
defdelegate link(url, text, title), to: HtmlRenderer
defdelegate image(path, alt, title), to: HtmlRenderer
defdelegate footnote_link(ref, backref, number), to: HtmlRenderer
# Table rows
defp add_trs(context, rows, tag, aligns, lnb) do
numbered_rows =
rows
|> Enum.zip(Stream.iterate(lnb, &(&1 + 1)))
numbered_rows
|> Enum.reduce(context, fn {row, lnb}, ctx ->
append(add_tds(append(ctx, "<tr>"), row, tag, aligns, lnb), "</tr>")
end)
end
defp add_tds(context, row, tag, aligns, lnb) do
Enum.reduce(1..length(row), context, add_td_fn(row, tag, aligns, lnb))
end
defp add_td_fn(row, tag, aligns, lnb) do
fn n, ctx ->
style =
case Enum.at(aligns, n - 1, :default) do
:default -> ""
align -> " style=\"text-align: #{align}\""
end
col = Enum.at(row, n - 1)
converted = convert(col, lnb, set_messages(ctx, []))
append(add_messages_from(ctx, converted), "<#{tag}#{style}>#{converted.value}</#{tag}>")
end
end
###############################
# Append Footnote Return Link #
###############################
defdelegate append_footnote_link(note), to: HtmlRenderer
defdelegate append_footnote_link(note, fnlink), to: HtmlRenderer
defdelegate render_code(lines), to: HtmlRenderer
defp code_classes(language, prefix) do
["" | String.split(prefix || "")]
|> Enum.map(fn pfx -> "#{pfx}#{language}" end)
|> Enum.join(" ")
end
end

View File

@ -15,9 +15,24 @@ def call(%{assigns: %{user: %User{}}} = conn, _) do
conn conn
end end
def call(conn, _) do def call(conn, options) do
perform =
cond do
options[:if_func] -> options[:if_func].()
options[:unless_func] -> !options[:unless_func].()
true -> true
end
if perform do
fail(conn)
else
conn
end
end
def fail(conn) do
conn conn
|> render_error(:forbidden, "Invalid credentials.") |> render_error(:forbidden, "Invalid credentials.")
|> halt |> halt()
end end
end end

View File

@ -10,14 +10,20 @@ def init(options) do
end end
def call(conn, _opts) do def call(conn, _opts) do
if Pleroma.Config.get([:instance, :federating]) do if federating?() do
conn conn
else else
conn fail(conn)
|> put_status(404)
|> Phoenix.Controller.put_view(Pleroma.Web.ErrorView)
|> Phoenix.Controller.render("404.json")
|> halt()
end end
end end
def federating?, do: Pleroma.Config.get([:instance, :federating])
defp fail(conn) do
conn
|> put_status(404)
|> Phoenix.Controller.put_view(Pleroma.Web.ErrorView)
|> Phoenix.Controller.render("404.json")
|> halt()
end
end end

View File

@ -78,7 +78,7 @@ def init(plug_opts) do
end end
def call(conn, plug_opts) do def call(conn, plug_opts) do
if disabled?() do if disabled?(conn) do
handle_disabled(conn) handle_disabled(conn)
else else
action_settings = action_settings(plug_opts) action_settings = action_settings(plug_opts)
@ -87,9 +87,9 @@ def call(conn, plug_opts) do
end end
defp handle_disabled(conn) do defp handle_disabled(conn) do
if Config.get(:env) == :prod do Logger.warn(
Logger.warn("Rate limiter is disabled for localhost/socket") "Rate limiter disabled due to forwarded IP not being found. Please ensure your reverse proxy is providing the X-Forwarded-For header or disable the RemoteIP plug/rate limiter."
end )
conn conn
end end
@ -109,16 +109,21 @@ defp handle(conn, action_settings) do
end end
end end
def disabled? do def disabled?(conn) do
localhost_or_socket = localhost_or_socket =
Config.get([Pleroma.Web.Endpoint, :http, :ip]) case Config.get([Pleroma.Web.Endpoint, :http, :ip]) do
|> Tuple.to_list() {127, 0, 0, 1} -> true
|> Enum.join(".") {0, 0, 0, 0, 0, 0, 0, 1} -> true
|> String.match?(~r/^local|^127.0.0.1/) {:local, _} -> true
_ -> false
end
remote_ip_disabled = not Config.get([Pleroma.Plugs.RemoteIp, :enabled]) remote_ip_not_found =
if Map.has_key?(conn.assigns, :remote_ip_found),
do: !conn.assigns.remote_ip_found,
else: false
localhost_or_socket and remote_ip_disabled localhost_or_socket and remote_ip_not_found
end end
@inspect_bucket_not_found {:error, :not_found} @inspect_bucket_not_found {:error, :not_found}

View File

@ -7,6 +7,8 @@ defmodule Pleroma.Plugs.RemoteIp do
This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration. This is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.
""" """
import Plug.Conn
@behaviour Plug @behaviour Plug
@headers ~w[ @headers ~w[
@ -26,11 +28,12 @@ defmodule Pleroma.Plugs.RemoteIp do
def init(_), do: nil def init(_), do: nil
def call(conn, _) do def call(%{remote_ip: original_remote_ip} = conn, _) do
config = Pleroma.Config.get(__MODULE__, []) config = Pleroma.Config.get(__MODULE__, [])
if Keyword.get(config, :enabled, false) do if Keyword.get(config, :enabled, false) do
RemoteIp.call(conn, remote_ip_opts(config)) %{remote_ip: new_remote_ip} = conn = RemoteIp.call(conn, remote_ip_opts(config))
assign(conn, :remote_ip_found, original_remote_ip != new_remote_ip)
else else
conn conn
end end

View File

@ -21,6 +21,9 @@ def call(conn, _) do
defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false) defp enabled?, do: Pleroma.Config.get([:static_fe, :enabled], false)
defp accepts_html?(conn) do defp accepts_html?(conn) do
conn |> get_req_header("accept") |> List.first() |> String.contains?("text/html") case get_req_header(conn, "accept") do
[accept | _] -> String.contains?(accept, "text/html")
_ -> false
end
end end
end end

View File

@ -14,9 +14,14 @@ defmodule Pleroma.Plugs.UploadedMedia do
# no slashes # no slashes
@path "media" @path "media"
@default_cache_control_header "public, max-age=1209600"
def init(_opts) do def init(_opts) do
static_plug_opts = static_plug_opts =
[] [
headers: %{"cache-control" => @default_cache_control_header},
cache_control_for_etags: @default_cache_control_header
]
|> Keyword.put(:from, "__unconfigured_media_plug") |> Keyword.put(:from, "__unconfigured_media_plug")
|> Keyword.put(:at, "/__unconfigured_media_plug") |> Keyword.put(:at, "/__unconfigured_media_plug")
|> Plug.Static.init() |> Plug.Static.init()

View File

@ -5,7 +5,7 @@
defmodule Pleroma.ReverseProxy do defmodule Pleroma.ReverseProxy do
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++ @keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
~w(if-unmodified-since if-none-match if-range range) ~w(if-unmodified-since if-none-match if-range range)
@resp_cache_headers ~w(etag date last-modified cache-control) @resp_cache_headers ~w(etag date last-modified)
@keep_resp_headers @resp_cache_headers ++ @keep_resp_headers @resp_cache_headers ++
~w(content-type content-disposition content-encoding content-range) ++ ~w(content-type content-disposition content-encoding content-range) ++
~w(accept-ranges vary) ~w(accept-ranges vary)
@ -32,9 +32,6 @@ defmodule Pleroma.ReverseProxy do
* request: `#{inspect(@keep_req_headers)}` * request: `#{inspect(@keep_req_headers)}`
* response: `#{inspect(@keep_resp_headers)}` * response: `#{inspect(@keep_resp_headers)}`
If no caching headers (`#{inspect(@resp_cache_headers)}`) are returned by upstream, `cache-control` will be
set to `#{inspect(@default_cache_control_header)}`.
Options: Options:
* `redirect_on_failure` (default `false`). Redirects the client to the real remote URL if there's any HTTP * `redirect_on_failure` (default `false`). Redirects the client to the real remote URL if there's any HTTP
@ -291,16 +288,17 @@ defp build_resp_headers(headers, opts) do
defp build_resp_cache_headers(headers, _opts) do defp build_resp_cache_headers(headers, _opts) do
has_cache? = Enum.any?(headers, fn {k, _} -> k in @resp_cache_headers end) has_cache? = Enum.any?(headers, fn {k, _} -> k in @resp_cache_headers end)
has_cache_control? = List.keymember?(headers, "cache-control", 0)
cond do cond do
has_cache? && has_cache_control? ->
headers
has_cache? -> has_cache? ->
# There's caching header present but no cache-control -- we need to explicitely override it # There's caching header present but no cache-control -- we need to set our own
# to public as Plug defaults to "max-age=0, private, must-revalidate" # as Plug defaults to "max-age=0, private, must-revalidate"
List.keystore(headers, "cache-control", 0, {"cache-control", "public"}) List.keystore(
headers,
"cache-control",
0,
{"cache-control", @default_cache_control_header}
)
true -> true ->
List.keystore( List.keystore(

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.HTML
alias Pleroma.Keys alias Pleroma.Keys
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
@ -839,10 +840,6 @@ def get_or_fetch_by_nickname(nickname) do
_e -> _e ->
with [_nick, _domain] <- String.split(nickname, "@"), with [_nick, _domain] <- String.split(nickname, "@"),
{:ok, user} <- fetch_by_nickname(nickname) do {:ok, user} <- fetch_by_nickname(nickname) do
if Pleroma.Config.get([:fetch_initial_posts, :enabled]) do
fetch_initial_posts(user)
end
{:ok, user} {:ok, user}
else else
_e -> {:error, "not found " <> nickname} _e -> {:error, "not found " <> nickname}
@ -850,11 +847,6 @@ def get_or_fetch_by_nickname(nickname) do
end end
end end
@doc "Fetch some posts when the user has just been federated with"
def fetch_initial_posts(user) do
BackgroundWorker.enqueue("fetch_initial_posts", %{"user_id" => user.id})
end
@spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t() @spec get_followers_query(User.t(), pos_integer() | nil) :: Ecto.Query.t()
def get_followers_query(%User{} = user, nil) do def get_followers_query(%User{} = user, nil) do
User.Query.build(%{followers: user, deactivated: false}) User.Query.build(%{followers: user, deactivated: false})
@ -1320,16 +1312,6 @@ def perform(:delete, %User{} = user) do
Repo.delete(user) Repo.delete(user)
end end
def perform(:fetch_initial_posts, %User{} = user) do
pages = Pleroma.Config.get!([:fetch_initial_posts, :pages])
# Insert all the posts in reverse order, so they're in the right order on the timeline
user.source_data["outbox"]
|> Utils.fetch_ordered_collection(pages)
|> Enum.reverse()
|> Enum.each(&Pleroma.Web.Federator.incoming_ap_doc/1)
end
def perform(:deactivate_async, user, status), do: deactivate(user, status) def perform(:deactivate_async, user, status), do: deactivate(user, status)
@spec perform(atom(), User.t(), list()) :: list() | {:error, any()} @spec perform(atom(), User.t(), list()) :: list() | {:error, any()}
@ -1458,18 +1440,7 @@ def get_or_fetch_by_ap_id(ap_id) do
if !is_nil(user) and !needs_update?(user) do if !is_nil(user) and !needs_update?(user) do
{:ok, user} {:ok, user}
else else
# Whether to fetch initial posts for the user (if it's a new user & the fetching is enabled) fetch_by_ap_id(ap_id)
should_fetch_initial = is_nil(user) and Pleroma.Config.get([:fetch_initial_posts, :enabled])
resp = fetch_by_ap_id(ap_id)
if should_fetch_initial do
with {:ok, %User{} = user} <- resp do
fetch_initial_posts(user)
end
end
resp
end end
end end
@ -2062,4 +2033,27 @@ def set_invisible(user, invisible) do
|> validate_required([:invisible]) |> validate_required([:invisible])
|> update_and_set_cache() |> update_and_set_cache()
end end
def sanitize_html(%User{} = user) do
sanitize_html(user, nil)
end
# User data that mastodon isn't filtering (treated as plaintext):
# - field name
# - display name
def sanitize_html(%User{} = user, filter) do
fields =
user
|> User.fields()
|> Enum.map(fn %{"name" => name, "value" => value} ->
%{
"name" => name,
"value" => HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
}
end)
user
|> Map.put(:bio, HTML.filter_tags(user.bio, filter))
|> Map.put(:fields, fields)
end
end end

View File

@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Delivery alias Pleroma.Delivery
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Object.Fetcher alias Pleroma.Object.Fetcher
alias Pleroma.Plugs.EnsureAuthenticatedPlug
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.InternalFetchActor
@ -18,23 +19,37 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.ActivityPub.UserView
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.FederatingPlug
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
require Logger require Logger
action_fallback(:errors) action_fallback(:errors)
@federating_only_actions [:internal_fetch, :relay, :relay_following, :relay_followers]
plug(FederatingPlug when action in @federating_only_actions)
plug(
EnsureAuthenticatedPlug,
[unless_func: &FederatingPlug.federating?/0] when action not in @federating_only_actions
)
plug(
EnsureAuthenticatedPlug
when action in [:read_inbox, :update_outbox, :whoami, :upload_media, :following, :followers]
)
plug( plug(
Pleroma.Plugs.Cache, Pleroma.Plugs.Cache,
[query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2] [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
when action in [:activity, :object] when action in [:activity, :object]
) )
plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
plug(:set_requester_reachable when action in [:inbox]) plug(:set_requester_reachable when action in [:inbox])
plug(:relay_active? when action in [:relay]) plug(:relay_active? when action in [:relay])
def relay_active?(conn, _) do defp relay_active?(conn, _) do
if Pleroma.Config.get([:instance, :allow_relay]) do if Pleroma.Config.get([:instance, :allow_relay]) do
conn conn
else else
@ -127,11 +142,13 @@ defp set_cache_ttl_for(conn, entity) do
end end
# GET /relay/following # GET /relay/following
def following(%{assigns: %{relay: true}} = conn, _params) do def relay_following(conn, _params) do
conn with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
|> put_resp_content_type("application/activity+json") conn
|> put_view(UserView) |> put_resp_content_type("application/activity+json")
|> render("following.json", %{user: Relay.get_actor()}) |> put_view(UserView)
|> render("following.json", %{user: Relay.get_actor()})
end
end end
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
@ -164,11 +181,13 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
end end
# GET /relay/followers # GET /relay/followers
def followers(%{assigns: %{relay: true}} = conn, _params) do def relay_followers(conn, _params) do
conn with %{halted: false} = conn <- FederatingPlug.call(conn, []) do
|> put_resp_content_type("application/activity+json") conn
|> put_view(UserView) |> put_resp_content_type("application/activity+json")
|> render("followers.json", %{user: Relay.get_actor()}) |> put_view(UserView)
|> render("followers.json", %{user: Relay.get_actor()})
end
end end
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
@ -200,13 +219,16 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
end end
end end
def outbox(conn, %{"nickname" => nickname, "page" => page?} = params) def outbox(
%{assigns: %{user: for_user}} = conn,
%{"nickname" => nickname, "page" => page?} = params
)
when page? in [true, "true"] do when page? in [true, "true"] do
with %User{} = user <- User.get_cached_by_nickname(nickname), with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- User.ensure_keys_present(user) do {:ok, user} <- User.ensure_keys_present(user) do
activities = activities =
if params["max_id"] do if params["max_id"] do
ActivityPub.fetch_user_activities(user, nil, %{ ActivityPub.fetch_user_activities(user, for_user, %{
"max_id" => params["max_id"], "max_id" => params["max_id"],
# This is a hack because postgres generates inefficient queries when filtering by # This is a hack because postgres generates inefficient queries when filtering by
# 'Answer', poll votes will be hidden by the visibility filter in this case anyway # 'Answer', poll votes will be hidden by the visibility filter in this case anyway
@ -214,7 +236,7 @@ def outbox(conn, %{"nickname" => nickname, "page" => page?} = params)
"limit" => 10 "limit" => 10
}) })
else else
ActivityPub.fetch_user_activities(user, nil, %{ ActivityPub.fetch_user_activities(user, for_user, %{
"limit" => 10, "limit" => 10,
"include_poll_votes" => true "include_poll_votes" => true
}) })
@ -255,8 +277,16 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
json(conn, "ok") json(conn, "ok")
end end
# only accept relayed Creates # POST /relay/inbox -or- POST /internal/fetch/inbox
def inbox(conn, %{"type" => "Create"} = params) do def inbox(conn, params) do
if params["type"] == "Create" && FederatingPlug.federating?() do
post_inbox_relayed_create(conn, params)
else
post_inbox_fallback(conn, params)
end
end
defp post_inbox_relayed_create(conn, params) do
Logger.debug( Logger.debug(
"Signature missing or not from author, relayed Create message, fetching object from source" "Signature missing or not from author, relayed Create message, fetching object from source"
) )
@ -266,10 +296,11 @@ def inbox(conn, %{"type" => "Create"} = params) do
json(conn, "ok") json(conn, "ok")
end end
def inbox(conn, params) do defp post_inbox_fallback(conn, params) do
headers = Enum.into(conn.req_headers, %{}) headers = Enum.into(conn.req_headers, %{})
if String.contains?(headers["signature"], params["actor"]) do if headers["signature"] && params["actor"] &&
String.contains?(headers["signature"], params["actor"]) do
Logger.debug( Logger.debug(
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!" "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
) )
@ -277,7 +308,9 @@ def inbox(conn, params) do
Logger.debug(inspect(conn.req_headers)) Logger.debug(inspect(conn.req_headers))
end end
json(conn, dgettext("errors", "error")) conn
|> put_status(:bad_request)
|> json(dgettext("errors", "error"))
end end
defp represent_service_actor(%User{} = user, conn) do defp represent_service_actor(%User{} = user, conn) do
@ -311,10 +344,8 @@ def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
|> render("user.json", %{user: user}) |> render("user.json", %{user: user})
end end
def whoami(_conn, _params), do: {:error, :not_found}
def read_inbox( def read_inbox(
%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
%{"nickname" => nickname, "page" => page?} = params %{"nickname" => nickname, "page" => page?} = params
) )
when page? in [true, "true"] do when page? in [true, "true"] do
@ -337,7 +368,7 @@ def read_inbox(
}) })
end end
def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{ def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{
"nickname" => nickname "nickname" => nickname
}) do }) do
with {:ok, user} <- User.ensure_keys_present(user) do with {:ok, user} <- User.ensure_keys_present(user) do
@ -348,15 +379,7 @@ def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
end end
end end
def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{
err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
conn
|> put_status(:forbidden)
|> json(err)
end
def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
"nickname" => nickname "nickname" => nickname
}) do }) do
err = err =
@ -370,7 +393,7 @@ def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
|> json(err) |> json(err)
end end
def handle_user_activity(user, %{"type" => "Create"} = params) do defp handle_user_activity(%User{} = user, %{"type" => "Create"} = params) do
object = object =
params["object"] params["object"]
|> Map.merge(Map.take(params, ["to", "cc"])) |> Map.merge(Map.take(params, ["to", "cc"]))
@ -386,7 +409,7 @@ def handle_user_activity(user, %{"type" => "Create"} = params) do
}) })
end end
def handle_user_activity(user, %{"type" => "Delete"} = params) do defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
with %Object{} = object <- Object.normalize(params["object"]), with %Object{} = object <- Object.normalize(params["object"]),
true <- user.is_moderator || user.ap_id == object.data["actor"], true <- user.is_moderator || user.ap_id == object.data["actor"],
{:ok, delete} <- ActivityPub.delete(object) do {:ok, delete} <- ActivityPub.delete(object) do
@ -396,7 +419,7 @@ def handle_user_activity(user, %{"type" => "Delete"} = params) do
end end
end end
def handle_user_activity(user, %{"type" => "Like"} = params) do defp handle_user_activity(%User{} = user, %{"type" => "Like"} = params) do
with %Object{} = object <- Object.normalize(params["object"]), with %Object{} = object <- Object.normalize(params["object"]),
{:ok, activity, _object} <- ActivityPub.like(user, object) do {:ok, activity, _object} <- ActivityPub.like(user, object) do
{:ok, activity} {:ok, activity}
@ -405,7 +428,7 @@ def handle_user_activity(user, %{"type" => "Like"} = params) do
end end
end end
def handle_user_activity(_, _) do defp handle_user_activity(_, _) do
{:error, dgettext("errors", "Unhandled activity type")} {:error, dgettext("errors", "Unhandled activity type")}
end end
@ -434,7 +457,7 @@ def update_outbox(
end end
end end
def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do def update_outbox(%{assigns: %{user: %User{} = user}} = conn, %{"nickname" => nickname}) do
err = err =
dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}", dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
nickname: nickname, nickname: nickname,
@ -446,13 +469,13 @@ def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} =
|> json(err) |> json(err)
end end
def errors(conn, {:error, :not_found}) do defp errors(conn, {:error, :not_found}) do
conn conn
|> put_status(:not_found) |> put_status(:not_found)
|> json(dgettext("errors", "Not found")) |> json(dgettext("errors", "Not found"))
end end
def errors(conn, _e) do defp errors(conn, _e) do
conn conn
|> put_status(:internal_server_error) |> put_status(:internal_server_error)
|> json(dgettext("errors", "error")) |> json(dgettext("errors", "error"))
@ -492,7 +515,7 @@ defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
- HTTP Code: 201 Created - HTTP Code: 201 Created
- HTTP Body: ActivityPub object to be inserted into another's `attachment` field - HTTP Body: ActivityPub object to be inserted into another's `attachment` field
""" """
def upload_media(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do
with {:ok, object} <- with {:ok, object} <-
ActivityPub.upload( ActivityPub.upload(
file, file,

View File

@ -784,45 +784,6 @@ defp build_flag_object(act) when is_map(act) or is_binary(act) do
defp build_flag_object(_), do: [] defp build_flag_object(_), do: []
@doc """
Fetches the OrderedCollection/OrderedCollectionPage from `from`, limiting the amount of pages fetched after
the first one to `pages_left` pages.
If the amount of pages is higher than the collection has, it returns whatever was there.
"""
def fetch_ordered_collection(from, pages_left, acc \\ []) do
with {:ok, response} <- Tesla.get(from),
{:ok, collection} <- Jason.decode(response.body) do
case collection["type"] do
"OrderedCollection" ->
# If we've encountered the OrderedCollection and not the page,
# just call the same function on the page address
fetch_ordered_collection(collection["first"], pages_left)
"OrderedCollectionPage" ->
if pages_left > 0 do
# There are still more pages
if Map.has_key?(collection, "next") do
# There are still more pages, go deeper saving what we have into the accumulator
fetch_ordered_collection(
collection["next"],
pages_left - 1,
acc ++ collection["orderedItems"]
)
else
# No more pages left, just return whatever we already have
acc ++ collection["orderedItems"]
end
else
# Got the amount of pages needed, add them all to the accumulator
acc ++ collection["orderedItems"]
end
_ ->
{:error, "Not an OrderedCollection or OrderedCollectionPage"}
end
end
end
#### Report-related helpers #### Report-related helpers
def get_reports(params, page, page_size) do def get_reports(params, page, page_size) do
params = params =

View File

@ -73,6 +73,7 @@ def render("user.json", %{user: user}) do
{:ok, _, public_key} = Keys.keys_from_pem(user.keys) {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key]) public_key = :public_key.pem_encode([public_key])
user = User.sanitize_html(user)
endpoints = render("endpoints.json", %{user: user}) endpoints = render("endpoints.json", %{user: user})
@ -81,12 +82,6 @@ def render("user.json", %{user: user}) do
fields = fields =
user user
|> User.fields() |> User.fields()
|> Enum.map(fn %{"name" => name, "value" => value} ->
%{
"name" => Pleroma.HTML.strip_tags(name),
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
}
end)
|> Enum.map(&Map.put(&1, "type", "PropertyValue")) |> Enum.map(&Map.put(&1, "type", "PropertyValue"))
%{ %{

View File

@ -745,14 +745,14 @@ def report_notes_delete(%{assigns: %{user: user}} = conn, %{
end end
end end
def list_statuses(%{assigns: %{user: admin}} = conn, params) do def list_statuses(%{assigns: %{user: _admin}} = conn, params) do
godmode = params["godmode"] == "true" || params["godmode"] == true godmode = params["godmode"] == "true" || params["godmode"] == true
local_only = params["local_only"] == "true" || params["local_only"] == true local_only = params["local_only"] == "true" || params["local_only"] == true
with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
{page, page_size} = page_params(params) {page, page_size} = page_params(params)
activities = activities =
ActivityPub.fetch_statuses(admin, %{ ActivityPub.fetch_statuses(nil, %{
"godmode" => godmode, "godmode" => godmode,
"local_only" => local_only, "local_only" => local_only,
"limit" => page_size, "limit" => page_size,

View File

@ -5,7 +5,6 @@
defmodule Pleroma.Web.AdminAPI.AccountView do defmodule Pleroma.Web.AdminAPI.AccountView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.HTML
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
@ -26,7 +25,8 @@ def render("index.json", %{users: users}) do
def render("show.json", %{user: user}) do def render("show.json", %{user: user}) do
avatar = User.avatar_url(user) |> MediaProxy.url() avatar = User.avatar_url(user) |> MediaProxy.url()
display_name = HTML.strip_tags(user.name || user.nickname) display_name = Pleroma.HTML.strip_tags(user.name || user.nickname)
user = User.sanitize_html(user, FastSanitize.Sanitizer.StripTags)
%{ %{
"id" => user.id, "id" => user.id,

View File

@ -331,7 +331,7 @@ def format_input(text, "text/html", options) do
def format_input(text, "text/markdown", options) do def format_input(text, "text/markdown", options) do
text text
|> Formatter.mentions_escape(options) |> Formatter.mentions_escape(options)
|> Earmark.as_html!() |> Earmark.as_html!(%Earmark.Options{renderer: Pleroma.EarmarkRenderer})
|> Formatter.linkify(options) |> Formatter.linkify(options)
|> Formatter.html_escape("text/html") |> Formatter.html_escape("text/html")
end end

View File

@ -12,7 +12,7 @@ defmodule Pleroma.Web.Endpoint do
plug(Pleroma.Plugs.HTTPSecurityPlug) plug(Pleroma.Plugs.HTTPSecurityPlug)
plug(Pleroma.Plugs.UploadedMedia) plug(Pleroma.Plugs.UploadedMedia)
@static_cache_control "public max-age=86400 must-revalidate" @static_cache_control "public, no-cache"
# InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files
# If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well

View File

@ -25,7 +25,12 @@ def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname
def feed_redirect(%{assigns: %{format: format}} = conn, _params) def feed_redirect(%{assigns: %{format: format}} = conn, _params)
when format in ["json", "activity+json"] do when format in ["json", "activity+json"] do
ActivityPubController.call(conn, :user) with %{halted: false} = conn <-
Pleroma.Plugs.EnsureAuthenticatedPlug.call(conn,
unless_func: &Pleroma.Web.FederatingPlug.federating?/0
) do
ActivityPubController.call(conn, :user)
end
end end
def feed_redirect(conn, %{"nickname" => nickname}) do def feed_redirect(conn, %{"nickname" => nickname}) do

View File

@ -86,6 +86,6 @@ defp local_mastodon_root_path(conn) do
@spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
defp get_or_make_app do defp get_or_make_app do
%{client_name: @local_mastodon_name, redirect_uris: "."} %{client_name: @local_mastodon_name, redirect_uris: "."}
|> App.get_or_make(["read", "write", "follow", "push"]) |> App.get_or_make(["read", "write", "follow", "push", "admin"])
end end
end end

View File

@ -5,7 +5,6 @@
defmodule Pleroma.Web.MastodonAPI.AccountView do defmodule Pleroma.Web.MastodonAPI.AccountView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.HTML
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
@ -67,6 +66,7 @@ def render("relationships.json", %{user: user, targets: targets}) do
end end
defp do_render("show.json", %{user: user} = opts) do defp do_render("show.json", %{user: user} = opts) do
user = User.sanitize_html(user, User.html_filter_policy(opts[:for]))
display_name = user.name || user.nickname display_name = user.name || user.nickname
image = User.avatar_url(user) |> MediaProxy.url() image = User.avatar_url(user) |> MediaProxy.url()
@ -100,17 +100,6 @@ defp do_render("show.json", %{user: user} = opts) do
} }
end) end)
fields =
user
|> User.fields()
|> Enum.map(fn %{"name" => name, "value" => value} ->
%{
"name" => name,
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
}
end)
bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for]))
relationship = render("relationship.json", %{user: opts[:for], target: user}) relationship = render("relationship.json", %{user: opts[:for], target: user})
%{ %{
@ -123,17 +112,17 @@ defp do_render("show.json", %{user: user} = opts) do
followers_count: followers_count, followers_count: followers_count,
following_count: following_count, following_count: following_count,
statuses_count: user.note_count, statuses_count: user.note_count,
note: bio || "", note: user.bio || "",
url: User.profile_url(user), url: User.profile_url(user),
avatar: image, avatar: image,
avatar_static: image, avatar_static: image,
header: header, header: header,
header_static: header, header_static: header,
emojis: emojis, emojis: emojis,
fields: fields, fields: user.fields,
bot: bot, bot: bot,
source: %{ source: %{
note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")), note: Pleroma.HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
sensitive: false, sensitive: false,
fields: user.raw_fields, fields: user.raw_fields,
pleroma: %{ pleroma: %{

View File

@ -16,6 +16,10 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.Metadata.PlayerView alias Pleroma.Web.Metadata.PlayerView
alias Pleroma.Web.Router alias Pleroma.Web.Router
plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
unless_func: &Pleroma.Web.FederatingPlug.federating?/0
)
plug( plug(
RateLimiter, RateLimiter,
[name: :ap_routes, params: ["uuid"]] when action in [:object, :activity] [name: :ap_routes, params: ["uuid"]] when action in [:object, :activity]
@ -135,13 +139,13 @@ def notice_player(conn, %{"id" => id}) do
end end
end end
def errors(conn, {:error, :not_found}) do defp errors(conn, {:error, :not_found}) do
render_error(conn, :not_found, "Not found") render_error(conn, :not_found, "Not found")
end end
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found}) defp errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
def errors(conn, _) do defp errors(conn, _) do
render_error(conn, :internal_server_error, "Something went wrong") render_error(conn, :internal_server_error, "Something went wrong")
end end
end end

View File

@ -101,6 +101,11 @@ def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id})
conn conn
|> put_view(ConversationView) |> put_view(ConversationView)
|> render("participation.json", %{participation: participation, for: user}) |> render("participation.json", %{participation: participation, for: user})
else
_error ->
conn
|> put_status(404)
|> json(%{"error" => "Unknown conversation id"})
end end
end end
@ -108,9 +113,9 @@ def conversation_statuses(
%{assigns: %{user: user}} = conn, %{assigns: %{user: user}} = conn,
%{"id" => participation_id} = params %{"id" => participation_id} = params
) do ) do
participation = Participation.get(participation_id, preload: [:conversation]) with %Participation{} = participation <-
Participation.get(participation_id, preload: [:conversation]),
if user.id == participation.user_id do true <- user.id == participation.user_id do
params = params =
params params
|> Map.put("blocking_user", user) |> Map.put("blocking_user", user)
@ -126,6 +131,11 @@ def conversation_statuses(
|> 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})
else
_error ->
conn
|> put_status(404)
|> json(%{"error" => "Unknown conversation id"})
end end
end end
@ -133,15 +143,22 @@ def update_conversation(
%{assigns: %{user: user}} = conn, %{assigns: %{user: user}} = conn,
%{"id" => participation_id, "recipients" => recipients} %{"id" => participation_id, "recipients" => recipients}
) do ) do
participation = with %Participation{} = participation <- Participation.get(participation_id),
participation_id true <- user.id == participation.user_id,
|> Participation.get()
with true <- user.id == participation.user_id,
{:ok, participation} <- Participation.set_recipients(participation, recipients) do {:ok, participation} <- Participation.set_recipients(participation, recipients) do
conn conn
|> put_view(ConversationView) |> put_view(ConversationView)
|> render("participation.json", %{participation: participation, for: user}) |> render("participation.json", %{participation: participation, for: user})
else
{:error, message} ->
conn
|> put_status(:bad_request)
|> json(%{"error" => message})
_error ->
conn
|> put_status(404)
|> json(%{"error" => "Unknown conversation id"})
end end
end end

View File

@ -541,6 +541,7 @@ defmodule Pleroma.Web.Router do
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe) get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
end end
# Server to Server (S2S) AP interactions
pipeline :activitypub do pipeline :activitypub do
plug(:accepts, ["activity+json", "json"]) plug(:accepts, ["activity+json", "json"])
plug(Pleroma.Web.Plugs.HTTPSignaturePlug) plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
@ -554,6 +555,7 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname/outbox", ActivityPubController, :outbox) get("/users/:nickname/outbox", ActivityPubController, :outbox)
end end
# Client to Server (C2S) AP interactions
pipeline :activitypub_client do pipeline :activitypub_client do
plug(:accepts, ["activity+json", "json"]) plug(:accepts, ["activity+json", "json"])
plug(:fetch_session) plug(:fetch_session)
@ -597,8 +599,8 @@ defmodule Pleroma.Web.Router do
post("/inbox", ActivityPubController, :inbox) post("/inbox", ActivityPubController, :inbox)
end end
get("/following", ActivityPubController, :following, assigns: %{relay: true}) get("/following", ActivityPubController, :relay_following)
get("/followers", ActivityPubController, :followers, assigns: %{relay: true}) get("/followers", ActivityPubController, :relay_followers)
end end
scope "/internal/fetch", Pleroma.Web.ActivityPub do scope "/internal/fetch", Pleroma.Web.ActivityPub do

View File

@ -17,6 +17,10 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
plug(:put_view, Pleroma.Web.StaticFE.StaticFEView) plug(:put_view, Pleroma.Web.StaticFE.StaticFEView)
plug(:assign_id) plug(:assign_id)
plug(Pleroma.Plugs.EnsureAuthenticatedPlug,
unless_func: &Pleroma.Web.FederatingPlug.federating?/0
)
@page_keys ["max_id", "min_id", "limit", "since_id", "order"] @page_keys ["max_id", "min_id", "limit", "since_id", "order"]
defp get_title(%Object{data: %{"name" => name}}) when is_binary(name), defp get_title(%Object{data: %{"name" => name}}) when is_binary(name),
@ -33,7 +37,7 @@ defp not_found(conn, message) do
|> render("error.html", %{message: message, meta: ""}) |> render("error.html", %{message: message, meta: ""})
end end
def get_counts(%Activity{} = activity) do defp get_counts(%Activity{} = activity) do
%Object{data: data} = Object.normalize(activity) %Object{data: data} = Object.normalize(activity)
%{ %{
@ -43,9 +47,9 @@ def get_counts(%Activity{} = activity) do
} }
end end
def represent(%Activity{} = activity), do: represent(activity, false) defp represent(%Activity{} = activity), do: represent(activity, false)
def represent(%Activity{object: %Object{data: data}} = activity, selected) do defp represent(%Activity{object: %Object{data: data}} = activity, selected) do
{:ok, user} = User.get_or_fetch(activity.object.data["actor"]) {:ok, user} = User.get_or_fetch(activity.object.data["actor"])
link = link =
@ -54,10 +58,17 @@ def represent(%Activity{object: %Object{data: data}} = activity, selected) do
_ -> data["url"] || data["external_url"] || data["id"] _ -> data["url"] || data["external_url"] || data["id"]
end end
content =
if data["content"] do
Pleroma.HTML.filter_tags(data["content"])
else
nil
end
%{ %{
user: user, user: User.sanitize_html(user),
title: get_title(activity.object), title: get_title(activity.object),
content: data["content"] || nil, content: content,
attachment: data["attachment"], attachment: data["attachment"],
link: link, link: link,
published: data["published"], published: data["published"],
@ -109,7 +120,7 @@ def show(%{assigns: %{username_or_id: username_or_id}} = conn, params) do
next_page_id = List.last(timeline) && List.last(timeline).id next_page_id = List.last(timeline) && List.last(timeline).id
render(conn, "profile.html", %{ render(conn, "profile.html", %{
user: user, user: User.sanitize_html(user),
timeline: timeline, timeline: timeline,
prev_page_id: prev_page_id, prev_page_id: prev_page_id,
next_page_id: next_page_id, next_page_id: next_page_id,
@ -147,17 +158,17 @@ def show(%{assigns: %{activity_id: _}} = conn, _params) do
end end
end end
def assign_id(%{path_info: ["notice", notice_id]} = conn, _opts), defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id) do: assign(conn, :notice_id, notice_id)
def assign_id(%{path_info: ["users", user_id]} = conn, _opts), defp assign_id(%{path_info: ["users", user_id]} = conn, _opts),
do: assign(conn, :username_or_id, user_id) do: assign(conn, :username_or_id, user_id)
def assign_id(%{path_info: ["objects", object_id]} = conn, _opts), defp assign_id(%{path_info: ["objects", object_id]} = conn, _opts),
do: assign(conn, :object_id, object_id) do: assign(conn, :object_id, object_id)
def assign_id(%{path_info: ["activities", activity_id]} = conn, _opts), defp assign_id(%{path_info: ["activities", activity_id]} = conn, _opts),
do: assign(conn, :activity_id, activity_id) do: assign(conn, :activity_id, activity_id)
def assign_id(conn, _opts), do: conn defp assign_id(conn, _opts), do: conn
end end

View File

@ -16,6 +16,8 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do
@status_types ["Article", "Event", "Note", "Video", "Page", "Question"] @status_types ["Article", "Event", "Note", "Video", "Page", "Question"]
plug(Pleroma.Web.FederatingPlug)
# Note: follower can submit the form (with password auth) not being signed in (having no token) # Note: follower can submit the form (with password auth) not being signed in (having no token)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,

View File

@ -17,6 +17,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.WebFinger alias Pleroma.Web.WebFinger
plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
plug( plug(
OAuthScopesPlug, OAuthScopesPlug,
%{scopes: ["follow", "write:follows"]} %{scopes: ["follow", "write:follows"]}

View File

@ -10,10 +10,6 @@ defmodule Pleroma.Workers.BackgroundWorker do
use Pleroma.Workers.WorkerHelper, queue: "background" use Pleroma.Workers.WorkerHelper, queue: "background"
@impl Oban.Worker @impl Oban.Worker
def perform(%{"op" => "fetch_initial_posts", "user_id" => user_id}, _job) do
user = User.get_cached_by_id(user_id)
User.perform(:fetch_initial_posts, user)
end
def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}, _job) do def perform(%{"op" => "deactivate_user", "user_id" => user_id, "status" => status}, _job) do
user = User.get_cached_by_id(user_id) user = User.get_cached_by_id(user_id)

View File

@ -134,7 +134,7 @@ defp deps do
{:ex_aws_s3, "~> 2.0"}, {:ex_aws_s3, "~> 2.0"},
{:sweet_xml, "~> 0.6.6"}, {:sweet_xml, "~> 0.6.6"},
{:earmark, "~> 1.3"}, {:earmark, "~> 1.3"},
{:bbcode, "~> 0.1.1"}, {:bbcode_pleroma, "~> 0.2.0"},
{:ex_machina, "~> 2.3", only: :test}, {:ex_machina, "~> 2.3", only: :test},
{:credo, "~> 1.1.0", only: [:dev, :test], runtime: false}, {:credo, "~> 1.1.0", only: [:dev, :test], runtime: false},
{:mock, "~> 0.3.3", only: :test}, {:mock, "~> 0.3.3", only: :test},

View File

@ -3,7 +3,8 @@
"auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]}, "auto_linker": {:git, "https://git.pleroma.social/pleroma/auto_linker.git", "95e8188490e97505c56636c1379ffdf036c1fdde", [ref: "95e8188490e97505c56636c1379ffdf036c1fdde"]},
"base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"}, "base62": {:hex, :base62, "1.2.1", "4866763e08555a7b3917064e9eef9194c41667276c51b59de2bc42c6ea65f806", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "3b29948de2013d3f93aa898c884a9dff847e7aec75d9d6d8c1dc4c61c2716c42"},
"base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"}, "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], [], "hexpm"},
"bbcode": {:hex, :bbcode, "0.1.1", "0023e2c7814119b2e620b7add67182e3f6019f92bfec9a22da7e99821aceba70", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5a981b98ac7d366a9b6bf40eac389aaf4d6e623c631e6b6f8a6b571efaafd338"}, "bbcode": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/bbcode.git", "f2d267675e9a7e1ad1ea9beb4cc23382762b66c2", [ref: "v0.2.0"]},
"bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"},
"benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"}, "benchee": {:hex, :benchee, "1.0.1", "66b211f9bfd84bd97e6d1beaddf8fc2312aaabe192f776e8931cb0c16f53a521", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm", "3ad58ae787e9c7c94dd7ceda3b587ec2c64604563e049b2a0e8baafae832addb"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"}, "cachex": {:hex, :cachex, "3.2.0", "a596476c781b0646e6cb5cd9751af2e2974c3e0d5498a8cab71807618b74fe2f", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "aef93694067a43697ae0531727e097754a9e992a1e7946296f5969d6dd9ac986"},

View File

@ -0,0 +1,10 @@
defmodule Pleroma.Repo.Migrations.ConfigRemoveFetchInitialPosts do
use Ecto.Migration
def change do
execute(
"delete from config where config.key = ':fetch_initial_posts' and config.group = ':pleroma';",
""
)
end
end

View File

@ -0,0 +1,10 @@
defmodule Pleroma.Repo.Migrations.DeleteFetchInitialPostsJobs do
use Ecto.Migration
def change do
execute(
"delete from oban_jobs where worker = 'Pleroma.Workers.BackgroundWorker' and args->>'op' = 'fetch_initial_posts';",
""
)
end
end

View File

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.1abbc9b8.css rel=stylesheet><link href=chunk-libs.686b5876.css rel=stylesheet><link href=app.c836e084.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.ae93ea9f.js></script><script type=text/javascript src=static/js/chunk-elementUI.fba0efec.js></script><script type=text/javascript src=static/js/chunk-libs.b8c453ab.js></script><script type=text/javascript src=static/js/app.55df3157.js></script></body></html> <!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.1abbc9b8.css rel=stylesheet><link href=chunk-libs.686b5876.css rel=stylesheet><link href=app.c836e084.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.fa19e5d1.js></script><script type=text/javascript src=static/js/chunk-elementUI.fba0efec.js></script><script type=text/javascript src=static/js/chunk-libs.b8c453ab.js></script><script type=text/javascript src=static/js/app.d2c3c6b3.js></script></body></html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -59,8 +59,8 @@ test "non-local action does not produce public:local topic", %{activity: activit
describe "public visibility create events" do describe "public visibility create events" do
setup do setup do
activity = %Activity{ activity = %Activity{
object: %Object{data: %{"type" => "Create", "attachment" => []}}, object: %Object{data: %{"attachment" => []}},
data: %{"to" => [Pleroma.Constants.as_public()]} data: %{"type" => "Create", "to" => [Pleroma.Constants.as_public()]}
} }
{:ok, activity: activity} {:ok, activity: activity}
@ -98,8 +98,8 @@ test "only converts strings to hash tags", %{
describe "public visibility create events with attachments" do describe "public visibility create events with attachments" do
setup do setup do
activity = %Activity{ activity = %Activity{
object: %Object{data: %{"type" => "Create", "attachment" => ["foo"]}}, object: %Object{data: %{"attachment" => ["foo"]}},
data: %{"to" => [Pleroma.Constants.as_public()]} data: %{"type" => "Create", "to" => [Pleroma.Constants.as_public()]}
} }
{:ok, activity: activity} {:ok, activity: activity}

View File

@ -0,0 +1,79 @@
# Pleroma: A lightweight social networking server
# Copyright © 2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.EarmarkRendererTest do
use ExUnit.Case
test "Paragraph" do
code = ~s[Hello\n\nWorld!]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == "<p>Hello</p><p>World!</p>"
end
test "raw HTML" do
code = ~s[<a href="http://example.org/">OwO</a><!-- what's this?-->]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == "<p>#{code}</p>"
end
test "rulers" do
code = ~s[before\n\n-----\n\nafter]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == "<p>before</p><hr /><p>after</p>"
end
test "headings" do
code = ~s[# h1\n## h2\n### h3\n]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == ~s[<h1>h1</h1><h2>h2</h2><h3>h3</h3>]
end
test "blockquote" do
code = ~s[> whoms't are you quoting?]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == "<blockquote><p>whomst are you quoting?</p></blockquote>"
end
test "code" do
code = ~s[`mix`]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == ~s[<p><code class="inline">mix</code></p>]
code = ~s[``mix``]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == ~s[<p><code class="inline">mix</code></p>]
code = ~s[```\nputs "Hello World"\n```]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == ~s[<pre><code class="">puts &quot;Hello World&quot;</code></pre>]
end
test "lists" do
code = ~s[- one\n- two\n- three\n- four]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == "<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>"
code = ~s[1. one\n2. two\n3. three\n4. four\n]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == "<ol><li>one</li><li>two</li><li>three</li><li>four</li></ol>"
end
test "delegated renderers" do
code = ~s[a<br/>b]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == "<p>#{code}</p>"
code = ~s[*aaaa~*]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == ~s[<p><em>aaaa~</em></p>]
code = ~s[**aaaa~**]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == ~s[<p><strong>aaaa~</strong></p>]
# strikethrought
code = ~s[<del>aaaa~</del>]
result = Earmark.as_html!(code, %Earmark.Options{renderer: Pleroma.EarmarkRenderer})
assert result == ~s[<p><del>aaaa~</del></p>]
end
end

View File

@ -9,7 +9,7 @@ defmodule Pleroma.Web.CacheControlTest do
test "Verify Cache-Control header on static assets", %{conn: conn} do test "Verify Cache-Control header on static assets", %{conn: conn} do
conn = get(conn, "/index.html") conn = get(conn, "/index.html")
assert Conn.get_resp_header(conn, "cache-control") == ["public max-age=86400 must-revalidate"] assert Conn.get_resp_header(conn, "cache-control") == ["public, no-cache"]
end end
test "Verify Cache-Control header on the API", %{conn: conn} do test "Verify Cache-Control header on the API", %{conn: conn} do

View File

@ -8,24 +8,62 @@ defmodule Pleroma.Plugs.EnsureAuthenticatedPlugTest do
alias Pleroma.Plugs.EnsureAuthenticatedPlug alias Pleroma.Plugs.EnsureAuthenticatedPlug
alias Pleroma.User alias Pleroma.User
test "it halts if no user is assigned", %{conn: conn} do describe "without :if_func / :unless_func options" do
conn = test "it halts if user is NOT assigned", %{conn: conn} do
conn conn = EnsureAuthenticatedPlug.call(conn, %{})
|> EnsureAuthenticatedPlug.call(%{})
assert conn.status == 403 assert conn.status == 403
assert conn.halted == true assert conn.halted == true
end
test "it continues if a user is assigned", %{conn: conn} do
conn = assign(conn, :user, %User{})
ret_conn = EnsureAuthenticatedPlug.call(conn, %{})
assert ret_conn == conn
end
end end
test "it continues if a user is assigned", %{conn: conn} do describe "with :if_func / :unless_func options" do
conn = setup do
conn %{
|> assign(:user, %User{}) true_fn: fn -> true end,
false_fn: fn -> false end
}
end
ret_conn = test "it continues if a user is assigned", %{conn: conn, true_fn: true_fn, false_fn: false_fn} do
conn conn = assign(conn, :user, %User{})
|> EnsureAuthenticatedPlug.call(%{}) assert EnsureAuthenticatedPlug.call(conn, if_func: true_fn) == conn
assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn
assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn
assert EnsureAuthenticatedPlug.call(conn, unless_func: false_fn) == conn
end
assert ret_conn == conn test "it continues if a user is NOT assigned but :if_func evaluates to `false`",
%{conn: conn, false_fn: false_fn} do
assert EnsureAuthenticatedPlug.call(conn, if_func: false_fn) == conn
end
test "it continues if a user is NOT assigned but :unless_func evaluates to `true`",
%{conn: conn, true_fn: true_fn} do
assert EnsureAuthenticatedPlug.call(conn, unless_func: true_fn) == conn
end
test "it halts if a user is NOT assigned and :if_func evaluates to `true`",
%{conn: conn, true_fn: true_fn} do
conn = EnsureAuthenticatedPlug.call(conn, if_func: true_fn)
assert conn.status == 403
assert conn.halted == true
end
test "it halts if a user is NOT assigned and :unless_func evaluates to `false`",
%{conn: conn, false_fn: false_fn} do
conn = EnsureAuthenticatedPlug.call(conn, unless_func: false_fn)
assert conn.status == 403
assert conn.halted == true
end
end end
end end

View File

@ -38,7 +38,7 @@ test "with valid token(downcase), it assigns the user", %{conn: conn} = opts do
assert conn.assigns[:user] == opts[:user] assert conn.assigns[:user] == opts[:user]
end end
test "with valid token(downcase) in url parameters, it assings the user", opts do test "with valid token(downcase) in url parameters, it assigns the user", opts do
conn = conn =
:get :get
|> build_conn("/?access_token=#{opts[:token]}") |> build_conn("/?access_token=#{opts[:token]}")

View File

@ -3,8 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Plugs.RateLimiterTest do defmodule Pleroma.Plugs.RateLimiterTest do
use ExUnit.Case, async: true use Pleroma.Web.ConnCase
use Plug.Test
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.Plugs.RateLimiter alias Pleroma.Plugs.RateLimiter
@ -36,63 +35,44 @@ test "config is required for plug to work" do
|> RateLimiter.init() |> RateLimiter.init()
|> RateLimiter.action_settings() |> RateLimiter.action_settings()
end end
end
test "it is disabled for localhost" do test "it is disabled if it remote ip plug is enabled but no remote ip is found" do
Config.put([:rate_limit, @limiter_name], {1, 1}) Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1})
Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1}) assert RateLimiter.disabled?(Plug.Conn.assign(build_conn(), :remote_ip_found, false))
Config.put([Pleroma.Plugs.RemoteIp, :enabled], false) end
assert RateLimiter.disabled?() == true test "it restricts based on config values" do
end limiter_name = :test_plug_opts
scale = 80
limit = 5
test "it is disabled for socket" do Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
Config.put([:rate_limit, @limiter_name], {1, 1}) Config.put([:rate_limit, limiter_name], {scale, limit})
Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"})
Config.put([Pleroma.Plugs.RemoteIp, :enabled], false)
assert RateLimiter.disabled?() == true plug_opts = RateLimiter.init(name: limiter_name)
end conn = conn(:get, "/")
test "it is enabled for socket when remote ip is enabled" do
Config.put([:rate_limit, @limiter_name], {1, 1})
Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"})
Config.put([Pleroma.Plugs.RemoteIp, :enabled], true)
assert RateLimiter.disabled?() == false
end
test "it restricts based on config values" do
limiter_name = :test_plug_opts
scale = 80
limit = 5
Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8})
Config.put([:rate_limit, limiter_name], {scale, limit})
plug_opts = RateLimiter.init(name: limiter_name)
conn = conn(:get, "/")
for i <- 1..5 do
conn = RateLimiter.call(conn, plug_opts)
assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
Process.sleep(10)
end
for i <- 1..5 do
conn = RateLimiter.call(conn, plug_opts) conn = RateLimiter.call(conn, plug_opts)
assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests) assert {^i, _} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
assert conn.halted Process.sleep(10)
Process.sleep(50)
conn = conn(:get, "/")
conn = RateLimiter.call(conn, plug_opts)
assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
refute conn.status == Plug.Conn.Status.code(:too_many_requests)
refute conn.resp_body
refute conn.halted
end end
conn = RateLimiter.call(conn, plug_opts)
assert %{"error" => "Throttled"} = Phoenix.ConnTest.json_response(conn, :too_many_requests)
assert conn.halted
Process.sleep(50)
conn = conn(:get, "/")
conn = RateLimiter.call(conn, plug_opts)
assert {1, 4} = RateLimiter.inspect_bucket(conn, limiter_name, plug_opts)
refute conn.status == Plug.Conn.Status.code(:too_many_requests)
refute conn.resp_body
refute conn.halted
end end
describe "options" do describe "options" do

View File

@ -278,17 +278,6 @@ test "returns 400 on non GET, HEAD requests", %{conn: conn} do
end end
describe "cache resp headers" do describe "cache resp headers" do
test "returns headers", %{conn: conn} do
ClientMock
|> expect(:request, fn :get, "/cache/" <> ttl, _, _, _ ->
{:ok, 200, [{"cache-control", "public, max-age=" <> ttl}], %{}}
end)
|> expect(:stream_body, fn _ -> :done end)
conn = ReverseProxy.call(conn, "/cache/10")
assert {"cache-control", "public, max-age=10"} in conn.resp_headers
end
test "add cache-control", %{conn: conn} do test "add cache-control", %{conn: conn} do
ClientMock ClientMock
|> expect(:request, fn :get, "/cache", _, _, _ -> |> expect(:request, fn :get, "/cache", _, _, _ ->
@ -297,7 +286,7 @@ test "add cache-control", %{conn: conn} do
|> expect(:stream_body, fn _ -> :done end) |> expect(:stream_body, fn _ -> :done end)
conn = ReverseProxy.call(conn, "/cache") conn = ReverseProxy.call(conn, "/cache")
assert {"cache-control", "public"} in conn.resp_headers assert {"cache-control", "public, max-age=1209600"} in conn.resp_headers
end end
end end

View File

@ -26,6 +26,8 @@ defmodule Pleroma.Web.ConnCase do
use Pleroma.Tests.Helpers use Pleroma.Tests.Helpers
import Pleroma.Web.Router.Helpers import Pleroma.Web.Router.Helpers
alias Pleroma.Config
# The default endpoint for testing # The default endpoint for testing
@endpoint Pleroma.Web.Endpoint @endpoint Pleroma.Web.Endpoint
@ -48,6 +50,28 @@ defp oauth_access(scopes, opts \\ []) do
%{user: user, token: token, conn: conn} %{user: user, token: token, conn: conn}
end end
defp ensure_federating_or_authenticated(conn, url, user) do
initial_setting = Config.get([:instance, :federating])
on_exit(fn -> Config.put([:instance, :federating], initial_setting) end)
Config.put([:instance, :federating], false)
conn
|> get(url)
|> response(403)
conn
|> assign(:user, user)
|> get(url)
|> response(200)
Config.put([:instance, :federating], true)
conn
|> get(url)
|> response(200)
end
end end
end end

View File

@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Delivery alias Pleroma.Delivery
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.Object alias Pleroma.Object
@ -25,9 +26,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
:ok :ok
end end
clear_config_all([:instance, :federating], clear_config([:instance, :federating]) do
do: Pleroma.Config.put([:instance, :federating], true) Config.put([:instance, :federating], true)
) end
describe "/relay" do describe "/relay" do
clear_config([:instance, :allow_relay]) clear_config([:instance, :allow_relay])
@ -42,12 +43,21 @@ test "with the relay active, it returns the relay user", %{conn: conn} do
end end
test "with the relay disabled, it returns 404", %{conn: conn} do test "with the relay disabled, it returns 404", %{conn: conn} do
Pleroma.Config.put([:instance, :allow_relay], false) Config.put([:instance, :allow_relay], false)
conn conn
|> get(activity_pub_path(conn, :relay)) |> get(activity_pub_path(conn, :relay))
|> json_response(404) |> json_response(404)
|> assert end
test "on non-federating instance, it returns 404", %{conn: conn} do
Config.put([:instance, :federating], false)
user = insert(:user)
conn
|> assign(:user, user)
|> get(activity_pub_path(conn, :relay))
|> json_response(404)
end end
end end
@ -60,6 +70,16 @@ test "it returns the internal fetch user", %{conn: conn} do
assert res["id"] =~ "/fetch" assert res["id"] =~ "/fetch"
end end
test "on non-federating instance, it returns 404", %{conn: conn} do
Config.put([:instance, :federating], false)
user = insert(:user)
conn
|> assign(:user, user)
|> get(activity_pub_path(conn, :internal_fetch))
|> json_response(404)
end
end end
describe "/users/:nickname" do describe "/users/:nickname" do
@ -123,9 +143,34 @@ test "it returns 404 for remote users", %{
assert json_response(conn, 404) assert json_response(conn, 404)
end end
test "it returns error when user is not found", %{conn: conn} do
response =
conn
|> put_req_header("accept", "application/json")
|> get("/users/jimm")
|> json_response(404)
assert response == "Not found"
end
test "it requires authentication if instance is NOT federating", %{
conn: conn
} do
user = insert(:user)
conn =
put_req_header(
conn,
"accept",
"application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
)
ensure_federating_or_authenticated(conn, "/users/#{user.nickname}.json", user)
end
end end
describe "/object/:uuid" do describe "/objects/:uuid" do
test "it returns a json representation of the object with accept application/json", %{ test "it returns a json representation of the object with accept application/json", %{
conn: conn conn: conn
} do } do
@ -236,6 +281,18 @@ test "cached purged after object deletion", %{conn: conn} do
assert "Not found" == json_response(conn2, :not_found) assert "Not found" == json_response(conn2, :not_found)
end end
test "it requires authentication if instance is NOT federating", %{
conn: conn
} do
user = insert(:user)
note = insert(:note)
uuid = String.split(note.data["id"], "/") |> List.last()
conn = put_req_header(conn, "accept", "application/activity+json")
ensure_federating_or_authenticated(conn, "/objects/#{uuid}", user)
end
end end
describe "/activities/:uuid" do describe "/activities/:uuid" do
@ -307,6 +364,18 @@ test "cached purged after activity deletion", %{conn: conn} do
assert "Not found" == json_response(conn2, :not_found) assert "Not found" == json_response(conn2, :not_found)
end end
test "it requires authentication if instance is NOT federating", %{
conn: conn
} do
user = insert(:user)
activity = insert(:note_activity)
uuid = String.split(activity.data["id"], "/") |> List.last()
conn = put_req_header(conn, "accept", "application/activity+json")
ensure_federating_or_authenticated(conn, "/activities/#{uuid}", user)
end
end end
describe "/inbox" do describe "/inbox" do
@ -379,6 +448,34 @@ test "accept follow activity", %{conn: conn} do
:ok = Mix.Tasks.Pleroma.Relay.run(["list"]) :ok = Mix.Tasks.Pleroma.Relay.run(["list"])
assert_receive {:mix_shell, :info, ["relay.mastodon.host"]} assert_receive {:mix_shell, :info, ["relay.mastodon.host"]}
end end
test "without valid signature, " <>
"it only accepts Create activities and requires enabled federation",
%{conn: conn} do
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!()
non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Poison.decode!()
conn = put_req_header(conn, "content-type", "application/activity+json")
Config.put([:instance, :federating], false)
conn
|> post("/inbox", data)
|> json_response(403)
conn
|> post("/inbox", non_create_data)
|> json_response(403)
Config.put([:instance, :federating], true)
ret_conn = post(conn, "/inbox", data)
assert "ok" == json_response(ret_conn, 200)
conn
|> post("/inbox", non_create_data)
|> json_response(400)
end
end end
describe "/users/:nickname/inbox" do describe "/users/:nickname/inbox" do
@ -517,22 +614,11 @@ test "it accepts messages from actors that are followed by the user", %{
test "it rejects reads from other users", %{conn: conn} do test "it rejects reads from other users", %{conn: conn} do
user = insert(:user) user = insert(:user)
otheruser = insert(:user) other_user = insert(:user)
conn =
conn
|> assign(:user, otheruser)
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/inbox")
assert json_response(conn, 403)
end
test "it doesn't crash without an authenticated user", %{conn: conn} do
user = insert(:user)
conn = conn =
conn conn
|> assign(:user, other_user)
|> put_req_header("accept", "application/activity+json") |> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/inbox") |> get("/users/#{user.nickname}/inbox")
@ -613,14 +699,30 @@ test "it removes all follower collections but actor's", %{conn: conn} do
refute recipient.follower_address in activity.data["cc"] refute recipient.follower_address in activity.data["cc"]
refute recipient.follower_address in activity.data["to"] refute recipient.follower_address in activity.data["to"]
end end
test "it requires authentication", %{conn: conn} do
user = insert(:user)
conn = put_req_header(conn, "accept", "application/activity+json")
ret_conn = get(conn, "/users/#{user.nickname}/inbox")
assert json_response(ret_conn, 403)
ret_conn =
conn
|> assign(:user, user)
|> get("/users/#{user.nickname}/inbox")
assert json_response(ret_conn, 200)
end
end end
describe "/users/:nickname/outbox" do describe "GET /users/:nickname/outbox" do
test "it will not bomb when there is no activity", %{conn: conn} do test "it returns 200 even if there're no activities", %{conn: conn} do
user = insert(:user) user = insert(:user)
conn = conn =
conn conn
|> assign(:user, user)
|> put_req_header("accept", "application/activity+json") |> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/outbox") |> get("/users/#{user.nickname}/outbox")
@ -635,6 +737,7 @@ test "it returns a note activity in a collection", %{conn: conn} do
conn = conn =
conn conn
|> assign(:user, user)
|> put_req_header("accept", "application/activity+json") |> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/outbox?page=true") |> get("/users/#{user.nickname}/outbox?page=true")
@ -647,24 +750,38 @@ test "it returns an announce activity in a collection", %{conn: conn} do
conn = conn =
conn conn
|> assign(:user, user)
|> put_req_header("accept", "application/activity+json") |> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/outbox?page=true") |> get("/users/#{user.nickname}/outbox?page=true")
assert response(conn, 200) =~ announce_activity.data["object"] assert response(conn, 200) =~ announce_activity.data["object"]
end end
test "it rejects posts from other users", %{conn: conn} do test "it requires authentication if instance is NOT federating", %{
conn: conn
} do
user = insert(:user)
conn = put_req_header(conn, "accept", "application/activity+json")
ensure_federating_or_authenticated(conn, "/users/#{user.nickname}/outbox", user)
end
end
describe "POST /users/:nickname/outbox" do
test "it rejects posts from other users / unauuthenticated users", %{conn: conn} do
data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
user = insert(:user) user = insert(:user)
otheruser = insert(:user) other_user = insert(:user)
conn = put_req_header(conn, "content-type", "application/activity+json")
conn = conn
conn |> post("/users/#{user.nickname}/outbox", data)
|> assign(:user, otheruser) |> json_response(403)
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/outbox", data)
assert json_response(conn, 403) conn
|> assign(:user, other_user)
|> post("/users/#{user.nickname}/outbox", data)
|> json_response(403)
end end
test "it inserts an incoming create activity into the database", %{conn: conn} do test "it inserts an incoming create activity into the database", %{conn: conn} do
@ -779,24 +896,42 @@ test "it returns relay followers", %{conn: conn} do
result = result =
conn conn
|> assign(:relay, true)
|> get("/relay/followers") |> get("/relay/followers")
|> json_response(200) |> json_response(200)
assert result["first"]["orderedItems"] == [user.ap_id] assert result["first"]["orderedItems"] == [user.ap_id]
end end
test "on non-federating instance, it returns 404", %{conn: conn} do
Config.put([:instance, :federating], false)
user = insert(:user)
conn
|> assign(:user, user)
|> get("/relay/followers")
|> json_response(404)
end
end end
describe "/relay/following" do describe "/relay/following" do
test "it returns relay following", %{conn: conn} do test "it returns relay following", %{conn: conn} do
result = result =
conn conn
|> assign(:relay, true)
|> get("/relay/following") |> get("/relay/following")
|> json_response(200) |> json_response(200)
assert result["first"]["orderedItems"] == [] assert result["first"]["orderedItems"] == []
end end
test "on non-federating instance, it returns 404", %{conn: conn} do
Config.put([:instance, :federating], false)
user = insert(:user)
conn
|> assign(:user, user)
|> get("/relay/following")
|> json_response(404)
end
end end
describe "/users/:nickname/followers" do describe "/users/:nickname/followers" do
@ -807,32 +942,36 @@ test "it returns the followers in a collection", %{conn: conn} do
result = result =
conn conn
|> assign(:user, user_two)
|> get("/users/#{user_two.nickname}/followers") |> get("/users/#{user_two.nickname}/followers")
|> json_response(200) |> json_response(200)
assert result["first"]["orderedItems"] == [user.ap_id] assert result["first"]["orderedItems"] == [user.ap_id]
end end
test "it returns returns a uri if the user has 'hide_followers' set", %{conn: conn} do test "it returns a uri if the user has 'hide_followers' set", %{conn: conn} do
user = insert(:user) user = insert(:user)
user_two = insert(:user, hide_followers: true) user_two = insert(:user, hide_followers: true)
User.follow(user, user_two) User.follow(user, user_two)
result = result =
conn conn
|> assign(:user, user)
|> get("/users/#{user_two.nickname}/followers") |> get("/users/#{user_two.nickname}/followers")
|> json_response(200) |> json_response(200)
assert is_binary(result["first"]) assert is_binary(result["first"])
end end
test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is not authenticated", test "it returns a 403 error on pages, if the user has 'hide_followers' set and the request is from another user",
%{conn: conn} do %{conn: conn} do
user = insert(:user, hide_followers: true) user = insert(:user)
other_user = insert(:user, hide_followers: true)
result = result =
conn conn
|> get("/users/#{user.nickname}/followers?page=1") |> assign(:user, user)
|> get("/users/#{other_user.nickname}/followers?page=1")
assert result.status == 403 assert result.status == 403
assert result.resp_body == "" assert result.resp_body == ""
@ -864,6 +1003,7 @@ test "it works for more than 10 users", %{conn: conn} do
result = result =
conn conn
|> assign(:user, user)
|> get("/users/#{user.nickname}/followers") |> get("/users/#{user.nickname}/followers")
|> json_response(200) |> json_response(200)
@ -873,12 +1013,21 @@ test "it works for more than 10 users", %{conn: conn} do
result = result =
conn conn
|> assign(:user, user)
|> get("/users/#{user.nickname}/followers?page=2") |> get("/users/#{user.nickname}/followers?page=2")
|> json_response(200) |> json_response(200)
assert length(result["orderedItems"]) == 5 assert length(result["orderedItems"]) == 5
assert result["totalItems"] == 15 assert result["totalItems"] == 15
end end
test "returns 403 if requester is not logged in", %{conn: conn} do
user = insert(:user)
conn
|> get("/users/#{user.nickname}/followers")
|> json_response(403)
end
end end
describe "/users/:nickname/following" do describe "/users/:nickname/following" do
@ -889,6 +1038,7 @@ test "it returns the following in a collection", %{conn: conn} do
result = result =
conn conn
|> assign(:user, user)
|> get("/users/#{user.nickname}/following") |> get("/users/#{user.nickname}/following")
|> json_response(200) |> json_response(200)
@ -896,25 +1046,28 @@ test "it returns the following in a collection", %{conn: conn} do
end end
test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do test "it returns a uri if the user has 'hide_follows' set", %{conn: conn} do
user = insert(:user, hide_follows: true) user = insert(:user)
user_two = insert(:user) user_two = insert(:user, hide_follows: true)
User.follow(user, user_two) User.follow(user, user_two)
result = result =
conn conn
|> get("/users/#{user.nickname}/following") |> assign(:user, user)
|> get("/users/#{user_two.nickname}/following")
|> json_response(200) |> json_response(200)
assert is_binary(result["first"]) assert is_binary(result["first"])
end end
test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is not authenticated", test "it returns a 403 error on pages, if the user has 'hide_follows' set and the request is from another user",
%{conn: conn} do %{conn: conn} do
user = insert(:user, hide_follows: true) user = insert(:user)
user_two = insert(:user, hide_follows: true)
result = result =
conn conn
|> get("/users/#{user.nickname}/following?page=1") |> assign(:user, user)
|> get("/users/#{user_two.nickname}/following?page=1")
assert result.status == 403 assert result.status == 403
assert result.resp_body == "" assert result.resp_body == ""
@ -947,6 +1100,7 @@ test "it works for more than 10 users", %{conn: conn} do
result = result =
conn conn
|> assign(:user, user)
|> get("/users/#{user.nickname}/following") |> get("/users/#{user.nickname}/following")
|> json_response(200) |> json_response(200)
@ -956,12 +1110,21 @@ test "it works for more than 10 users", %{conn: conn} do
result = result =
conn conn
|> assign(:user, user)
|> get("/users/#{user.nickname}/following?page=2") |> get("/users/#{user.nickname}/following?page=2")
|> json_response(200) |> json_response(200)
assert length(result["orderedItems"]) == 5 assert length(result["orderedItems"]) == 5
assert result["totalItems"] == 15 assert result["totalItems"] == 15
end end
test "returns 403 if requester is not logged in", %{conn: conn} do
user = insert(:user)
conn
|> get("/users/#{user.nickname}/following")
|> json_response(403)
end
end end
describe "delivery tracking" do describe "delivery tracking" do
@ -1046,8 +1209,8 @@ test "it tracks a signed activity fetch when the json is cached", %{conn: conn}
end end
end end
describe "Additionnal ActivityPub C2S endpoints" do describe "Additional ActivityPub C2S endpoints" do
test "/api/ap/whoami", %{conn: conn} do test "GET /api/ap/whoami", %{conn: conn} do
user = insert(:user) user = insert(:user)
conn = conn =
@ -1058,12 +1221,16 @@ test "/api/ap/whoami", %{conn: conn} do
user = User.get_cached_by_id(user.id) user = User.get_cached_by_id(user.id)
assert UserView.render("user.json", %{user: user}) == json_response(conn, 200) assert UserView.render("user.json", %{user: user}) == json_response(conn, 200)
conn
|> get("/api/ap/whoami")
|> json_response(403)
end end
clear_config([:media_proxy]) clear_config([:media_proxy])
clear_config([Pleroma.Upload]) clear_config([Pleroma.Upload])
test "uploadMedia", %{conn: conn} do test "POST /api/ap/upload_media", %{conn: conn} do
user = insert(:user) user = insert(:user)
desc = "Description of the image" desc = "Description of the image"
@ -1083,6 +1250,10 @@ test "uploadMedia", %{conn: conn} do
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
conn
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|> json_response(403)
end end
end end
end end

View File

@ -23,6 +23,10 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do
:ok :ok
end end
clear_config_all([:instance, :federating]) do
Pleroma.Config.put([:instance, :federating], true)
end
describe "gather_webfinger_links/1" do describe "gather_webfinger_links/1" do
test "it returns links" do test "it returns links" do
user = insert(:user) user = insert(:user)

View File

@ -177,71 +177,6 @@ test "does not adress actor's follower address if the activity is not public", %
end end
end end
describe "fetch_ordered_collection" do
import Tesla.Mock
test "fetches the first OrderedCollectionPage when an OrderedCollection is encountered" do
mock(fn
%{method: :get, url: "http://mastodon.com/outbox"} ->
json(%{"type" => "OrderedCollection", "first" => "http://mastodon.com/outbox?page=true"})
%{method: :get, url: "http://mastodon.com/outbox?page=true"} ->
json(%{"type" => "OrderedCollectionPage", "orderedItems" => ["ok"]})
end)
assert Utils.fetch_ordered_collection("http://mastodon.com/outbox", 1) == ["ok"]
end
test "fetches several pages in the right order one after another, but only the specified amount" do
mock(fn
%{method: :get, url: "http://example.com/outbox"} ->
json(%{
"type" => "OrderedCollectionPage",
"orderedItems" => [0],
"next" => "http://example.com/outbox?page=1"
})
%{method: :get, url: "http://example.com/outbox?page=1"} ->
json(%{
"type" => "OrderedCollectionPage",
"orderedItems" => [1],
"next" => "http://example.com/outbox?page=2"
})
%{method: :get, url: "http://example.com/outbox?page=2"} ->
json(%{"type" => "OrderedCollectionPage", "orderedItems" => [2]})
end)
assert Utils.fetch_ordered_collection("http://example.com/outbox", 0) == [0]
assert Utils.fetch_ordered_collection("http://example.com/outbox", 1) == [0, 1]
end
test "returns an error if the url doesn't have an OrderedCollection/Page" do
mock(fn
%{method: :get, url: "http://example.com/not-an-outbox"} ->
json(%{"type" => "NotAnOutbox"})
end)
assert {:error, _} = Utils.fetch_ordered_collection("http://example.com/not-an-outbox", 1)
end
test "returns the what was collected if there are less pages than specified" do
mock(fn
%{method: :get, url: "http://example.com/outbox"} ->
json(%{
"type" => "OrderedCollectionPage",
"orderedItems" => [0],
"next" => "http://example.com/outbox?page=1"
})
%{method: :get, url: "http://example.com/outbox?page=1"} ->
json(%{"type" => "OrderedCollectionPage", "orderedItems" => [1]})
end)
assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1]
end
end
test "make_json_ld_header/0" do test "make_json_ld_header/0" do
assert Utils.make_json_ld_header() == %{ assert Utils.make_json_ld_header() == %{
"@context" => [ "@context" => [

View File

@ -3048,7 +3048,7 @@ test "pleroma restarts", %{conn: conn} do
end end
describe "GET /api/pleroma/admin/statuses" do describe "GET /api/pleroma/admin/statuses" do
test "returns all public, unlisted, and direct statuses", %{conn: conn, admin: admin} do test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do
blocked = insert(:user) blocked = insert(:user)
user = insert(:user) user = insert(:user)
User.block(admin, blocked) User.block(admin, blocked)
@ -3067,7 +3067,7 @@ test "returns all public, unlisted, and direct statuses", %{conn: conn, admin: a
|> json_response(200) |> json_response(200)
refute "private" in Enum.map(response, & &1["visibility"]) refute "private" in Enum.map(response, & &1["visibility"])
assert length(response) == 4 assert length(response) == 3
end end
test "returns only local statuses with local_only on", %{conn: conn} do test "returns only local statuses with local_only on", %{conn: conn} do
@ -3084,12 +3084,16 @@ test "returns only local statuses with local_only on", %{conn: conn} do
assert length(response) == 1 assert length(response) == 1
end end
test "returns private statuses with godmode on", %{conn: conn} do test "returns private and direct statuses with godmode on", %{conn: conn, admin: admin} do
user = insert(:user) user = insert(:user)
{:ok, _} =
CommonAPI.post(user, %{"status" => "@#{admin.nickname}", "visibility" => "direct"})
{:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"})
{:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) {:ok, _} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"})
conn = get(conn, "/api/pleroma/admin/statuses?godmode=true") conn = get(conn, "/api/pleroma/admin/statuses?godmode=true")
assert json_response(conn, 200) |> length() == 2 assert json_response(conn, 200) |> length() == 3
end end
end end

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