Merge branch 'develop' into feature/matstodon-statuses-by-name

This commit is contained in:
Mark Felder 2019-07-19 16:55:10 -05:00
commit 9169f331b6
59 changed files with 1184 additions and 191 deletions

View File

@ -24,12 +24,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Added ### Added
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`) - MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
- MRF: Support for excluding specific domains from Transparency. - MRF: Support for excluding specific domains from Transparency.
- MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`)
- Configuration: `federation_incoming_replies_max_depth` option - Configuration: `federation_incoming_replies_max_depth` option
- Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses) - Mastodon API: Support for the [`tagged` filter](https://github.com/tootsuite/mastodon/pull/9755) in [`GET /api/v1/accounts/:id/statuses`](https://docs.joinmastodon.org/api/rest/accounts/#get-api-v1-accounts-id-statuses)
- Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header - Mastodon API, streaming: Add support for passing the token in the `Sec-WebSocket-Protocol` header
- Mastodon API, extension: Ability to reset avatar, profile banner, and background - Mastodon API, extension: Ability to reset avatar, profile banner, and background
- Mastodon API: Add support for categories for custom emojis by reusing the group feature. <https://github.com/tootsuite/mastodon/pull/11196> - Mastodon API: Add support for categories for custom emojis by reusing the group feature. <https://github.com/tootsuite/mastodon/pull/11196>
- Mastodon API: Add support for muting/unmuting notifications - Mastodon API: Add support for muting/unmuting notifications
- Mastodon API: Add support for the `blocked_by` attribute in the relationship API (`GET /api/v1/accounts/relationships`). <https://github.com/tootsuite/mastodon/pull/10373>
- Mastodon API: Add `pleroma.deactivated` to the Account entity
- Mastodon API: added `/auth/password` endpoint for password reset with rate limit.
- Mastodon API: /api/v1/accounts/:id/statuses now supports nicknames or user id - Mastodon API: /api/v1/accounts/:id/statuses now supports nicknames or user id
- Admin API: Return users' tags when querying reports - Admin API: Return users' tags when querying reports
- Admin API: Return avatar and display name when querying users - Admin API: Return avatar and display name when querying users
@ -39,6 +43,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`. - Configuration: `enabled` option for `Pleroma.Emails.Mailer`, defaulting to `false`.
- Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options. - Configuration: Pleroma.Plugs.RateLimiter `bucket_name`, `params` options.
- Addressable lists - Addressable lists
- Twitter API: added rate limit for `/api/account/password_reset` endpoint.
- ActivityPub: Add an internal service actor for fetching ActivityPub objects.
- ActivityPub: Optional signing of ActivityPub object fetches.
### Changed ### Changed
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text - Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text

View File

@ -305,7 +305,8 @@
accept_blocks: true, accept_blocks: true,
unfollow_blocked: true, unfollow_blocked: true,
outgoing_blocks: true, outgoing_blocks: true,
follow_handshake_timeout: 500 follow_handshake_timeout: 500,
sign_object_fetches: true
config :pleroma, :user, deny_follow_blocked: true config :pleroma, :user, deny_follow_blocked: true
@ -528,8 +529,11 @@
config :pleroma, :rate_limit, config :pleroma, :rate_limit,
search: [{1000, 10}, {1000, 30}], search: [{1000, 10}, {1000, 30}],
app_account_creation: {1_800_000, 25}, app_account_creation: {1_800_000, 25},
relations_actions: {10_000, 10},
relation_id_action: {60_000, 2},
statuses_actions: {10_000, 15}, statuses_actions: {10_000, 15},
status_id_action: {60_000, 3} status_id_action: {60_000, 3},
password_reset: {1_800_000, 5}
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.

View File

@ -31,6 +31,8 @@
skip_thread_containment: false, skip_thread_containment: false,
federating: false federating: false
config :pleroma, :activitypub, sign_object_fetches: false
# Configure your database # Configure your database
config :pleroma, Pleroma.Repo, config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres, adapter: Ecto.Adapters.Postgres,
@ -67,7 +69,8 @@
config :pleroma, :rate_limit, config :pleroma, :rate_limit,
search: [{1000, 30}, {1000, 30}], search: [{1000, 30}, {1000, 30}],
app_account_creation: {10_000, 5} app_account_creation: {10_000, 5},
password_reset: {1000, 30}
config :pleroma, :http_security, report_uri: "https://endpoint.com" config :pleroma, :http_security, report_uri: "https://endpoint.com"

View File

@ -50,6 +50,7 @@ Has these additional fields under the `pleroma` object:
- `hide_follows`: boolean, true when the user has follow hiding enabled - `hide_follows`: boolean, true when the user has follow hiding enabled
- `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials` - `settings_store`: A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`
- `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials` - `chat_token`: The token needed for Pleroma chat. Only returned in `verify_credentials`
- `deactivated`: boolean, true when the user is deactivated
### Source ### Source

View File

@ -31,10 +31,11 @@ Feel free to contact us to be added to this list!
- Features: No Streaming - Features: No Streaming
### Fedilab ### Fedilab
- Source Code: <https://gitlab.com/tom79/mastalab/> - Homepage: <https://fedilab.app/>
- Contact: [@tom79@mastodon.social](https://mastodon.social/users/tom79) - Source Code: <https://framagit.org/tom79/fedilab/>
- Contact: [@fedilab@framapiaf.org](https://framapiaf.org/users/fedilab)
- Platforms: Android - Platforms: Android
- Features: Streaming Ready - Features: Streaming Ready, Moderation, Text Formatting
### Nekonium ### Nekonium
- Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/) - Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/)

View File

@ -101,6 +101,7 @@ config :pleroma, Pleroma.Emails.Mailer,
* `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:. * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links. * `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed. * `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (see `:mrf_mention` section)
* `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network. * `public`: Makes the client API in authentificated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send. * `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
* `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json`` * `managed_config`: Whenether the config for pleroma-fe is configured in this config or in ``static/config.json``
@ -271,6 +272,9 @@ config :pleroma, :mrf_subchain,
* `federated_timeline_removal`: A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html) * `federated_timeline_removal`: A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
* `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html) * `replace`: A list of tuples containing `{pattern, replacement}`, `pattern` can be a string or a [regular expression](https://hexdocs.pm/elixir/Regex.html)
## :mrf_mention
* `actors`: A list of actors, for which to drop any posts mentioning.
## :media_proxy ## :media_proxy
* `enabled`: Enables proxying of remote media to the instances proxy * `enabled`: Enables proxying of remote media to the instances proxy
* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts. * `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.
@ -328,6 +332,7 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed * ``unfollow_blocked``: Whether blocks result in people getting unfollowed
* ``outgoing_blocks``: Whether to federate blocks to other instances * ``outgoing_blocks``: Whether to federate blocks to other instances
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question * ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
* ``sign_object_fetches``: Sign object fetches with HTTP signatures
## :http_security ## :http_security
* ``enabled``: Whether the managed content security policy is enabled * ``enabled``: Whether the managed content security policy is enabled
@ -647,5 +652,7 @@ Supported rate limiters:
* `:search` for the search requests (account & status search etc.) * `:search` for the search requests (account & status search etc.)
* `:app_account_creation` for registering user accounts from the same IP address * `:app_account_creation` for registering user accounts from the same IP address
* `:relations_actions` for actions on relations with all users (follow, unfollow)
* `:relation_id_action` for actions on relation with a specific user (follow, unfollow)
* `:statuses_actions` for create / delete / fav / unfav / reblog / unreblog actions on any statuses * `:statuses_actions` for create / delete / fav / unfav / reblog / unreblog actions on any statuses
* `:status_id_action` for fav / unfav or reblog / unreblog actions on the same status by the same user * `:status_id_action` for fav / unfav or reblog / unreblog actions on the same status by the same user

View File

@ -24,7 +24,9 @@ If you came here from one of the installation guides, take a look at the example
``` ```
config :pleroma, :media_proxy, config :pleroma, :media_proxy,
enabled: true, enabled: true,
redirect_on_failure: true proxy_opts: [
redirect_on_failure: true
]
#base_url: "https://cache.pleroma.social" #base_url: "https://cache.pleroma.social"
``` ```
If you want to use a subdomain to serve the files, uncomment `base_url`, change the url and add a comma after `true` in the previous line. If you want to use a subdomain to serve the files, uncomment `base_url`, change the url and add a comma after `true` in the previous line.

View File

@ -242,6 +242,14 @@ So for example, if the task is `mix pleroma.user set admin --admin`, you should
```sh ```sh
su pleroma -s $SHELL -lc "./bin/pleroma_ctl user set admin --admin" su pleroma -s $SHELL -lc "./bin/pleroma_ctl user set admin --admin"
``` ```
## Create your first user and set as admin
```sh
cd /opt/pleroma/bin
su pleroma -s $SHELL -lc "./bin/pleroma_ctl user new joeuser joeuser@sld.tld --admin"
```
This will create an account withe the username of 'joeuser' with the email address of joeuser@sld.tld, and set that user's account as an admin. This will result in a link that you can paste into the browser, which logs you in and enables you to set the password.
### Updating ### Updating
Generally, doing the following is enough: Generally, doing the following is enough:
```sh ```sh

View File

@ -62,6 +62,10 @@ defmodule Mix.Tasks.Pleroma.User do
mix pleroma.user unsubscribe NICKNAME mix pleroma.user unsubscribe NICKNAME
## Unsubscribe local users from an entire instance and deactivate all accounts
mix pleroma.user unsubscribe_all_from_instance INSTANCE
## Create a password reset link. ## Create a password reset link.
mix pleroma.user reset_password NICKNAME mix pleroma.user reset_password NICKNAME
@ -246,6 +250,20 @@ def run(["unsubscribe", nickname]) do
end end
end end
def run(["unsubscribe_all_from_instance", instance]) do
start_pleroma()
Pleroma.User.Query.build(%{nickname: "@#{instance}"})
|> Pleroma.RepoStreamer.chunk_stream(500)
|> Stream.each(fn users ->
users
|> Enum.each(fn user ->
run(["unsubscribe", user.nickname])
end)
end)
|> Stream.run()
end
def run(["set", nickname | rest]) do def run(["set", nickname | rest]) do
start_pleroma() start_pleroma()

View File

@ -140,6 +140,11 @@ def start(_type, _args) do
id: :federator_init, id: :federator_init,
start: {Task, :start_link, [&Pleroma.Web.Federator.init/0]}, start: {Task, :start_link, [&Pleroma.Web.Federator.init/0]},
restart: :temporary restart: :temporary
},
%{
id: :internal_fetch_init,
start: {Task, :start_link, [&Pleroma.Web.ActivityPub.InternalFetchActor.init/0]},
restart: :temporary
} }
] ++ ] ++
streamer_child() ++ streamer_child() ++

View File

@ -6,6 +6,8 @@ defmodule Pleroma.Object.Fetcher do
alias Pleroma.HTTP alias Pleroma.HTTP
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Object.Containment alias Pleroma.Object.Containment
alias Pleroma.Signature
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.OStatus alias Pleroma.Web.OStatus
@ -82,15 +84,52 @@ def fetch_object_from_id!(id, options \\ []) do
end end
end end
defp make_signature(id, date) do
uri = URI.parse(id)
signature =
InternalFetchActor.get_actor()
|> Signature.sign(%{
"(request-target)": "get #{uri.path}",
host: uri.host,
date: date
})
[{:Signature, signature}]
end
defp sign_fetch(headers, id, date) do
if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
headers ++ make_signature(id, date)
else
headers
end
end
defp maybe_date_fetch(headers, date) do
if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
headers ++ [{:Date, date}]
else
headers
end
end
def fetch_and_contain_remote_object_from_id(id) do def fetch_and_contain_remote_object_from_id(id) do
Logger.info("Fetching object #{id} via AP") Logger.info("Fetching object #{id} via AP")
date =
NaiveDateTime.utc_now()
|> Timex.format!("{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
headers =
[{:Accept, "application/activity+json"}]
|> maybe_date_fetch(date)
|> sign_fetch(id, date)
Logger.debug("Fetch headers: #{inspect(headers)}")
with true <- String.starts_with?(id, "http"), with true <- String.starts_with?(id, "http"),
{:ok, %{body: body, status: code}} when code in 200..299 <- {:ok, %{body: body, status: code}} when code in 200..299 <- HTTP.get(id, headers),
HTTP.get(
id,
[{:Accept, "application/activity+json"}]
),
{:ok, data} <- Jason.decode(body), {:ok, data} <- Jason.decode(body),
:ok <- Containment.contain_origin_from_id(id, data) do :ok <- Containment.contain_origin_from_id(id, data) do
{:ok, data} {:ok, data}

View File

@ -8,22 +8,19 @@ defmodule Pleroma.Plugs.AuthenticationPlug do
alias Pleroma.User alias Pleroma.User
require Logger require Logger
def init(options) do def init(options), do: options
options
def checkpw(password, "$6" <> _ = password_hash) do
:crypt.crypt(password, password_hash) == password_hash
end end
def checkpw(password, password_hash) do def checkpw(password, "$pbkdf2" <> _ = password_hash) do
cond do Pbkdf2.checkpw(password, password_hash)
String.starts_with?(password_hash, "$pbkdf2") -> end
Pbkdf2.checkpw(password, password_hash)
String.starts_with?(password_hash, "$6") -> def checkpw(_password, _password_hash) do
:crypt.crypt(password, password_hash) == password_hash Logger.error("Password hash not recognized")
false
true ->
Logger.error("Password hash not recognized")
false
end
end end
def call(%{assigns: %{user: %User{}}} = conn, _), do: conn def call(%{assigns: %{user: %User{}}} = conn, _), do: conn

View File

@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
alias Pleroma.Web.ActivityPub.Utils
import Plug.Conn import Plug.Conn
require Logger require Logger
@ -16,38 +15,30 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
end end
def call(conn, _opts) do def call(conn, _opts) do
user = Utils.get_ap_id(conn.params["actor"])
Logger.debug("Checking sig for #{user}")
[signature | _] = get_req_header(conn, "signature") [signature | _] = get_req_header(conn, "signature")
cond do if signature do
signature && String.contains?(signature, user) -> # set (request-target) header to the appropriate value
# set (request-target) header to the appropriate value # we also replace the digest header with the one we computed
# we also replace the digest header with the one we computed conn =
conn =
conn
|> put_req_header(
"(request-target)",
String.downcase("#{conn.method}") <> " #{conn.request_path}"
)
conn =
if conn.assigns[:digest] do
conn
|> put_req_header("digest", conn.assigns[:digest])
else
conn
end
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
signature ->
Logger.debug("Signature not from actor")
assign(conn, :valid_signature, false)
true ->
Logger.debug("No signature header!")
conn conn
|> put_req_header(
"(request-target)",
String.downcase("#{conn.method}") <> " #{conn.request_path}"
)
conn =
if conn.assigns[:digest] do
conn
|> put_req_header("digest", conn.assigns[:digest])
else
conn
end
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
else
Logger.debug("No signature header!")
conn
end end
end end
end end

View File

@ -0,0 +1,70 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
alias Pleroma.Signature
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Utils
import Plug.Conn
require Logger
def init(options), do: options
defp key_id_from_conn(conn) do
with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn) do
Signature.key_id_to_actor_id(key_id)
else
_ ->
nil
end
end
defp user_from_key_id(conn) do
with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn),
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do
user
else
_ ->
nil
end
end
def call(%{assigns: %{user: _}} = conn, _opts), do: conn
# if this has payload make sure it is signed by the same actor that made it
def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do
with actor_id <- Utils.get_ap_id(actor),
{:user, %User{} = user} <- {:user, user_from_key_id(conn)},
{:user_match, true} <- {:user_match, user.ap_id == actor_id} do
assign(conn, :user, user)
else
{:user_match, false} ->
Logger.debug("Failed to map identity from signature (payload actor mismatch)")
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
assign(conn, :valid_signature, false)
# remove me once testsuite uses mapped capabilities instead of what we do now
{:user, nil} ->
Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
conn
end
end
# no payload, probably a signed fetch
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
with %User{} = user <- user_from_key_id(conn) do
assign(conn, :user, user)
else
_ ->
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
Logger.debug("key_id=#{key_id_from_conn(conn)}")
assign(conn, :valid_signature, false)
end
end
# no signature at all
def call(conn, _opts), do: conn
end

View File

@ -8,10 +8,16 @@ defmodule Pleroma.Signature do
alias Pleroma.Keys alias Pleroma.Keys
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
def key_id_to_actor_id(key_id) do
URI.parse(key_id)
|> Map.put(:fragment, nil)
|> URI.to_string()
end
def fetch_public_key(conn) do def fetch_public_key(conn) do
with actor_id <- Utils.get_ap_id(conn.params["actor"]), with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
actor_id <- key_id_to_actor_id(kid),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key} {:ok, public_key}
else else
@ -21,7 +27,8 @@ def fetch_public_key(conn) do
end end
def refetch_public_key(conn) do def refetch_public_key(conn) do
with actor_id <- Utils.get_ap_id(conn.params["actor"]), with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
actor_id <- key_id_to_actor_id(kid),
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id), {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
{:ok, public_key} {:ok, public_key}

View File

@ -6,10 +6,19 @@ defmodule Pleroma.Upload.Filter.Dedupe do
@behaviour Pleroma.Upload.Filter @behaviour Pleroma.Upload.Filter
alias Pleroma.Upload alias Pleroma.Upload
def filter(%Upload{name: name} = upload) do def filter(%Upload{name: name, tempfile: tempfile} = upload) do
extension = String.split(name, ".") |> List.last() extension =
shasum = :crypto.hash(:sha256, File.read!(upload.tempfile)) |> Base.encode16(case: :lower) name
|> String.split(".")
|> List.last()
shasum =
:crypto.hash(:sha256, File.read!(tempfile))
|> Base.encode16(case: :lower)
filename = shasum <> "." <> extension filename = shasum <> "." <> extension
{:ok, %Upload{upload | id: shasum, path: filename}} {:ok, %Upload{upload | id: shasum, path: filename}}
end end
def filter(_), do: :ok
end end

View File

@ -4,6 +4,7 @@
defmodule Pleroma.Upload.Filter.Mogrifun do defmodule Pleroma.Upload.Filter.Mogrifun do
@behaviour Pleroma.Upload.Filter @behaviour Pleroma.Upload.Filter
alias Pleroma.Upload.Filter
@filters [ @filters [
{"implode", "1"}, {"implode", "1"},
@ -34,31 +35,10 @@ defmodule Pleroma.Upload.Filter.Mogrifun do
] ]
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
filter = Enum.random(@filters) Filter.Mogrify.do_filter(file, [Enum.random(@filters)])
file
|> Mogrify.open()
|> mogrify_filter(filter)
|> Mogrify.save(in_place: true)
:ok :ok
end end
def filter(_), do: :ok def filter(_), do: :ok
defp mogrify_filter(mogrify, [filter | rest]) do
mogrify
|> mogrify_filter(filter)
|> mogrify_filter(rest)
end
defp mogrify_filter(mogrify, []), do: mogrify
defp mogrify_filter(mogrify, {action, options}) do
Mogrify.custom(mogrify, action, options)
end
defp mogrify_filter(mogrify, string) when is_binary(string) do
Mogrify.custom(mogrify, string)
end
end end

View File

@ -11,16 +11,19 @@ defmodule Pleroma.Upload.Filter.Mogrify do
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
filters = Pleroma.Config.get!([__MODULE__, :args]) filters = Pleroma.Config.get!([__MODULE__, :args])
file do_filter(file, filters)
|> Mogrify.open()
|> mogrify_filter(filters)
|> Mogrify.save(in_place: true)
:ok :ok
end end
def filter(_), do: :ok def filter(_), do: :ok
def do_filter(file, filters) do
file
|> Mogrify.open()
|> mogrify_filter(filters)
|> Mogrify.save(in_place: true)
end
defp mogrify_filter(mogrify, nil), do: mogrify defp mogrify_filter(mogrify, nil), do: mogrify
defp mogrify_filter(mogrify, [filter | rest]) do defp mogrify_filter(mogrify, [filter | rest]) do

View File

@ -68,7 +68,14 @@ defp handle_callback(uploader, upload) do
{:error, error} {:error, error}
end end
after after
30_000 -> {:error, dgettext("errors", "Uploader callback timeout")} callback_timeout() -> {:error, dgettext("errors", "Uploader callback timeout")}
end
end
defp callback_timeout do
case Mix.env() do
:test -> 1_000
_ -> 30_000
end end
end end
end end

View File

@ -1157,19 +1157,18 @@ def get_or_fetch_by_ap_id(ap_id) do
end end
end end
def get_or_create_instance_user do @doc "Creates an internal service actor by URI if missing. Optionally takes nickname for addressing."
relay_uri = "#{Pleroma.Web.Endpoint.url()}/relay" def get_or_create_service_actor_by_ap_id(uri, nickname \\ nil) do
if user = get_cached_by_ap_id(uri) do
if user = get_cached_by_ap_id(relay_uri) do
user user
else else
changes = changes =
%User{info: %User.Info{}} %User{info: %User.Info{}}
|> cast(%{}, [:ap_id, :nickname, :local]) |> cast(%{}, [:ap_id, :nickname, :local])
|> put_change(:ap_id, relay_uri) |> put_change(:ap_id, uri)
|> put_change(:nickname, nil) |> put_change(:nickname, nickname)
|> put_change(:local, true) |> put_change(:local, true)
|> put_change(:follower_address, relay_uri <> "/followers") |> put_change(:follower_address, uri <> "/followers")
{:ok, user} = Repo.insert(changes) {:ok, user} = Repo.insert(changes)
user user
@ -1411,4 +1410,8 @@ defp put_password_hash(
end end
defp put_password_hash(changeset), do: changeset defp put_password_hash(changeset), do: changeset
def is_internal_user?(%User{nickname: nil}), do: true
def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true
def is_internal_user?(_), do: false
end end

View File

@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
alias Pleroma.Object.Fetcher alias Pleroma.Object.Fetcher
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.ObjectView alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
@ -206,9 +207,8 @@ def inbox(conn, params) do
json(conn, dgettext("errors", "error")) json(conn, dgettext("errors", "error"))
end end
def relay(conn, _params) do defp represent_service_actor(%User{} = user, conn) do
with %User{} = user <- Relay.get_actor(), with {:ok, user} <- User.ensure_keys_present(user) do
{:ok, user} <- User.ensure_keys_present(user) do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("user.json", %{user: user})) |> json(UserView.render("user.json", %{user: user}))
@ -217,6 +217,18 @@ def relay(conn, _params) do
end end
end end
defp represent_service_actor(nil, _), do: {:error, :not_found}
def relay(conn, _params) do
Relay.get_actor()
|> represent_service_actor(conn)
end
def internal_fetch(conn, _params) do
InternalFetchActor.get_actor()
|> represent_service_actor(conn)
end
def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")

View File

@ -0,0 +1,20 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.InternalFetchActor do
alias Pleroma.User
require Logger
def init do
# Wait for everything to settle.
Process.sleep(1000 * 5)
get_actor()
end
def get_actor do
"#{Pleroma.Web.Endpoint.url()}/internal/fetch"
|> User.get_or_create_service_actor_by_ap_id("internal.fetch")
end
end

View File

@ -0,0 +1,24 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do
@moduledoc "Block messages which mention a user"
@behaviour Pleroma.Web.ActivityPub.MRF
@impl true
def filter(%{"type" => "Create"} = message) do
reject_actors = Pleroma.Config.get([:mrf_mention, :actors], [])
recipients = (message["to"] || []) ++ (message["cc"] || [])
if Enum.any?(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do
{:reject, nil}
else
{:ok, message}
end
end
@impl true
def filter(message), do: {:ok, message}
end

View File

@ -131,7 +131,7 @@ def publish(actor, %{data: %{"bcc" => bcc}} = activity) when is_list(bcc) and bc
%User{ap_id: ap_id} = %User{ap_id: ap_id} =
Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end) Enum.find(recipients, fn %{info: %{source_data: data}} -> data["inbox"] == inbox end)
# Get all the recipients on the same host and add them to cc. Otherwise it a remote # Get all the recipients on the same host and add them to cc. Otherwise, a remote
# instance would only accept a first message for the first recipient and ignore the rest. # instance would only accept a first message for the first recipient and ignore the rest.
cc = get_cc_ap_ids(ap_id, recipients) cc = get_cc_ap_ids(ap_id, recipients)

View File

@ -10,7 +10,8 @@ defmodule Pleroma.Web.ActivityPub.Relay do
require Logger require Logger
def get_actor do def get_actor do
User.get_or_create_instance_user() "#{Pleroma.Web.Endpoint.url()}/relay"
|> User.get_or_create_service_actor_by_ap_id()
end end
def follow(target_instance) do def follow(target_instance) do

View File

@ -31,8 +31,7 @@ def render("endpoints.json", %{user: %User{local: true} = _user}) do
def render("endpoints.json", _), do: %{} def render("endpoints.json", _), do: %{}
# the instance itself is not a Person, but instead an Application def render("service.json", %{user: user}) do
def render("user.json", %{user: %{nickname: nil} = user}) do
{:ok, user} = User.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Keys.keys_from_pem(user.info.keys) {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
@ -47,7 +46,8 @@ def render("user.json", %{user: %{nickname: nil} = user}) do
"followers" => "#{user.ap_id}/followers", "followers" => "#{user.ap_id}/followers",
"inbox" => "#{user.ap_id}/inbox", "inbox" => "#{user.ap_id}/inbox",
"name" => "Pleroma", "name" => "Pleroma",
"summary" => "Virtual actor for Pleroma relay", "summary" =>
"An internal service actor for this Pleroma instance. No user-serviceable parts inside.",
"url" => user.ap_id, "url" => user.ap_id,
"manuallyApprovesFollowers" => false, "manuallyApprovesFollowers" => false,
"publicKey" => %{ "publicKey" => %{
@ -60,6 +60,13 @@ def render("user.json", %{user: %{nickname: nil} = user}) do
|> Map.merge(Utils.make_json_ld_header()) |> Map.merge(Utils.make_json_ld_header())
end end
# the instance itself is not a Person, but instead an Application
def render("user.json", %{user: %User{nickname: nil} = user}),
do: render("service.json", %{user: user})
def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
do: render("service.json", %{user: user})
def render("user.json", %{user: user}) do def render("user.json", %{user: user}) do
{:ok, user} = User.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Keys.keys_from_pem(user.info.keys) {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)

View File

@ -47,6 +47,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
require Logger require Logger
@rate_limited_relations_actions ~w(follow unfollow)a
@rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status @rate_limited_status_actions ~w(reblog_status unreblog_status fav_status unfav_status
post_status delete_status)a post_status delete_status)a
@ -62,9 +64,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
when action in ~w(fav_status unfav_status)a when action in ~w(fav_status unfav_status)a
) )
plug(
RateLimiter,
{:relations_id_action, params: ["id", "uri"]} when action in @rate_limited_relations_actions
)
plug(RateLimiter, :relations_actions when action in @rate_limited_relations_actions)
plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions) plug(RateLimiter, :statuses_actions when action in @rate_limited_status_actions)
plug(RateLimiter, :app_account_creation when action == :account_register) plug(RateLimiter, :app_account_creation when action == :account_register)
plug(RateLimiter, :search when action in [:search, :search2, :account_search]) plug(RateLimiter, :search when action in [:search, :search2, :account_search])
plug(RateLimiter, :password_reset when action == :password_reset)
@local_mastodon_name "Mastodon-Local" @local_mastodon_name "Mastodon-Local"
@ -1808,6 +1817,22 @@ def conversation_read(%{assigns: %{user: user}} = conn, %{"id" => participation_
end end
end end
def password_reset(conn, params) do
nickname_or_email = params["email"] || params["nickname"]
with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
conn
|> put_status(:no_content)
|> json("")
else
{:error, "unknown user"} ->
send_resp(conn, :not_found, "")
{:error, _} ->
send_resp(conn, :bad_request, "")
end
end
def try_render(conn, target, params) def try_render(conn, target, params)
when is_binary(target) do when is_binary(target) do
case render(conn, target, params) do case render(conn, target, params) do

View File

@ -51,6 +51,7 @@ def render("relationship.json", %{user: %User{} = user, target: %User{} = target
following: User.following?(user, target), following: User.following?(user, target),
followed_by: User.following?(target, user), followed_by: User.following?(target, user),
blocking: User.blocks?(user, target), blocking: User.blocks?(user, target),
blocked_by: User.blocks?(target, user),
muting: User.mutes?(user, target), muting: User.mutes?(user, target),
muting_notifications: User.muted_notifications?(user, target), muting_notifications: User.muted_notifications?(user, target),
subscribing: User.subscribed_to?(user, target), subscribing: User.subscribed_to?(user, target),
@ -136,6 +137,7 @@ defp do_render("account.json", %{user: user} = opts) do
|> maybe_put_notification_settings(user, opts[:for]) |> maybe_put_notification_settings(user, opts[:for])
|> maybe_put_settings_store(user, opts[:for], opts) |> maybe_put_settings_store(user, opts[:for], opts)
|> maybe_put_chat_token(user, opts[:for], opts) |> maybe_put_chat_token(user, opts[:for], opts)
|> maybe_put_activation_status(user, opts[:for])
end end
defp username_from_nickname(string) when is_binary(string) do defp username_from_nickname(string) when is_binary(string) do
@ -196,6 +198,12 @@ defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id:
defp maybe_put_notification_settings(data, _, _), do: data defp maybe_put_notification_settings(data, _, _), do: data
defp maybe_put_activation_status(data, user, %User{info: %{is_admin: true}}) do
Kernel.put_in(data, [:pleroma, :deactivated], user.info.deactivated)
end
defp maybe_put_activation_status(data, _, _), do: data
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil defp image_url(_), do: nil
end end

View File

@ -382,7 +382,7 @@ def render("poll.json", %{object: object} = opts) do
%{ %{
# Mastodon uses separate ids for polls, but an object can't have # Mastodon uses separate ids for polls, but an object can't have
# more than one poll embedded so object id is fine # more than one poll embedded so object id is fine
id: object.id, id: to_string(object.id),
expires_at: Utils.to_masto_date(end_time), expires_at: Utils.to_masto_date(end_time),
expired: expired, expired: expired,
multiple: multiple, multiple: multiple,

View File

@ -30,7 +30,7 @@ def remote(conn, %{"sig" => sig64, "url" => url64} = params) do
def filename_matches(%{"filename" => _} = _, path, url) do def filename_matches(%{"filename" => _} = _, path, url) do
filename = MediaProxy.filename(url) filename = MediaProxy.filename(url)
if filename && Path.basename(path) != filename do if filename && does_not_match(path, filename) do
{:wrong_filename, filename} {:wrong_filename, filename}
else else
:ok :ok
@ -38,4 +38,9 @@ def filename_matches(%{"filename" => _} = _, path, url) do
end end
def filename_matches(_, _, _), do: :ok def filename_matches(_, _, _), do: :ok
defp does_not_match(path, filename) do
basename = Path.basename(path)
basename != filename and URI.decode(basename) != filename and URI.encode(basename) != filename
end
end end

View File

@ -586,7 +586,7 @@ defmodule Pleroma.Web.Router do
end end
end end
pipeline :ap_relay do pipeline :ap_service_actor do
plug(:accepts, ["activity+json", "json"]) plug(:accepts, ["activity+json", "json"])
end end
@ -617,6 +617,7 @@ defmodule Pleroma.Web.Router do
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)
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
end end
scope "/", Pleroma.Web.ActivityPub do scope "/", Pleroma.Web.ActivityPub do
@ -663,8 +664,17 @@ defmodule Pleroma.Web.Router do
end end
scope "/relay", Pleroma.Web.ActivityPub do scope "/relay", Pleroma.Web.ActivityPub do
pipe_through(:ap_relay) pipe_through(:ap_service_actor)
get("/", ActivityPubController, :relay) get("/", ActivityPubController, :relay)
post("/inbox", ActivityPubController, :inbox)
end
scope "/internal/fetch", Pleroma.Web.ActivityPub do
pipe_through(:ap_service_actor)
get("/", ActivityPubController, :internal_fetch)
post("/inbox", ActivityPubController, :inbox)
end end
scope "/", Pleroma.Web.ActivityPub do scope "/", Pleroma.Web.ActivityPub do
@ -691,6 +701,8 @@ defmodule Pleroma.Web.Router do
get("/web/login", MastodonAPIController, :login) get("/web/login", MastodonAPIController, :login)
delete("/auth/sign_out", MastodonAPIController, :logout) delete("/auth/sign_out", MastodonAPIController, :logout)
post("/auth/password", MastodonAPIController, :password_reset)
scope [] do scope [] do
pipe_through(:oauth_read_or_public) pipe_through(:oauth_read_or_public)
get("/web/*path", MastodonAPIController, :index) get("/web/*path", MastodonAPIController, :index)

View File

@ -8,7 +8,9 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
require Logger require Logger
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config
alias Pleroma.Emoji alias Pleroma.Emoji
alias Pleroma.Healthcheck
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Plugs.AuthenticationPlug alias Pleroma.Plugs.AuthenticationPlug
alias Pleroma.User alias Pleroma.User
@ -23,7 +25,8 @@ def help_test(conn, _params) do
end end
def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do
with %User{} = user <- User.get_cached_by_nickname(nick), avatar = User.avatar_url(user) do with %User{} = user <- User.get_cached_by_nickname(nick),
avatar = User.avatar_url(user) do
conn conn
|> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false}) |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false})
else else
@ -338,20 +341,21 @@ def captcha(conn, _params) do
end end
def healthcheck(conn, _params) do def healthcheck(conn, _params) do
info = with true <- Config.get([:instance, :healthcheck]),
if Pleroma.Config.get([:instance, :healthcheck]) do %{healthy: true} = info <- Healthcheck.system_info() do
Pleroma.Healthcheck.system_info() json(conn, info)
else else
%{} %{healthy: false} = info ->
end service_unavailable(conn, info)
conn = _ ->
if info[:healthy] do service_unavailable(conn, %{})
conn end
else end
Plug.Conn.put_status(conn, :service_unavailable)
end
json(conn, info) defp service_unavailable(conn, info) do
conn
|> put_status(:service_unavailable)
|> json(info)
end end
end end

View File

@ -221,6 +221,8 @@ def password_reset(nickname_or_email) do
user user
|> UserEmail.password_reset_email(token_record.token) |> UserEmail.password_reset_email(token_record.token)
|> Mailer.deliver_async() |> Mailer.deliver_async()
{:ok, :enqueued}
else else
false -> false ->
{:error, "bad user identifier"} {:error, "bad user identifier"}

View File

@ -27,6 +27,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
require Logger require Logger
plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset)
plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline]) plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
action_fallback(:errors) action_fallback(:errors)
@ -437,6 +438,12 @@ def password_reset(conn, params) do
with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
json_response(conn, :no_content, "") json_response(conn, :no_content, "")
else
{:error, "unknown user"} ->
send_resp(conn, :not_found, "")
{:error, _} ->
send_resp(conn, :bad_request, "")
end end
end end

View File

@ -11,10 +11,6 @@ def callback(conn, %{"upload_path" => upload_path} = params) do
process_callback(conn, :global.whereis_name({Uploader, upload_path}), params) process_callback(conn, :global.whereis_name({Uploader, upload_path}), params)
end end
def callbacks(conn, _) do
render_error(conn, :bad_request, "bad request")
end
defp process_callback(conn, pid, params) when is_pid(pid) do defp process_callback(conn, pid, params) when is_pid(pid) do
send(pid, {Uploader, self(), conn, params}) send(pid, {Uploader, self(), conn, params})

View File

@ -32,7 +32,7 @@ def host_meta do
def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
host = Pleroma.Web.Endpoint.host() host = Pleroma.Web.Endpoint.host()
regex = ~r/(acct:)?(?<username>\w+)@#{host}/ regex = ~r/(acct:)?(?<username>[a-z0-9A-Z_\.-]+)@#{host}/
with %{"username" => username} <- Regex.named_captures(regex, resource), with %{"username" => username} <- Regex.named_captures(regex, resource),
%User{} = user <- User.get_cached_by_nickname(username) do %User{} = user <- User.get_cached_by_nickname(username) do

View File

@ -138,7 +138,7 @@ defp deps do
ref: "95e8188490e97505c56636c1379ffdf036c1fdde"}, ref: "95e8188490e97505c56636c1379ffdf036c1fdde"},
{:http_signatures, {:http_signatures,
git: "https://git.pleroma.social/pleroma/http_signatures.git", git: "https://git.pleroma.social/pleroma/http_signatures.git",
ref: "9789401987096ead65646b52b5a2ca6bf52fc531"}, ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"},
{:pleroma_job_queue, "~> 0.2.0"}, {:pleroma_job_queue, "~> 0.2.0"},
{:telemetry, "~> 0.3"}, {:telemetry, "~> 0.3"},
{:prometheus_ex, "~> 3.0"}, {:prometheus_ex, "~> 3.0"},

View File

@ -38,7 +38,7 @@
"hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"}, "hackney": {:hex, :hackney, "1.15.1", "9f8f471c844b8ce395f7b6d8398139e26ddca9ebc171a8b91342ee15a19963f4", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.4", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm"},
"html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"}, "html_entities": {:hex, :html_entities, "0.4.0", "f2fee876858cf6aaa9db608820a3209e45a087c5177332799592142b50e89a6b", [:mix], [], "hexpm"},
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"}, "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm"},
"http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "9789401987096ead65646b52b5a2ca6bf52fc531", [ref: "9789401987096ead65646b52b5a2ca6bf52fc531"]}, "http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]},
"httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"}, "httpoison": {:hex, :httpoison, "1.2.0", "2702ed3da5fd7a8130fc34b11965c8cfa21ade2f232c00b42d96d4967c39a3a3", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"}, "idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm"},
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},

View File

@ -150,4 +150,34 @@ test "it can refetch pruned objects" do
assert object.id != object_two.id assert object.id != object_two.id
end end
end end
describe "signed fetches" do
test_with_mock "it signs fetches when configured to do so",
Pleroma.Signature,
[:passthrough],
[] do
option = Pleroma.Config.get([:activitypub, :sign_object_fetches])
Pleroma.Config.put([:activitypub, :sign_object_fetches], true)
Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
assert called(Pleroma.Signature.sign(:_, :_))
Pleroma.Config.put([:activitypub, :sign_object_fetches], option)
end
test_with_mock "it doesn't sign fetches when not configured to do so",
Pleroma.Signature,
[:passthrough],
[] do
option = Pleroma.Config.get([:activitypub, :sign_object_fetches])
Pleroma.Config.put([:activitypub, :sign_object_fetches], false)
Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
refute called(Pleroma.Signature.sign(:_, :_))
Pleroma.Config.put([:activitypub, :sign_object_fetches], option)
end
end
end end

View File

@ -54,4 +54,29 @@ test "with a wrong password in the credentials, it does nothing", %{conn: conn}
assert conn == ret_conn assert conn == ret_conn
end end
describe "checkpw/2" do
test "check pbkdf2 hash" do
hash =
"$pbkdf2-sha512$160000$loXqbp8GYls43F0i6lEfIw$AY.Ep.2pGe57j2hAPY635sI/6w7l9Q9u9Bp02PkPmF3OrClDtJAI8bCiivPr53OKMF7ph6iHhN68Rom5nEfC2A"
assert AuthenticationPlug.checkpw("test-password", hash)
refute AuthenticationPlug.checkpw("test-password1", hash)
end
test "check sha512-crypt hash" do
hash =
"$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
assert AuthenticationPlug.checkpw("password", hash)
refute AuthenticationPlug.checkpw("password1", hash)
end
test "it returns false when hash invalid" do
hash =
"psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
refute Pleroma.Plugs.AuthenticationPlug.checkpw("password", hash)
end
end
end end

View File

@ -26,22 +26,4 @@ test "it call HTTPSignatures to check validity if the actor sighed it" do
assert called(HTTPSignatures.validate_conn(:_)) assert called(HTTPSignatures.validate_conn(:_))
end end
end end
test "bails out early if the signature isn't by the activity actor" do
params = %{"actor" => "https://mst3k.interlinked.me/users/luciferMysticus"}
conn = build_conn(:get, "/doesntmattter", params)
with_mock HTTPSignatures, validate_conn: fn _ -> false end do
conn =
conn
|> put_req_header(
"signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key"
)
|> HTTPSignaturePlug.call(%{})
assert conn.assigns.valid_signature == false
refute called(HTTPSignatures.validate_conn(:_))
end
end
end end

View File

@ -0,0 +1,59 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do
use Pleroma.Web.ConnCase
alias Pleroma.Web.Plugs.MappedSignatureToIdentityPlug
import Tesla.Mock
import Plug.Conn
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
defp set_signature(conn, key_id) do
conn
|> put_req_header("signature", "keyId=\"#{key_id}\"")
|> assign(:valid_signature, true)
end
test "it successfully maps a valid identity with a valid signature" do
conn =
build_conn(:get, "/doesntmattter")
|> set_signature("http://mastodon.example.org/users/admin")
|> MappedSignatureToIdentityPlug.call(%{})
refute is_nil(conn.assigns.user)
end
test "it successfully maps a valid identity with a valid signature with payload" do
conn =
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|> set_signature("http://mastodon.example.org/users/admin")
|> MappedSignatureToIdentityPlug.call(%{})
refute is_nil(conn.assigns.user)
end
test "it considers a mapped identity to be invalid when it mismatches a payload" do
conn =
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|> set_signature("https://niu.moe/users/rye")
|> MappedSignatureToIdentityPlug.call(%{})
assert %{valid_signature: false} == conn.assigns
end
@tag skip: "known breakage; the testsuite presently depends on it"
test "it considers a mapped identity to be invalid when the identity cannot be found" do
conn =
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|> set_signature("http://niu.moe/users/rye")
|> MappedSignatureToIdentityPlug.call(%{})
assert %{valid_signature: false} == conn.assigns
end
end

View File

@ -31,25 +31,29 @@ defmodule Pleroma.SignatureTest do
65_537 65_537
} }
defp make_fake_signature(key_id), do: "keyId=\"#{key_id}\""
defp make_fake_conn(key_id),
do: %Plug.Conn{req_headers: %{"signature" => make_fake_signature(key_id <> "#main-key")}}
describe "fetch_public_key/1" do describe "fetch_public_key/1" do
test "it returns key" do test "it returns key" do
expected_result = {:ok, @rsa_public_key} expected_result = {:ok, @rsa_public_key}
user = insert(:user, %{info: %{source_data: %{"publicKey" => @public_key}}}) user = insert(:user, %{info: %{source_data: %{"publicKey" => @public_key}}})
assert Signature.fetch_public_key(%Plug.Conn{params: %{"actor" => user.ap_id}}) == assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result
expected_result
end end
test "it returns error when not found user" do test "it returns error when not found user" do
assert Signature.fetch_public_key(%Plug.Conn{params: %{"actor" => "test-ap_id"}}) == assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) ==
{:error, :error} {:error, :error}
end end
test "it returns error if public key is empty" do test "it returns error if public key is empty" do
user = insert(:user, %{info: %{source_data: %{"publicKey" => %{}}}}) user = insert(:user, %{info: %{source_data: %{"publicKey" => %{}}}})
assert Signature.fetch_public_key(%Plug.Conn{params: %{"actor" => user.ap_id}}) == assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) ==
{:error, :error} {:error, :error}
end end
end end
@ -58,12 +62,12 @@ test "it returns error if public key is empty" do
test "it returns key" do test "it returns key" do
ap_id = "https://mastodon.social/users/lambadalambda" ap_id = "https://mastodon.social/users/lambadalambda"
assert Signature.refetch_public_key(%Plug.Conn{params: %{"actor" => ap_id}}) == assert Signature.refetch_public_key(make_fake_conn(ap_id)) ==
{:ok, @rsa_public_key} {:ok, @rsa_public_key}
end end
test "it returns error when not found user" do test "it returns error when not found user" do
assert Signature.refetch_public_key(%Plug.Conn{params: %{"actor" => "test-ap_id"}}) == assert Signature.refetch_public_key(make_fake_conn("test-ap_id")) ==
{:error, {:error, :ok}} {:error, {:error, :ok}}
end end
end end

View File

@ -42,19 +42,18 @@ defmodule Pleroma.DataCase do
:ok :ok
end end
def ensure_local_uploader(_context) do def ensure_local_uploader(context) do
test_uploader = Map.get(context, :uploader, Pleroma.Uploaders.Local)
uploader = Pleroma.Config.get([Pleroma.Upload, :uploader]) uploader = Pleroma.Config.get([Pleroma.Upload, :uploader])
filters = Pleroma.Config.get([Pleroma.Upload, :filters]) filters = Pleroma.Config.get([Pleroma.Upload, :filters])
unless uploader == Pleroma.Uploaders.Local || filters != [] do Pleroma.Config.put([Pleroma.Upload, :uploader], test_uploader)
Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) Pleroma.Config.put([Pleroma.Upload, :filters], [])
Pleroma.Config.put([Pleroma.Upload, :filters], [])
on_exit(fn -> on_exit(fn ->
Pleroma.Config.put([Pleroma.Upload, :uploader], uploader) Pleroma.Config.put([Pleroma.Upload, :uploader], uploader)
Pleroma.Config.put([Pleroma.Upload, :filters], filters) Pleroma.Config.put([Pleroma.Upload, :filters], filters)
end) end)
end
:ok :ok
end end

View File

@ -0,0 +1,31 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.DedupeTest do
use Pleroma.DataCase
alias Pleroma.Upload
alias Pleroma.Upload.Filter.Dedupe
@shasum "e30397b58d226d6583ab5b8b3c5defb0c682bda5c31ef07a9f57c1c4986e3781"
test "adds shasum" do
File.cp!(
"test/fixtures/image.jpg",
"test/fixtures/image_tmp.jpg"
)
upload = %Upload{
name: "an… image.jpg",
content_type: "image/jpg",
path: Path.absname("test/fixtures/image_tmp.jpg"),
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
}
assert {
:ok,
%Pleroma.Upload{id: @shasum, path: "#{@shasum}.jpg"}
} = Dedupe.filter(upload)
end
end

View File

@ -0,0 +1,44 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.MogrifunTest do
use Pleroma.DataCase
import Mock
alias Pleroma.Upload
alias Pleroma.Upload.Filter
test "apply mogrify filter" do
File.cp!(
"test/fixtures/image.jpg",
"test/fixtures/image_tmp.jpg"
)
upload = %Upload{
name: "an… image.jpg",
content_type: "image/jpg",
path: Path.absname("test/fixtures/image_tmp.jpg"),
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
}
task =
Task.async(fn ->
assert_receive {:apply_filter, {}}, 4_000
end)
with_mocks([
{Mogrify, [],
[
open: fn _f -> %Mogrify.Image{} end,
custom: fn _m, _a -> send(task.pid, {:apply_filter, {}}) end,
custom: fn _m, _a, _o -> send(task.pid, {:apply_filter, {}}) end,
save: fn _f, _o -> :ok end
]}
]) do
assert Filter.Mogrifun.filter(upload) == :ok
end
Task.await(task)
end
end

View File

@ -0,0 +1,51 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.Filter.MogrifyTest do
use Pleroma.DataCase
import Mock
alias Pleroma.Config
alias Pleroma.Upload
alias Pleroma.Upload.Filter
setup do
filter = Config.get([Filter.Mogrify, :args])
on_exit(fn ->
Config.put([Filter.Mogrify, :args], filter)
end)
end
test "apply mogrify filter" do
Config.put([Filter.Mogrify, :args], [{"tint", "40"}])
File.cp!(
"test/fixtures/image.jpg",
"test/fixtures/image_tmp.jpg"
)
upload = %Upload{
name: "an… image.jpg",
content_type: "image/jpg",
path: Path.absname("test/fixtures/image_tmp.jpg"),
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
}
task =
Task.async(fn ->
assert_receive {:apply_filter, {_, "tint", "40"}}, 4_000
end)
with_mock Mogrify,
open: fn _f -> %Mogrify.Image{} end,
custom: fn _m, _a -> :ok end,
custom: fn m, a, o -> send(task.pid, {:apply_filter, {m, a, o}}) end,
save: fn _f, _o -> :ok end do
assert Filter.Mogrify.filter(upload) == :ok
end
Task.await(task)
end
end

View File

@ -0,0 +1,39 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Upload.FilterTest do
use Pleroma.DataCase
alias Pleroma.Config
alias Pleroma.Upload.Filter
setup do
custom_filename = Config.get([Pleroma.Upload.Filter.AnonymizeFilename, :text])
on_exit(fn ->
Config.put([Pleroma.Upload.Filter.AnonymizeFilename, :text], custom_filename)
end)
end
test "applies filters" do
Config.put([Pleroma.Upload.Filter.AnonymizeFilename, :text], "custom-file.png")
File.cp!(
"test/fixtures/image.jpg",
"test/fixtures/image_tmp.jpg"
)
upload = %Pleroma.Upload{
name: "an… image.jpg",
content_type: "image/jpg",
path: Path.absname("test/fixtures/image_tmp.jpg"),
tempfile: Path.absname("test/fixtures/image_tmp.jpg")
}
assert Filter.filter([], upload) == {:ok, upload}
assert {:ok, upload} = Filter.filter([Pleroma.Upload.Filter.AnonymizeFilename], upload)
assert upload.name == "custom-file.png"
end
end

View File

@ -3,9 +3,96 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.UploadTest do defmodule Pleroma.UploadTest do
alias Pleroma.Upload
use Pleroma.DataCase use Pleroma.DataCase
alias Pleroma.Upload
alias Pleroma.Uploaders.Uploader
@upload_file %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image_tmp.jpg"),
filename: "image.jpg"
}
defmodule TestUploaderBase do
def put_file(%{path: path} = _upload, module_name) do
task_pid =
Task.async(fn ->
:timer.sleep(10)
{Uploader, path}
|> :global.whereis_name()
|> send({Uploader, self(), {:test}, %{}})
assert_receive {Uploader, {:test}}, 4_000
end)
Agent.start(fn -> task_pid end, name: module_name)
:wait_callback
end
end
describe "Tried storing a file when http callback response success result" do
defmodule TestUploaderSuccess do
def http_callback(conn, _params),
do: {:ok, conn, {:file, "post-process-file.jpg"}}
def put_file(upload), do: TestUploaderBase.put_file(upload, __MODULE__)
end
setup do: [uploader: TestUploaderSuccess]
setup [:ensure_local_uploader]
test "it returns file" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
assert Upload.store(@upload_file) ==
{:ok,
%{
"name" => "image.jpg",
"type" => "Document",
"url" => [
%{
"href" => "http://localhost:4001/media/post-process-file.jpg",
"mediaType" => "image/jpeg",
"type" => "Link"
}
]
}}
Task.await(Agent.get(TestUploaderSuccess, fn task_pid -> task_pid end))
end
end
describe "Tried storing a file when http callback response error" do
defmodule TestUploaderError do
def http_callback(conn, _params), do: {:error, conn, "Errors"}
def put_file(upload), do: TestUploaderBase.put_file(upload, __MODULE__)
end
setup do: [uploader: TestUploaderError]
setup [:ensure_local_uploader]
test "it returns error" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
assert Upload.store(@upload_file) == {:error, "Errors"}
Task.await(Agent.get(TestUploaderError, fn task_pid -> task_pid end))
end
end
describe "Tried storing a file when http callback doesn't response by timeout" do
defmodule(TestUploader, do: def(put_file(_upload), do: :wait_callback))
setup do: [uploader: TestUploader]
setup [:ensure_local_uploader]
test "it returns error" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
assert Upload.store(@upload_file) == {:error, "Uploader callback timeout"}
end
end
describe "Storing a file with the Local uploader" do describe "Storing a file with the Local uploader" do
setup [:ensure_local_uploader] setup [:ensure_local_uploader]

View File

@ -1310,4 +1310,21 @@ test "without args", %{user: user} do
assert following == 0 assert following == 0
end end
end end
describe "is_internal_user?/1" do
test "non-internal user returns false" do
user = insert(:user)
refute User.is_internal_user?(user)
end
test "user with no nickname returns true" do
user = insert(:user, %{nickname: nil})
assert User.is_internal_user?(user)
end
test "user with internal-prefixed nickname returns true" do
user = insert(:user, %{nickname: "internal.test"})
assert User.is_internal_user?(user)
end
end
end end

View File

@ -48,6 +48,17 @@ test "with the relay disabled, it returns 404", %{conn: conn} do
end end
end end
describe "/internal/fetch" do
test "it returns the internal fetch user", %{conn: conn} do
res =
conn
|> get(activity_pub_path(conn, :internal_fetch))
|> json_response(200)
assert res["id"] =~ "/fetch"
end
end
describe "/users/:nickname" do describe "/users/:nickname" do
test "it returns a json representation of the user with accept application/json", %{ test "it returns a json representation of the user with accept application/json", %{
conn: conn conn: conn

View File

@ -0,0 +1,92 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicyTest do
use Pleroma.DataCase
alias Pleroma.Web.ActivityPub.MRF.MentionPolicy
test "pass filter if allow list is empty" do
Pleroma.Config.delete([:mrf_mention])
message = %{
"type" => "Create",
"to" => ["https://example.com/ok"],
"cc" => ["https://example.com/blocked"]
}
assert MentionPolicy.filter(message) == {:ok, message}
end
describe "allow" do
test "empty" do
Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
message = %{
"type" => "Create"
}
assert MentionPolicy.filter(message) == {:ok, message}
end
test "to" do
Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
message = %{
"type" => "Create",
"to" => ["https://example.com/ok"]
}
assert MentionPolicy.filter(message) == {:ok, message}
end
test "cc" do
Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
message = %{
"type" => "Create",
"cc" => ["https://example.com/ok"]
}
assert MentionPolicy.filter(message) == {:ok, message}
end
test "both" do
Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
message = %{
"type" => "Create",
"to" => ["https://example.com/ok"],
"cc" => ["https://example.com/ok2"]
}
assert MentionPolicy.filter(message) == {:ok, message}
end
end
describe "deny" do
test "to" do
Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
message = %{
"type" => "Create",
"to" => ["https://example.com/blocked"]
}
assert MentionPolicy.filter(message) == {:reject, nil}
end
test "cc" do
Pleroma.Config.put([:mrf_mention], %{actors: ["https://example.com/blocked"]})
message = %{
"type" => "Create",
"to" => ["https://example.com/ok"],
"cc" => ["https://example.com/blocked"]
}
assert MentionPolicy.filter(message) == {:reject, nil}
end
end
end

View File

@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.AccountView
test "Represent a user account" do test "Represent a user account" do
@ -152,6 +153,13 @@ test "Represent a Service(bot) account" do
assert expected == AccountView.render("account.json", %{user: user}) assert expected == AccountView.render("account.json", %{user: user})
end end
test "Represent a deactivated user for an admin" do
admin = insert(:user, %{info: %{is_admin: true}})
deactivated_user = insert(:user, %{info: %{deactivated: true}})
represented = AccountView.render("account.json", %{user: deactivated_user, for: admin})
assert represented[:pleroma][:deactivated] == true
end
test "Represent a smaller mention" do test "Represent a smaller mention" do
user = insert(:user) user = insert(:user)
@ -165,28 +173,90 @@ test "Represent a smaller mention" do
assert expected == AccountView.render("mention.json", %{user: user}) assert expected == AccountView.render("mention.json", %{user: user})
end end
test "represent a relationship" do describe "relationship" do
user = insert(:user) test "represent a relationship for the following and followed user" do
other_user = insert(:user) user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user) {:ok, user} = User.follow(user, other_user)
{:ok, user} = User.block(user, other_user) {:ok, other_user} = User.follow(other_user, user)
{:ok, other_user} = User.subscribe(user, other_user)
{:ok, user} = User.mute(user, other_user, true)
{:ok, user} = CommonAPI.hide_reblogs(user, other_user)
expected = %{ expected = %{
id: to_string(other_user.id), id: to_string(other_user.id),
following: false, following: true,
followed_by: false, followed_by: true,
blocking: true, blocking: false,
muting: false, blocked_by: false,
muting_notifications: false, muting: true,
subscribing: false, muting_notifications: true,
requested: false, subscribing: true,
domain_blocking: false, requested: false,
showing_reblogs: true, domain_blocking: false,
endorsed: false showing_reblogs: false,
} endorsed: false
}
assert expected == AccountView.render("relationship.json", %{user: user, target: other_user}) assert expected ==
AccountView.render("relationship.json", %{user: user, target: other_user})
end
test "represent a relationship for the blocking and blocked user" do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.follow(user, other_user)
{:ok, other_user} = User.subscribe(user, other_user)
{:ok, user} = User.block(user, other_user)
{:ok, other_user} = User.block(other_user, user)
expected = %{
id: to_string(other_user.id),
following: false,
followed_by: false,
blocking: true,
blocked_by: true,
muting: false,
muting_notifications: false,
subscribing: false,
requested: false,
domain_blocking: false,
showing_reblogs: true,
endorsed: false
}
assert expected ==
AccountView.render("relationship.json", %{user: user, target: other_user})
end
test "represent a relationship for the user with a pending follow request" do
user = insert(:user)
other_user = insert(:user, %{info: %User.Info{locked: true}})
{:ok, user, other_user, _} = CommonAPI.follow(user, other_user)
user = User.get_cached_by_id(user.id)
other_user = User.get_cached_by_id(other_user.id)
expected = %{
id: to_string(other_user.id),
following: false,
followed_by: false,
blocking: false,
blocked_by: false,
muting: false,
muting_notifications: false,
subscribing: false,
requested: true,
domain_blocking: false,
showing_reblogs: true,
endorsed: false
}
assert expected ==
AccountView.render("relationship.json", %{user: user, target: other_user})
end
end end
test "represent an embedded relationship" do test "represent an embedded relationship" do
@ -240,6 +310,7 @@ test "represent an embedded relationship" do
following: false, following: false,
followed_by: false, followed_by: false,
blocking: true, blocking: true,
blocked_by: false,
subscribing: false, subscribing: false,
muting: false, muting: false,
muting_notifications: false, muting_notifications: false,

View File

@ -23,6 +23,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
import Pleroma.Factory import Pleroma.Factory
import ExUnit.CaptureLog import ExUnit.CaptureLog
import Tesla.Mock import Tesla.Mock
import Swoosh.TestAssertions
@image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7" @image "data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7"
@ -3576,7 +3577,7 @@ test "returns poll entity for object id", %{conn: conn} do
|> get("/api/v1/polls/#{object.id}") |> get("/api/v1/polls/#{object.id}")
response = json_response(conn, 200) response = json_response(conn, 200)
id = object.id id = to_string(object.id)
assert %{"id" => ^id, "expired" => false, "multiple" => false} = response assert %{"id" => ^id, "expired" => false, "multiple" => false} = response
end end
@ -3807,4 +3808,55 @@ test "returns empty array when status has not been reblogged yet", %{
assert Enum.empty?(response) assert Enum.empty?(response)
end end
end end
describe "POST /auth/password, with valid parameters" do
setup %{conn: conn} do
user = insert(:user)
conn = post(conn, "/auth/password?email=#{user.email}")
%{conn: conn, user: user}
end
test "it returns 204", %{conn: conn} do
assert json_response(conn, :no_content)
end
test "it creates a PasswordResetToken record for user", %{user: user} do
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
assert token_record
end
test "it sends an email to user", %{user: user} do
token_record = Repo.get_by(Pleroma.PasswordResetToken, user_id: user.id)
email = Pleroma.Emails.UserEmail.password_reset_email(user, token_record.token)
notify_email = Pleroma.Config.get([:instance, :notify_email])
instance_name = Pleroma.Config.get([:instance, :name])
assert_email_sent(
from: {instance_name, notify_email},
to: {user.name, user.email},
html_body: email.html_body
)
end
end
describe "POST /auth/password, with invalid parameters" do
setup do
user = insert(:user)
{:ok, user: user}
end
test "it returns 404 when user is not found", %{conn: conn, user: user} do
conn = post(conn, "/auth/password?email=nonexisting_#{user.email}")
assert conn.status == 404
assert conn.resp_body == ""
end
test "it returns 400 when user is not local", %{conn: conn, user: user} do
{:ok, user} = Repo.update(Changeset.change(user, local: false))
conn = post(conn, "/auth/password?email=#{user.email}")
assert conn.status == 400
assert conn.resp_body == ""
end
end
end end

View File

@ -423,7 +423,7 @@ test "renders a poll" do
expected = %{ expected = %{
emojis: [], emojis: [],
expired: false, expired: false,
id: object.id, id: to_string(object.id),
multiple: false, multiple: false,
options: [ options: [
%{title: "absolutely!", votes_count: 0}, %{title: "absolutely!", votes_count: 0},

View File

@ -114,6 +114,17 @@ test "filename_matches preserves the encoded or decoded path" do
) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"} ) == {:wrong_filename, "my%2Flong%2Furl%2F2019%2F07%2FS.jpg"}
end end
test "encoded url are tried to match for proxy as `conn.request_path` encodes the url" do
# conn.request_path will return encoded url
request_path = "/ANALYSE-DAI-_-LE-STABLECOIN-100-D%C3%89CENTRALIS%C3%89-BQ.jpg"
assert MediaProxyController.filename_matches(
true,
request_path,
"https://mydomain.com/uploads/2019/07/ANALYSE-DAI-_-LE-STABLECOIN-100-DÉCENTRALISÉ-BQ.jpg"
) == :ok
end
test "uses the configured base_url" do test "uses the configured base_url" do
base_url = Pleroma.Config.get([:media_proxy, :base_url]) base_url = Pleroma.Config.get([:media_proxy, :base_url])

View File

@ -1116,15 +1116,17 @@ test "it sends an email to user", %{user: user} do
describe "POST /api/account/password_reset, with invalid parameters" do describe "POST /api/account/password_reset, with invalid parameters" do
setup [:valid_user] setup [:valid_user]
test "it returns 500 when user is not found", %{conn: conn, user: user} do test "it returns 404 when user is not found", %{conn: conn, user: user} do
conn = post(conn, "/api/account/password_reset?email=nonexisting_#{user.email}") conn = post(conn, "/api/account/password_reset?email=nonexisting_#{user.email}")
assert json_response(conn, :internal_server_error) assert conn.status == 404
assert conn.resp_body == ""
end end
test "it returns 500 when user is not local", %{conn: conn, user: user} do test "it returns 400 when user is not local", %{conn: conn, user: user} do
{:ok, user} = Repo.update(Changeset.change(user, local: false)) {:ok, user} = Repo.update(Changeset.change(user, local: false))
conn = post(conn, "/api/account/password_reset?email=#{user.email}") conn = post(conn, "/api/account/password_reset?email=#{user.email}")
assert json_response(conn, :internal_server_error) assert conn.status == 400
assert conn.resp_body == ""
end end
end end

View File

@ -10,6 +10,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
import Pleroma.Factory import Pleroma.Factory
import Mock
setup do setup do
Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
@ -231,10 +232,67 @@ test "show follow account page if the `acct` is a account link", %{conn: conn} d
end end
end end
test "GET /api/pleroma/healthcheck", %{conn: conn} do describe "GET /api/pleroma/healthcheck" do
conn = get(conn, "/api/pleroma/healthcheck") setup do
config_healthcheck = Pleroma.Config.get([:instance, :healthcheck])
assert conn.status in [200, 503] on_exit(fn ->
Pleroma.Config.put([:instance, :healthcheck], config_healthcheck)
end)
:ok
end
test "returns 503 when healthcheck disabled", %{conn: conn} do
Pleroma.Config.put([:instance, :healthcheck], false)
response =
conn
|> get("/api/pleroma/healthcheck")
|> json_response(503)
assert response == %{}
end
test "returns 200 when healthcheck enabled and all ok", %{conn: conn} do
Pleroma.Config.put([:instance, :healthcheck], true)
with_mock Pleroma.Healthcheck,
system_info: fn -> %Pleroma.Healthcheck{healthy: true} end do
response =
conn
|> get("/api/pleroma/healthcheck")
|> json_response(200)
assert %{
"active" => _,
"healthy" => true,
"idle" => _,
"memory_used" => _,
"pool_size" => _
} = response
end
end
test "returns 503 when healthcheck enabled and health is false", %{conn: conn} do
Pleroma.Config.put([:instance, :healthcheck], true)
with_mock Pleroma.Healthcheck,
system_info: fn -> %Pleroma.Healthcheck{healthy: false} end do
response =
conn
|> get("/api/pleroma/healthcheck")
|> json_response(503)
assert %{
"active" => _,
"healthy" => false,
"idle" => _,
"memory_used" => _,
"pool_size" => _
} = response
end
end
end end
describe "POST /api/pleroma/disable_account" do describe "POST /api/pleroma/disable_account" do

View File

@ -0,0 +1,43 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.UploaderControllerTest do
use Pleroma.Web.ConnCase
alias Pleroma.Uploaders.Uploader
describe "callback/2" do
test "it returns 400 response when process callback isn't alive", %{conn: conn} do
res =
conn
|> post(uploader_path(conn, :callback, "test-path"))
assert res.status == 400
assert res.resp_body == "{\"error\":\"bad request\"}"
end
test "it returns success result", %{conn: conn} do
task =
Task.async(fn ->
receive do
{Uploader, pid, conn, _params} ->
conn =
conn
|> put_status(:ok)
|> Phoenix.Controller.json(%{upload_path: "test-path"})
send(pid, {Uploader, conn})
end
end)
:global.register_name({Uploader, "test-path"}, task.pid)
res =
conn
|> post(uploader_path(conn, :callback, "test-path"))
|> json_response(200)
assert res == %{"upload_path" => "test-path"}
end
end
end