Merge branch 'develop' into feature/digest-email

This commit is contained in:
Roman Chvanikov 2019-07-24 16:37:52 +03:00
commit d2da3d30f3
40 changed files with 1195 additions and 319 deletions

View file

@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set - Federation: Return 403 errors when trying to request pages from a user's follower/following collections if they have `hide_followers`/`hide_follows` set
- NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option - NodeInfo: Return `skipThreadContainment` in `metadata` for the `skip_thread_containment` option
- Mastodon API: Unsubscribe followers when they unfollow a user - Mastodon API: Unsubscribe followers when they unfollow a user
- AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses)
### Fixed ### Fixed
- Not being able to pin unlisted posts - Not being able to pin unlisted posts
@ -19,13 +20,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`) - Mastodon API: Handling of search timeouts (`/api/v1/search` and `/api/v2/search`)
- Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity - Mastodon API: Embedded relationships not being properly rendered in the Account entity of Status entity
- Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`) - Mastodon API: Add `account_id`, `type`, `offset`, and `limit` to search API (`/api/v1/search` and `/api/v2/search`)
- Mastodon API, streaming: Fix filtering of notifications based on blocks/mutes/thread mutes
- ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set - ActivityPub C2S: follower/following collection pages being inaccessible even when authentifucated if `hide_followers`/ `hide_follows` was set
- Existing user id not being preserved on insert conflict - Existing user id not being preserved on insert conflict
- Rich Media: Parser failing when no TTL can be found by image TTL setters
- Rich Media: The crawled URL is now spliced into the rich media data.
- ActivityPub S2S: sharedInbox usage has been mostly aligned with the rules in the AP specification.
### 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`) - MRF: Support for filtering posts based on who they mention (`Pleroma.Web.ActivityPub.MRF.MentionPolicy`)
- MRF (Simple Policy): Support for wildcard domains.
- Support for wildcard domains in user domain blocks setting.
- Configuration: `quarantined_instances` support wildcard domains.
- 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
@ -47,6 +55,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Twitter API: added rate limit for `/api/account/password_reset` endpoint. - Twitter API: added rate limit for `/api/account/password_reset` endpoint.
- ActivityPub: Add an internal service actor for fetching ActivityPub objects. - ActivityPub: Add an internal service actor for fetching ActivityPub objects.
- ActivityPub: Optional signing of ActivityPub object fetches. - ActivityPub: Optional signing of ActivityPub object fetches.
- Admin API: Endpoint for fetching latest user's statuses
### 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

@ -187,6 +187,19 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- On failure: `Not found` - On failure: `Not found`
- On success: JSON of the user - On success: JSON of the user
## `/api/pleroma/admin/users/:nickname_or_id/statuses`
### Retrive user's latest statuses
- Method: `GET`
- Params:
- `nickname` or `id`
- *optional* `page_size`: number of statuses to return (default is `20`)
- *optional* `godmode`: `true`/`false` allows to see private statuses
- Response:
- On failure: `Not found`
- On success: JSON array of user's latest statuses
## `/api/pleroma/admin/relay` ## `/api/pleroma/admin/relay`
### Follow a Relay ### Follow a Relay
@ -564,6 +577,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
## `/api/pleroma/admin/config` ## `/api/pleroma/admin/config`
### List config settings ### List config settings
List config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
- Method `GET` - Method `GET`
- Params: none - Params: none
- Response: - Response:
@ -582,6 +596,7 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
## `/api/pleroma/admin/config` ## `/api/pleroma/admin/config`
### Update config settings ### Update config settings
Updating config settings only works with `:pleroma => :instance => :dynamic_configuration` setting to `true`.
Module name can be passed as string, which starts with `Pleroma`, e.g. `"Pleroma.Upload"`. Module name can be passed as string, which starts with `Pleroma`, e.g. `"Pleroma.Upload"`.
Atom keys and values can be passed with `:` in the beginning, e.g. `":upload"`. Atom keys and values can be passed with `:` in the beginning, e.g. `":upload"`.
Tuples can be passed as `{"tuple": ["first_val", Pleroma.Module, []]}`. Tuples can be passed as `{"tuple": ["first_val", Pleroma.Module, []]}`.

View file

@ -1,35 +1,12 @@
# Small customizations # Small customizations
Replace `dev.secret.exs` with `prod.secret.exs` according to your setup.
# Thumbnail See also static_dir.md for visual settings.
Replace `priv/static/instance/thumbnail.jpeg` with your selfie or other neat picture. It will appear in [Pleroma Instances](http://distsn.org/pleroma-instances.html). ## Theme
# Instance-specific panel
![instance-specific panel demo](/uploads/296b19ec806b130e0b49b16bfe29ce8a/image.png)
To show the instance specific panel, set `show_instance_panel` to `true` in `config/dev.secret.exs`. You can modify its content by editing `priv/static/instance/panel.html`.
# Background
You can change the background of your Pleroma instance by uploading it to `priv/static/static`, and then changing `"background"` in `config/dev.secret.exs` accordingly.
# Logo
![logo modification demo](/uploads/c70b14de60fa74245e7f0dcfa695ebff/image.png)
If you want to give a brand to your instance, look no further. You can change the logo of your instance by uploading it to `priv/static/static`, and then changing `logo` in `config/dev.secret.exs` accordingly.
# Theme
All users of your instance will be able to change the theme they use by going to the settings (the cog in the top-right hand corner). However, if you wish to change the default theme, you can do so by editing `theme` in `config/dev.secret.exs` accordingly. All users of your instance will be able to change the theme they use by going to the settings (the cog in the top-right hand corner). However, if you wish to change the default theme, you can do so by editing `theme` in `config/dev.secret.exs` accordingly.
# Terms of Service ## Message Visibility
Terms of Service will be shown to all users on the registration page. It's the best place where to write down the rules for your instance. You can modify the rules by changing `priv/static/static/terms-of-service.html`.
# Message Visibility
To enable message visibility options when posting like in the Mastodon frontend, set To enable message visibility options when posting like in the Mastodon frontend, set
`scope_options_enabled` to `true` in `config/dev.secret.exs`. `scope_options_enabled` to `true` in `config/dev.secret.exs`.

View file

@ -7,7 +7,13 @@ config :pleroma, :instance,
static_dir: "instance/static/", static_dir: "instance/static/",
``` ```
You can overwrite this value in your configuration to use a different static instance directory. For example, edit `instance/static/instance/panel.html` .
Alternatively, you can overwrite this value in your configuration to use a different static instance directory.
This document is written assuming `instance/static/`.
Or, if you want to manage your custom file in git repository, basically remove the `instance/` entry from `.gitignore`.
## robots.txt ## robots.txt
@ -18,3 +24,46 @@ If you want to generate a restrictive `robots.txt`, you can run the following mi
``` ```
mix pleroma.robots_txt disallow_all mix pleroma.robots_txt disallow_all
``` ```
## Thumbnail
Put on `instance/static/instance/thumbnail.jpeg` with your selfie or other neat picture. It will appear in [Pleroma Instances](http://distsn.org/pleroma-instances.html).
## Instance-specific panel
![instance-specific panel demo](/uploads/296b19ec806b130e0b49b16bfe29ce8a/image.png)
Create and Edit your file on `instance/static/instance/panel.html`.
## Background
You can change the background of your Pleroma instance by uploading it to `instance/static/`, and then changing `background` in `config/prod.secret.exs` accordingly.
If you put `instance/static/images/background.jpg`
```
config :pleroma, :frontend_configurations,
pleroma_fe: %{
background: "/images/background.jpg"
}
```
## Logo
![logo modification demo](/uploads/c70b14de60fa74245e7f0dcfa695ebff/image.png)
If you want to give a brand to your instance, You can change the logo of your instance by uploading it to `instance/static/`.
Alternatively, you can specify the path with config.
If you put `instance/static/static/mylogo-file.png`
```
config :pleroma, :frontend_configurations,
pleroma_fe: %{
logo: "/static/mylogo-file.png"
}
```
## Terms of Service
Terms of Service will be shown to all users on the registration page. It's the best place where to write down the rules for your instance. You can modify the rules by changing `instance/static/static/terms-of-service.html`.

View file

@ -10,9 +10,18 @@ defmodule Pleroma.Signature do
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
def key_id_to_actor_id(key_id) do def key_id_to_actor_id(key_id) do
URI.parse(key_id) uri =
|> Map.put(:fragment, nil) URI.parse(key_id)
|> URI.to_string() |> Map.put(:fragment, nil)
uri =
if String.ends_with?(uri.path, "/publickey") do
Map.put(uri, :path, String.replace(uri.path, "/publickey", ""))
else
uri
end
URI.to_string(uri)
end end
def fetch_public_key(conn) do def fetch_public_key(conn) do

View file

@ -587,12 +587,23 @@ def get_followers_query(user, page) do
@spec get_followers_query(User.t()) :: Ecto.Query.t() @spec get_followers_query(User.t()) :: Ecto.Query.t()
def get_followers_query(user), do: get_followers_query(user, nil) def get_followers_query(user), do: get_followers_query(user, nil)
@spec get_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
def get_followers(user, page \\ nil) do def get_followers(user, page \\ nil) do
q = get_followers_query(user, page) q = get_followers_query(user, page)
{:ok, Repo.all(q)} {:ok, Repo.all(q)}
end end
@spec get_external_followers(User.t(), pos_integer()) :: {:ok, list(User.t())}
def get_external_followers(user, page \\ nil) do
q =
user
|> get_followers_query(page)
|> User.Query.build(%{external: true})
{:ok, Repo.all(q)}
end
def get_followers_ids(user, page \\ nil) do def get_followers_ids(user, page \\ nil) do
q = get_followers_query(user, page) q = get_followers_query(user, page)
@ -874,12 +885,17 @@ def muted_notifications?(user, %{ap_id: ap_id}),
def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do def blocks?(%User{info: info} = _user, %{ap_id: ap_id}) do
blocks = info.blocks blocks = info.blocks
domain_blocks = info.domain_blocks
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(info.domain_blocks)
%{host: host} = URI.parse(ap_id) %{host: host} = URI.parse(ap_id)
Enum.member?(blocks, ap_id) || Enum.any?(domain_blocks, &(&1 == host)) Enum.member?(blocks, ap_id) ||
Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host)
end end
def blocks?(nil, _), do: false
def subscribed_to?(user, %{ap_id: ap_id}) do def subscribed_to?(user, %{ap_id: ap_id}) do
with %User{} = target <- get_cached_by_ap_id(ap_id) do with %User{} = target <- get_cached_by_ap_id(ap_id) do
Enum.member?(target.info.subscribers, user.ap_id) Enum.member?(target.info.subscribers, user.ap_id)

View file

@ -74,7 +74,7 @@ def find_by_token!(token), do: Repo.get_by!(UserInviteToken, token: token)
@spec find_by_token(token()) :: {:ok, UserInviteToken.t()} | nil @spec find_by_token(token()) :: {:ok, UserInviteToken.t()} | nil
def find_by_token(token) do def find_by_token(token) do
with invite <- Repo.get_by(UserInviteToken, token: token) do with %UserInviteToken{} = invite <- Repo.get_by(UserInviteToken, token: token) do
{:ok, invite} {:ok, invite}
end end
end end

View file

@ -631,17 +631,28 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do
|> Map.put("pinned_activity_ids", user.info.pinned_activities) |> Map.put("pinned_activity_ids", user.info.pinned_activities)
recipients = recipients =
if reading_user do user_activities_recipients(%{
["https://www.w3.org/ns/activitystreams#Public"] ++ "godmode" => params["godmode"],
[reading_user.ap_id | reading_user.following] "reading_user" => reading_user
else })
["https://www.w3.org/ns/activitystreams#Public"]
end
fetch_activities(recipients, params) fetch_activities(recipients, params)
|> Enum.reverse() |> Enum.reverse()
end end
defp user_activities_recipients(%{"godmode" => true}) do
[]
end
defp user_activities_recipients(%{"reading_user" => reading_user}) do
if reading_user do
["https://www.w3.org/ns/activitystreams#Public"] ++
[reading_user.ap_id | reading_user.following]
else
["https://www.w3.org/ns/activitystreams#Public"]
end
end
defp restrict_since(query, %{"since_id" => ""}), do: query defp restrict_since(query, %{"since_id" => ""}), do: query
defp restrict_since(query, %{"since_id" => since_id}) do defp restrict_since(query, %{"since_id" => since_id}) do

View file

@ -25,4 +25,14 @@ def get_policies do
defp get_policies(policy) when is_atom(policy), do: [policy] defp get_policies(policy) when is_atom(policy), do: [policy]
defp get_policies(policies) when is_list(policies), do: policies defp get_policies(policies) when is_list(policies), do: policies
defp get_policies(_), do: [] defp get_policies(_), do: []
@spec subdomains_regex([String.t()]) :: [Regex.t()]
def subdomains_regex(domains) when is_list(domains) do
for domain <- domains, do: ~r(^#{String.replace(domain, "*.", "(.*\\.)*")}$)
end
@spec subdomain_match?([Regex.t()], String.t()) :: boolean()
def subdomain_match?(domains, host) do
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
end
end end

View file

@ -4,22 +4,29 @@
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
@moduledoc "Filter activities depending on their origin instance" @moduledoc "Filter activities depending on their origin instance"
@behaviour Pleroma.Web.ActivityPub.MRF @behaviour MRF
defp check_accept(%{host: actor_host} = _actor_info, object) do defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts = Pleroma.Config.get([:mrf_simple, :accept]) accepts =
Pleroma.Config.get([:mrf_simple, :accept])
|> MRF.subdomains_regex()
cond do cond do
accepts == [] -> {:ok, object} accepts == [] -> {:ok, object}
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object} actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
Enum.member?(accepts, actor_host) -> {:ok, object} MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
true -> {:reject, nil} true -> {:reject, nil}
end end
end end
defp check_reject(%{host: actor_host} = _actor_info, object) do defp check_reject(%{host: actor_host} = _actor_info, object) do
if Enum.member?(Pleroma.Config.get([:mrf_simple, :reject]), actor_host) do rejects =
Pleroma.Config.get([:mrf_simple, :reject])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
{:reject, nil} {:reject, nil}
else else
{:ok, object} {:ok, object}
@ -31,8 +38,12 @@ defp check_media_removal(
%{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object %{"type" => "Create", "object" => %{"attachment" => child_attachment}} = object
) )
when length(child_attachment) > 0 do when length(child_attachment) > 0 do
media_removal =
Pleroma.Config.get([:mrf_simple, :media_removal])
|> MRF.subdomains_regex()
object = object =
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_removal]), actor_host) do if MRF.subdomain_match?(media_removal, actor_host) do
child_object = Map.delete(object["object"], "attachment") child_object = Map.delete(object["object"], "attachment")
Map.put(object, "object", child_object) Map.put(object, "object", child_object)
else else
@ -51,8 +62,12 @@ defp check_media_nsfw(
"object" => child_object "object" => child_object
} = object } = object
) do ) do
media_nsfw =
Pleroma.Config.get([:mrf_simple, :media_nsfw])
|> MRF.subdomains_regex()
object = object =
if Enum.member?(Pleroma.Config.get([:mrf_simple, :media_nsfw]), actor_host) do if MRF.subdomain_match?(media_nsfw, actor_host) do
tags = (child_object["tag"] || []) ++ ["nsfw"] tags = (child_object["tag"] || []) ++ ["nsfw"]
child_object = Map.put(child_object, "tag", tags) child_object = Map.put(child_object, "tag", tags)
child_object = Map.put(child_object, "sensitive", true) child_object = Map.put(child_object, "sensitive", true)
@ -67,12 +82,12 @@ defp check_media_nsfw(
defp check_media_nsfw(_actor_info, object), do: {:ok, object} defp check_media_nsfw(_actor_info, object), do: {:ok, object}
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
timeline_removal =
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
|> MRF.subdomains_regex()
object = object =
with true <- with true <- MRF.subdomain_match?(timeline_removal, actor_host),
Enum.member?(
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal]),
actor_host
),
user <- User.get_cached_by_ap_id(object["actor"]), user <- User.get_cached_by_ap_id(object["actor"]),
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do
to = to =
@ -94,7 +109,11 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
end end
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
if actor_host in Pleroma.Config.get([:mrf_simple, :report_removal]) do report_removal =
Pleroma.Config.get([:mrf_simple, :report_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(report_removal, actor_host) do
{:reject, nil} {:reject, nil}
else else
{:ok, object} {:ok, object}
@ -104,7 +123,11 @@ defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"}
defp check_report_removal(_actor_info, object), do: {:ok, object} defp check_report_removal(_actor_info, object), do: {:ok, object}
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
if actor_host in Pleroma.Config.get([:mrf_simple, :avatar_removal]) do avatar_removal =
Pleroma.Config.get([:mrf_simple, :avatar_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(avatar_removal, actor_host) do
{:ok, Map.delete(object, "icon")} {:ok, Map.delete(object, "icon")}
else else
{:ok, object} {:ok, object}
@ -114,7 +137,11 @@ defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon}
defp check_avatar_removal(_actor_info, object), do: {:ok, object} defp check_avatar_removal(_actor_info, object), do: {:ok, object}
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
if actor_host in Pleroma.Config.get([:mrf_simple, :banner_removal]) do banner_removal =
Pleroma.Config.get([:mrf_simple, :banner_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do
{:ok, Map.delete(object, "image")} {:ok, Map.delete(object, "image")}
else else
{:ok, object} {:ok, object}

View file

@ -87,18 +87,23 @@ defp should_federate?(inbox, public) do
if public do if public do
true true
else else
inbox_info = URI.parse(inbox) %{host: host} = URI.parse(inbox)
!Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host)
quarantined_instances =
Config.get([:instance, :quarantined_instances], [])
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
!Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host)
end end
end end
@spec recipients(User.t(), Activity.t()) :: list(User.t()) | []
defp recipients(actor, activity) do defp recipients(actor, activity) do
followers = {:ok, followers} =
if actor.follower_address in activity.recipients do if actor.follower_address in activity.recipients do
{:ok, followers} = User.get_followers(actor) User.get_external_followers(actor)
Enum.filter(followers, &(!&1.local))
else else
[] {:ok, []}
end end
Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers Pleroma.Web.Salmon.remote_users(actor, activity) ++ followers
@ -112,6 +117,45 @@ defp get_cc_ap_ids(ap_id, recipients) do
|> Enum.map(& &1.ap_id) |> Enum.map(& &1.ap_id)
end end
@as_public "https://www.w3.org/ns/activitystreams#Public"
defp maybe_use_sharedinbox(%User{info: %{source_data: data}}),
do: (is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"]
@doc """
Determine a user inbox to use based on heuristics. These heuristics
are based on an approximation of the ``sharedInbox`` rules in the
[ActivityPub specification][ap-sharedinbox].
Please do not edit this function (or its children) without reading
the spec, as editing the code is likely to introduce some breakage
without some familiarity.
[ap-sharedinbox]: https://www.w3.org/TR/activitypub/#shared-inbox-delivery
"""
def determine_inbox(
%Activity{data: activity_data},
%User{info: %{source_data: data}} = user
) do
to = activity_data["to"] || []
cc = activity_data["cc"] || []
type = activity_data["type"]
cond do
type == "Delete" ->
maybe_use_sharedinbox(user)
@as_public in to || @as_public in cc ->
maybe_use_sharedinbox(user)
length(to) + length(cc) > 1 ->
maybe_use_sharedinbox(user)
true ->
data["inbox"]
end
end
@doc """ @doc """
Publishes an activity with BCC to all relevant peers. Publishes an activity with BCC to all relevant peers.
""" """
@ -166,8 +210,8 @@ def publish(%User{} = actor, %Activity{} = activity) do
recipients(actor, activity) recipients(actor, activity)
|> Enum.filter(fn user -> User.ap_enabled?(user) end) |> Enum.filter(fn user -> User.ap_enabled?(user) end)
|> Enum.map(fn %{info: %{source_data: data}} -> |> Enum.map(fn %User{} = user ->
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"] determine_inbox(activity, user)
end) end)
|> Enum.uniq() |> Enum.uniq()
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end) |> Enum.filter(fn inbox -> should_federate?(inbox, public) end)

View file

@ -8,14 +8,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
@public "https://www.w3.org/ns/activitystreams#Public"
@spec is_public?(Object.t() | Activity.t() | map()) :: boolean()
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
def is_public?(%Object{data: data}), do: is_public?(data) def is_public?(%Object{data: data}), do: is_public?(data)
def is_public?(%Activity{data: data}), do: is_public?(data) def is_public?(%Activity{data: data}), do: is_public?(data)
def is_public?(%{"directMessage" => true}), do: false def is_public?(%{"directMessage" => true}), do: false
def is_public?(data), do: @public in (data["to"] ++ (data["cc"] || []))
def is_public?(data) do
"https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || []))
end
def is_private?(activity) do def is_private?(activity) do
with false <- is_public?(activity), with false <- is_public?(activity),
@ -69,15 +69,14 @@ def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do
end end
def get_visibility(object) do def get_visibility(object) do
public = "https://www.w3.org/ns/activitystreams#Public"
to = object.data["to"] || [] to = object.data["to"] || []
cc = object.data["cc"] || [] cc = object.data["cc"] || []
cond do cond do
public in to -> @public in to ->
"public" "public"
public in cc -> @public in cc ->
"unlisted" "unlisted"
# this should use the sql for the object's activity # this should use the sql for the object's activity

View file

@ -82,6 +82,25 @@ def user_show(conn, %{"nickname" => nickname}) do
end end
end end
def list_user_statuses(conn, %{"nickname" => nickname} = params) do
godmode = params["godmode"] == "true" || params["godmode"] == true
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
{_, page_size} = page_params(params)
activities =
ActivityPub.fetch_user_activities(user, nil, %{
"limit" => page_size,
"godmode" => godmode
})
conn
|> json(StatusView.render("index.json", %{activities: activities, as: :activity}))
else
_ -> {:error, :not_found}
end
end
def user_toggle_activation(conn, %{"nickname" => nickname}) do def user_toggle_activation(conn, %{"nickname" => nickname}) do
user = User.get_cached_by_nickname(nickname) user = User.get_cached_by_nickname(nickname)
@ -272,11 +291,13 @@ def invites(conn, _params) do
@doc "Revokes invite by token" @doc "Revokes invite by token"
def revoke_invite(conn, %{"token" => token}) do def revoke_invite(conn, %{"token" => token}) do
invite = UserInviteToken.find_by_token!(token) with {:ok, invite} <- UserInviteToken.find_by_token(token),
{:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
conn
conn |> json(AccountView.render("invite.json", %{invite: updated_invite}))
|> json(AccountView.render("invite.json", %{invite: updated_invite})) else
nil -> {:error, :not_found}
end
end end
@doc "Get a password reset token (base64 string) for given nickname" @doc "Get a password reset token (base64 string) for given nickname"

View file

@ -84,6 +84,7 @@ defp do_convert(entity) when is_map(entity) do
end end
defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]} defp do_convert({:dispatch, [entity]}), do: %{"tuple" => [":dispatch", [inspect(entity)]]}
defp do_convert({:partial_chain, entity}), do: %{"tuple" => [":partial_chain", inspect(entity)]}
defp do_convert(entity) when is_tuple(entity), defp do_convert(entity) when is_tuple(entity),
do: %{"tuple" => do_convert(Tuple.to_list(entity))} do: %{"tuple" => do_convert(Tuple.to_list(entity))}
@ -113,11 +114,15 @@ def transform(entity), do: :erlang.term_to_binary(entity)
defp do_transform(%Regex{} = entity) when is_map(entity), do: entity defp do_transform(%Regex{} = entity) when is_map(entity), do: entity
defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do
cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "") {dispatch_settings, []} = do_eval(entity)
{dispatch_settings, []} = Code.eval_string(cleaned_string, [], requires: [], macros: [])
{:dispatch, [dispatch_settings]} {:dispatch, [dispatch_settings]}
end end
defp do_transform(%{"tuple" => [":partial_chain", entity]}) do
{partial_chain, []} = do_eval(entity)
{:partial_chain, partial_chain}
end
defp do_transform(%{"tuple" => entity}) do defp do_transform(%{"tuple" => entity}) do
Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end) Enum.reduce(entity, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
end end
@ -149,4 +154,9 @@ defp do_transform_string(value) do
do: String.to_existing_atom("Elixir." <> value), do: String.to_existing_atom("Elixir." <> value),
else: value else: value
end end
defp do_eval(entity) do
cleaned_string = String.replace(entity, ~r/[^\w|^{:,[|^,|^[|^\]^}|^\/|^\.|^"]^\s/, "")
Code.eval_string(cleaned_string, [], requires: [], macros: [])
end
end end

View file

@ -439,6 +439,13 @@ def maybe_notify_mentioned_recipients(
def maybe_notify_mentioned_recipients(recipients, _), do: recipients def maybe_notify_mentioned_recipients(recipients, _), do: recipients
# Do not notify subscribers if author is making a reply
def maybe_notify_subscribers(recipients, %Activity{
object: %Object{data: %{"inReplyTo" => _ap_id}}
}) do
recipients
end
def maybe_notify_subscribers( def maybe_notify_subscribers(
recipients, recipients,
%Activity{data: %{"actor" => actor, "type" => type}} = activity %Activity{data: %{"actor" => actor, "type" => type}} = activity

View file

@ -883,7 +883,10 @@ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id), with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
%Object{data: %{"likes" => likes}} <- Object.normalize(object) do %Object{data: %{"likes" => likes}} <- Object.normalize(object) do
q = from(u in User, where: u.ap_id in ^likes) q = from(u in User, where: u.ap_id in ^likes)
users = Repo.all(q)
users =
Repo.all(q)
|> Enum.filter(&(not User.blocks?(user, &1)))
conn conn
|> put_view(AccountView) |> put_view(AccountView)
@ -897,7 +900,10 @@ def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id), with %Activity{data: %{"object" => object}} <- Activity.get_by_id(id),
%Object{data: %{"announcements" => announces}} <- Object.normalize(object) do %Object{data: %{"announcements" => announces}} <- Object.normalize(object) do
q = from(u in User, where: u.ap_id in ^announces) q = from(u in User, where: u.ap_id in ^announces)
users = Repo.all(q)
users =
Repo.all(q)
|> Enum.filter(&(not User.blocks?(user, &1)))
conn conn
|> put_view(AccountView) |> put_view(AccountView)

View file

@ -55,8 +55,8 @@ def ttl(data, url) do
ttl_setters: [MyModule] ttl_setters: [MyModule]
""" """
def set_ttl_based_on_image({:ok, data}, url) do def set_ttl_based_on_image({:ok, data}, url) do
with {:ok, nil} <- Cachex.ttl(:rich_media_cache, url) do with {:ok, nil} <- Cachex.ttl(:rich_media_cache, url),
ttl = get_ttl_from_image(data, url) ttl when is_number(ttl) <- get_ttl_from_image(data, url) do
Cachex.expire_at(:rich_media_cache, url, ttl * 1000) Cachex.expire_at(:rich_media_cache, url, ttl * 1000)
{:ok, data} {:ok, data}
else else
@ -82,6 +82,7 @@ defp parse_url(url) do
html html
|> maybe_parse() |> maybe_parse()
|> Map.put(:url, url)
|> clean_parsed_data() |> clean_parsed_data()
|> check_parsed_data() |> check_parsed_data()
rescue rescue

View file

@ -154,22 +154,12 @@ defmodule Pleroma.Web.Router do
post("/users/follow", AdminAPIController, :user_follow) post("/users/follow", AdminAPIController, :user_follow)
post("/users/unfollow", AdminAPIController, :user_unfollow) post("/users/unfollow", AdminAPIController, :user_unfollow)
# TODO: to be removed at version 1.0
delete("/user", AdminAPIController, :user_delete)
post("/user", AdminAPIController, :user_create)
delete("/users", AdminAPIController, :user_delete) delete("/users", AdminAPIController, :user_delete)
post("/users", AdminAPIController, :user_create) post("/users", AdminAPIController, :user_create)
patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation) patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
put("/users/tag", AdminAPIController, :tag_users) put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users) delete("/users/tag", AdminAPIController, :untag_users)
# TODO: to be removed at version 1.0
get("/permission_group/:nickname", AdminAPIController, :right_get)
get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get)
post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
get("/users/:nickname/permission_group", AdminAPIController, :right_get) get("/users/:nickname/permission_group", AdminAPIController, :right_get)
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get) get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
post("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_add) post("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_add)
@ -190,13 +180,11 @@ defmodule Pleroma.Web.Router do
post("/users/revoke_invite", AdminAPIController, :revoke_invite) post("/users/revoke_invite", AdminAPIController, :revoke_invite)
post("/users/email_invite", AdminAPIController, :email_invite) post("/users/email_invite", AdminAPIController, :email_invite)
# TODO: to be removed at version 1.0
get("/password_reset", AdminAPIController, :get_password_reset)
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset) get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
get("/users", AdminAPIController, :list_users) get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show) get("/users/:nickname", AdminAPIController, :user_show)
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/reports", AdminAPIController, :list_reports) get("/reports", AdminAPIController, :list_reports)
get("/reports/:id", AdminAPIController, :report_show) get("/reports/:id", AdminAPIController, :report_show)
@ -665,6 +653,12 @@ defmodule Pleroma.Web.Router do
end end
end end
scope "/", Pleroma.Web.ActivityPub do
pipe_through(:activitypub)
post("/inbox", ActivityPubController, :inbox)
post("/users/:nickname/inbox", ActivityPubController, :inbox)
end
scope "/relay", Pleroma.Web.ActivityPub do scope "/relay", Pleroma.Web.ActivityPub do
pipe_through(:ap_service_actor) pipe_through(:ap_service_actor)
@ -679,12 +673,6 @@ defmodule Pleroma.Web.Router do
post("/inbox", ActivityPubController, :inbox) post("/inbox", ActivityPubController, :inbox)
end end
scope "/", Pleroma.Web.ActivityPub do
pipe_through(:activitypub)
post("/inbox", ActivityPubController, :inbox)
post("/users/:nickname/inbox", ActivityPubController, :inbox)
end
scope "/.well-known", Pleroma.Web do scope "/.well-known", Pleroma.Web do
pipe_through(:well_known) pipe_through(:well_known)

View file

@ -13,6 +13,7 @@ defmodule Pleroma.Web.Streamer do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.NotificationView
@keepalive_interval :timer.seconds(30) @keepalive_interval :timer.seconds(30)
@ -118,10 +119,14 @@ def handle_cast(
topics topics
|> Map.get("#{topic}:#{item.user_id}", []) |> Map.get("#{topic}:#{item.user_id}", [])
|> Enum.each(fn socket -> |> Enum.each(fn socket ->
send( with %User{} = user <- User.get_cached_by_ap_id(socket.assigns[:user].ap_id),
socket.transport_pid, true <- should_send?(user, item),
{:text, represent_notification(socket.assigns[:user], item)} false <- CommonAPI.thread_muted?(user, item.activity) do
) send(
socket.transport_pid,
{:text, represent_notification(socket.assigns[:user], item)}
)
end
end) end)
{:noreply, topics} {:noreply, topics}
@ -225,19 +230,37 @@ defp represent_notification(%User{} = user, %Notification{} = notify) do
|> Jason.encode!() |> Jason.encode!()
end end
defp should_send?(%User{} = user, %Activity{} = item) do
blocks = user.info.blocks || []
mutes = user.info.mutes || []
reblog_mutes = user.info.muted_reblogs || []
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
with parent when not is_nil(parent) <- Object.normalize(item),
true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
%{host: item_host} <- URI.parse(item.actor),
%{host: parent_host} <- URI.parse(parent.data["actor"]),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host),
true <- thread_containment(item, user) do
true
else
_ -> false
end
end
defp should_send?(%User{} = user, %Notification{activity: activity}) do
should_send?(user, activity)
end
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
Enum.each(topics[topic] || [], fn socket -> Enum.each(topics[topic] || [], fn socket ->
# Get the current user so we have up-to-date blocks etc. # Get the current user so we have up-to-date blocks etc.
if socket.assigns[:user] do if socket.assigns[:user] do
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id) user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
blocks = user.info.blocks || []
mutes = user.info.mutes || []
reblog_mutes = user.info.muted_reblogs || []
with parent when not is_nil(parent) <- Object.normalize(item), if should_send?(user, item) do
true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
true <- thread_containment(item, user) do
send(socket.transport_pid, {:text, represent_update(item, user)}) send(socket.transport_pid, {:text, represent_update(item, user)})
end end
else else

View file

@ -144,7 +144,7 @@ defp deps do
{:telemetry, "~> 0.3"}, {:telemetry, "~> 0.3"},
{:prometheus_ex, "~> 3.0"}, {:prometheus_ex, "~> 3.0"},
{:prometheus_plugs, "~> 1.1"}, {:prometheus_plugs, "~> 1.1"},
{:prometheus_phoenix, "~> 1.2"}, {:prometheus_phoenix, "~> 1.3"},
{:prometheus_ecto, "~> 1.4"}, {:prometheus_ecto, "~> 1.4"},
{:recon, github: "ferd/recon", tag: "2.4.0"}, {:recon, github: "ferd/recon", tag: "2.4.0"},
{:quack, "~> 0.1.1"}, {:quack, "~> 0.1.1"},

View file

@ -58,22 +58,22 @@
"nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.0", "90e2eca3d0266e5c53f8fbe0079694740b9c91b6747f2b7e3c5d21966bba8300", [:mix], [], "hexpm"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"},
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.3", "6706a148809a29c306062862c803406e88f048277f6e85b68faf73291e820b84", [:mix], [], "hexpm"},
"phoenix": {:hex, :phoenix, "1.4.8", "c72dc3adeb49c70eb963a0ea24f7a064ec1588e651e84e1b7ad5ed8253c0b4a2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix": {:hex, :phoenix, "1.4.9", "746d098e10741c334d88143d3c94cab1756435f94387a63441792e66ec0ee974", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.2.0", "a7e0b32077cd6d2323ae15198839b05d9caddfa20663fd85787479e81f89520e", [:mix], [{:phoenix, "~> 1.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 0.1", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm"},
"pleroma_job_queue": {:hex, :pleroma_job_queue, "0.3.0", "b84538d621f0c3d6fcc1cff9d5648d3faaf873b8b21b94e6503428a07a48ec47", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}], "hexpm"}, "pleroma_job_queue": {:hex, :pleroma_job_queue, "0.3.0", "b84538d621f0c3d6fcc1cff9d5648d3faaf873b8b21b94e6503428a07a48ec47", [:mix], [{:crontab, "~> 1.1", [hex: :crontab, repo: "hexpm", optional: false]}], "hexpm"},
"plug": {:hex, :plug, "1.8.2", "0bcce1daa420f189a6491f3940cc77ea7fb1919761175c9c3b59800d897440fc", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"}, "plug": {:hex, :plug, "1.8.2", "0bcce1daa420f189a6491f3940cc77ea7fb1919761175c9c3b59800d897440fc", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "2.0.2", "6055f16868cc4882b24b6e1d63d2bada94fb4978413377a3b32ac16c18dffba2", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "plug_cowboy": {:hex, :plug_cowboy, "2.1.0", "b75768153c3a8a9e8039d4b25bb9b14efbc58e9c4a6e6a270abff1cd30cbe320", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"},
"postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "postgrex": {:hex, :postgrex, "0.14.3", "5754dee2fdf6e9e508cbf49ab138df964278700b764177e8f3871e658b345a1e", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"prometheus": {:hex, :prometheus, "4.2.2", "a830e77b79dc6d28183f4db050a7cac926a6c58f1872f9ef94a35cd989aceef8", [:mix, :rebar3], [], "hexpm"}, "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm"},
"prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.1", "6c768ea9654de871e5b32fab2eac348467b3021604ebebbcbd8bcbe806a65ed5", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"}, "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_phoenix": {:hex, :prometheus_phoenix, "1.2.1", "964a74dfbc055f781d3a75631e06ce3816a2913976d1df7830283aa3118a797a", [:mix], [{:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"}, "prometheus_phoenix": {:hex, :prometheus_phoenix, "1.3.0", "c4b527e0b3a9ef1af26bdcfbfad3998f37795b9185d475ca610fe4388fdd3bb5", [:mix], [{:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.3 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"},
"prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"}, "prometheus_plugs": {:hex, :prometheus_plugs, "1.1.5", "25933d48f8af3a5941dd7b621c889749894d8a1082a6ff7c67cc99dec26377c5", [:mix], [{:accept, "~> 0.1", [hex: :accept, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}, {:prometheus_process_collector, "~> 1.1", [hex: :prometheus_process_collector, repo: "hexpm", optional: true]}], "hexpm"},
"quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"}, "quack": {:hex, :quack, "0.1.1", "cca7b4da1a233757fdb44b3334fce80c94785b3ad5a602053b7a002b5a8967bf", [:mix], [{:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: false]}, {:tesla, "~> 1.2.0", [hex: :tesla, repo: "hexpm", optional: false]}], "hexpm"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},

View file

@ -44,6 +44,28 @@ test "it creates a notification for subscribed users" do
assert notification.user_id == subscriber.id assert notification.user_id == subscriber.id
end end
test "does not create a notification for subscribed users if status is a reply" do
user = insert(:user)
other_user = insert(:user)
subscriber = insert(:user)
User.subscribe(subscriber, other_user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"})
{:ok, _reply_activity} =
CommonAPI.post(other_user, %{
"status" => "test reply",
"in_reply_to_status_id" => activity.id
})
user_notifications = Notification.for_user(user)
assert length(user_notifications) == 1
subscriber_notifications = Notification.for_user(subscriber)
assert Enum.empty?(subscriber_notifications)
end
end end
describe "create_notification" do describe "create_notification" do

View file

@ -9,7 +9,6 @@ defmodule Pleroma.Plugs.AuthenticationPlugTest do
alias Pleroma.User alias Pleroma.User
import ExUnit.CaptureLog import ExUnit.CaptureLog
import Mock
setup %{conn: conn} do setup %{conn: conn} do
user = %User{ user = %User{
@ -67,13 +66,12 @@ test "check pbkdf2 hash" do
refute AuthenticationPlug.checkpw("test-password1", hash) refute AuthenticationPlug.checkpw("test-password1", hash)
end end
@tag :skip_on_mac
test "check sha512-crypt hash" do test "check sha512-crypt hash" do
hash = hash =
"$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
with_mock :crypt, crypt: fn _password, password_hash -> password_hash end do assert AuthenticationPlug.checkpw("password", hash)
assert AuthenticationPlug.checkpw("password", hash)
end
end end
test "it returns false when hash invalid" do test "it returns false when hash invalid" do

View file

@ -5,19 +5,18 @@
defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do defmodule Pleroma.Plugs.LegacyAuthenticationPlugTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
import Pleroma.Factory
alias Pleroma.Plugs.LegacyAuthenticationPlug alias Pleroma.Plugs.LegacyAuthenticationPlug
alias Pleroma.User alias Pleroma.User
import Mock
setup do setup do
# password is "password" user =
user = %User{ insert(:user,
id: 1, password: "password",
name: "dude", password_hash:
password_hash: "$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1"
"$6$9psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" )
}
%{user: user} %{user: user}
end end
@ -36,6 +35,7 @@ test "it does nothing if a user is assigned", %{conn: conn, user: user} do
assert ret_conn == conn assert ret_conn == conn
end end
@tag :skip_on_mac
test "it authenticates the auth_user if present and password is correct and resets the password", test "it authenticates the auth_user if present and password is correct and resets the password",
%{ %{
conn: conn, conn: conn,
@ -46,22 +46,12 @@ test "it authenticates the auth_user if present and password is correct and rese
|> assign(:auth_credentials, %{username: "dude", password: "password"}) |> assign(:auth_credentials, %{username: "dude", password: "password"})
|> assign(:auth_user, user) |> assign(:auth_user, user)
conn = conn = LegacyAuthenticationPlug.call(conn, %{})
with_mocks([
{:crypt, [], [crypt: fn _password, password_hash -> password_hash end]},
{User, [],
[
reset_password: fn user, %{password: password, password_confirmation: password} ->
{:ok, user}
end
]}
]) do
LegacyAuthenticationPlug.call(conn, %{})
end
assert conn.assigns.user == user assert conn.assigns.user.id == user.id
end end
@tag :skip_on_mac
test "it does nothing if the password is wrong", %{ test "it does nothing if the password is wrong", %{
conn: conn, conn: conn,
user: user user: user

View file

@ -48,16 +48,14 @@ test "it returns key" do
test "it returns error when not found user" do test "it returns error when not found user" do
assert capture_log(fn -> assert capture_log(fn ->
assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) == assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) == {:error, :error}
{:error, :error}
end) =~ "[error] Could not decode user" end) =~ "[error] Could not decode user"
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(make_fake_conn(user.ap_id)) == assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == {:error, :error}
{:error, :error}
end end
end end
@ -65,8 +63,7 @@ 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(make_fake_conn(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
@ -105,4 +102,16 @@ test "it returns error" do
) == {:error, []} ) == {:error, []}
end end
end end
describe "key_id_to_actor_id/1" do
test "it properly deduces the actor id for misskey" do
assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") ==
"https://example.com/users/1234"
end
test "it properly deduces the actor id for mastodon and pleroma" do
assert Signature.key_id_to_actor_id("https://example.com/users/1234#main-key") ==
"https://example.com/users/1234"
end
end
end end

View file

@ -119,17 +119,21 @@ def direct_note_activity_factory do
def note_activity_factory(attrs \\ %{}) do def note_activity_factory(attrs \\ %{}) do
user = attrs[:user] || insert(:user) user = attrs[:user] || insert(:user)
note = attrs[:note] || insert(:note, user: user) note = attrs[:note] || insert(:note, user: user)
attrs = Map.drop(attrs, [:user, :note])
data = %{ data_attrs = attrs[:data_attrs] || %{}
"id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), attrs = Map.drop(attrs, [:user, :note, :data_attrs])
"type" => "Create",
"actor" => note.data["actor"], data =
"to" => note.data["to"], %{
"object" => note.data["id"], "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(),
"published" => DateTime.utc_now() |> DateTime.to_iso8601(), "type" => "Create",
"context" => note.data["context"] "actor" => note.data["actor"],
} "to" => note.data["to"],
"object" => note.data["id"],
"published" => DateTime.utc_now() |> DateTime.to_iso8601(),
"context" => note.data["context"]
}
|> Map.merge(data_attrs)
%Pleroma.Activity{ %Pleroma.Activity{
data: data, data: data,

View file

@ -5,6 +5,9 @@
defmodule Mix.Tasks.Pleroma.UserTest do defmodule Mix.Tasks.Pleroma.UserTest do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
@ -327,6 +330,13 @@ test "invite is revoked" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message =~ "Invite for token #{invite.token} was revoked." assert message =~ "Invite for token #{invite.token} was revoked."
end end
test "it prints an error message when invite is not exist" do
Mix.Tasks.Pleroma.User.run(["revoke_invite", "foo"])
assert_received {:mix_shell, :error, [message]}
assert message =~ "No invite found"
end
end end
describe "running delete_activities" do describe "running delete_activities" do
@ -337,6 +347,13 @@ test "activities are deleted" do
assert_received {:mix_shell, :info, [message]} assert_received {:mix_shell, :info, [message]}
assert message == "User #{nickname} statuses deleted." assert message == "User #{nickname} statuses deleted."
end end
test "it prints an error message when user is not exist" do
Mix.Tasks.Pleroma.User.run(["delete_activities", "foo"])
assert_received {:mix_shell, :error, [message]}
assert message =~ "No local user"
end
end end
describe "running toggle_confirmed" do describe "running toggle_confirmed" do
@ -364,6 +381,13 @@ test "user is not confirmed" do
refute user.info.confirmation_pending refute user.info.confirmation_pending
refute user.info.confirmation_token refute user.info.confirmation_token
end end
test "it prints an error message when user is not exist" do
Mix.Tasks.Pleroma.User.run(["toggle_confirmed", "foo"])
assert_received {:mix_shell, :error, [message]}
assert message =~ "No local user"
end
end end
describe "search" do describe "search" do
@ -386,4 +410,64 @@ test "it returns users matching" do
User.Search.search("moon fediverse", for_user: user) |> Enum.map(& &1.id) User.Search.search("moon fediverse", for_user: user) |> Enum.map(& &1.id)
end end
end end
describe "signing out" do
test "it deletes all user's tokens and authorizations" do
user = insert(:user)
insert(:oauth_token, user: user)
insert(:oauth_authorization, user: user)
assert Repo.get_by(Token, user_id: user.id)
assert Repo.get_by(Authorization, user_id: user.id)
:ok = Mix.Tasks.Pleroma.User.run(["sign_out", user.nickname])
refute Repo.get_by(Token, user_id: user.id)
refute Repo.get_by(Authorization, user_id: user.id)
end
test "it prints an error message when user is not exist" do
Mix.Tasks.Pleroma.User.run(["sign_out", "foo"])
assert_received {:mix_shell, :error, [message]}
assert message =~ "No local user"
end
end
describe "tagging" do
test "it add tags to a user" do
user = insert(:user)
:ok = Mix.Tasks.Pleroma.User.run(["tag", user.nickname, "pleroma"])
user = User.get_cached_by_nickname(user.nickname)
assert "pleroma" in user.tags
end
test "it prints an error message when user is not exist" do
Mix.Tasks.Pleroma.User.run(["tag", "foo"])
assert_received {:mix_shell, :error, [message]}
assert message =~ "Could not change user tags"
end
end
describe "untagging" do
test "it deletes tags from a user" do
user = insert(:user, tags: ["pleroma"])
assert "pleroma" in user.tags
:ok = Mix.Tasks.Pleroma.User.run(["untag", user.nickname, "pleroma"])
user = User.get_cached_by_nickname(user.nickname)
assert Enum.empty?(user.tags)
end
test "it prints an error message when user is not exist" do
Mix.Tasks.Pleroma.User.run(["untag", "foo"])
assert_received {:mix_shell, :error, [message]}
assert message =~ "Could not change user tags"
end
end
end end

View file

@ -2,7 +2,8 @@
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> # Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
ExUnit.start() os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: []
ExUnit.start(exclude: os_exclude)
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client) Mox.defmock(Pleroma.ReverseProxy.ClientMock, for: Pleroma.ReverseProxy.Client)
{:ok, _} = Application.ensure_all_started(:ex_machina) {:ok, _} = Application.ensure_all_started(:ex_machina)

View file

@ -824,6 +824,48 @@ test "blocks domains" do
assert User.blocks?(user, collateral_user) assert User.blocks?(user, collateral_user)
end end
test "does not block domain with same end" do
user = insert(:user)
collateral_user =
insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"})
{:ok, user} = User.block_domain(user, "awful-and-rude-instance.com")
refute User.blocks?(user, collateral_user)
end
test "does not block domain with same end if wildcard added" do
user = insert(:user)
collateral_user =
insert(:user, %{ap_id: "https://another-awful-and-rude-instance.com/user/bully"})
{:ok, user} = User.block_domain(user, "*.awful-and-rude-instance.com")
refute User.blocks?(user, collateral_user)
end
test "blocks domain with wildcard for subdomain" do
user = insert(:user)
user_from_subdomain =
insert(:user, %{ap_id: "https://subdomain.awful-and-rude-instance.com/user/bully"})
user_with_two_subdomains =
insert(:user, %{
ap_id: "https://subdomain.second_subdomain.awful-and-rude-instance.com/user/bully"
})
user_domain = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
{:ok, user} = User.block_domain(user, "*.awful-and-rude-instance.com")
assert User.blocks?(user, user_from_subdomain)
assert User.blocks?(user, user_with_two_subdomains)
assert User.blocks?(user, user_domain)
end
test "unblocks domains" do test "unblocks domains" do
user = insert(:user) user = insert(:user)
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})

View file

@ -6,11 +6,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
use Pleroma.DataCase use Pleroma.DataCase
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Builders.ActivityBuilder alias Pleroma.Builders.ActivityBuilder
alias Pleroma.Instances
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Publisher
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
@ -1083,113 +1081,6 @@ test "it can create a Flag activity" do
} = activity } = activity
end end
describe "publish_one/1" do
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
assert called(Instances.set_reachable(inbox))
end
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} =
Publisher.publish_one(%{
inbox: inbox,
json: "{}",
actor: actor,
id: 1,
unreachable_since: NaiveDateTime.utc_now()
})
assert called(Instances.set_reachable(inbox))
end
test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} =
Publisher.publish_one(%{
inbox: inbox,
json: "{}",
actor: actor,
id: 1,
unreachable_since: nil
})
refute called(Instances.set_reachable(inbox))
end
test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://404.site/users/nick1/inbox"
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
assert called(Instances.set_unreachable(inbox))
end
test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://connrefused.site/users/nick1/inbox"
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
assert called(Instances.set_unreachable(inbox))
end
test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
refute called(Instances.set_unreachable(inbox))
end
test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://connrefused.site/users/nick1/inbox"
assert {:error, _} =
Publisher.publish_one(%{
inbox: inbox,
json: "{}",
actor: actor,
id: 1,
unreachable_since: NaiveDateTime.utc_now()
})
refute called(Instances.set_unreachable(inbox))
end
end
test "fetch_activities/2 returns activities addressed to a list " do test "fetch_activities/2 returns activities addressed to a list " do
user = insert(:user) user = insert(:user)
member = insert(:user) member = insert(:user)

View file

@ -0,0 +1,46 @@
defmodule Pleroma.Web.ActivityPub.MRFTest do
use ExUnit.Case, async: true
alias Pleroma.Web.ActivityPub.MRF
test "subdomains_regex/1" do
assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [
~r/^unsafe.tld$/,
~r/^(.*\.)*unsafe.tld$/
]
end
describe "subdomain_match/2" do
test "common domains" do
regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"])
assert regexes == [~r/^unsafe.tld$/, ~r/^unsafe2.tld$/]
assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "unsafe2.tld")
refute MRF.subdomain_match?(regexes, "example.com")
end
test "wildcard domains with one subdomain" do
regexes = MRF.subdomains_regex(["*.unsafe.tld"])
assert regexes == [~r/^(.*\.)*unsafe.tld$/]
assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "sub.unsafe.tld")
refute MRF.subdomain_match?(regexes, "anotherunsafe.tld")
refute MRF.subdomain_match?(regexes, "unsafe.tldanother")
end
test "wildcard domains with two subdomains" do
regexes = MRF.subdomains_regex(["*.unsafe.tld"])
assert regexes == [~r/^(.*\.)*unsafe.tld$/]
assert MRF.subdomain_match?(regexes, "unsafe.tld")
assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld")
refute MRF.subdomain_match?(regexes, "sub.anotherunsafe.tld")
refute MRF.subdomain_match?(regexes, "sub.unsafe.tldanother")
end
end
end

View file

@ -49,6 +49,19 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :media_removal], ["*.remote.instance"])
media_message = build_media_message()
local_message = build_local_message()
assert SimplePolicy.filter(media_message) ==
{:ok,
media_message
|> Map.put("object", Map.delete(media_message["object"], "attachment"))}
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
end end
describe "when :media_nsfw" do describe "when :media_nsfw" do
@ -74,6 +87,20 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :media_nsfw], ["*.remote.instance"])
media_message = build_media_message()
local_message = build_local_message()
assert SimplePolicy.filter(media_message) ==
{:ok,
media_message
|> put_in(["object", "tag"], ["foo", "nsfw"])
|> put_in(["object", "sensitive"], true)}
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
end end
defp build_media_message do defp build_media_message do
@ -106,6 +133,15 @@ test "has a matching host" do
assert SimplePolicy.filter(report_message) == {:reject, nil} assert SimplePolicy.filter(report_message) == {:reject, nil}
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :report_removal], ["*.remote.instance"])
report_message = build_report_message()
local_message = build_local_message()
assert SimplePolicy.filter(report_message) == {:reject, nil}
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
end end
defp build_report_message do defp build_report_message do
@ -146,6 +182,27 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
test "match with wildcard domain" do
{actor, ftl_message} = build_ftl_actor_and_message()
ftl_message_actor_host =
ftl_message
|> Map.fetch!("actor")
|> URI.parse()
|> Map.fetch!(:host)
Config.put([:mrf_simple, :federated_timeline_removal], ["*." <> ftl_message_actor_host])
local_message = build_local_message()
assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
assert actor.follower_address in ftl_message["to"]
refute actor.follower_address in ftl_message["cc"]
refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
assert SimplePolicy.filter(local_message) == {:ok, local_message}
end
test "has a matching host but only as:Public in to" do test "has a matching host but only as:Public in to" do
{_actor, ftl_message} = build_ftl_actor_and_message() {_actor, ftl_message} = build_ftl_actor_and_message()
@ -192,6 +249,14 @@ test "has a matching host" do
assert SimplePolicy.filter(remote_message) == {:reject, nil} assert SimplePolicy.filter(remote_message) == {:reject, nil}
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :reject], ["*.remote.instance"])
remote_message = build_remote_message()
assert SimplePolicy.filter(remote_message) == {:reject, nil}
end
end end
describe "when :accept" do describe "when :accept" do
@ -224,6 +289,16 @@ test "has a matching host" do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:ok, remote_message} assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :accept], ["*.remote.instance"])
local_message = build_local_message()
remote_message = build_remote_message()
assert SimplePolicy.filter(local_message) == {:ok, local_message}
assert SimplePolicy.filter(remote_message) == {:ok, remote_message}
end
end end
describe "when :avatar_removal" do describe "when :avatar_removal" do
@ -251,6 +326,15 @@ test "has a matching host" do
refute filtered["icon"] refute filtered["icon"]
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :avatar_removal], ["*.remote.instance"])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
refute filtered["icon"]
end
end end
describe "when :banner_removal" do describe "when :banner_removal" do
@ -278,6 +362,15 @@ test "has a matching host" do
refute filtered["image"] refute filtered["image"]
end end
test "match with wildcard domain" do
Config.put([:mrf_simple, :banner_removal], ["*.remote.instance"])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
refute filtered["image"]
end
end end
defp build_local_message do defp build_local_message do

View file

@ -0,0 +1,266 @@
# Pleroma: A lightweight social networking server
# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.PublisherTest do
use Pleroma.DataCase
import Pleroma.Factory
import Tesla.Mock
import Mock
alias Pleroma.Activity
alias Pleroma.Instances
alias Pleroma.Web.ActivityPub.Publisher
@as_public "https://www.w3.org/ns/activitystreams#Public"
setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end
describe "determine_inbox/2" do
test "it returns sharedInbox for messages involving as:Public in to" do
user =
insert(:user, %{
info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
})
activity = %Activity{
data: %{"to" => [@as_public], "cc" => [user.follower_address]}
}
assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
end
test "it returns sharedInbox for messages involving as:Public in cc" do
user =
insert(:user, %{
info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
})
activity = %Activity{
data: %{"cc" => [@as_public], "to" => [user.follower_address]}
}
assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
end
test "it returns sharedInbox for messages involving multiple recipients in to" do
user =
insert(:user, %{
info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
})
user_two = insert(:user)
user_three = insert(:user)
activity = %Activity{
data: %{"cc" => [], "to" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
}
assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
end
test "it returns sharedInbox for messages involving multiple recipients in cc" do
user =
insert(:user, %{
info: %{source_data: %{"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}}}
})
user_two = insert(:user)
user_three = insert(:user)
activity = %Activity{
data: %{"to" => [], "cc" => [user.ap_id, user_two.ap_id, user_three.ap_id]}
}
assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
end
test "it returns sharedInbox for messages involving multiple recipients in total" do
user =
insert(:user, %{
info: %{
source_data: %{
"inbox" => "http://example.com/personal-inbox",
"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
}
}
})
user_two = insert(:user)
activity = %Activity{
data: %{"to" => [user_two.ap_id], "cc" => [user.ap_id]}
}
assert Publisher.determine_inbox(activity, user) == "http://example.com/inbox"
end
test "it returns inbox for messages involving single recipients in total" do
user =
insert(:user, %{
info: %{
source_data: %{
"inbox" => "http://example.com/personal-inbox",
"endpoints" => %{"sharedInbox" => "http://example.com/inbox"}
}
}
})
activity = %Activity{
data: %{"to" => [user.ap_id], "cc" => []}
}
assert Publisher.determine_inbox(activity, user) == "http://example.com/personal-inbox"
end
end
describe "publish_one/1" do
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
assert called(Instances.set_reachable(inbox))
end
test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} =
Publisher.publish_one(%{
inbox: inbox,
json: "{}",
actor: actor,
id: 1,
unreachable_since: NaiveDateTime.utc_now()
})
assert called(Instances.set_reachable(inbox))
end
test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} =
Publisher.publish_one(%{
inbox: inbox,
json: "{}",
actor: actor,
id: 1,
unreachable_since: nil
})
refute called(Instances.set_reachable(inbox))
end
test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://404.site/users/nick1/inbox"
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
assert called(Instances.set_unreachable(inbox))
end
test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://connrefused.site/users/nick1/inbox"
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
assert called(Instances.set_unreachable(inbox))
end
test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://200.site/users/nick1/inbox"
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
refute called(Instances.set_unreachable(inbox))
end
test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`",
Instances,
[:passthrough],
[] do
actor = insert(:user)
inbox = "http://connrefused.site/users/nick1/inbox"
assert {:error, _} =
Publisher.publish_one(%{
inbox: inbox,
json: "{}",
actor: actor,
id: 1,
unreachable_since: NaiveDateTime.utc_now()
})
refute called(Instances.set_unreachable(inbox))
end
end
describe "publish/2" do
test_with_mock "publishes an activity with BCC to all relevant peers.",
Pleroma.Web.Federator.Publisher,
[:passthrough],
[] do
follower =
insert(:user,
local: false,
info: %{
ap_enabled: true,
source_data: %{"inbox" => "https://domain.com/users/nick1/inbox"}
}
)
actor = insert(:user, follower_address: follower.ap_id)
user = insert(:user)
{:ok, _follower_one} = Pleroma.User.follow(follower, actor)
actor = refresh_record(actor)
note_activity =
insert(:note_activity,
recipients: [follower.ap_id],
data_attrs: %{"bcc" => [user.ap_id]}
)
res = Publisher.publish(actor, note_activity)
assert res == :ok
assert called(
Pleroma.Web.Federator.Publisher.enqueue_one(Publisher, %{
inbox: "https://domain.com/users/nick1/inbox",
actor: actor,
id: note_activity.data["id"]
})
)
end
end
end

View file

@ -1010,6 +1010,17 @@ test "with token" do
"uses" => 0 "uses" => 0
} }
end end
test "with invalid token" do
admin = insert(:user, info: %{is_admin: true})
conn =
build_conn()
|> assign(:user, admin)
|> post("/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"})
assert json_response(conn, :not_found) == "Not found"
end
end end
describe "GET /api/pleroma/admin/reports/:id" do describe "GET /api/pleroma/admin/reports/:id" do
@ -1560,7 +1571,8 @@ test "common config example", %{conn: conn} do
%{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]}, %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
%{"tuple" => [":seconds_valid", 60]}, %{"tuple" => [":seconds_valid", 60]},
%{"tuple" => [":path", ""]}, %{"tuple" => [":path", ""]},
%{"tuple" => [":key1", nil]} %{"tuple" => [":key1", nil]},
%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}
] ]
} }
] ]
@ -1576,7 +1588,8 @@ test "common config example", %{conn: conn} do
%{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]}, %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
%{"tuple" => [":seconds_valid", 60]}, %{"tuple" => [":seconds_valid", 60]},
%{"tuple" => [":path", ""]}, %{"tuple" => [":path", ""]},
%{"tuple" => [":key1", nil]} %{"tuple" => [":key1", nil]},
%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}
] ]
} }
] ]
@ -1902,6 +1915,63 @@ test "queues key as atom", %{conn: conn} do
} }
end end
end end
describe "GET /api/pleroma/admin/users/:nickname/statuses" do
setup do
admin = insert(:user, info: %{is_admin: true})
user = insert(:user)
date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!()
date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!()
insert(:note_activity, user: user, published: date1)
insert(:note_activity, user: user, published: date2)
insert(:note_activity, user: user, published: date3)
conn =
build_conn()
|> assign(:user, admin)
{:ok, conn: conn, user: user}
end
test "renders user's statuses", %{conn: conn, user: user} do
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
assert json_response(conn, 200) |> length() == 3
end
test "renders user's statuses with a limit", %{conn: conn, user: user} do
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2")
assert json_response(conn, 200) |> length() == 2
end
test "doesn't return private statuses by default", %{conn: conn, user: user} do
{:ok, _private_status} =
CommonAPI.post(user, %{"status" => "private", "visibility" => "private"})
{:ok, _public_status} =
CommonAPI.post(user, %{"status" => "public", "visibility" => "public"})
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
assert json_response(conn, 200) |> length() == 4
end
test "returns private statuses with godmode on", %{conn: conn, user: user} do
{:ok, _private_status} =
CommonAPI.post(user, %{"status" => "private", "visibility" => "private"})
{:ok, _public_status} =
CommonAPI.post(user, %{"status" => "public", "visibility" => "public"})
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
assert json_response(conn, 200) |> length() == 5
end
end
end end
# Needed for testing # Needed for testing

View file

@ -238,6 +238,14 @@ test "simple keyword" do
assert Config.from_binary(binary) == [key: "value"] assert Config.from_binary(binary) == [key: "value"]
end end
test "keyword with partial_chain key" do
binary =
Config.transform([%{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}])
assert binary == :erlang.term_to_binary(partial_chain: &:hackney_connect.partial_chain/1)
assert Config.from_binary(binary) == [partial_chain: &:hackney_connect.partial_chain/1]
end
test "keyword" do test "keyword" do
binary = binary =
Config.transform([ Config.transform([

View file

@ -22,6 +22,15 @@ defmodule Pleroma.Web.FederatorTest do
:ok :ok
end end
describe "Publisher.perform" do
test "call `perform` with unknown task" do
assert {
:error,
"Don't know what to do with this"
} = Pleroma.Web.Federator.Publisher.perform("test", :ok, :ok)
end
end
describe "Publish an activity" do describe "Publish an activity" do
setup do setup do
user = insert(:user) user = insert(:user)

View file

@ -2815,11 +2815,11 @@ test "returns rich-media card", %{conn: conn, user: user} do
card_data = %{ card_data = %{
"image" => "http://ia.media-imdb.com/images/rock.jpg", "image" => "http://ia.media-imdb.com/images/rock.jpg",
"provider_name" => "www.imdb.com", "provider_name" => "example.com",
"provider_url" => "http://www.imdb.com", "provider_url" => "https://example.com",
"title" => "The Rock", "title" => "The Rock",
"type" => "link", "type" => "link",
"url" => "http://www.imdb.com/title/tt0117500/", "url" => "https://example.com/ogp",
"description" => "description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
"pleroma" => %{ "pleroma" => %{
@ -2827,7 +2827,7 @@ test "returns rich-media card", %{conn: conn, user: user} do
"image" => "http://ia.media-imdb.com/images/rock.jpg", "image" => "http://ia.media-imdb.com/images/rock.jpg",
"title" => "The Rock", "title" => "The Rock",
"type" => "video.movie", "type" => "video.movie",
"url" => "http://www.imdb.com/title/tt0117500/", "url" => "https://example.com/ogp",
"description" => "description" =>
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer." "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer."
} }
@ -2868,14 +2868,14 @@ test "replaces missing description with an empty string", %{conn: conn, user: us
"title" => "Pleroma", "title" => "Pleroma",
"description" => "", "description" => "",
"image" => nil, "image" => nil,
"provider_name" => "pleroma.social", "provider_name" => "example.com",
"provider_url" => "https://pleroma.social", "provider_url" => "https://example.com",
"url" => "https://pleroma.social/", "url" => "https://example.com/ogp-missing-data",
"pleroma" => %{ "pleroma" => %{
"opengraph" => %{ "opengraph" => %{
"title" => "Pleroma", "title" => "Pleroma",
"type" => "website", "type" => "website",
"url" => "https://pleroma.social/" "url" => "https://example.com/ogp-missing-data"
} }
} }
} }
@ -3768,6 +3768,38 @@ test "returns empty array when status has not been favorited yet", %{
assert Enum.empty?(response) assert Enum.empty?(response)
end end
test "does not return users who have favorited the status but are blocked", %{
conn: %{assigns: %{user: user}} = conn,
activity: activity
} do
other_user = insert(:user)
{:ok, user} = User.block(user, other_user)
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
response =
conn
|> assign(:user, user)
|> get("/api/v1/statuses/#{activity.id}/favourited_by")
|> json_response(:ok)
assert Enum.empty?(response)
end
test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
other_user = insert(:user)
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
response =
conn
|> assign(:user, nil)
|> get("/api/v1/statuses/#{activity.id}/favourited_by")
|> json_response(:ok)
[%{"id" => id}] = response
assert id == other_user.id
end
end end
describe "GET /api/v1/statuses/:id/reblogged_by" do describe "GET /api/v1/statuses/:id/reblogged_by" do
@ -3807,6 +3839,38 @@ test "returns empty array when status has not been reblogged yet", %{
assert Enum.empty?(response) assert Enum.empty?(response)
end end
test "does not return users who have reblogged the status but are blocked", %{
conn: %{assigns: %{user: user}} = conn,
activity: activity
} do
other_user = insert(:user)
{:ok, user} = User.block(user, other_user)
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
response =
conn
|> assign(:user, user)
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|> json_response(:ok)
assert Enum.empty?(response)
end
test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do
other_user = insert(:user)
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user)
response =
conn
|> assign(:user, nil)
|> get("/api/v1/statuses/#{activity.id}/reblogged_by")
|> json_response(:ok)
[%{"id" => id}] = response
assert id == other_user.id
end
end end
describe "POST /auth/password, with valid parameters" do describe "POST /auth/password, with valid parameters" do

View file

@ -5,9 +5,7 @@
defmodule Pleroma.Web.OAuth.OAuthControllerTest do defmodule Pleroma.Web.OAuth.OAuthControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
import Pleroma.Factory import Pleroma.Factory
import Mock
alias Pleroma.Registration
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.OAuthController
@ -108,28 +106,26 @@ test "with user-bound registration, GET /oauth/<provider>/callback redirects to
"state" => "" "state" => ""
} }
with_mock Pleroma.Web.Auth.Authenticator, conn =
get_registration: fn _ -> {:ok, registration} end do conn
conn = |> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid})
get( |> get(
conn, "/oauth/twitter/callback",
"/oauth/twitter/callback", %{
%{ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", "provider" => "twitter",
"provider" => "twitter", "state" => Poison.encode!(state_params)
"state" => Poison.encode!(state_params) }
} )
)
assert response = html_response(conn, 302) assert response = html_response(conn, 302)
assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/ assert redirected_to(conn) =~ ~r/#{redirect_uri}\?code=.+/
end
end end
test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page", test "with user-unbound registration, GET /oauth/<provider>/callback renders registration_details page",
%{app: app, conn: conn} do %{app: app, conn: conn} do
registration = insert(:registration, user: nil) user = insert(:user)
state_params = %{ state_params = %{
"scope" => "read write", "scope" => "read write",
@ -138,26 +134,28 @@ test "with user-unbound registration, GET /oauth/<provider>/callback renders reg
"state" => "a_state" "state" => "a_state"
} }
with_mock Pleroma.Web.Auth.Authenticator, conn =
get_registration: fn _ -> {:ok, registration} end do conn
conn = |> assign(:ueberauth_auth, %{
get( provider: "twitter",
conn, uid: "171799000",
"/oauth/twitter/callback", info: %{nickname: user.nickname, email: user.email, name: user.name, description: nil}
%{ })
"oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", |> get(
"oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", "/oauth/twitter/callback",
"provider" => "twitter", %{
"state" => Poison.encode!(state_params) "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM",
} "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs",
) "provider" => "twitter",
"state" => Poison.encode!(state_params)
}
)
assert response = html_response(conn, 200) assert response = html_response(conn, 200)
assert response =~ ~r/name="op" type="submit" value="register"/ assert response =~ ~r/name="op" type="submit" value="register"/
assert response =~ ~r/name="op" type="submit" value="connect"/ assert response =~ ~r/name="op" type="submit" value="connect"/
assert response =~ Registration.email(registration) assert response =~ user.email
assert response =~ Registration.nickname(registration) assert response =~ user.nickname
end
end end
test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{ test "on authentication error, GET /oauth/<provider>/callback redirects to `redirect_uri`", %{

View file

@ -59,7 +59,8 @@ test "returns error when no metadata present" do
test "doesn't just add a title" do test "doesn't just add a title" do
assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") == assert Pleroma.Web.RichMedia.Parser.parse("http://example.com/non-ogp") ==
{:error, "Found metadata was invalid or incomplete: %{}"} {:error,
"Found metadata was invalid or incomplete: %{url: \"http://example.com/non-ogp\"}"}
end end
test "parses ogp" do test "parses ogp" do
@ -71,7 +72,7 @@ test "parses ogp" do
description: description:
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
type: "video.movie", type: "video.movie",
url: "http://www.imdb.com/title/tt0117500/" url: "http://example.com/ogp"
}} }}
end end
@ -84,7 +85,7 @@ test "falls back to <title> when ogp:title is missing" do
description: description:
"Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.", "Directed by Michael Bay. With Sean Connery, Nicolas Cage, Ed Harris, John Spencer.",
type: "video.movie", type: "video.movie",
url: "http://www.imdb.com/title/tt0117500/" url: "http://example.com/ogp-missing-title"
}} }}
end end
@ -96,7 +97,8 @@ test "parses twitter card" do
site: "@flickr", site: "@flickr",
image: "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg", image: "https://farm6.staticflickr.com/5510/14338202952_93595258ff_z.jpg",
title: "Small Island Developing States Photo Submission", title: "Small Island Developing States Photo Submission",
description: "View the album on Flickr." description: "View the album on Flickr.",
url: "http://example.com/twitter-card"
}} }}
end end
@ -120,7 +122,7 @@ test "parses OEmbed" do
thumbnail_width: 150, thumbnail_width: 150,
title: "Bacon Lollys", title: "Bacon Lollys",
type: "photo", type: "photo",
url: "https://farm4.staticflickr.com/3040/2362225867_4a87ab8baf_b.jpg", url: "http://example.com/oembed",
version: "1.0", version: "1.0",
web_page: "https://www.flickr.com/photos/bees/2362225867/", web_page: "https://www.flickr.com/photos/bees/2362225867/",
web_page_short_url: "https://flic.kr/p/4AK2sc", web_page_short_url: "https://flic.kr/p/4AK2sc",

View file

@ -65,6 +65,63 @@ test "it sends notify to in the 'user:notification' stream", %{user: user, notif
Streamer.stream("user:notification", notify) Streamer.stream("user:notification", notify)
Task.await(task) Task.await(task)
end end
test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{
user: user
} do
blocked = insert(:user)
{:ok, user} = User.block(user, blocked)
task = Task.async(fn -> refute_receive {:text, _}, 4_000 end)
Streamer.add_socket(
"user:notification",
%{transport_pid: task.pid, assigns: %{user: user}}
)
{:ok, activity} = CommonAPI.post(user, %{"status" => ":("})
{:ok, notif, _} = CommonAPI.favorite(activity.id, blocked)
Streamer.stream("user:notification", notif)
Task.await(task)
end
test "it doesn't send notify to the 'user:notification' stream when a thread is muted", %{
user: user
} do
user2 = insert(:user)
task = Task.async(fn -> refute_receive {:text, _}, 4_000 end)
Streamer.add_socket(
"user:notification",
%{transport_pid: task.pid, assigns: %{user: user}}
)
{:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
{:ok, activity} = CommonAPI.add_mute(user, activity)
{:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
Streamer.stream("user:notification", notif)
Task.await(task)
end
test "it doesn't send notify to the 'user:notification' stream' when a domain is blocked", %{
user: user
} do
user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"})
task = Task.async(fn -> refute_receive {:text, _}, 4_000 end)
Streamer.add_socket(
"user:notification",
%{transport_pid: task.pid, assigns: %{user: user}}
)
{:ok, user} = User.block_domain(user, "hecking-lewd-place.com")
{:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
{:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
Streamer.stream("user:notification", notif)
Task.await(task)
end
end end
test "it sends to public" do test "it sends to public" do