Merge branch 'develop' into tests/mastodon_api_controller.ex

This commit is contained in:
Maksim Pechnikov 2019-09-24 08:06:04 +03:00
commit d75d0ae134
102 changed files with 781 additions and 309 deletions

View File

@ -6,12 +6,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased]
### Added
- Refreshing poll results for remote polls
- Admin API: Add ability to require password reset
### Changed
- **Breaking:** Elixir >=1.8 is now required (was >= 1.7)
- Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings)
- Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler
- Admin API: Return `total` when querying for reports
- Mastodon API: Return `pleroma.direct_conversation_id` when creating a direct message (`POST /api/v1/statuses`)
### Fixed
- Mastodon API: Fix private and direct statuses not being filtered out from the public timeline for an authenticated user (`GET /api/v1/timelines/public`)
## [1.1.0] - 2019-??-??
### Security
@ -39,6 +43,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- AdminAPI: Add "godmode" while fetching user statuses (i.e. admin can see private statuses)
- Improve digest email template
Pagination: (optional) return `total` alongside with `items` when paginating
- Add `rel="ugc"` to all links in statuses, to prevent SEO spam
### Fixed
- Following from Osada

View File

@ -109,6 +109,7 @@
config :pleroma, Pleroma.Uploaders.S3,
bucket: nil,
streaming_enabled: true,
public_endpoint: "https://s3.amazonaws.com"
config :pleroma, Pleroma.Uploaders.MDII,
@ -508,7 +509,7 @@
class: false,
strip_prefix: false,
new_window: false,
rel: false
rel: "ugc"
]
config :pleroma, :ldap,

View File

@ -110,6 +110,12 @@
description:
"If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or \"\" etc." <>
" For example, when using CDN to S3 virtual host format, set \"\". At this time, write CNAME to CDN in public_endpoint."
},
%{
key: :streaming_enabled,
type: :boolean,
description:
"Enable streaming uploads, when enabled the file will be sent to the server in chunks as it's being read. This may be unsupported by some providers, try disabling this if you have upload problems."
}
]
},
@ -1900,7 +1906,7 @@
key: :rel,
type: [:string, false],
description: "override the rel attribute. false to clear",
suggestions: ["noopener noreferrer", false]
suggestions: ["ugc", "noopener noreferrer", false]
},
%{
key: :new_window,

View File

@ -310,6 +310,14 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
- Params: none
- Response: password reset token (base64 string)
## `/api/pleroma/admin/users/:nickname/force_password_reset`
### Force passord reset for a user with a given nickname
- Methods: `PATCH`
- Params: none
- Response: none (code `204`)
## `/api/pleroma/admin/reports`
### Get a list of reports
- Method `GET`

View File

@ -23,6 +23,7 @@ Note: `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`.
* `truncated_namespace`: If you use S3 compatible service such as Digital Ocean Spaces or CDN, set folder name or "" etc.
For example, when using CDN to S3 virtual host format, set "".
At this time, write CNAME to CDN in public_endpoint.
* `streaming_enabled`: Enable streaming uploads, when enabled the file will be sent to the server in chunks as it's being read. This may be unsupported by some providers, try disabling this if you have upload problems.
## Pleroma.Upload.Filter.Mogrify
@ -521,7 +522,7 @@ config :auto_linker,
class: false,
strip_prefix: false,
new_window: false,
rel: false
rel: "ugc"
]
```

View File

@ -36,9 +36,9 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
nickname_text = get_nickname_text(nickname, opts)
link =
"<span class='h-card'><a data-user='#{id}' class='u-url mention' href='#{ap_id}'>@<span>#{
~s(<span class="h-card"><a data-user="#{id}" class="u-url mention" href="#{ap_id}" rel="ugc">@<span>#{
nickname_text
}</span></a></span>"
}</span></a></span>)
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
@ -50,7 +50,7 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
tag = String.downcase(tag)
url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
link = "<a class='hashtag' data-tag='#{tag}' href='#{url}' rel='tag'>#{tag_text}</a>"
link = ~s(<a class="hashtag" data-tag="#{tag}" href="#{url}" rel="tag ugc">#{tag_text}</a>)
{link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}
end

View File

@ -184,7 +184,8 @@ defmodule Pleroma.HTML.Scrubber.Default do
"tag",
"nofollow",
"noopener",
"noreferrer"
"noreferrer",
"ugc"
])
Meta.allow_tag_with_these_attributes("a", ["name", "title"])
@ -304,7 +305,8 @@ defmodule Pleroma.HTML.Scrubber.LinksOnly do
"nofollow",
"noopener",
"noreferrer",
"me"
"me",
"ugc"
])
Meta.allow_tag_with_these_attributes("a", ["name", "title"])

View File

@ -31,6 +31,7 @@ defp maybe_reinject_internal_fields(data, %{data: %{} = old_data}) do
defp maybe_reinject_internal_fields(data, _), do: data
@spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()}
defp reinject_object(struct, data) do
Logger.debug("Reinjecting object #{data["id"]}")
@ -61,21 +62,10 @@ def refetch_object(%Object{data: %{"id" => id}} = object) do
# TODO:
# This will create a Create activity, which we need internally at the moment.
def fetch_object_from_id(id, options \\ []) do
if object = Object.get_cached_by_ap_id(id) do
{:ok, object}
else
Logger.info("Fetching #{id} via AP")
with {:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
with {:fetch_object, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
{:fetch, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
{:normalize, nil} <- {:normalize, Object.normalize(data, false)},
params <- %{
"type" => "Create",
"to" => data["to"],
"cc" => data["cc"],
# Should we seriously keep this attributedTo thing?
"actor" => data["actor"] || data["attributedTo"],
"object" => data
},
params <- prepare_activity_params(data),
{:containment, :ok} <- {:containment, Containment.contain_origin(id, params)},
{:ok, activity} <- Transmogrifier.handle_incoming(params, options),
{:object, _data, %Object{} = object} <-
@ -94,6 +84,9 @@ def fetch_object_from_id(id, options \\ []) do
{:normalize, object = %Object{}} ->
{:ok, object}
{:fetch_object, %Object{} = object} ->
{:ok, object}
_e ->
# Only fallback when receiving a fetch/normalization error with ActivityPub
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
@ -105,6 +98,16 @@ def fetch_object_from_id(id, options \\ []) do
end
end
end
defp prepare_activity_params(data) do
%{
"type" => "Create",
"to" => data["to"],
"cc" => data["cc"],
# Should we seriously keep this attributedTo thing?
"actor" => data["actor"] || data["attributedTo"],
"object" => data
}
end
def fetch_object_from_id!(id, options \\ []) do

View File

@ -38,16 +38,26 @@ def get_file(file) do
def put_file(%Pleroma.Upload{} = upload) do
config = Config.get([__MODULE__])
bucket = Keyword.get(config, :bucket)
streaming = Keyword.get(config, :streaming_enabled)
s3_name = strict_encode(upload.path)
op =
if streaming do
upload.tempfile
|> ExAws.S3.Upload.stream_file()
|> ExAws.S3.upload(bucket, s3_name, [
{:acl, :public_read},
{:content_type, upload.content_type}
])
else
{:ok, file_data} = File.read(upload.tempfile)
ExAws.S3.put_object(bucket, s3_name, file_data, [
{:acl, :public_read},
{:content_type, upload.content_type}
])
end
case ExAws.request(op) do
{:ok, _} ->

View File

@ -269,6 +269,7 @@ def password_update_changeset(struct, params) do
|> validate_required([:password, :password_confirmation])
|> validate_confirmation(:password)
|> put_password_hash
|> put_embed(:info, User.Info.set_password_reset_pending(struct.info, false))
end
@spec reset_password(User.t(), map) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
@ -285,6 +286,20 @@ def reset_password(%User{id: user_id} = user, data) do
end
end
def force_password_reset_async(user) do
BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id})
end
@spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def force_password_reset(user) do
info_cng = User.Info.set_password_reset_pending(user.info, true)
user
|> change()
|> put_embed(:info, info_cng)
|> update_and_set_cache()
end
def register_changeset(struct, params \\ %{}, opts \\ []) do
bio_limit = Pleroma.Config.get([:instance, :user_bio_length], 5000)
name_limit = Pleroma.Config.get([:instance, :user_name_length], 100)
@ -1131,6 +1146,8 @@ def delete(%User{} = user) do
BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id})
end
def perform(:force_password_reset, user), do: force_password_reset(user)
@spec perform(atom(), User.t()) :: {:ok, User.t()}
def perform(:delete, %User{} = user) do
{:ok, _user} = ActivityPub.delete(user)

View File

@ -20,6 +20,7 @@ defmodule Pleroma.User.Info do
field(:following_count, :integer, default: nil)
field(:locked, :boolean, default: false)
field(:confirmation_pending, :boolean, default: false)
field(:password_reset_pending, :boolean, default: false)
field(:confirmation_token, :string, default: nil)
field(:default_scope, :string, default: "public")
field(:blocks, {:array, :string}, default: [])
@ -82,6 +83,14 @@ def set_activation_status(info, deactivated) do
|> validate_required([:deactivated])
end
def set_password_reset_pending(info, pending) do
params = %{password_reset_pending: pending}
info
|> cast(params, [:password_reset_pending])
|> validate_required([:password_reset_pending])
end
def update_notification_settings(info, settings) do
settings =
settings

View File

@ -520,9 +520,10 @@ def fetch_latest_activity_id_for_context(context, opts \\ %{}) do
end
def fetch_public_activities(opts \\ %{}) do
q = fetch_activities_query([Pleroma.Constants.as_public()], opts)
opts = Map.drop(opts, ["user"])
q
[Pleroma.Constants.as_public()]
|> fetch_activities_query(opts)
|> restrict_unlisted()
|> Pagination.fetch_paginated(opts)
|> Enum.reverse()

View File

@ -49,7 +49,8 @@ def user(conn, %{"nickname" => nickname}) do
{:ok, user} <- User.ensure_keys_present(user) do
conn
|> put_resp_content_type("application/activity+json")
|> json(UserView.render("user.json", %{user: user}))
|> put_view(UserView)
|> render("user.json", %{user: user})
else
nil -> {:error, :not_found}
end
@ -90,7 +91,8 @@ def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
conn
|> put_resp_content_type("application/activity+json")
|> json(ObjectView.render("likes.json", ap_id, likes, page))
|> put_view(ObjectView)
|> render("likes.json", %{ap_id: ap_id, likes: likes, page: page})
else
{:public?, false} ->
{:error, :not_found}
@ -104,7 +106,8 @@ def object_likes(conn, %{"uuid" => uuid}) do
likes <- Utils.get_object_likes(object) do
conn
|> put_resp_content_type("application/activity+json")
|> json(ObjectView.render("likes.json", ap_id, likes))
|> put_view(ObjectView)
|> render("likes.json", %{ap_id: ap_id, likes: likes})
else
{:public?, false} ->
{:error, :not_found}
@ -158,7 +161,8 @@ defp set_cache_ttl_for(conn, entity) do
def following(%{assigns: %{relay: true}} = conn, _params) do
conn
|> put_resp_content_type("application/activity+json")
|> json(UserView.render("following.json", %{user: Relay.get_actor()}))
|> put_view(UserView)
|> render("following.json", %{user: Relay.get_actor()})
end
def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
@ -170,7 +174,8 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "p
conn
|> put_resp_content_type("application/activity+json")
|> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
|> put_view(UserView)
|> render("following.json", %{user: user, page: page, for: for_user})
else
{:show_follows, _} ->
conn
@ -184,7 +189,8 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
conn
|> put_resp_content_type("application/activity+json")
|> json(UserView.render("following.json", %{user: user, for: for_user}))
|> put_view(UserView)
|> render("following.json", %{user: user, for: for_user})
end
end
@ -192,7 +198,8 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
def followers(%{assigns: %{relay: true}} = conn, _params) do
conn
|> put_resp_content_type("application/activity+json")
|> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
|> put_view(UserView)
|> render("followers.json", %{user: Relay.get_actor()})
end
def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
@ -204,7 +211,8 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "p
conn
|> put_resp_content_type("application/activity+json")
|> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
|> put_view(UserView)
|> render("followers.json", %{user: user, page: page, for: for_user})
else
{:show_followers, _} ->
conn
@ -218,7 +226,8 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) d
{user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
conn
|> put_resp_content_type("application/activity+json")
|> json(UserView.render("followers.json", %{user: user, for: for_user}))
|> put_view(UserView)
|> render("followers.json", %{user: user, for: for_user})
end
end
@ -227,7 +236,8 @@ def outbox(conn, %{"nickname" => nickname} = params) do
{:ok, user} <- User.ensure_keys_present(user) do
conn
|> put_resp_content_type("application/activity+json")
|> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
|> put_view(UserView)
|> render("outbox.json", %{user: user, max_id: params["max_id"]})
end
end
@ -275,7 +285,8 @@ defp represent_service_actor(%User{} = user, conn) do
with {:ok, user} <- User.ensure_keys_present(user) do
conn
|> put_resp_content_type("application/activity+json")
|> json(UserView.render("user.json", %{user: user}))
|> put_view(UserView)
|> render("user.json", %{user: user})
else
nil -> {:error, :not_found}
end
@ -296,7 +307,8 @@ def internal_fetch(conn, _params) do
def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
conn
|> put_resp_content_type("application/activity+json")
|> json(UserView.render("user.json", %{user: user}))
|> put_view(UserView)
|> render("user.json", %{user: user})
end
def whoami(_conn, _params), do: {:error, :not_found}

View File

@ -42,8 +42,7 @@ def fix_object(object, options \\ []) do
end
def fix_summary(%{"summary" => nil} = object) do
object
|> Map.put("summary", "")
Map.put(object, "summary", "")
end
def fix_summary(%{"summary" => _} = object) do
@ -51,10 +50,7 @@ def fix_summary(%{"summary" => _} = object) do
object
end
def fix_summary(object) do
object
|> Map.put("summary", "")
end
def fix_summary(object), do: Map.put(object, "summary", "")
def fix_addressing_list(map, field) do
cond do
@ -74,13 +70,9 @@ def fix_explicit_addressing(
explicit_mentions,
follower_collection
) do
explicit_to =
to
|> Enum.filter(fn x -> x in explicit_mentions end)
explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
explicit_cc =
to
|> Enum.filter(fn x -> x not in explicit_mentions end)
explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end)
final_cc =
(cc ++ explicit_cc)
@ -98,13 +90,19 @@ def fix_explicit_addressing(object, _explicit_mentions, _followers_collection),
def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
def fix_explicit_addressing(object) do
explicit_mentions =
explicit_mentions = Utils.determine_explicit_mentions(object)
%User{follower_address: follower_collection} =
object
|> Utils.determine_explicit_mentions()
|> Containment.get_actor()
|> User.get_cached_by_ap_id()
follower_collection = User.get_cached_by_ap_id(Containment.get_actor(object)).follower_address
explicit_mentions = explicit_mentions ++ [Pleroma.Constants.as_public(), follower_collection]
explicit_mentions =
explicit_mentions ++
[
Pleroma.Constants.as_public(),
follower_collection
]
fix_explicit_addressing(object, explicit_mentions, follower_collection)
end
@ -148,37 +146,19 @@ def fix_addressing(object) do
end
def fix_actor(%{"attributedTo" => actor} = object) do
object
|> Map.put("actor", Containment.get_actor(%{"actor" => actor}))
Map.put(object, "actor", Containment.get_actor(%{"actor" => actor}))
end
def fix_in_reply_to(object, options \\ [])
def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
when not is_nil(in_reply_to) do
in_reply_to_id =
cond do
is_bitstring(in_reply_to) ->
in_reply_to
is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
in_reply_to["id"]
is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) ->
Enum.at(in_reply_to, 0)
# Maybe I should output an error too?
true ->
""
end
in_reply_to_id = prepare_in_reply_to(in_reply_to)
object = Map.put(object, "inReplyToAtomUri", in_reply_to_id)
if Federator.allowed_incoming_reply_depth?(options[:depth]) do
case get_obj_helper(in_reply_to_id, options) do
{:ok, replied_object} ->
with %Activity{} = _activity <-
Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
with {:ok, replied_object} <- get_obj_helper(in_reply_to_id, options),
%Activity{} = _ <- Activity.get_create_by_object_ap_id(replied_object.data["id"]) do
object
|> Map.put("inReplyTo", replied_object.data["id"])
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
@ -189,11 +169,6 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
object
end
e ->
Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")
object
end
else
object
end
@ -201,6 +176,22 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to} = object, options)
def fix_in_reply_to(object, _options), do: object
defp prepare_in_reply_to(in_reply_to) do
cond do
is_bitstring(in_reply_to) ->
in_reply_to
is_map(in_reply_to) && is_bitstring(in_reply_to["id"]) ->
in_reply_to["id"]
is_list(in_reply_to) && is_bitstring(Enum.at(in_reply_to, 0)) ->
Enum.at(in_reply_to, 0)
true ->
""
end
end
def fix_context(object) do
context = object["context"] || object["conversation"] || Utils.generate_context_id()
@ -211,11 +202,9 @@ def fix_context(object) do
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
attachments =
attachment
|> Enum.map(fn data ->
Enum.map(attachment, fn data ->
media_type = data["mediaType"] || data["mimeType"]
href = data["url"] || data["href"]
url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}]
data
@ -223,30 +212,25 @@ def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachm
|> Map.put("url", url)
end)
object
|> Map.put("attachment", attachments)
Map.put(object, "attachment", attachments)
end
def fix_attachments(%{"attachment" => attachment} = object) when is_map(attachment) do
Map.put(object, "attachment", [attachment])
object
|> Map.put("attachment", [attachment])
|> fix_attachments()
end
def fix_attachments(object), do: object
def fix_url(%{"url" => url} = object) when is_map(url) do
object
|> Map.put("url", url["href"])
Map.put(object, "url", url["href"])
end
def fix_url(%{"type" => "Video", "url" => url} = object) when is_list(url) do
first_element = Enum.at(url, 0)
link_element =
url
|> Enum.filter(fn x -> is_map(x) end)
|> Enum.filter(fn x -> x["mimeType"] == "text/html" end)
|> Enum.at(0)
link_element = Enum.find(url, fn x -> is_map(x) and x["mimeType"] == "text/html" end)
object
|> Map.put("attachment", [first_element])
@ -264,36 +248,32 @@ def fix_url(%{"type" => object_type, "url" => url} = object)
true -> ""
end
object
|> Map.put("url", url_string)
Map.put(object, "url", url_string)
end
def fix_url(object), do: object
def fix_emoji(%{"tag" => tags} = object) when is_list(tags) do
emoji = tags |> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
emoji =
emoji
tags
|> Enum.filter(fn data -> data["type"] == "Emoji" and data["icon"] end)
|> Enum.reduce(%{}, fn data, mapping ->
name = String.trim(data["name"], ":")
mapping |> Map.put(name, data["icon"]["url"])
Map.put(mapping, name, data["icon"]["url"])
end)
# we merge mastodon and pleroma emoji into a single mapping, to allow for both wire formats
emoji = Map.merge(object["emoji"] || %{}, emoji)
object
|> Map.put("emoji", emoji)
Map.put(object, "emoji", emoji)
end
def fix_emoji(%{"tag" => %{"type" => "Emoji"} = tag} = object) do
name = String.trim(tag["name"], ":")
emoji = %{name => tag["icon"]["url"]}
object
|> Map.put("emoji", emoji)
Map.put(object, "emoji", emoji)
end
def fix_emoji(object), do: object
@ -304,17 +284,13 @@ def fix_tag(%{"tag" => tag} = object) when is_list(tag) do
|> Enum.filter(fn data -> data["type"] == "Hashtag" and data["name"] end)
|> Enum.map(fn data -> String.slice(data["name"], 1..-1) end)
combined = tag ++ tags
object
|> Map.put("tag", combined)
Map.put(object, "tag", tag ++ tags)
end
def fix_tag(%{"tag" => %{"type" => "Hashtag", "name" => hashtag} = tag} = object) do
combined = [tag, String.slice(hashtag, 1..-1)]
object
|> Map.put("tag", combined)
Map.put(object, "tag", combined)
end
def fix_tag(%{"tag" => %{} = tag} = object), do: Map.put(object, "tag", [tag])
@ -326,8 +302,7 @@ def fix_content_map(%{"contentMap" => content_map} = object) do
content_groups = Map.to_list(content_map)
{_, content} = Enum.at(content_groups, 0)
object
|> Map.put("content", content)
Map.put(object, "content", content)
end
def fix_content_map(object), do: object
@ -336,16 +311,11 @@ def fix_type(object, options \\ [])
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
when is_binary(reply_id) do
reply =
with true <- Federator.allowed_incoming_reply_depth?(options[:depth]),
{:ok, object} <- get_obj_helper(reply_id, options) do
object
end
if reply && reply.data["type"] == "Question" do
{:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
Map.put(object, "type", "Answer")
else
object
_ -> object
end
end
@ -377,6 +347,17 @@ defp get_follow_activity(follow_object, followed) do
end
end
# Reduce the object list to find the reported user.
defp get_reported(objects) do
Enum.reduce_while(objects, nil, fn ap_id, _ ->
with %User{} = user <- User.get_cached_by_ap_id(ap_id) do
{:halt, user}
else
_ -> {:cont, nil}
end
end)
end
def handle_incoming(data, options \\ [])
# Flag objects are placed ahead of the ID check because Mastodon 2.8 and earlier send them
@ -385,31 +366,19 @@ def handle_incoming(%{"type" => "Flag", "object" => objects, "actor" => actor} =
with context <- data["context"] || Utils.generate_context_id(),
content <- data["content"] || "",
%User{} = actor <- User.get_cached_by_ap_id(actor),
# Reduce the object list to find the reported user.
%User{} = account <-
Enum.reduce_while(objects, nil, fn ap_id, _ ->
with %User{} = user <- User.get_cached_by_ap_id(ap_id) do
{:halt, user}
else
_ -> {:cont, nil}
end
end),
%User{} = account <- get_reported(objects),
# Remove the reported user from the object list.
statuses <- Enum.filter(objects, fn ap_id -> ap_id != account.ap_id end) do
params = %{
%{
actor: actor,
context: context,
account: account,
statuses: statuses,
content: content,
additional: %{
"cc" => [account.ap_id]
additional: %{"cc" => [account.ap_id]}
}
}
ActivityPub.flag(params)
|> ActivityPub.flag()
end
end
@ -756,8 +725,12 @@ def handle_incoming(
def handle_incoming(_, _), do: :error
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
def get_obj_helper(id, options \\ []) do
if object = Object.normalize(id, true, options), do: {:ok, object}, else: nil
case Object.normalize(id, true, options) do
%Object{} = object -> {:ok, object}
_ -> nil
end
end
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
@ -856,26 +829,23 @@ def prepare_outgoing(%{"type" => _type} = data) do
{:ok, data}
end
def maybe_fix_object_url(data) do
if is_binary(data["object"]) and not String.starts_with?(data["object"], "http") do
case get_obj_helper(data["object"]) do
{:ok, relative_object} ->
if relative_object.data["external_url"] do
_data =
data
|> Map.put("object", relative_object.data["external_url"])
def maybe_fix_object_url(%{"object" => object} = data) when is_binary(object) do
with false <- String.starts_with?(object, "http"),
{:fetch, {:ok, relative_object}} <- {:fetch, get_obj_helper(object)},
%{data: %{"external_url" => external_url}} when not is_nil(external_url) <-
relative_object do
Map.put(data, "object", external_url)
else
{:fetch, e} ->
Logger.error("Couldn't fetch #{object} #{inspect(e)}")
data
_ ->
data
end
end
e ->
Logger.error("Couldn't fetch #{data["object"]} #{inspect(e)}")
data
end
else
data
end
end
def maybe_fix_object_url(data), do: data
def add_hashtags(object) do
tags =
@ -894,38 +864,42 @@ def add_hashtags(object) do
tag
end)
object
|> Map.put("tag", tags)
Map.put(object, "tag", tags)
end
def add_mention_tags(object) do
mentions =
object
|> Utils.get_notified_from_object()
|> Enum.map(fn user ->
%{"type" => "Mention", "href" => user.ap_id, "name" => "@#{user.nickname}"}
end)
|> Enum.map(&build_mention_tag/1)
tags = object["tag"] || []
object
|> Map.put("tag", tags ++ mentions)
Map.put(object, "tag", tags ++ mentions)
end
def add_emoji_tags(%User{info: %{"emoji" => _emoji} = user_info} = object) do
user_info = add_emoji_tags(user_info)
defp build_mention_tag(%{ap_id: ap_id, nickname: nickname} = _) do
%{"type" => "Mention", "href" => ap_id, "name" => "@#{nickname}"}
end
object
|> Map.put(:info, user_info)
def take_emoji_tags(%User{info: %{emoji: emoji} = _user_info} = _user) do
emoji
|> Enum.flat_map(&Map.to_list/1)
|> Enum.map(&build_emoji_tag/1)
end
# TODO: we should probably send mtime instead of unix epoch time for updated
def add_emoji_tags(%{"emoji" => emoji} = object) do
tags = object["tag"] || []
out =
emoji
|> Enum.map(fn {name, url} ->
out = Enum.map(emoji, &build_emoji_tag/1)
Map.put(object, "tag", tags ++ out)
end
def add_emoji_tags(object), do: object
defp build_emoji_tag({name, url}) do
%{
"icon" => %{"url" => url, "type" => "Image"},
"name" => ":" <> name <> ":",
@ -933,14 +907,6 @@ def add_emoji_tags(%{"emoji" => emoji} = object) do
"updated" => "1970-01-01T00:00:00Z",
"id" => url
}
end)
object
|> Map.put("tag", tags ++ out)
end
def add_emoji_tags(object) do
object
end
def set_conversation(object) do
@ -960,9 +926,7 @@ def set_type(object), do: object
def add_attributed_to(object) do
attributed_to = object["attributedTo"] || object["actor"]
object
|> Map.put("attributedTo", attributed_to)
Map.put(object, "attributedTo", attributed_to)
end
def prepare_attachments(object) do
@ -973,8 +937,7 @@ def prepare_attachments(object) do
%{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"}
end)
object
|> Map.put("attachment", attachments)
Map.put(object, "attachment", attachments)
end
defp strip_internal_fields(object) do
@ -983,12 +946,9 @@ defp strip_internal_fields(object) do
end
defp strip_internal_tags(%{"tag" => tags} = object) do
tags =
tags
|> Enum.filter(fn x -> is_map(x) end)
tags = Enum.filter(tags, fn x -> is_map(x) end)
object
|> Map.put("tag", tags)
Map.put(object, "tag", tags)
end
defp strip_internal_tags(object), do: object
@ -1073,16 +1033,11 @@ def maybe_retire_websub(ap_id) do
end
end
def maybe_fix_user_url(data) do
if is_map(data["url"]) do
Map.put(data, "url", data["url"]["href"])
else
data
end
def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do
Map.put(data, "url", url["href"])
end
def maybe_fix_user_object(data) do
data
|> maybe_fix_user_url
end
def maybe_fix_user_url(data), do: data
def maybe_fix_user_object(data), do: maybe_fix_user_url(data)
end

View File

@ -37,12 +37,12 @@ def render("object.json", %{object: %Activity{} = activity}) do
Map.merge(base, additional)
end
def render("likes.json", ap_id, likes, page) do
def render("likes.json", %{ap_id: ap_id, likes: likes, page: page}) do
collection(likes, "#{ap_id}/likes", page)
|> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
end
def render("likes.json", ap_id, likes) do
def render("likes.json", %{ap_id: ap_id, likes: likes}) do
%{
"id" => "#{ap_id}/likes",
"type" => "OrderedCollection",

View File

@ -75,10 +75,7 @@ def render("user.json", %{user: user}) do
endpoints = render("endpoints.json", %{user: user})
user_tags =
user
|> Transmogrifier.add_emoji_tags()
|> Map.get("tag", [])
emoji_tags = Transmogrifier.take_emoji_tags(user)
fields =
user.info
@ -110,7 +107,7 @@ def render("user.json", %{user: user}) do
},
"endpoints" => endpoints,
"attachment" => fields,
"tag" => (user.info.source_data["tag"] || []) ++ user_tags
"tag" => (user.info.source_data["tag"] || []) ++ emoji_tags
}
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))

View File

@ -14,6 +14,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
alias Pleroma.Web.AdminAPI.Config
alias Pleroma.Web.AdminAPI.ConfigView
alias Pleroma.Web.AdminAPI.ModerationLogView
alias Pleroma.Web.AdminAPI.Report
alias Pleroma.Web.AdminAPI.ReportView
alias Pleroma.Web.AdminAPI.Search
alias Pleroma.Web.CommonAPI
@ -139,7 +140,8 @@ def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
def user_show(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
conn
|> json(AccountView.render("show.json", %{user: user}))
|> put_view(AccountView)
|> render("show.json", %{user: user})
else
_ -> {:error, :not_found}
end
@ -158,7 +160,8 @@ def list_user_statuses(conn, %{"nickname" => nickname} = params) do
})
conn
|> json(StatusView.render("index.json", %{activities: activities, as: :activity}))
|> put_view(StatusView)
|> render("index.json", %{activities: activities, as: :activity})
else
_ -> {:error, :not_found}
end
@ -178,7 +181,8 @@ def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => ni
})
conn
|> json(AccountView.render("show.json", %{user: updated_user}))
|> put_view(AccountView)
|> render("show.json", %{user: updated_user})
end
def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
@ -424,7 +428,8 @@ def invites(conn, _params) do
invites = UserInviteToken.list_invites()
conn
|> json(AccountView.render("invites.json", %{invites: invites}))
|> put_view(AccountView)
|> render("invites.json", %{invites: invites})
end
@doc "Revokes invite by token"
@ -432,7 +437,8 @@ def revoke_invite(conn, %{"token" => token}) do
with {:ok, invite} <- UserInviteToken.find_by_token(token),
{:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
conn
|> json(AccountView.render("invite.json", %{invite: updated_invite}))
|> put_view(AccountView)
|> render("invite.json", %{invite: updated_invite})
else
nil -> {:error, :not_found}
end
@ -447,6 +453,15 @@ def get_password_reset(conn, %{"nickname" => nickname}) do
|> json(token.token)
end
@doc "Force password reset for a given user"
def force_password_reset(conn, %{"nickname" => nickname}) do
(%User{local: true} = user) = User.get_cached_by_nickname(nickname)
User.force_password_reset_async(user)
json_response(conn, :no_content, "")
end
def list_reports(conn, params) do
params =
params
@ -465,7 +480,7 @@ def report_show(conn, %{"id" => id}) do
with %Activity{} = report <- Activity.get_by_id(id) do
conn
|> put_view(ReportView)
|> render("show.json", %{report: report})
|> render("show.json", Report.extract_report_info(report))
else
_ -> {:error, :not_found}
end
@ -481,7 +496,7 @@ def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state"
conn
|> put_view(ReportView)
|> render("show.json", %{report: report})
|> render("show.json", Report.extract_report_info(report))
end
end

View File

@ -0,0 +1,22 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.Report do
alias Pleroma.Activity
alias Pleroma.User
def extract_report_info(
%{data: %{"actor" => actor, "object" => [account_ap_id | status_ap_ids]}} = report
) do
user = User.get_cached_by_ap_id(actor)
account = User.get_cached_by_ap_id(account_ap_id)
statuses =
Enum.map(status_ap_ids, fn ap_id ->
Activity.get_by_ap_id_with_object(ap_id)
end)
%{report: report, user: user, account: account, statuses: statuses}
end
end

View File

@ -4,27 +4,26 @@
defmodule Pleroma.Web.AdminAPI.ReportView do
use Pleroma.Web, :view
alias Pleroma.Activity
alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.AdminAPI.Report
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.StatusView
def render("index.json", %{reports: reports}) do
%{
reports:
render_many(reports[:items], __MODULE__, "show.json", as: :report) |> Enum.reverse(),
reports[:items]
|> Enum.map(&Report.extract_report_info(&1))
|> Enum.map(&render(__MODULE__, "show.json", &1))
|> Enum.reverse(),
total: reports[:total]
}
end
def render("show.json", %{report: report}) do
user = User.get_cached_by_ap_id(report.data["actor"])
def render("show.json", %{report: report, user: user, account: account, statuses: statuses}) do
created_at = Utils.to_masto_date(report.data["published"])
[account_ap_id | status_ap_ids] = report.data["object"]
account = User.get_cached_by_ap_id(account_ap_id)
content =
unless is_nil(report.data["content"]) do
HTML.filter_tags(report.data["content"])
@ -32,11 +31,6 @@ def render("show.json", %{report: report}) do
nil
end
statuses =
Enum.map(status_ap_ids, fn ap_id ->
Activity.get_by_ap_id_with_object(ap_id)
end)
%{
id: report.id,
account: merge_account_views(account),

View File

@ -382,7 +382,6 @@ def public_timeline(%{assigns: %{user: user}} = conn, params) do
|> Map.put("local_only", local_only)
|> Map.put("blocking_user", user)
|> Map.put("muting_user", user)
|> Map.put("user", user)
|> ActivityPub.fetch_public_activities()
|> Enum.reverse()

View File

@ -19,9 +19,10 @@ defmodule Pleroma.Web.MastodonAPI.SearchController do
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, search_options(params, user))
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
json(conn, res)
conn
|> put_view(AccountView)
|> render("accounts.json", users: accounts, for: user, as: :user)
end
def search2(conn, params), do: do_search(:v2, conn, params)

View File

@ -202,6 +202,8 @@ def token_exchange(
{:ok, app} <- Token.Utils.fetch_app(conn),
{:auth_active, true} <- {:auth_active, User.auth_active?(user)},
{:user_active, true} <- {:user_active, !user.info.deactivated},
{:password_reset_pending, false} <-
{:password_reset_pending, user.info.password_reset_pending},
{:ok, scopes} <- validate_scopes(app, params),
{:ok, auth} <- Authorization.create_authorization(app, user, scopes),
{:ok, token} <- Token.exchange_token(app, auth) do
@ -215,6 +217,9 @@ def token_exchange(
{:user_active, false} ->
render_error(conn, :forbidden, "Your account is currently disabled")
{:password_reset_pending, true} ->
render_error(conn, :forbidden, "Password reset is required")
_error ->
render_invalid_credentials_error(conn)
end

View File

@ -216,7 +216,8 @@ defp represent_activity(
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(ObjectView.render("object.json", %{object: object}))
|> put_view(ObjectView)
|> render("object.json", %{object: object})
end
defp represent_activity(_conn, "activity+json", _, _) do

View File

@ -242,7 +242,7 @@ def create(conn, %{"name" => name}) do
File.write!(
pack_file_p,
Jason.encode!(%{pack: %{}, files: %{}})
Jason.encode!(%{pack: %{}, files: %{}}, pretty: true)
)
conn |> json("ok")

View File

@ -186,6 +186,7 @@ defmodule Pleroma.Web.Router do
post("/users/email_invite", AdminAPIController, :email_invite)
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
patch("/users/:nickname/force_password_reset", AdminAPIController, :force_password_reset)
get("/users", AdminAPIController, :list_users)
get("/users/:nickname", AdminAPIController, :user_show)

View File

@ -26,6 +26,11 @@ def perform(%{"op" => "delete_user", "user_id" => user_id}, _job) do
User.perform(:delete, user)
end
def perform(%{"op" => "force_password_reset", "user_id" => user_id}, _job) do
user = User.get_cached_by_id(user_id)
User.perform(:force_password_reset, user)
end
def perform(
%{
"op" => "blocks_import",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +1 @@
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.e5cd8da6.css rel=stylesheet><link href=chunk-libs.4e8c4664.css rel=stylesheet><link href=app.34fc670f.css rel=stylesheet></head><body><script src=/pleroma/admin/static/tinymce4.7.5/tinymce.min.js></script><div id=app></div><script type=text/javascript src=static/js/runtime.f40c8ec4.js></script><script type=text/javascript src=static/js/chunk-elementUI.1911151b.js></script><script type=text/javascript src=static/js/chunk-libs.fb0b7f4a.js></script><script type=text/javascript src=static/js/app.8e186193.js></script></body></html>
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.f35d8ab1.css rel=stylesheet><link href=chunk-libs.00388c73.css rel=stylesheet><link href=app.40438ff5.css rel=stylesheet></head><body><script src=/pleroma/admin/static/tinymce4.7.5/tinymce.min.js></script><div id=app></div><script type=text/javascript src=static/js/runtime.e85850af.js></script><script type=text/javascript src=static/js/chunk-elementUI.708d6b68.js></script><script type=text/javascript src=static/js/chunk-libs.14514767.js></script><script type=text/javascript src=static/js/app.90c455c5.js></script></body></html>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -19,7 +19,7 @@ test "turns hashtags into links" do
text = "I love #cofe and #2hu"
expected_text =
"I love <a class='hashtag' data-tag='cofe' href='http://localhost:4001/tag/cofe' rel='tag'>#cofe</a> and <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a>"
~s(I love <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe" rel="tag ugc">#cofe</a> and <a class="hashtag" data-tag="2hu" href="http://localhost:4001/tag/2hu" rel="tag ugc">#2hu</a>)
assert {^expected_text, [], _tags} = Formatter.linkify(text)
end
@ -28,7 +28,7 @@ test "does not turn html characters to tags" do
text = "#fact_3: pleroma does what mastodon't"
expected_text =
"<a class='hashtag' data-tag='fact_3' href='http://localhost:4001/tag/fact_3' rel='tag'>#fact_3</a>: pleroma does what mastodon't"
~s(<a class="hashtag" data-tag="fact_3" href="http://localhost:4001/tag/fact_3" rel="tag ugc">#fact_3</a>: pleroma does what mastodon't)
assert {^expected_text, [], _tags} = Formatter.linkify(text)
end
@ -39,21 +39,21 @@ test "turning urls into links" do
text = "Hey, check out https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla ."
expected =
"Hey, check out <a href=\"https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla\">https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla</a> ."
~S(Hey, check out <a href="https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla" rel="ugc">https://www.youtube.com/watch?v=8Zg1-TufF%20zY?x=1&y=2#blabla</a> .)
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://mastodon.social/@lambadalambda"
expected =
"<a href=\"https://mastodon.social/@lambadalambda\">https://mastodon.social/@lambadalambda</a>"
~S(<a href="https://mastodon.social/@lambadalambda" rel="ugc">https://mastodon.social/@lambadalambda</a>)
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://mastodon.social:4000/@lambadalambda"
expected =
"<a href=\"https://mastodon.social:4000/@lambadalambda\">https://mastodon.social:4000/@lambadalambda</a>"
~S(<a href="https://mastodon.social:4000/@lambadalambda" rel="ugc">https://mastodon.social:4000/@lambadalambda</a>)
assert {^expected, [], []} = Formatter.linkify(text)
@ -63,55 +63,57 @@ test "turning urls into links" do
assert {^expected, [], []} = Formatter.linkify(text)
text = "http://www.cs.vu.nl/~ast/intel/"
expected = "<a href=\"http://www.cs.vu.nl/~ast/intel/\">http://www.cs.vu.nl/~ast/intel/</a>"
expected =
~S(<a href="http://www.cs.vu.nl/~ast/intel/" rel="ugc">http://www.cs.vu.nl/~ast/intel/</a>)
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://forum.zdoom.org/viewtopic.php?f=44&t=57087"
expected =
"<a href=\"https://forum.zdoom.org/viewtopic.php?f=44&t=57087\">https://forum.zdoom.org/viewtopic.php?f=44&t=57087</a>"
"<a href=\"https://forum.zdoom.org/viewtopic.php?f=44&t=57087\" rel=\"ugc\">https://forum.zdoom.org/viewtopic.php?f=44&t=57087</a>"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul"
expected =
"<a href=\"https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul\">https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul</a>"
"<a href=\"https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul\" rel=\"ugc\">https://en.wikipedia.org/wiki/Sophia_(Gnosticism)#Mythos_of_the_soul</a>"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://www.google.co.jp/search?q=Nasim+Aghdam"
expected =
"<a href=\"https://www.google.co.jp/search?q=Nasim+Aghdam\">https://www.google.co.jp/search?q=Nasim+Aghdam</a>"
"<a href=\"https://www.google.co.jp/search?q=Nasim+Aghdam\" rel=\"ugc\">https://www.google.co.jp/search?q=Nasim+Aghdam</a>"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://en.wikipedia.org/wiki/Duff's_device"
expected =
"<a href=\"https://en.wikipedia.org/wiki/Duff's_device\">https://en.wikipedia.org/wiki/Duff's_device</a>"
"<a href=\"https://en.wikipedia.org/wiki/Duff's_device\" rel=\"ugc\">https://en.wikipedia.org/wiki/Duff's_device</a>"
assert {^expected, [], []} = Formatter.linkify(text)
text = "https://pleroma.com https://pleroma.com/sucks"
expected =
"<a href=\"https://pleroma.com\">https://pleroma.com</a> <a href=\"https://pleroma.com/sucks\">https://pleroma.com/sucks</a>"
"<a href=\"https://pleroma.com\" rel=\"ugc\">https://pleroma.com</a> <a href=\"https://pleroma.com/sucks\" rel=\"ugc\">https://pleroma.com/sucks</a>"
assert {^expected, [], []} = Formatter.linkify(text)
text = "xmpp:contact@hacktivis.me"
expected = "<a href=\"xmpp:contact@hacktivis.me\">xmpp:contact@hacktivis.me</a>"
expected = "<a href=\"xmpp:contact@hacktivis.me\" rel=\"ugc\">xmpp:contact@hacktivis.me</a>"
assert {^expected, [], []} = Formatter.linkify(text)
text =
"magnet:?xt=urn:btih:7ec9d298e91d6e4394d1379caf073c77ff3e3136&tr=udp%3A%2F%2Fopentor.org%3A2710&tr=udp%3A%2F%2Ftracker.blackunicorn.xyz%3A6969&tr=udp%3A%2F%2Ftracker.ccc.de%3A80&tr=udp%3A%2F%2Ftracker.coppersurfer.tk%3A6969&tr=udp%3A%2F%2Ftracker.leechers-paradise.org%3A6969&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=wss%3A%2F%2Ftracker.btorrent.xyz&tr=wss%3A%2F%2Ftracker.fastcast.nz&tr=wss%3A%2F%2Ftracker.openwebtorrent.com"
expected = "<a href=\"#{text}\">#{text}</a>"
expected = "<a href=\"#{text}\" rel=\"ugc\">#{text}</a>"
assert {^expected, [], []} = Formatter.linkify(text)
end
@ -135,13 +137,13 @@ test "gives a replacement for user links, using local nicknames in user links te
assert length(mentions) == 3
expected_text =
"<span class='h-card'><a data-user='#{gsimg.id}' class='u-url mention' href='#{
~s(<span class="h-card"><a data-user="#{gsimg.id}" class="u-url mention" href="#{
gsimg.ap_id
}'>@<span>gsimg</span></a></span> According to <span class='h-card'><a data-user='#{
}" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a data-user="#{
archaeme.id
}' class='u-url mention' href='#{"https://archeme/@archa_eme_"}'>@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class='h-card'><a data-user='#{
}" class="u-url mention" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a data-user="#{
archaeme_remote.id
}' class='u-url mention' href='#{archaeme_remote.ap_id}'>@<span>archaeme</span></a></span>"
}" class="u-url mention" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>)
assert expected_text == text
end
@ -156,7 +158,9 @@ test "gives a replacement for user links when the user is using Osada" do
assert length(mentions) == 1
expected_text =
"<span class='h-card'><a data-user='#{mike.id}' class='u-url mention' href='#{mike.ap_id}'>@<span>mike</span></a></span> test"
~s(<span class="h-card"><a data-user="#{mike.id}" class="u-url mention" href="#{
mike.ap_id
}" rel="ugc">@<span>mike</span></a></span> test)
assert expected_text == text
end
@ -170,7 +174,7 @@ test "gives a replacement for single-character local nicknames" do
assert length(mentions) == 1
expected_text =
"<span class='h-card'><a data-user='#{o.id}' class='u-url mention' href='#{o.ap_id}'>@<span>o</span></a></span> hi"
~s(<span class="h-card"><a data-user="#{o.id}" class="u-url mention" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi)
assert expected_text == text
end
@ -192,13 +196,17 @@ test "given the 'safe_mention' option, it will only mention people in the beginn
assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}]
assert expected_text ==
"<span class='h-card'><a data-user='#{user.id}' class='u-url mention' href='#{
~s(<span class="h-card"><a data-user="#{user.id}" class="u-url mention" href="#{
user.ap_id
}'>@<span>#{user.nickname}</span></a></span> <span class='h-card'><a data-user='#{
}" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a data-user="#{
other_user.id
}' class='u-url mention' href='#{other_user.ap_id}'>@<span>#{other_user.nickname}</span></a></span> hey dudes i hate <span class='h-card'><a data-user='#{
}" class="u-url mention" href="#{other_user.ap_id}" rel="ugc">@<span>#{
other_user.nickname
}</span></a></span> hey dudes i hate <span class="h-card"><a data-user="#{
third_user.id
}' class='u-url mention' href='#{third_user.ap_id}'>@<span>#{third_user.nickname}</span></a></span>"
}" class="u-url mention" href="#{third_user.ap_id}" rel="ugc">@<span>#{
third_user.nickname
}</span></a></span>)
end
test "given the 'safe_mention' option, it will still work without any mention" do

View File

@ -1294,9 +1294,9 @@ test "preserves hosts in user links text" do
bio = "A.k.a. @nick@domain.com"
expected_text =
"A.k.a. <span class='h-card'><a data-user='#{remote_user.id}' class='u-url mention' href='#{
~s(A.k.a. <span class="h-card"><a data-user="#{remote_user.id}" class="u-url mention" href="#{
remote_user.ap_id
}'>@<span>nick@domain.com</span></a></span>"
}" rel="ugc">@<span>nick@domain.com</span></a></span>)
assert expected_text == User.parse_bio(bio, user)
end
@ -1690,4 +1690,21 @@ test "changes email", %{user: user} do
assert {:ok, %User{email: "cofe@cofe.party"}} = User.change_email(user, "cofe@cofe.party")
end
end
describe "set_password_reset_pending/2" do
setup do
[user: insert(:user)]
end
test "sets password_reset_pending to true", %{user: user} do
%{password_reset_pending: password_reset_pending} = user.info
refute password_reset_pending
{:ok, %{info: %{password_reset_pending: password_reset_pending}}} =
User.force_password_reset(user)
assert password_reset_pending
end
end
end

View File

@ -1455,4 +1455,271 @@ test "removes recipient's follower collection from cc", %{user: user} do
refute recipient.follower_address in fixed_object["to"]
end
end
describe "fix_summary/1" do
test "returns fixed object" do
assert Transmogrifier.fix_summary(%{"summary" => nil}) == %{"summary" => ""}
assert Transmogrifier.fix_summary(%{"summary" => "ok"}) == %{"summary" => "ok"}
assert Transmogrifier.fix_summary(%{}) == %{"summary" => ""}
end
end
describe "fix_in_reply_to/2" do
clear_config([:instance, :federation_incoming_replies_max_depth])
setup do
data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
[data: data]
end
test "returns not modified object when hasn't containts inReplyTo field", %{data: data} do
assert Transmogrifier.fix_in_reply_to(data) == data
end
test "returns object with inReplyToAtomUri when denied incoming reply", %{data: data} do
Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 0)
object_with_reply =
Map.put(data["object"], "inReplyTo", "https://shitposter.club/notice/2827873")
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
assert modified_object["inReplyTo"] == "https://shitposter.club/notice/2827873"
assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
object_with_reply =
Map.put(data["object"], "inReplyTo", %{"id" => "https://shitposter.club/notice/2827873"})
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
assert modified_object["inReplyTo"] == %{"id" => "https://shitposter.club/notice/2827873"}
assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
object_with_reply =
Map.put(data["object"], "inReplyTo", ["https://shitposter.club/notice/2827873"])
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
assert modified_object["inReplyTo"] == ["https://shitposter.club/notice/2827873"]
assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
object_with_reply = Map.put(data["object"], "inReplyTo", [])
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
assert modified_object["inReplyTo"] == []
assert modified_object["inReplyToAtomUri"] == ""
end
test "returns modified object when allowed incoming reply", %{data: data} do
object_with_reply =
Map.put(
data["object"],
"inReplyTo",
"https://shitposter.club/notice/2827873"
)
Pleroma.Config.put([:instance, :federation_incoming_replies_max_depth], 5)
modified_object = Transmogrifier.fix_in_reply_to(object_with_reply)
assert modified_object["inReplyTo"] ==
"tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"
assert modified_object["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873"
assert modified_object["conversation"] ==
"tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
assert modified_object["context"] ==
"tag:shitposter.club,2017-05-05:objectType=thread:nonce=3c16e9c2681f6d26"
end
end
describe "fix_url/1" do
test "fixes data for object when url is map" do
object = %{
"url" => %{
"type" => "Link",
"mimeType" => "video/mp4",
"href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
}
}
assert Transmogrifier.fix_url(object) == %{
"url" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
}
end
test "fixes data for video object" do
object = %{
"type" => "Video",
"url" => [
%{
"type" => "Link",
"mimeType" => "video/mp4",
"href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4"
},
%{
"type" => "Link",
"mimeType" => "video/mp4",
"href" => "https://peertube46fb-ad81-2d4c2d1630e3-240.mp4"
},
%{
"type" => "Link",
"mimeType" => "text/html",
"href" => "https://peertube.-2d4c2d1630e3"
},
%{
"type" => "Link",
"mimeType" => "text/html",
"href" => "https://peertube.-2d4c2d16377-42"
}
]
}
assert Transmogrifier.fix_url(object) == %{
"attachment" => [
%{
"href" => "https://peede8d-46fb-ad81-2d4c2d1630e3-480.mp4",
"mimeType" => "video/mp4",
"type" => "Link"
}
],
"type" => "Video",
"url" => "https://peertube.-2d4c2d1630e3"
}
end
test "fixes url for not Video object" do
object = %{
"type" => "Text",
"url" => [
%{
"type" => "Link",
"mimeType" => "text/html",
"href" => "https://peertube.-2d4c2d1630e3"
},
%{
"type" => "Link",
"mimeType" => "text/html",
"href" => "https://peertube.-2d4c2d16377-42"
}
]
}
assert Transmogrifier.fix_url(object) == %{
"type" => "Text",
"url" => "https://peertube.-2d4c2d1630e3"
}
assert Transmogrifier.fix_url(%{"type" => "Text", "url" => []}) == %{
"type" => "Text",
"url" => ""
}
end
test "retunrs not modified object" do
assert Transmogrifier.fix_url(%{"type" => "Text"}) == %{"type" => "Text"}
end
end
describe "get_obj_helper/2" do
test "returns nil when cannot normalize object" do
refute Transmogrifier.get_obj_helper("test-obj-id")
end
test "returns {:ok, %Object{}} for success case" do
assert {:ok, %Object{}} =
Transmogrifier.get_obj_helper("https://shitposter.club/notice/2827873")
end
end
describe "fix_attachments/1" do
test "returns not modified object" do
data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
assert Transmogrifier.fix_attachments(data) == data
end
test "returns modified object when attachment is map" do
assert Transmogrifier.fix_attachments(%{
"attachment" => %{
"mediaType" => "video/mp4",
"url" => "https://peertube.moe/stat-480.mp4"
}
}) == %{
"attachment" => [
%{
"mediaType" => "video/mp4",
"url" => [
%{
"href" => "https://peertube.moe/stat-480.mp4",
"mediaType" => "video/mp4",
"type" => "Link"
}
]
}
]
}
end
test "returns modified object when attachment is list" do
assert Transmogrifier.fix_attachments(%{
"attachment" => [
%{"mediaType" => "video/mp4", "url" => "https://pe.er/stat-480.mp4"},
%{"mimeType" => "video/mp4", "href" => "https://pe.er/stat-480.mp4"}
]
}) == %{
"attachment" => [
%{
"mediaType" => "video/mp4",
"url" => [
%{
"href" => "https://pe.er/stat-480.mp4",
"mediaType" => "video/mp4",
"type" => "Link"
}
]
},
%{
"href" => "https://pe.er/stat-480.mp4",
"mediaType" => "video/mp4",
"mimeType" => "video/mp4",
"url" => [
%{
"href" => "https://pe.er/stat-480.mp4",
"mediaType" => "video/mp4",
"type" => "Link"
}
]
}
]
}
end
end
describe "fix_emoji/1" do
test "returns not modified object when object not contains tags" do
data = Poison.decode!(File.read!("test/fixtures/mastodon-post-activity.json"))
assert Transmogrifier.fix_emoji(data) == data
end
test "returns object with emoji when object contains list tags" do
assert Transmogrifier.fix_emoji(%{
"tag" => [
%{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}},
%{"type" => "Hashtag"}
]
}) == %{
"emoji" => %{"bib" => "/test"},
"tag" => [
%{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"},
%{"type" => "Hashtag"}
]
}
end
test "returns object with emoji when object contains map tag" do
assert Transmogrifier.fix_emoji(%{
"tag" => %{"type" => "Emoji", "name" => ":bib:", "icon" => %{"url" => "/test"}}
}) == %{
"emoji" => %{"bib" => "/test"},
"tag" => %{"icon" => %{"url" => "/test"}, "name" => ":bib:", "type" => "Emoji"}
}
end
end
end

View File

@ -37,6 +37,22 @@ test "Renders profile fields" do
} = UserView.render("user.json", %{user: user})
end
test "Renders with emoji tags" do
user = insert(:user, %{info: %{emoji: [%{"bib" => "/test"}]}})
assert %{
"tag" => [
%{
"icon" => %{"type" => "Image", "url" => "/test"},
"id" => "/test",
"name" => ":bib:",
"type" => "Emoji",
"updated" => "1970-01-01T00:00:00Z"
}
]
} = UserView.render("user.json", %{user: user})
end
test "Does not add an avatar image if the user hasn't set one" do
user = insert(:user)
{:ok, user} = User.ensure_keys_present(user)

View File

@ -4,11 +4,13 @@
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
use Pleroma.Web.ConnCase
use Oban.Testing, repo: Pleroma.Repo
alias Pleroma.Activity
alias Pleroma.HTML
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.UserInviteToken
alias Pleroma.Web.CommonAPI
@ -2351,6 +2353,30 @@ test "returns the log with pagination", %{conn: conn, admin: admin} do
"@#{admin.nickname} followed relay: https://example.org/relay"
end
end
describe "PATCH /users/:nickname/force_password_reset" do
setup %{conn: conn} do
admin = insert(:user, info: %{is_admin: true})
user = insert(:user)
%{conn: assign(conn, :user, admin), admin: admin, user: user}
end
test "sets password_reset_pending to true", %{admin: admin, user: user} do
assert user.info.password_reset_pending == false
conn =
build_conn()
|> assign(:user, admin)
|> patch("/api/pleroma/admin/users/#{user.nickname}/force_password_reset")
assert json_response(conn, 204) == ""
ObanHelpers.perform_all()
assert User.get_by_id(user.id).info.password_reset_pending == true
end
end
end
# Needed for testing

View File

@ -5,6 +5,7 @@
defmodule Pleroma.Web.AdminAPI.ReportViewTest do
use Pleroma.DataCase
import Pleroma.Factory
alias Pleroma.Web.AdminAPI.Report
alias Pleroma.Web.AdminAPI.ReportView
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.AccountView
@ -34,7 +35,7 @@ test "renders a report" do
}
result =
ReportView.render("show.json", %{report: activity})
ReportView.render("show.json", Report.extract_report_info(activity))
|> Map.delete(:created_at)
assert result == expected
@ -66,7 +67,7 @@ test "includes reported statuses" do
}
result =
ReportView.render("show.json", %{report: report_activity})
ReportView.render("show.json", Report.extract_report_info(report_activity))
|> Map.delete(:created_at)
assert result == expected
@ -78,7 +79,9 @@ test "renders report's state" do
{:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id})
{:ok, activity} = CommonAPI.update_report_state(activity.id, "closed")
assert %{state: "closed"} = ReportView.render("show.json", %{report: activity})
assert %{state: "closed"} =
ReportView.render("show.json", Report.extract_report_info(activity))
end
test "renders report description" do
@ -92,7 +95,7 @@ test "renders report description" do
})
assert %{content: "posts are too good for this instance"} =
ReportView.render("show.json", %{report: activity})
ReportView.render("show.json", Report.extract_report_info(activity))
end
test "sanitizes report description" do
@ -109,7 +112,7 @@ test "sanitizes report description" do
activity = Map.put(activity, :data, data)
refute "<script> alert('hecked :D:D:D:D:D:D:D') </script>" ==
ReportView.render("show.json", %{report: activity})[:content]
ReportView.render("show.json", Report.extract_report_info(activity))[:content]
end
test "doesn't error out when the user doesn't exists" do
@ -125,6 +128,6 @@ test "doesn't error out when the user doesn't exists" do
Pleroma.User.delete(other_user)
Pleroma.User.invalidate_cache(other_user)
assert %{} = ReportView.render("show.json", %{report: activity})
assert %{} = ReportView.render("show.json", Report.extract_report_info(activity))
end
end

View File

@ -157,11 +157,11 @@ test "works for text/markdown with mentions" do
text = "**hello world**\n\n*another @user__test and @user__test google.com paragraph*"
expected =
"<p><strong>hello world</strong></p>\n<p><em>another <span class=\"h-card\"><a data-user=\"#{
~s(<p><strong>hello world</strong></p>\n<p><em>another <span class="h-card"><a data-user="#{
user.id
}\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> and <span class=\"h-card\"><a data-user=\"#{
}" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a data-user="#{
user.id
}\" class=\"u-url mention\" href=\"http://foo.com/user__test\">@<span>user__test</span></a></span> <a href=\"http://google.com\">google.com</a> paragraph</em></p>\n"
}" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>\n)
{output, _, _} = Utils.format_input(text, "text/markdown")

View File

@ -86,10 +86,9 @@ test "updates the user's bio", %{conn: conn} do
assert user = json_response(conn, 200)
assert user["note"] ==
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe" rel="tag">#cofe</a> with <span class="h-card"><a data-user=") <>
user2.id <>
~s(" class="u-url mention" href=") <>
user2.ap_id <> ~s(">@<span>) <> user2.nickname <> ~s(</span></a></span>)
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a data-user="#{
user2.id
}" class="u-url mention" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span>)
end
test "updates the user's locking status", %{conn: conn} do
@ -334,7 +333,7 @@ test "update fields", %{conn: conn} do
assert account["fields"] == [
%{"name" => "foo", "value" => "bar"},
%{"name" => "link", "value" => "<a href=\"http://cofe.io\">cofe.io</a>"}
%{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
]
assert account["source"]["fields"] == [

View File

@ -97,6 +97,22 @@ test "the public timeline when public is set to false", %{conn: conn} do
|> json_response(403) == %{"error" => "This resource requires authentication."}
end
test "the public timeline includes only public statuses for an authenticated user" do
user = insert(:user)
conn =
build_conn()
|> assign(:user, user)
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test"})
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "private"})
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "unlisted"})
{:ok, _activity} = CommonAPI.post(user, %{"status" => "test", "visibility" => "direct"})
res_conn = get(conn, "/api/v1/timelines/public")
assert length(json_response(res_conn, 200)) == 1
end
describe "posting statuses" do
setup do
user = insert(:user)
@ -998,9 +1014,9 @@ test "list of notifications", %{conn: conn} do
|> get("/api/v1/notifications")
expected_response =
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
~s(hi <span class="h-card"><a data-user="#{user.id}" class="u-url mention" href="#{
user.ap_id
}\">@<span>#{user.nickname}</span></a></span>"
}" rel="ugc">@<span>#{user.nickname}</span></a></span>)
assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200)
assert response == expected_response
@ -1020,9 +1036,9 @@ test "getting a single notification", %{conn: conn} do
|> get("/api/v1/notifications/#{notification.id}")
expected_response =
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
~s(hi <span class="h-card"><a data-user="#{user.id}" class="u-url mention" href="#{
user.ap_id
}\">@<span>#{user.nickname}</span></a></span>"
}" rel="ugc">@<span>#{user.nickname}</span></a></span>)
assert %{"status" => %{"content" => response}} = json_response(conn, 200)
assert response == expected_response

View File

@ -831,6 +831,33 @@ test "rejects token exchange for valid credentials belonging to deactivated user
refute Map.has_key?(resp, "access_token")
end
test "rejects token exchange for user with password_reset_pending set to true" do
password = "testpassword"
user =
insert(:user,
password_hash: Comeonin.Pbkdf2.hashpwsalt(password),
info: %{password_reset_pending: true}
)
app = insert(:oauth_app, scopes: ["read", "write"])
conn =
build_conn()
|> post("/oauth/token", %{
"grant_type" => "password",
"username" => user.nickname,
"password" => password,
"client_id" => app.client_id,
"client_secret" => app.client_secret
})
assert resp = json_response(conn, 403)
assert resp["error"] == "Password reset is required"
refute Map.has_key?(resp, "access_token")
end
test "rejects an invalid authorization code" do
app = insert(:oauth_app)

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