Merge branch 'develop' into feature/hide-follows-remote
This commit is contained in:
commit
c88a5d3251
@ -6,10 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
## [Unreleased]
|
||||
### Changed
|
||||
- **Breaking:** Configuration: A setting to explicitly disable the mailer was added, defaulting to true, if you are using a mailer add `config :pleroma, Pleroma.Emails.Mailer, enabled: true` to your config
|
||||
- **Breaking:** Configuration: `/media/` is now removed when `base_url` is configured, append `/media/` to your `base_url` config to keep the old behaviour if desired
|
||||
- Configuration: OpenGraph and TwitterCard providers enabled by default
|
||||
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
|
||||
- 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 `mailerEnabled` in `metadata`
|
||||
- Mastodon API: Unsubscribe followers when they unfollow a user
|
||||
- AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses)
|
||||
|
||||
@ -26,6 +28,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- 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.
|
||||
- ActivityPub S2S: remote user deletions now work the same as local user deletions.
|
||||
- Not being able to access the Mastodon FE login page on private instances
|
||||
|
||||
### Added
|
||||
- MRF: Support for priming the mediaproxy cache (`Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`)
|
||||
@ -57,6 +61,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- ActivityPub: Add an internal service actor for fetching ActivityPub objects.
|
||||
- ActivityPub: Optional signing of ActivityPub object fetches.
|
||||
- Admin API: Endpoint for fetching latest user's statuses
|
||||
- Pleroma API: Add `/api/v1/pleroma/accounts/confirmation_resend?email=<email>` for resending account confirmation.
|
||||
|
||||
### Changed
|
||||
- Configuration: Filter.AnonymizeFilename added ability to retain file extension with custom text
|
||||
|
@ -534,7 +534,8 @@ config :pleroma, :rate_limit,
|
||||
relation_id_action: {60_000, 2},
|
||||
statuses_actions: {10_000, 15},
|
||||
status_id_action: {60_000, 3},
|
||||
password_reset: {1_800_000, 5}
|
||||
password_reset: {1_800_000, 5},
|
||||
account_confirmation_resend: {8_640_000, 5}
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
|
@ -245,6 +245,14 @@ See [Admin-API](Admin-API.md)
|
||||
- PATCH `/api/v1/pleroma/accounts/update_banner`: Set/clear user banner image
|
||||
- PATCH `/api/v1/pleroma/accounts/update_background`: Set/clear user background image
|
||||
|
||||
## `/api/v1/pleroma/accounts/confirmation_resend`
|
||||
### Resend confirmation email
|
||||
* Method `POST`
|
||||
* Params:
|
||||
* `email`: email of that needs to be verified
|
||||
* Authentication: not required
|
||||
* Response: 204 No Content
|
||||
|
||||
## `/api/v1/pleroma/mascot`
|
||||
### Gets user mascot image
|
||||
* Method `GET`
|
||||
|
@ -8,6 +8,7 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
import Mix.Pleroma
|
||||
use Mix.Task
|
||||
|
||||
@ -99,10 +100,15 @@ defmodule Mix.Tasks.Pleroma.Database do
|
||||
NaiveDateTime.utc_now()
|
||||
|> NaiveDateTime.add(-(deadline * 86_400))
|
||||
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
from(o in Object,
|
||||
where: fragment("?->'to' \\? ? OR ?->'cc' \\? ?", o.data, ^public, o.data, ^public),
|
||||
where:
|
||||
fragment(
|
||||
"?->'to' \\? ? OR ?->'cc' \\? ?",
|
||||
o.data,
|
||||
^Pleroma.Constants.as_public(),
|
||||
o.data,
|
||||
^Pleroma.Constants.as_public()
|
||||
),
|
||||
where: o.inserted_at < ^time_deadline,
|
||||
where:
|
||||
fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host())
|
||||
|
@ -9,6 +9,8 @@ defmodule Pleroma.Activity.Search do
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
def search(user, search_query, options \\ []) do
|
||||
@ -39,7 +41,7 @@ defmodule Pleroma.Activity.Search do
|
||||
defp restrict_public(q) do
|
||||
from([a, o] in q,
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients
|
||||
where: ^Pleroma.Constants.as_public() in a.recipients
|
||||
)
|
||||
end
|
||||
|
||||
|
9
lib/pleroma/constants.ex
Normal file
9
lib/pleroma/constants.ex
Normal file
@ -0,0 +1,9 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Constants do
|
||||
use Const
|
||||
|
||||
const(as_public, do: "https://www.w3.org/ns/activitystreams#Public")
|
||||
end
|
@ -11,6 +11,7 @@ defmodule Pleroma.HTTP.Connection do
|
||||
connect_timeout: 10_000,
|
||||
recv_timeout: 20_000,
|
||||
follow_redirect: true,
|
||||
force_redirect: true,
|
||||
pool: :federation
|
||||
]
|
||||
@adapter Application.get_env(:tesla, :adapter)
|
||||
|
@ -15,7 +15,7 @@ defmodule Pleroma.Signature do
|
||||
|> Map.put(:fragment, nil)
|
||||
|
||||
uri =
|
||||
if String.ends_with?(uri.path, "/publickey") do
|
||||
if not is_nil(uri.path) and String.ends_with?(uri.path, "/publickey") do
|
||||
Map.put(uri, :path, String.replace(uri.path, "/publickey", ""))
|
||||
else
|
||||
uri
|
||||
|
@ -477,7 +477,7 @@ defmodule Pleroma.User do
|
||||
end
|
||||
|
||||
def update_and_set_cache(changeset) do
|
||||
with {:ok, user} <- Repo.update(changeset) do
|
||||
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
|
||||
set_cache(user)
|
||||
else
|
||||
e -> e
|
||||
|
@ -23,6 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
import Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
# For Announce activities, we filter the recipients based on following status for any actors
|
||||
# that match actual users. See issue #164 for more information about why this is necessary.
|
||||
@ -207,8 +208,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
def stream_out_participations(_, _), do: :noop
|
||||
|
||||
def stream_out(activity) do
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
if activity.data["type"] in ["Create", "Announce", "Delete"] do
|
||||
object = Object.normalize(activity)
|
||||
# Do not stream out poll replies
|
||||
@ -216,7 +215,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
Pleroma.Web.Streamer.stream("user", activity)
|
||||
Pleroma.Web.Streamer.stream("list", activity)
|
||||
|
||||
if Enum.member?(activity.data["to"], public) do
|
||||
if get_visibility(activity) == "public" do
|
||||
Pleroma.Web.Streamer.stream("public", activity)
|
||||
|
||||
if activity.local do
|
||||
@ -238,13 +237,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
end
|
||||
end
|
||||
else
|
||||
# TODO: Write test, replace with visibility test
|
||||
if !Enum.member?(activity.data["cc"] || [], public) &&
|
||||
!Enum.member?(
|
||||
activity.data["to"],
|
||||
User.get_cached_by_ap_id(activity.data["actor"]).follower_address
|
||||
),
|
||||
do: Pleroma.Web.Streamer.stream("direct", activity)
|
||||
if get_visibility(activity) == "direct",
|
||||
do: Pleroma.Web.Streamer.stream("direct", activity)
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -514,7 +508,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
end
|
||||
|
||||
defp fetch_activities_for_context_query(context, opts) do
|
||||
public = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
public = [Pleroma.Constants.as_public()]
|
||||
|
||||
recipients =
|
||||
if opts["user"], do: [opts["user"].ap_id | opts["user"].following] ++ public, else: public
|
||||
@ -555,7 +549,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
end
|
||||
|
||||
def fetch_public_activities(opts \\ %{}) do
|
||||
q = fetch_activities_query(["https://www.w3.org/ns/activitystreams#Public"], opts)
|
||||
q = fetch_activities_query([Pleroma.Constants.as_public()], opts)
|
||||
|
||||
q
|
||||
|> restrict_unlisted()
|
||||
@ -646,10 +640,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
|
||||
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]
|
||||
[Pleroma.Constants.as_public()] ++ [reading_user.ap_id | reading_user.following]
|
||||
else
|
||||
["https://www.w3.org/ns/activitystreams#Public"]
|
||||
[Pleroma.Constants.as_public()]
|
||||
end
|
||||
end
|
||||
|
||||
@ -834,7 +827,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
fragment(
|
||||
"not (coalesce(?->'cc', '{}'::jsonb) \\?| ?)",
|
||||
activity.data,
|
||||
^["https://www.w3.org/ns/activitystreams#Public"]
|
||||
^[Pleroma.Constants.as_public()]
|
||||
)
|
||||
)
|
||||
end
|
||||
@ -971,7 +964,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
||||
where:
|
||||
fragment("? && ?", activity.recipients, ^recipients) or
|
||||
(fragment("? && ?", activity.recipients, ^recipients_with_public) and
|
||||
"https://www.w3.org/ns/activitystreams#Public" in activity.recipients)
|
||||
^Pleroma.Constants.as_public() in activity.recipients)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -4,6 +4,9 @@
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
|
||||
alias Pleroma.User
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
@moduledoc "Block messages with too much mentions (configurable)"
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
@ -19,12 +22,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
|
||||
when follower_collection? and recipients > threshold ->
|
||||
message
|
||||
|> Map.put("to", [follower_collection])
|
||||
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"])
|
||||
|> Map.put("cc", [Pleroma.Constants.as_public()])
|
||||
|
||||
{:public, recipients} when recipients > threshold ->
|
||||
message
|
||||
|> Map.put("to", [])
|
||||
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"])
|
||||
|> Map.put("cc", [Pleroma.Constants.as_public()])
|
||||
|
||||
_ ->
|
||||
message
|
||||
@ -51,10 +54,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do
|
||||
recipients = (message["to"] || []) ++ (message["cc"] || [])
|
||||
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address
|
||||
|
||||
if Enum.member?(recipients, "https://www.w3.org/ns/activitystreams#Public") do
|
||||
if Enum.member?(recipients, Pleroma.Constants.as_public()) do
|
||||
recipients =
|
||||
recipients
|
||||
|> List.delete("https://www.w3.org/ns/activitystreams#Public")
|
||||
|> List.delete(Pleroma.Constants.as_public())
|
||||
|> List.delete(follower_collection)
|
||||
|
||||
{:public, length(recipients)}
|
||||
|
@ -3,6 +3,8 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
||||
require Pleroma.Constants
|
||||
|
||||
@moduledoc "Reject or Word-Replace messages with a keyword or regex"
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
@ -31,12 +33,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do
|
||||
defp check_ftl_removal(
|
||||
%{"to" => to, "object" => %{"content" => content, "summary" => summary}} = message
|
||||
) do
|
||||
if "https://www.w3.org/ns/activitystreams#Public" in to and
|
||||
if Pleroma.Constants.as_public() in to and
|
||||
Enum.any?(Pleroma.Config.get([:mrf_keyword, :federated_timeline_removal]), fn pattern ->
|
||||
string_matches?(content, pattern) or string_matches?(summary, pattern)
|
||||
end) do
|
||||
to = List.delete(to, "https://www.w3.org/ns/activitystreams#Public")
|
||||
cc = ["https://www.w3.org/ns/activitystreams#Public" | message["cc"] || []]
|
||||
to = List.delete(to, Pleroma.Constants.as_public())
|
||||
cc = [Pleroma.Constants.as_public() | message["cc"] || []]
|
||||
|
||||
message =
|
||||
message
|
||||
|
@ -10,7 +10,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
||||
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||
require Pleroma.Constants
|
||||
|
||||
@impl true
|
||||
def filter(%{"type" => "Create"} = object) do
|
||||
@ -19,8 +19,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
||||
# Determine visibility
|
||||
visibility =
|
||||
cond do
|
||||
@public in object["to"] -> "public"
|
||||
@public in object["cc"] -> "unlisted"
|
||||
Pleroma.Constants.as_public() in object["to"] -> "public"
|
||||
Pleroma.Constants.as_public() in object["cc"] -> "unlisted"
|
||||
user.follower_address in object["to"] -> "followers"
|
||||
true -> "direct"
|
||||
end
|
||||
|
@ -8,6 +8,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||
@moduledoc "Filter activities depending on their origin instance"
|
||||
@behaviour MRF
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
defp check_accept(%{host: actor_host} = _actor_info, object) do
|
||||
accepts =
|
||||
Pleroma.Config.get([:mrf_simple, :accept])
|
||||
@ -89,14 +91,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||
object =
|
||||
with true <- MRF.subdomain_match?(timeline_removal, actor_host),
|
||||
user <- User.get_cached_by_ap_id(object["actor"]),
|
||||
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do
|
||||
to =
|
||||
List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++
|
||||
[user.follower_address]
|
||||
true <- Pleroma.Constants.as_public() in object["to"] do
|
||||
to = List.delete(object["to"], Pleroma.Constants.as_public()) ++ [user.follower_address]
|
||||
|
||||
cc =
|
||||
List.delete(object["cc"], user.follower_address) ++
|
||||
["https://www.w3.org/ns/activitystreams#Public"]
|
||||
cc = List.delete(object["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()]
|
||||
|
||||
object
|
||||
|> Map.put("to", to)
|
||||
|
@ -19,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|
||||
- `mrf_tag:disable-any-subscription`: Reject any follow requests
|
||||
"""
|
||||
|
||||
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||
require Pleroma.Constants
|
||||
|
||||
defp get_tags(%User{tags: tags}) when is_list(tags), do: tags
|
||||
defp get_tags(_), do: []
|
||||
@ -70,9 +70,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|
||||
) do
|
||||
user = User.get_cached_by_ap_id(actor)
|
||||
|
||||
if Enum.member?(to, @public) do
|
||||
to = List.delete(to, @public) ++ [user.follower_address]
|
||||
cc = List.delete(cc, user.follower_address) ++ [@public]
|
||||
if Enum.member?(to, Pleroma.Constants.as_public()) do
|
||||
to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address]
|
||||
cc = List.delete(cc, user.follower_address) ++ [Pleroma.Constants.as_public()]
|
||||
|
||||
object =
|
||||
object
|
||||
@ -103,9 +103,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do
|
||||
) do
|
||||
user = User.get_cached_by_ap_id(actor)
|
||||
|
||||
if Enum.member?(to, @public) or Enum.member?(cc, @public) do
|
||||
to = List.delete(to, @public) ++ [user.follower_address]
|
||||
cc = List.delete(cc, @public)
|
||||
if Enum.member?(to, Pleroma.Constants.as_public()) or
|
||||
Enum.member?(cc, Pleroma.Constants.as_public()) do
|
||||
to = List.delete(to, Pleroma.Constants.as_public()) ++ [user.follower_address]
|
||||
cc = List.delete(cc, Pleroma.Constants.as_public())
|
||||
|
||||
object =
|
||||
object
|
||||
|
@ -11,6 +11,8 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
import Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
@behaviour Pleroma.Web.Federator.Publisher
|
||||
@ -117,8 +119,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
||||
|> Enum.map(& &1.ap_id)
|
||||
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"]
|
||||
|
||||
@ -145,7 +145,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
||||
type == "Delete" ->
|
||||
maybe_use_sharedinbox(user)
|
||||
|
||||
@as_public in to || @as_public in cc ->
|
||||
Pleroma.Constants.as_public() in to || Pleroma.Constants.as_public() in cc ->
|
||||
maybe_use_sharedinbox(user)
|
||||
|
||||
length(to) + length(cc) > 1 ->
|
||||
|
@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
@doc """
|
||||
Modifies an incoming AP object (mastodon format) to our internal format.
|
||||
@ -102,8 +103,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
|
||||
follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address
|
||||
|
||||
explicit_mentions =
|
||||
explicit_mentions ++ ["https://www.w3.org/ns/activitystreams#Public", follower_collection]
|
||||
explicit_mentions = explicit_mentions ++ [Pleroma.Constants.as_public(), follower_collection]
|
||||
|
||||
fix_explicit_addressing(object, explicit_mentions, follower_collection)
|
||||
end
|
||||
@ -115,11 +115,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
|
||||
if followers_collection not in recipients do
|
||||
cond do
|
||||
"https://www.w3.org/ns/activitystreams#Public" in cc ->
|
||||
Pleroma.Constants.as_public() in cc ->
|
||||
to = to ++ [followers_collection]
|
||||
Map.put(object, "to", to)
|
||||
|
||||
"https://www.w3.org/ns/activitystreams#Public" in to ->
|
||||
Pleroma.Constants.as_public() in to ->
|
||||
cc = cc ++ [followers_collection]
|
||||
Map.put(object, "cc", cc)
|
||||
|
||||
@ -480,8 +480,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
{:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
|
||||
with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),
|
||||
{_, false} <-
|
||||
{:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
|
||||
{_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked},
|
||||
{_, false} <- {:user_locked, User.locked?(followed)},
|
||||
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
|
||||
{_, {:ok, _}} <-
|
||||
@ -656,20 +655,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
||||
nil ->
|
||||
case User.get_cached_by_ap_id(object_id) do
|
||||
%User{ap_id: ^actor} = user ->
|
||||
{:ok, followers} = User.get_followers(user)
|
||||
|
||||
Enum.each(followers, fn follower ->
|
||||
User.unfollow(follower, user)
|
||||
end)
|
||||
|
||||
{:ok, friends} = User.get_friends(user)
|
||||
|
||||
Enum.each(friends, fn followed ->
|
||||
User.unfollow(user, followed)
|
||||
end)
|
||||
|
||||
User.invalidate_cache(user)
|
||||
Repo.delete(user)
|
||||
User.delete(user)
|
||||
|
||||
nil ->
|
||||
:error
|
||||
|
@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
@supported_object_types ["Article", "Note", "Video", "Page", "Question", "Answer"]
|
||||
@supported_report_states ~w(open closed resolved)
|
||||
@ -418,7 +419,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
"type" => "Follow",
|
||||
"actor" => follower_id,
|
||||
"to" => [followed_id],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"cc" => [Pleroma.Constants.as_public()],
|
||||
"object" => followed_id,
|
||||
"state" => "pending"
|
||||
}
|
||||
@ -510,7 +511,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
"actor" => ap_id,
|
||||
"object" => id,
|
||||
"to" => [user.follower_address, object.data["actor"]],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"cc" => [Pleroma.Constants.as_public()],
|
||||
"context" => object.data["context"]
|
||||
}
|
||||
|
||||
@ -530,7 +531,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
"actor" => ap_id,
|
||||
"object" => activity.data,
|
||||
"to" => [user.follower_address, activity.data["actor"]],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"cc" => [Pleroma.Constants.as_public()],
|
||||
"context" => context
|
||||
}
|
||||
|
||||
@ -547,7 +548,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
"actor" => ap_id,
|
||||
"object" => activity.data,
|
||||
"to" => [user.follower_address, activity.data["actor"]],
|
||||
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"cc" => [Pleroma.Constants.as_public()],
|
||||
"context" => context
|
||||
}
|
||||
|
||||
@ -556,7 +557,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
|
||||
def add_announce_to_object(
|
||||
%Activity{
|
||||
data: %{"actor" => actor, "cc" => ["https://www.w3.org/ns/activitystreams#Public"]}
|
||||
data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}
|
||||
},
|
||||
object
|
||||
) do
|
||||
@ -765,7 +766,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
||||
) do
|
||||
cc = Map.get(data, "cc", [])
|
||||
follower_address = User.get_cached_by_ap_id(data["actor"]).follower_address
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
public = Pleroma.Constants.as_public()
|
||||
|
||||
case visibility do
|
||||
"public" ->
|
||||
|
@ -8,14 +8,14 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||
require Pleroma.Constants
|
||||
|
||||
@spec is_public?(Object.t() | Activity.t() | map()) :: boolean()
|
||||
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false
|
||||
def is_public?(%Object{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?(data), do: @public in (data["to"] ++ (data["cc"] || []))
|
||||
def is_public?(data), do: Pleroma.Constants.as_public() in (data["to"] ++ (data["cc"] || []))
|
||||
|
||||
def is_private?(activity) do
|
||||
with false <- is_public?(activity),
|
||||
@ -73,10 +73,10 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
|
||||
cc = object.data["cc"] || []
|
||||
|
||||
cond do
|
||||
@public in to ->
|
||||
Pleroma.Constants.as_public() in to ->
|
||||
"public"
|
||||
|
||||
@public in cc ->
|
||||
Pleroma.Constants.as_public() in cc ->
|
||||
"unlisted"
|
||||
|
||||
# this should use the sql for the object's activity
|
||||
|
@ -21,8 +21,7 @@ defmodule Pleroma.Web.Auth.Authenticator do
|
||||
def create_from_registration(plug, registration),
|
||||
do: implementation().create_from_registration(plug, registration)
|
||||
|
||||
@callback get_registration(Plug.Conn.t()) ::
|
||||
{:ok, Registration.t()} | {:error, any()}
|
||||
@callback get_registration(Plug.Conn.t()) :: {:ok, Registration.t()} | {:error, any()}
|
||||
def get_registration(plug), do: implementation().get_registration(plug)
|
||||
|
||||
@callback handle_error(Plug.Conn.t(), any()) :: any()
|
||||
|
@ -300,8 +300,7 @@ defmodule Pleroma.Web.CommonAPI do
|
||||
}
|
||||
} = activity <- get_by_id_or_ap_id(id_or_ap_id),
|
||||
true <- Visibility.is_public?(activity),
|
||||
%{valid?: true} = info_changeset <-
|
||||
User.Info.add_pinnned_activity(user.info, activity),
|
||||
%{valid?: true} = info_changeset <- User.Info.add_pinnned_activity(user.info, activity),
|
||||
changeset <-
|
||||
Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_changeset),
|
||||
{:ok, _user} <- User.update_and_set_cache(changeset) do
|
||||
|
@ -19,6 +19,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||
alias Pleroma.Web.MediaProxy
|
||||
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
# This is a hack for twidere.
|
||||
def get_by_id_or_ap_id(id) do
|
||||
@ -66,7 +67,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||
@spec get_to_and_cc(User.t(), list(String.t()), Activity.t() | nil, String.t()) ::
|
||||
{list(String.t()), list(String.t())}
|
||||
def get_to_and_cc(user, mentioned_users, inReplyTo, "public") do
|
||||
to = ["https://www.w3.org/ns/activitystreams#Public" | mentioned_users]
|
||||
to = [Pleroma.Constants.as_public() | mentioned_users]
|
||||
cc = [user.follower_address]
|
||||
|
||||
if inReplyTo do
|
||||
@ -78,7 +79,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
||||
|
||||
def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted") do
|
||||
to = [user.follower_address | mentioned_users]
|
||||
cc = ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
cc = [Pleroma.Constants.as_public()]
|
||||
|
||||
if inReplyTo do
|
||||
{Enum.uniq([inReplyTo.data["actor"] | to]), cc}
|
||||
|
77
lib/pleroma/web/fallback_redirect_controller.ex
Normal file
77
lib/pleroma/web/fallback_redirect_controller.ex
Normal file
@ -0,0 +1,77 @@
|
||||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Fallback.RedirectController do
|
||||
use Pleroma.Web, :controller
|
||||
require Logger
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Metadata
|
||||
|
||||
def api_not_implemented(conn, _params) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "Not implemented"})
|
||||
end
|
||||
|
||||
def redirector(conn, _params, code \\ 200)
|
||||
|
||||
# redirect to admin section
|
||||
# /pleroma/admin -> /pleroma/admin/
|
||||
#
|
||||
def redirector(conn, %{"path" => ["pleroma", "admin"]} = _, _code) do
|
||||
redirect(conn, to: "/pleroma/admin/")
|
||||
end
|
||||
|
||||
def redirector(conn, _params, code) do
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(code, index_file_path())
|
||||
end
|
||||
|
||||
def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
|
||||
redirector_with_meta(conn, %{user: user})
|
||||
else
|
||||
nil ->
|
||||
redirector(conn, params)
|
||||
end
|
||||
end
|
||||
|
||||
def redirector_with_meta(conn, params) do
|
||||
{:ok, index_content} = File.read(index_file_path())
|
||||
|
||||
tags =
|
||||
try do
|
||||
Metadata.build_tags(params)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error(
|
||||
"Metadata rendering for #{conn.request_path} failed.\n" <>
|
||||
Exception.format(:error, e, __STACKTRACE__)
|
||||
)
|
||||
|
||||
""
|
||||
end
|
||||
|
||||
response = String.replace(index_content, "<!--server-generated-meta-->", tags)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_resp(200, response)
|
||||
end
|
||||
|
||||
def index_file_path do
|
||||
Pleroma.Plugs.InstanceStatic.file_path("index.html")
|
||||
end
|
||||
|
||||
def registration_page(conn, params) do
|
||||
redirector(conn, params)
|
||||
end
|
||||
|
||||
def empty(conn, _params) do
|
||||
conn
|
||||
|> put_status(204)
|
||||
|> text("")
|
||||
end
|
||||
end
|
@ -4,6 +4,9 @@
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Bookmark
|
||||
@ -46,6 +49,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
import Ecto.Query
|
||||
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
@rate_limited_relations_actions ~w(follow unfollow)a
|
||||
|
||||
@ -74,6 +78,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
plug(RateLimiter, :app_account_creation when action == :account_register)
|
||||
plug(RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||
plug(RateLimiter, :password_reset when action == :password_reset)
|
||||
plug(RateLimiter, :account_confirmation_resend when action == :account_confirmation_resend)
|
||||
|
||||
@local_mastodon_name "Mastodon-Local"
|
||||
|
||||
@ -1220,10 +1225,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
|
||||
recipients =
|
||||
if for_user do
|
||||
["https://www.w3.org/ns/activitystreams#Public"] ++
|
||||
[for_user.ap_id | for_user.following]
|
||||
[Pleroma.Constants.as_public()] ++ [for_user.ap_id | for_user.following]
|
||||
else
|
||||
["https://www.w3.org/ns/activitystreams#Public"]
|
||||
[Pleroma.Constants.as_public()]
|
||||
end
|
||||
|
||||
activities =
|
||||
@ -1839,6 +1843,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
||||
end
|
||||
end
|
||||
|
||||
def account_confirmation_resend(conn, params) do
|
||||
nickname_or_email = params["email"] || params["nickname"]
|
||||
|
||||
with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
|
||||
{:ok, _} <- User.try_send_confirmation_email(user) do
|
||||
conn
|
||||
|> json_response(:no_content, "")
|
||||
end
|
||||
end
|
||||
|
||||
def try_render(conn, target, params)
|
||||
when is_binary(target) do
|
||||
case render(conn, target, params) do
|
||||
|
@ -165,6 +165,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
||||
},
|
||||
accountActivationRequired: Config.get([:instance, :account_activation_required], false),
|
||||
invitesEnabled: Config.get([:instance, :invites_enabled], false),
|
||||
mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
|
||||
features: features,
|
||||
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
|
||||
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false)
|
||||
|
@ -365,8 +365,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{_, {:ok, auth}} <-
|
||||
{:create_authorization, do_create_authorization(conn, params)},
|
||||
{_, {:ok, auth}} <- {:create_authorization, do_create_authorization(conn, params)},
|
||||
%User{} = user <- Repo.preload(auth, :user).user,
|
||||
{:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
|
||||
conn
|
||||
|
@ -44,8 +44,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
||||
|> Repo.find_resource()
|
||||
end
|
||||
|
||||
@spec exchange_token(App.t(), Authorization.t()) ::
|
||||
{:ok, Token.t()} | {:error, Changeset.t()}
|
||||
@spec exchange_token(App.t(), Authorization.t()) :: {:ok, Token.t()} | {:error, Changeset.t()}
|
||||
def exchange_token(app, auth) do
|
||||
with {:ok, auth} <- Authorization.use_token(auth),
|
||||
true <- auth.app_id == app.id do
|
||||
|
@ -9,6 +9,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
||||
alias Pleroma.Web.OStatus.UserRepresenter
|
||||
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
defp get_href(id) do
|
||||
with %Object{data: %{"external_url" => external_url}} <- Object.get_cached_by_ap_id(id) do
|
||||
@ -34,7 +35,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
|
||||
Enum.map(to, fn id ->
|
||||
cond do
|
||||
# Special handling for the AP/Ostatus public collections
|
||||
"https://www.w3.org/ns/activitystreams#Public" == id ->
|
||||
Pleroma.Constants.as_public() == id ->
|
||||
{:link,
|
||||
[
|
||||
rel: "mentioned",
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
defmodule Pleroma.Web.OStatus.NoteHandler do
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
@ -49,7 +50,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
|
||||
def get_collection_mentions(entry) do
|
||||
transmogrify = fn
|
||||
"http://activityschema.org/collection/public" ->
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
Pleroma.Constants.as_public()
|
||||
|
||||
group ->
|
||||
group
|
||||
@ -126,7 +127,7 @@ defmodule Pleroma.Web.OStatus.NoteHandler do
|
||||
to <- make_to_list(actor, mentions),
|
||||
date <- XML.string_from_xpath("//published", entry),
|
||||
unlisted <- XML.string_from_xpath("//mastodon:scope", entry) == "unlisted",
|
||||
cc <- if(unlisted, do: ["https://www.w3.org/ns/activitystreams#Public"], else: []),
|
||||
cc <- if(unlisted, do: [Pleroma.Constants.as_public()], else: []),
|
||||
note <-
|
||||
CommonAPI.Utils.make_note_data(
|
||||
actor.ap_id,
|
||||
|
@ -5,6 +5,7 @@
|
||||
defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Fallback.RedirectController
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
@ -12,42 +13,44 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
alias Pleroma.Web.ActivityPub.ActivityPubController
|
||||
alias Pleroma.Web.ActivityPub.ObjectView
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.Federator
|
||||
alias Pleroma.Web.Metadata.PlayerView
|
||||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.OStatus.ActivityRepresenter
|
||||
alias Pleroma.Web.OStatus.FeedRepresenter
|
||||
alias Pleroma.Web.Router
|
||||
alias Pleroma.Web.XML
|
||||
|
||||
plug(Pleroma.Web.FederatingPlug when action in [:salmon_incoming])
|
||||
|
||||
plug(
|
||||
Pleroma.Plugs.SetFormatPlug
|
||||
when action in [:feed_redirect, :object, :activity, :notice]
|
||||
)
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
def feed_redirect(%{assigns: %{format: "html"}} = conn, %{"nickname" => nickname}) do
|
||||
with {_, %User{} = user} <-
|
||||
{:fetch_user, User.get_cached_by_nickname_or_id(nickname)} do
|
||||
RedirectController.redirector_with_meta(conn, %{user: user})
|
||||
end
|
||||
end
|
||||
|
||||
def feed_redirect(%{assigns: %{format: format}} = conn, _params)
|
||||
when format in ["json", "activity+json"] do
|
||||
ActivityPubController.call(conn, :user)
|
||||
end
|
||||
|
||||
def feed_redirect(conn, %{"nickname" => nickname}) do
|
||||
case get_format(conn) do
|
||||
"html" ->
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
|
||||
Fallback.RedirectController.redirector_with_meta(conn, %{user: user})
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
end
|
||||
|
||||
"activity+json" ->
|
||||
ActivityPubController.call(conn, :user)
|
||||
|
||||
"json" ->
|
||||
ActivityPubController.call(conn, :user)
|
||||
|
||||
_ ->
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
redirect(conn, external: OStatus.feed_path(user))
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
end
|
||||
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
|
||||
redirect(conn, external: OStatus.feed_path(user))
|
||||
end
|
||||
end
|
||||
|
||||
def feed(conn, %{"nickname" => nickname} = params) do
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do
|
||||
query_params =
|
||||
Map.take(params, ["max_id"])
|
||||
|> Map.merge(%{"whole_db" => true, "actor_id" => user.ap_id})
|
||||
@ -65,8 +68,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
conn
|
||||
|> put_resp_content_type("application/atom+xml")
|
||||
|> send_resp(200, response)
|
||||
else
|
||||
nil -> {:error, :not_found}
|
||||
end
|
||||
end
|
||||
|
||||
@ -97,93 +98,82 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
|> send_resp(200, "")
|
||||
end
|
||||
|
||||
def object(conn, %{"uuid" => uuid}) do
|
||||
if get_format(conn) in ["activity+json", "json"] do
|
||||
ActivityPubController.call(conn, :object)
|
||||
else
|
||||
with id <- o_status_url(conn, :object, uuid),
|
||||
{_, %Activity{} = activity} <-
|
||||
{:activity, Activity.get_create_by_object_ap_id_with_object(id)},
|
||||
{_, true} <- {:public?, Visibility.is_public?(activity)},
|
||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
case get_format(conn) do
|
||||
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
||||
_ -> represent_activity(conn, nil, activity, user)
|
||||
end
|
||||
else
|
||||
{:public?, false} ->
|
||||
{:error, :not_found}
|
||||
def object(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
|
||||
when format in ["json", "activity+json"] do
|
||||
ActivityPubController.call(conn, :object)
|
||||
end
|
||||
|
||||
{:activity, nil} ->
|
||||
{:error, :not_found}
|
||||
|
||||
e ->
|
||||
e
|
||||
def object(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
|
||||
with id <- o_status_url(conn, :object, uuid),
|
||||
{_, %Activity{} = activity} <-
|
||||
{:activity, Activity.get_create_by_object_ap_id_with_object(id)},
|
||||
{_, true} <- {:public?, Visibility.is_public?(activity)},
|
||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
case format do
|
||||
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
||||
_ -> represent_activity(conn, nil, activity, user)
|
||||
end
|
||||
else
|
||||
reason when reason in [{:public?, false}, {:activity, nil}] ->
|
||||
{:error, :not_found}
|
||||
|
||||
e ->
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
def activity(conn, %{"uuid" => uuid}) do
|
||||
if get_format(conn) in ["activity+json", "json"] do
|
||||
ActivityPubController.call(conn, :activity)
|
||||
else
|
||||
with id <- o_status_url(conn, :activity, uuid),
|
||||
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
|
||||
{_, true} <- {:public?, Visibility.is_public?(activity)},
|
||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
case format = get_format(conn) do
|
||||
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
||||
_ -> represent_activity(conn, format, activity, user)
|
||||
end
|
||||
else
|
||||
{:public?, false} ->
|
||||
{:error, :not_found}
|
||||
def activity(%{assigns: %{format: format}} = conn, %{"uuid" => _uuid})
|
||||
when format in ["json", "activity+json"] do
|
||||
ActivityPubController.call(conn, :activity)
|
||||
end
|
||||
|
||||
{:activity, nil} ->
|
||||
{:error, :not_found}
|
||||
|
||||
e ->
|
||||
e
|
||||
def activity(%{assigns: %{format: format}} = conn, %{"uuid" => uuid}) do
|
||||
with id <- o_status_url(conn, :activity, uuid),
|
||||
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
|
||||
{_, true} <- {:public?, Visibility.is_public?(activity)},
|
||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
case format do
|
||||
"html" -> redirect(conn, to: "/notice/#{activity.id}")
|
||||
_ -> represent_activity(conn, format, activity, user)
|
||||
end
|
||||
else
|
||||
reason when reason in [{:public?, false}, {:activity, nil}] ->
|
||||
{:error, :not_found}
|
||||
|
||||
e ->
|
||||
e
|
||||
end
|
||||
end
|
||||
|
||||
def notice(conn, %{"id" => id}) do
|
||||
def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do
|
||||
with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id_with_object(id)},
|
||||
{_, true} <- {:public?, Visibility.is_public?(activity)},
|
||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
|
||||
case format = get_format(conn) do
|
||||
"html" ->
|
||||
if activity.data["type"] == "Create" do
|
||||
%Object{} = object = Object.normalize(activity)
|
||||
cond do
|
||||
format == "html" && activity.data["type"] == "Create" ->
|
||||
%Object{} = object = Object.normalize(activity)
|
||||
|
||||
Fallback.RedirectController.redirector_with_meta(conn, %{
|
||||
RedirectController.redirector_with_meta(
|
||||
conn,
|
||||
%{
|
||||
activity_id: activity.id,
|
||||
object: object,
|
||||
url:
|
||||
Pleroma.Web.Router.Helpers.o_status_url(
|
||||
Pleroma.Web.Endpoint,
|
||||
:notice,
|
||||
activity.id
|
||||
),
|
||||
url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id),
|
||||
user: user
|
||||
})
|
||||
else
|
||||
Fallback.RedirectController.redirector(conn, nil)
|
||||
end
|
||||
}
|
||||
)
|
||||
|
||||
_ ->
|
||||
format == "html" ->
|
||||
RedirectController.redirector(conn, nil)
|
||||
|
||||
true ->
|
||||
represent_activity(conn, format, activity, user)
|
||||
end
|
||||
else
|
||||
{:public?, false} ->
|
||||
reason when reason in [{:public?, false}, {:activity, nil}] ->
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> Fallback.RedirectController.redirector(nil, 404)
|
||||
|
||||
{:activity, nil} ->
|
||||
conn
|
||||
|> Fallback.RedirectController.redirector(nil, 404)
|
||||
|> RedirectController.redirector(nil, 404)
|
||||
|
||||
e ->
|
||||
e
|
||||
@ -204,13 +194,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
"content-security-policy",
|
||||
"default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
|
||||
)
|
||||
|> put_view(Pleroma.Web.Metadata.PlayerView)
|
||||
|> put_view(PlayerView)
|
||||
|> render("player.html", url)
|
||||
else
|
||||
_error ->
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> Fallback.RedirectController.redirector(nil, 404)
|
||||
|> RedirectController.redirector(nil, 404)
|
||||
end
|
||||
end
|
||||
|
||||
@ -248,6 +238,8 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
render_error(conn, :not_found, "Not found")
|
||||
end
|
||||
|
||||
def errors(conn, {:fetch_user, nil}), do: errors(conn, {:error, :not_found})
|
||||
|
||||
def errors(conn, _) do
|
||||
render_error(conn, :internal_server_error, "Something went wrong")
|
||||
end
|
||||
|
@ -19,8 +19,7 @@ defmodule Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl do
|
||||
defp is_aws_signed_url(image) when is_binary(image) do
|
||||
%URI{host: host, query: query} = URI.parse(image)
|
||||
|
||||
if String.contains?(host, "amazonaws.com") and
|
||||
String.contains?(query, "X-Amz-Expires") do
|
||||
if String.contains?(host, "amazonaws.com") and String.contains?(query, "X-Amz-Expires") do
|
||||
image
|
||||
else
|
||||
nil
|
||||
|
@ -412,6 +412,12 @@ defmodule Pleroma.Web.Router do
|
||||
|
||||
get("/accounts/search", SearchController, :account_search)
|
||||
|
||||
post(
|
||||
"/pleroma/accounts/confirmation_resend",
|
||||
MastodonAPIController,
|
||||
:account_confirmation_resend
|
||||
)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_public)
|
||||
|
||||
@ -692,7 +698,7 @@ defmodule Pleroma.Web.Router do
|
||||
post("/auth/password", MastodonAPIController, :password_reset)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_public)
|
||||
pipe_through(:oauth_read)
|
||||
get("/web/*path", MastodonAPIController, :index)
|
||||
end
|
||||
end
|
||||
@ -729,68 +735,3 @@ defmodule Pleroma.Web.Router do
|
||||
options("/*path", RedirectController, :empty)
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Fallback.RedirectController do
|
||||
use Pleroma.Web, :controller
|
||||
require Logger
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.Metadata
|
||||
|
||||
def api_not_implemented(conn, _params) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "Not implemented"})
|
||||
end
|
||||
|
||||
def redirector(conn, _params, code \\ 200) do
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(code, index_file_path())
|
||||
end
|
||||
|
||||
def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do
|
||||
with %User{} = user <- User.get_cached_by_nickname_or_id(maybe_nickname_or_id) do
|
||||
redirector_with_meta(conn, %{user: user})
|
||||
else
|
||||
nil ->
|
||||
redirector(conn, params)
|
||||
end
|
||||
end
|
||||
|
||||
def redirector_with_meta(conn, params) do
|
||||
{:ok, index_content} = File.read(index_file_path())
|
||||
|
||||
tags =
|
||||
try do
|
||||
Metadata.build_tags(params)
|
||||
rescue
|
||||
e ->
|
||||
Logger.error(
|
||||
"Metadata rendering for #{conn.request_path} failed.\n" <>
|
||||
Exception.format(:error, e, __STACKTRACE__)
|
||||
)
|
||||
|
||||
""
|
||||
end
|
||||
|
||||
response = String.replace(index_content, "<!--server-generated-meta-->", tags)
|
||||
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_resp(200, response)
|
||||
end
|
||||
|
||||
def index_file_path do
|
||||
Pleroma.Plugs.InstanceStatic.file_path("index.html")
|
||||
end
|
||||
|
||||
def registration_page(conn, params) do
|
||||
redirector(conn, params)
|
||||
end
|
||||
|
||||
def empty(conn, _params) do
|
||||
conn
|
||||
|> put_status(204)
|
||||
|> text("")
|
||||
end
|
||||
end
|
||||
|
@ -15,6 +15,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
def create_status(%User{} = user, %{"status" => _} = data) do
|
||||
CommonAPI.post(user, data)
|
||||
end
|
||||
@ -286,7 +288,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
|
||||
from(
|
||||
[a, o] in Activity.with_preloaded_object(Activity),
|
||||
where: fragment("?->>'type' = 'Create'", a.data),
|
||||
where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
|
||||
where: ^Pleroma.Constants.as_public() in a.recipients,
|
||||
where:
|
||||
fragment(
|
||||
"to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)",
|
||||
|
@ -19,6 +19,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
||||
|
||||
import Ecto.Query
|
||||
require Logger
|
||||
require Pleroma.Constants
|
||||
|
||||
defp query_context_ids([]), do: []
|
||||
|
||||
@ -91,7 +92,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|
||||
String.ends_with?(ap_id, "/followers") ->
|
||||
nil
|
||||
|
||||
ap_id == "https://www.w3.org/ns/activitystreams#Public" ->
|
||||
ap_id == Pleroma.Constants.as_public() ->
|
||||
nil
|
||||
|
||||
user = User.get_cached_by_ap_id(ap_id) ->
|
||||
|
@ -10,6 +10,8 @@ defmodule Pleroma.Web.TwitterAPI.NotificationView do
|
||||
alias Pleroma.Web.TwitterAPI.ActivityView
|
||||
alias Pleroma.Web.TwitterAPI.UserView
|
||||
|
||||
require Pleroma.Constants
|
||||
|
||||
defp get_user(ap_id, opts) do
|
||||
cond do
|
||||
user = opts[:users][ap_id] ->
|
||||
@ -18,7 +20,7 @@ defmodule Pleroma.Web.TwitterAPI.NotificationView do
|
||||
String.ends_with?(ap_id, "/followers") ->
|
||||
nil
|
||||
|
||||
ap_id == "https://www.w3.org/ns/activitystreams#Public" ->
|
||||
ap_id == Pleroma.Constants.as_public() ->
|
||||
nil
|
||||
|
||||
true ->
|
||||
|
1
mix.exs
1
mix.exs
@ -150,6 +150,7 @@ defmodule Pleroma.Mixfile do
|
||||
{:benchee, "~> 1.0"},
|
||||
{:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
|
||||
{:ex_rated, "~> 1.3"},
|
||||
{:ex_const, "~> 0.2"},
|
||||
{:plug_static_index_html, "~> 1.0.0"},
|
||||
{:excoveralls, "~> 0.11.1", only: :test},
|
||||
{:mox, "~> 0.5", only: :test}
|
||||
|
1
mix.lock
1
mix.lock
@ -27,6 +27,7 @@
|
||||
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},
|
||||
"ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ex_aws_s3": {:hex, :ex_aws_s3, "2.0.1", "9e09366e77f25d3d88c5393824e613344631be8db0d1839faca49686e99b6704", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm"},
|
||||
"ex_doc": {:hex, :ex_doc, "0.20.2", "1bd0dfb0304bade58beb77f20f21ee3558cc3c753743ae0ddbb0fd7ba2912331", [:mix], [{:earmark, "~> 1.3", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.10", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
"ex_machina": {:hex, :ex_machina, "2.3.0", "92a5ad0a8b10ea6314b876a99c8c9e3f25f4dde71a2a835845b136b9adaf199a", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm"},
|
||||
"ex_rated": {:hex, :ex_rated, "1.3.3", "30ecbdabe91f7eaa9d37fa4e81c85ba420f371babeb9d1910adbcd79ec798d27", [:mix], [{:ex2ms, "~> 1.5", [hex: :ex2ms, repo: "hexpm", optional: false]}], "hexpm"},
|
||||
|
@ -564,6 +564,64 @@ defmodule Pleroma.NotificationTest do
|
||||
|
||||
assert Enum.empty?(Notification.for_user(user))
|
||||
end
|
||||
|
||||
test "notifications are deleted if a local user is deleted" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, _activity} =
|
||||
CommonAPI.post(user, %{"status" => "hi @#{other_user.nickname}", "visibility" => "direct"})
|
||||
|
||||
refute Enum.empty?(Notification.for_user(other_user))
|
||||
|
||||
User.delete(user)
|
||||
|
||||
assert Enum.empty?(Notification.for_user(other_user))
|
||||
end
|
||||
|
||||
test "notifications are deleted if a remote user is deleted" do
|
||||
remote_user = insert(:user)
|
||||
local_user = insert(:user)
|
||||
|
||||
dm_message = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Create",
|
||||
"actor" => remote_user.ap_id,
|
||||
"id" => remote_user.ap_id <> "/activities/test",
|
||||
"to" => [local_user.ap_id],
|
||||
"cc" => [],
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "Hello!",
|
||||
"tag" => [
|
||||
%{
|
||||
"type" => "Mention",
|
||||
"href" => local_user.ap_id,
|
||||
"name" => "@#{local_user.nickname}"
|
||||
}
|
||||
],
|
||||
"to" => [local_user.ap_id],
|
||||
"cc" => [],
|
||||
"attributedTo" => remote_user.ap_id
|
||||
}
|
||||
}
|
||||
|
||||
{:ok, _dm_activity} = Transmogrifier.handle_incoming(dm_message)
|
||||
|
||||
refute Enum.empty?(Notification.for_user(local_user))
|
||||
|
||||
delete_user_message = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => remote_user.ap_id <> "/activities/delete",
|
||||
"actor" => remote_user.ap_id,
|
||||
"type" => "Delete",
|
||||
"object" => remote_user.ap_id
|
||||
}
|
||||
|
||||
{:ok, _delete_activity} = Transmogrifier.handle_incoming(delete_user_message)
|
||||
|
||||
assert Enum.empty?(Notification.for_user(local_user))
|
||||
end
|
||||
end
|
||||
|
||||
describe "for_user" do
|
||||
|
@ -1369,4 +1369,28 @@ defmodule Pleroma.UserTest do
|
||||
assert User.is_internal_user?(user)
|
||||
end
|
||||
end
|
||||
|
||||
describe "update_and_set_cache/1" do
|
||||
test "returns error when user is stale instead Ecto.StaleEntryError" do
|
||||
user = insert(:user)
|
||||
|
||||
changeset = Ecto.Changeset.change(user, bio: "test")
|
||||
|
||||
Repo.delete(user)
|
||||
|
||||
assert {:error, %Ecto.Changeset{errors: [id: {"is stale", [stale: true]}], valid?: false}} =
|
||||
User.update_and_set_cache(changeset)
|
||||
end
|
||||
|
||||
test "performs update cache if user updated" do
|
||||
user = insert(:user)
|
||||
assert {:ok, nil} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
|
||||
|
||||
changeset = Ecto.Changeset.change(user, bio: "test-bio")
|
||||
|
||||
assert {:ok, %User{bio: "test-bio"} = user} = User.update_and_set_cache(changeset)
|
||||
assert {:ok, user} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
|
||||
assert %User{bio: "test-bio"} = User.get_cached_by_ap_id(user.ap_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -30,6 +30,10 @@ defmodule Pleroma.Web.FallbackTest do
|
||||
|> json_response(404) == %{"error" => "Not implemented"}
|
||||
end
|
||||
|
||||
test "GET /pleroma/admin -> /pleroma/admin/", %{conn: conn} do
|
||||
assert redirected_to(get(conn, "/pleroma/admin")) =~ "/pleroma/admin/"
|
||||
end
|
||||
|
||||
test "GET /*path", %{conn: conn} do
|
||||
assert conn
|
||||
|> get("/foo")
|
||||
|
@ -3154,6 +3154,21 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
||||
assert redirected_to(conn) == "/web/login"
|
||||
end
|
||||
|
||||
test "redirects not logged-in users to the login page on private instances", %{
|
||||
conn: conn,
|
||||
path: path
|
||||
} do
|
||||
is_public = Pleroma.Config.get([:instance, :public])
|
||||
Pleroma.Config.put([:instance, :public], false)
|
||||
|
||||
conn = get(conn, path)
|
||||
|
||||
assert conn.status == 302
|
||||
assert redirected_to(conn) == "/web/login"
|
||||
|
||||
Pleroma.Config.put([:instance, :public], is_public)
|
||||
end
|
||||
|
||||
test "does not redirect logged in users to the login page", %{conn: conn, path: path} do
|
||||
token = insert(:oauth_token)
|
||||
|
||||
@ -3923,4 +3938,45 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
||||
assert conn.resp_body == ""
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/v1/pleroma/accounts/confirmation_resend" do
|
||||
setup do
|
||||
setting = Pleroma.Config.get([:instance, :account_activation_required])
|
||||
|
||||
unless setting do
|
||||
Pleroma.Config.put([:instance, :account_activation_required], true)
|
||||
on_exit(fn -> Pleroma.Config.put([:instance, :account_activation_required], setting) end)
|
||||
end
|
||||
|
||||
user = insert(:user)
|
||||
info_change = User.Info.confirmation_changeset(user.info, need_confirmation: true)
|
||||
|
||||
{:ok, user} =
|
||||
user
|
||||
|> Changeset.change()
|
||||
|> Changeset.put_embed(:info, info_change)
|
||||
|> Repo.update()
|
||||
|
||||
assert user.info.confirmation_pending
|
||||
|
||||
[user: user]
|
||||
end
|
||||
|
||||
test "resend account confirmation email", %{conn: conn, user: user} do
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/pleroma/accounts/confirmation_resend?email=#{user.email}")
|
||||
|> json_response(:no_content)
|
||||
|
||||
email = Pleroma.Emails.UserEmail.account_confirmation_email(user)
|
||||
notify_email = Pleroma.Config.get([:instance, :notify_email])
|
||||
instance_name = Pleroma.Config.get([:instance, :name])
|
||||
|
||||
assert_email_sent(
|
||||
from: {instance_name, notify_email},
|
||||
to: {user.name, user.email},
|
||||
html_body: email.html_body
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -101,160 +101,538 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|
||||
assert response(conn, 404)
|
||||
end
|
||||
|
||||
test "gets an object", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Object.normalize(note_activity)
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
|
||||
url = "/objects/#{uuid}"
|
||||
describe "GET object/2" do
|
||||
test "gets an object", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Object.normalize(note_activity)
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
|
||||
url = "/objects/#{uuid}"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/xml")
|
||||
|> get(url)
|
||||
|
||||
expected =
|
||||
ActivityRepresenter.to_simple_form(note_activity, user, true)
|
||||
|> ActivityRepresenter.wrap_with_entry()
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
|
||||
assert response(conn, 200) == expected
|
||||
end
|
||||
|
||||
test "redirects to /notice/id for html format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Object.normalize(note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
|
||||
url = "/objects/#{uuid}"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "text/html")
|
||||
|> get(url)
|
||||
|
||||
assert redirected_to(conn) == "/notice/#{note_activity.id}"
|
||||
end
|
||||
|
||||
test "500s when user not found", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Object.normalize(note_activity)
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
User.invalidate_cache(user)
|
||||
Pleroma.Repo.delete(user)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
|
||||
url = "/objects/#{uuid}"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/xml")
|
||||
|> get(url)
|
||||
|
||||
assert response(conn, 500) == ~S({"error":"Something went wrong"})
|
||||
end
|
||||
|
||||
test "404s on private objects", %{conn: conn} do
|
||||
note_activity = insert(:direct_note_activity)
|
||||
object = Object.normalize(note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
|
||||
|
||||
conn
|
||||
|> get("/objects/#{uuid}")
|
||||
|> response(404)
|
||||
end
|
||||
|
||||
test "404s on nonexisting objects", %{conn: conn} do
|
||||
conn
|
||||
|> get("/objects/123")
|
||||
|> response(404)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET activity/2" do
|
||||
test "gets an activity in xml format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/xml")
|
||||
|> get(url)
|
||||
|> get("/activities/#{uuid}")
|
||||
|> response(200)
|
||||
end
|
||||
|
||||
expected =
|
||||
ActivityRepresenter.to_simple_form(note_activity, user, true)
|
||||
|> ActivityRepresenter.wrap_with_entry()
|
||||
|> :xmerl.export_simple(:xmerl_xml)
|
||||
|> to_string
|
||||
test "redirects to /notice/id for html format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||
|
||||
assert response(conn, 200) == expected
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "text/html")
|
||||
|> get("/activities/#{uuid}")
|
||||
|
||||
assert redirected_to(conn) == "/notice/#{note_activity.id}"
|
||||
end
|
||||
|
||||
test "505s when user not found", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
User.invalidate_cache(user)
|
||||
Pleroma.Repo.delete(user)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "text/html")
|
||||
|> get("/activities/#{uuid}")
|
||||
|
||||
assert response(conn, 500) == ~S({"error":"Something went wrong"})
|
||||
end
|
||||
|
||||
test "404s on deleted objects", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Object.normalize(note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
|
||||
|
||||
conn
|
||||
|> put_req_header("accept", "application/xml")
|
||||
|> get("/objects/#{uuid}")
|
||||
|> response(200)
|
||||
|
||||
Object.delete(object)
|
||||
|
||||
conn
|
||||
|> put_req_header("accept", "application/xml")
|
||||
|> get("/objects/#{uuid}")
|
||||
|> response(404)
|
||||
end
|
||||
|
||||
test "404s on private activities", %{conn: conn} do
|
||||
note_activity = insert(:direct_note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||
|
||||
conn
|
||||
|> get("/activities/#{uuid}")
|
||||
|> response(404)
|
||||
end
|
||||
|
||||
test "404s on nonexistent activities", %{conn: conn} do
|
||||
conn
|
||||
|> get("/activities/123")
|
||||
|> response(404)
|
||||
end
|
||||
|
||||
test "gets an activity in AS2 format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||
url = "/activities/#{uuid}"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(url)
|
||||
|
||||
assert json_response(conn, 200)
|
||||
end
|
||||
end
|
||||
|
||||
test "404s on private objects", %{conn: conn} do
|
||||
note_activity = insert(:direct_note_activity)
|
||||
object = Object.normalize(note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
|
||||
describe "GET notice/2" do
|
||||
test "gets a notice in xml format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
|
||||
conn
|
||||
|> get("/objects/#{uuid}")
|
||||
|> response(404)
|
||||
end
|
||||
conn
|
||||
|> get("/notice/#{note_activity.id}")
|
||||
|> response(200)
|
||||
end
|
||||
|
||||
test "404s on nonexisting objects", %{conn: conn} do
|
||||
conn
|
||||
|> get("/objects/123")
|
||||
|> response(404)
|
||||
end
|
||||
test "gets a notice in AS2 format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
|
||||
test "gets an activity in xml format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||
|
||||
conn
|
||||
|> put_req_header("accept", "application/xml")
|
||||
|> get("/activities/#{uuid}")
|
||||
|> response(200)
|
||||
end
|
||||
|
||||
test "404s on deleted objects", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Object.normalize(note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, object.data["id"]))
|
||||
|
||||
conn
|
||||
|> put_req_header("accept", "application/xml")
|
||||
|> get("/objects/#{uuid}")
|
||||
|> response(200)
|
||||
|
||||
Object.delete(object)
|
||||
|
||||
conn
|
||||
|> put_req_header("accept", "application/xml")
|
||||
|> get("/objects/#{uuid}")
|
||||
|> response(404)
|
||||
end
|
||||
|
||||
test "404s on private activities", %{conn: conn} do
|
||||
note_activity = insert(:direct_note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||
|
||||
conn
|
||||
|> get("/activities/#{uuid}")
|
||||
|> response(404)
|
||||
end
|
||||
|
||||
test "404s on nonexistent activities", %{conn: conn} do
|
||||
conn
|
||||
|> get("/activities/123")
|
||||
|> response(404)
|
||||
end
|
||||
|
||||
test "gets a notice in xml format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
|
||||
conn
|
||||
|> get("/notice/#{note_activity.id}")
|
||||
|> response(200)
|
||||
end
|
||||
|
||||
test "gets a notice in AS2 format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/notice/#{note_activity.id}")
|
||||
|> json_response(200)
|
||||
end
|
||||
|
||||
test "only gets a notice in AS2 format for Create messages", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
url = "/notice/#{note_activity.id}"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(url)
|
||||
|> get("/notice/#{note_activity.id}")
|
||||
|> json_response(200)
|
||||
end
|
||||
|
||||
assert json_response(conn, 200)
|
||||
test "500s when actor not found", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
User.invalidate_cache(user)
|
||||
Pleroma.Repo.delete(user)
|
||||
|
||||
user = insert(:user)
|
||||
conn =
|
||||
conn
|
||||
|> get("/notice/#{note_activity.id}")
|
||||
|
||||
{:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
|
||||
url = "/notice/#{like_activity.id}"
|
||||
assert response(conn, 500) == ~S({"error":"Something went wrong"})
|
||||
end
|
||||
|
||||
assert like_activity.data["type"] == "Like"
|
||||
test "only gets a notice in AS2 format for Create messages", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
url = "/notice/#{note_activity.id}"
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(url)
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(url)
|
||||
|
||||
assert response(conn, 404)
|
||||
assert json_response(conn, 200)
|
||||
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
|
||||
url = "/notice/#{like_activity.id}"
|
||||
|
||||
assert like_activity.data["type"] == "Like"
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(url)
|
||||
|
||||
assert response(conn, 404)
|
||||
end
|
||||
|
||||
test "render html for redirect for html format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
|
||||
resp =
|
||||
conn
|
||||
|> put_req_header("accept", "text/html")
|
||||
|> get("/notice/#{note_activity.id}")
|
||||
|> response(200)
|
||||
|
||||
assert resp =~
|
||||
"<meta content=\"#{Pleroma.Web.base_url()}/notice/#{note_activity.id}\" property=\"og:url\">"
|
||||
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
|
||||
|
||||
assert like_activity.data["type"] == "Like"
|
||||
|
||||
resp =
|
||||
conn
|
||||
|> put_req_header("accept", "text/html")
|
||||
|> get("/notice/#{like_activity.id}")
|
||||
|> response(200)
|
||||
|
||||
assert resp =~ "<!--server-generated-meta-->"
|
||||
end
|
||||
|
||||
test "404s a private notice", %{conn: conn} do
|
||||
note_activity = insert(:direct_note_activity)
|
||||
url = "/notice/#{note_activity.id}"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get(url)
|
||||
|
||||
assert response(conn, 404)
|
||||
end
|
||||
|
||||
test "404s a nonexisting notice", %{conn: conn} do
|
||||
url = "/notice/123"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get(url)
|
||||
|
||||
assert response(conn, 404)
|
||||
end
|
||||
end
|
||||
|
||||
test "gets an activity in AS2 format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||
url = "/activities/#{uuid}"
|
||||
describe "feed_redirect" do
|
||||
test "undefined format. it redirects to feed", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(url)
|
||||
response =
|
||||
conn
|
||||
|> put_req_header("accept", "application/xml")
|
||||
|> get("/users/#{user.nickname}")
|
||||
|> response(302)
|
||||
|
||||
assert json_response(conn, 200)
|
||||
assert response ==
|
||||
"<html><body>You are being <a href=\"#{Pleroma.Web.base_url()}/users/#{
|
||||
user.nickname
|
||||
}/feed.atom\">redirected</a>.</body></html>"
|
||||
end
|
||||
|
||||
test "undefined format. it returns error when user not found", %{conn: conn} do
|
||||
response =
|
||||
conn
|
||||
|> put_req_header("accept", "application/xml")
|
||||
|> get("/users/jimm")
|
||||
|> response(404)
|
||||
|
||||
assert response == ~S({"error":"Not found"})
|
||||
end
|
||||
|
||||
test "activity+json format. it redirects on actual feed of user", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
|
||||
response =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/users/#{user.nickname}")
|
||||
|> json_response(200)
|
||||
|
||||
assert response["endpoints"] == %{
|
||||
"oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize",
|
||||
"oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps",
|
||||
"oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token",
|
||||
"sharedInbox" => "#{Pleroma.Web.base_url()}/inbox"
|
||||
}
|
||||
|
||||
assert response["@context"] == [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"http://localhost:4001/schemas/litepub-0.1.jsonld",
|
||||
%{"@language" => "und"}
|
||||
]
|
||||
|
||||
assert Map.take(response, [
|
||||
"followers",
|
||||
"following",
|
||||
"id",
|
||||
"inbox",
|
||||
"manuallyApprovesFollowers",
|
||||
"name",
|
||||
"outbox",
|
||||
"preferredUsername",
|
||||
"summary",
|
||||
"tag",
|
||||
"type",
|
||||
"url"
|
||||
]) == %{
|
||||
"followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers",
|
||||
"following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following",
|
||||
"id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}",
|
||||
"inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox",
|
||||
"manuallyApprovesFollowers" => false,
|
||||
"name" => user.name,
|
||||
"outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox",
|
||||
"preferredUsername" => user.nickname,
|
||||
"summary" => user.bio,
|
||||
"tag" => [],
|
||||
"type" => "Person",
|
||||
"url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
|
||||
}
|
||||
end
|
||||
|
||||
test "activity+json format. it returns error whe use not found", %{conn: conn} do
|
||||
response =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get("/users/jimm")
|
||||
|> json_response(404)
|
||||
|
||||
assert response == "Not found"
|
||||
end
|
||||
|
||||
test "json format. it redirects on actual feed of user", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
|
||||
response =
|
||||
conn
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> get("/users/#{user.nickname}")
|
||||
|> json_response(200)
|
||||
|
||||
assert response["endpoints"] == %{
|
||||
"oauthAuthorizationEndpoint" => "#{Pleroma.Web.base_url()}/oauth/authorize",
|
||||
"oauthRegistrationEndpoint" => "#{Pleroma.Web.base_url()}/api/v1/apps",
|
||||
"oauthTokenEndpoint" => "#{Pleroma.Web.base_url()}/oauth/token",
|
||||
"sharedInbox" => "#{Pleroma.Web.base_url()}/inbox"
|
||||
}
|
||||
|
||||
assert response["@context"] == [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"http://localhost:4001/schemas/litepub-0.1.jsonld",
|
||||
%{"@language" => "und"}
|
||||
]
|
||||
|
||||
assert Map.take(response, [
|
||||
"followers",
|
||||
"following",
|
||||
"id",
|
||||
"inbox",
|
||||
"manuallyApprovesFollowers",
|
||||
"name",
|
||||
"outbox",
|
||||
"preferredUsername",
|
||||
"summary",
|
||||
"tag",
|
||||
"type",
|
||||
"url"
|
||||
]) == %{
|
||||
"followers" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/followers",
|
||||
"following" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/following",
|
||||
"id" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}",
|
||||
"inbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/inbox",
|
||||
"manuallyApprovesFollowers" => false,
|
||||
"name" => user.name,
|
||||
"outbox" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}/outbox",
|
||||
"preferredUsername" => user.nickname,
|
||||
"summary" => user.bio,
|
||||
"tag" => [],
|
||||
"type" => "Person",
|
||||
"url" => "#{Pleroma.Web.base_url()}/users/#{user.nickname}"
|
||||
}
|
||||
end
|
||||
|
||||
test "json format. it returns error whe use not found", %{conn: conn} do
|
||||
response =
|
||||
conn
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> get("/users/jimm")
|
||||
|> json_response(404)
|
||||
|
||||
assert response == "Not found"
|
||||
end
|
||||
|
||||
test "html format. it redirects on actual feed of user", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
||||
|
||||
response =
|
||||
conn
|
||||
|> get("/users/#{user.nickname}")
|
||||
|> response(200)
|
||||
|
||||
assert response ==
|
||||
Fallback.RedirectController.redirector_with_meta(
|
||||
conn,
|
||||
%{user: user}
|
||||
).resp_body
|
||||
end
|
||||
|
||||
test "html format. it returns error when user not found", %{conn: conn} do
|
||||
response =
|
||||
conn
|
||||
|> get("/users/jimm")
|
||||
|> json_response(404)
|
||||
|
||||
assert response == %{"error" => "Not found"}
|
||||
end
|
||||
end
|
||||
|
||||
test "404s a private notice", %{conn: conn} do
|
||||
note_activity = insert(:direct_note_activity)
|
||||
url = "/notice/#{note_activity.id}"
|
||||
describe "GET /notice/:id/embed_player" do
|
||||
test "render embed player", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Pleroma.Object.normalize(note_activity)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get(url)
|
||||
object_data =
|
||||
Map.put(object.data, "attachment", [
|
||||
%{
|
||||
"url" => [
|
||||
%{
|
||||
"href" =>
|
||||
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
|
||||
"mediaType" => "video/mp4",
|
||||
"type" => "Link"
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
assert response(conn, 404)
|
||||
end
|
||||
object
|
||||
|> Ecto.Changeset.change(data: object_data)
|
||||
|> Pleroma.Repo.update()
|
||||
|
||||
test "404s a nonexisting notice", %{conn: conn} do
|
||||
url = "/notice/123"
|
||||
conn =
|
||||
conn
|
||||
|> get("/notice/#{note_activity.id}/embed_player")
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get(url)
|
||||
assert Plug.Conn.get_resp_header(conn, "x-frame-options") == ["ALLOW"]
|
||||
|
||||
assert response(conn, 404)
|
||||
assert Plug.Conn.get_resp_header(
|
||||
conn,
|
||||
"content-security-policy"
|
||||
) == [
|
||||
"default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;"
|
||||
]
|
||||
|
||||
assert response(conn, 200) =~
|
||||
"<video controls loop><source src=\"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4\" type=\"video/mp4\">Your browser does not support video/mp4 playback.</video>"
|
||||
end
|
||||
|
||||
test "404s when activity isn't create", %{conn: conn} do
|
||||
note_activity = insert(:note_activity, data_attrs: %{"type" => "Like"})
|
||||
|
||||
assert conn
|
||||
|> get("/notice/#{note_activity.id}/embed_player")
|
||||
|> response(404)
|
||||
end
|
||||
|
||||
test "404s when activity is direct message", %{conn: conn} do
|
||||
note_activity = insert(:note_activity, data_attrs: %{"directMessage" => true})
|
||||
|
||||
assert conn
|
||||
|> get("/notice/#{note_activity.id}/embed_player")
|
||||
|> response(404)
|
||||
end
|
||||
|
||||
test "404s when attachment is empty", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Pleroma.Object.normalize(note_activity)
|
||||
object_data = Map.put(object.data, "attachment", [])
|
||||
|
||||
object
|
||||
|> Ecto.Changeset.change(data: object_data)
|
||||
|> Pleroma.Repo.update()
|
||||
|
||||
assert conn
|
||||
|> get("/notice/#{note_activity.id}/embed_player")
|
||||
|> response(404)
|
||||
end
|
||||
|
||||
test "404s when attachment isn't audio or video", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
object = Pleroma.Object.normalize(note_activity)
|
||||
|
||||
object_data =
|
||||
Map.put(object.data, "attachment", [
|
||||
%{
|
||||
"url" => [
|
||||
%{
|
||||
"href" => "https://peertube.moe/static/webseed/480.jpg",
|
||||
"mediaType" => "image/jpg",
|
||||
"type" => "Link"
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
|
||||
object
|
||||
|> Ecto.Changeset.change(data: object_data)
|
||||
|> Pleroma.Repo.update()
|
||||
|
||||
assert conn
|
||||
|> get("/notice/#{note_activity.id}/embed_player")
|
||||
|> response(404)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -124,8 +124,7 @@ defmodule Pleroma.Web.Push.ImplTest do
|
||||
{:ok, _, _, activity} = CommonAPI.follow(user, other_user)
|
||||
object = Object.normalize(activity)
|
||||
|
||||
assert Impl.format_body(%{activity: activity}, user, object) ==
|
||||
"@Bob has followed you"
|
||||
assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has followed you"
|
||||
end
|
||||
|
||||
test "renders body for announce activity" do
|
||||
@ -156,7 +155,6 @@ defmodule Pleroma.Web.Push.ImplTest do
|
||||
{:ok, activity, _} = CommonAPI.favorite(activity.id, user)
|
||||
object = Object.normalize(activity)
|
||||
|
||||
assert Impl.format_body(%{activity: activity}, user, object) ==
|
||||
"@Bob has favorited your post"
|
||||
assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post"
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user