Merge branch 'develop' into feature/configurable-blocks

This commit is contained in:
squidboi 2018-06-16 15:37:16 -07:00
commit 2e294ee44a
51 changed files with 1066 additions and 167 deletions

72
CONFIGURATION.md Normal file
View file

@ -0,0 +1,72 @@
# Configuring Pleroma
In the `config/` directory, you will find the following relevant files:
* `config.exs`: default base configuration
* `dev.exs`: default additional configuration for `MIX_ENV=dev`
* `prod.exs`: default additional configuration for `MIX_ENV=prod`
Do not modify files in the list above.
Instead, overload the settings by editing the following files:
* `dev.secret.exs`: custom additional configuration for `MIX_ENV=dev`
* `prod.secret.exs`: custom additional configuration for `MIX_ENV=prod`
## Message Rewrite Filters (MRFs)
Modify incoming and outgoing posts.
config :pleroma, :instance,
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy
`rewrite_policy` specifies which MRF policies to apply.
It can either be a single policy or a list of policies.
Currently, MRFs availible by default are:
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`
Some policies, such as SimplePolicy and RejectNonPublic,
can be additionally configured in their respective sections.
### NoOpPolicy
Does not modify posts (this is the default `rewrite_policy`)
### DropPolicy
Drops all posts.
It generally does not make sense to use this in production.
### SimplePolicy
Restricts the visibility of posts from certain instances.
config :pleroma, :mrf_simple,
media_removal: [],
media_nsfw: [],
federated_timeline_removal: [],
reject: []
* `media_removal`: posts from these instances will have attachments
removed
* `media_nsfw`: posts from these instances will have attachments marked
as nsfw
* `federated_timeline_removal`: posts from these instances will be
marked as unlisted
* `reject`: posts from these instances will be dropped
### RejectNonPublic
Drops posts with non-public visibility settings.
config :pleroma :mrf_rejectnonpublic
allow_followersonly: false,
allow_direct: false,
* `allow_followersonly`: whether to allow follower-only posts through
the filter
* `allow_direct`: whether to allow direct messages through the filter

View file

@ -64,6 +64,10 @@ config :pleroma, :activitypub,
config :pleroma, :user, deny_follow_blocked: true config :pleroma, :user, deny_follow_blocked: true
config :pleroma, :mrf_rejectnonpublic,
allow_followersonly: false,
allow_direct: false
config :pleroma, :mrf_simple, config :pleroma, :mrf_simple,
media_removal: [], media_removal: [],
media_nsfw: [], media_nsfw: [],

View file

@ -24,18 +24,27 @@ server {
# } # }
} }
# Enable SSL session caching for improved performance
ssl_session_cache shared:ssl_session_cache:10m;
server { server {
listen 443 ssl http2; listen 443 ssl http2;
ssl on;
ssl_session_timeout 5m; ssl_session_timeout 5m;
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem; ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem; ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # Add TLSv1.0 to support older devices
ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES"; ssl_protocols TLSv1.2;
# Uncomment line below if you want to support older devices (Before Android 4.4.2, IE 8, etc.)
# ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
ssl_prefer_server_ciphers on; ssl_prefer_server_ciphers on;
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
ssl_stapling on;
ssl_stapling_verify on;
server_name example.tld; server_name example.tld;
gzip_vary on; gzip_vary on;

View file

@ -5,7 +5,7 @@ defmodule Mix.Tasks.SetModerator do
@shortdoc "Set moderator status" @shortdoc "Set moderator status"
def run([nickname | rest]) do def run([nickname | rest]) do
ensure_started(Repo, []) Application.ensure_all_started(:pleroma)
moderator = moderator =
case rest do case rest do
@ -19,7 +19,7 @@ defmodule Mix.Tasks.SetModerator do
|> Map.put("is_moderator", !!moderator) |> Map.put("is_moderator", !!moderator)
cng = User.info_changeset(user, %{info: info}) cng = User.info_changeset(user, %{info: info})
user = Repo.update!(cng) {:ok, user} = User.update_and_set_cache(cng)
IO.puts("Moderator status of #{nickname}: #{user.info["is_moderator"]}") IO.puts("Moderator status of #{nickname}: #{user.info["is_moderator"]}")
else else

View file

@ -8,7 +8,8 @@ config :pleroma, :instance,
name: "<%= name %>", name: "<%= name %>",
email: "<%= email %>", email: "<%= email %>",
limit: 5000, limit: 5000,
registrations_open: true registrations_open: true,
dedupe_media: false
config :pleroma, :media_proxy, config :pleroma, :media_proxy,
enabled: false, enabled: false,

View file

@ -0,0 +1,30 @@
defmodule Mix.Tasks.SetLocked do
use Mix.Task
import Mix.Ecto
alias Pleroma.{Repo, User}
@shortdoc "Set locked status"
def run([nickname | rest]) do
ensure_started(Repo, [])
locked =
case rest do
[locked] -> locked == "true"
_ -> true
end
with %User{local: true} = user <- User.get_by_nickname(nickname) do
info =
user.info
|> Map.put("locked", !!locked)
cng = User.info_changeset(user, %{info: info})
user = Repo.update!(cng)
IO.puts("locked status of #{nickname}: #{user.info["locked"]}")
else
_ ->
IO.puts("No local user #{nickname}")
end
end
end

View file

@ -1,7 +1,7 @@
defmodule Pleroma.List do defmodule Pleroma.List do
use Ecto.Schema use Ecto.Schema
import Ecto.{Changeset, Query} import Ecto.{Changeset, Query}
alias Pleroma.{User, Repo} alias Pleroma.{User, Repo, Activity}
schema "lists" do schema "lists" do
belongs_to(:user, Pleroma.User) belongs_to(:user, Pleroma.User)
@ -56,6 +56,19 @@ defmodule Pleroma.List do
{:ok, Repo.all(q)} {:ok, Repo.all(q)}
end end
# Get lists the activity should be streamed to.
def get_lists_from_activity(%Activity{actor: ap_id}) do
actor = User.get_cached_by_ap_id(ap_id)
query =
from(
l in Pleroma.List,
where: fragment("? && ?", l.following, ^[actor.follower_address])
)
Repo.all(query)
end
def rename(%Pleroma.List{} = list, title) do def rename(%Pleroma.List{} = list, title) do
list list
|> title_changeset(%{title: title}) |> title_changeset(%{title: title})

View file

@ -2,20 +2,21 @@ defmodule Pleroma.Upload do
alias Ecto.UUID alias Ecto.UUID
alias Pleroma.Web alias Pleroma.Web
def store(%Plug.Upload{} = file) do def store(%Plug.Upload{} = file, should_dedupe) do
uuid = UUID.generate() content_type = get_content_type(file.path)
upload_folder = Path.join(upload_path(), uuid) uuid = get_uuid(file, should_dedupe)
File.mkdir_p!(upload_folder) name = get_name(file, uuid, content_type, should_dedupe)
result_file = Path.join(upload_folder, file.filename) upload_folder = get_upload_path(uuid, should_dedupe)
File.cp!(file.path, result_file) url_path = get_url(name, uuid, should_dedupe)
# fix content type on some image uploads File.mkdir_p!(upload_folder)
content_type = result_file = Path.join(upload_folder, name)
if file.content_type in [nil, "application/octet-stream"] do
get_content_type(file.path) if File.exists?(result_file) do
else File.rm!(file.path)
file.content_type else
end File.cp!(file.path, result_file)
end
%{ %{
"type" => "Image", "type" => "Image",
@ -23,26 +24,48 @@ defmodule Pleroma.Upload do
%{ %{
"type" => "Link", "type" => "Link",
"mediaType" => content_type, "mediaType" => content_type,
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(file.filename))) "href" => url_path
} }
], ],
"name" => file.filename, "name" => name
"uuid" => uuid
} }
end end
def store(%{"img" => "data:image/" <> image_data}) do def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data) parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
data = Base.decode64!(parsed["data"]) data = Base.decode64!(parsed["data"], ignore: :whitespace)
uuid = UUID.generate() uuid = UUID.generate()
upload_folder = Path.join(upload_path(), uuid) uuidpath = Path.join(upload_path(), uuid)
uuid = UUID.generate()
File.mkdir_p!(upload_path())
File.write!(uuidpath, data)
content_type = get_content_type(uuidpath)
name =
create_name(
String.downcase(Base.encode16(:crypto.hash(:sha256, data))),
parsed["filetype"],
content_type
)
upload_folder = get_upload_path(uuid, should_dedupe)
url_path = get_url(name, uuid, should_dedupe)
File.mkdir_p!(upload_folder) File.mkdir_p!(upload_folder)
filename = Base.encode16(:crypto.hash(:sha256, data)) <> ".#{parsed["filetype"]}" result_file = Path.join(upload_folder, name)
result_file = Path.join(upload_folder, filename)
File.write!(result_file, data) if should_dedupe do
if !File.exists?(result_file) do
content_type = "image/#{parsed["filetype"]}" File.rename(uuidpath, result_file)
else
File.rm!(uuidpath)
end
else
File.rename(uuidpath, result_file)
end
%{ %{
"type" => "Image", "type" => "Image",
@ -50,11 +73,10 @@ defmodule Pleroma.Upload do
%{ %{
"type" => "Link", "type" => "Link",
"mediaType" => content_type, "mediaType" => content_type,
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(filename))) "href" => url_path
} }
], ],
"name" => filename, "name" => name
"uuid" => uuid
} }
end end
@ -63,6 +85,65 @@ defmodule Pleroma.Upload do
Keyword.fetch!(settings, :uploads) Keyword.fetch!(settings, :uploads)
end end
defp create_name(uuid, ext, type) do
case type do
"application/octet-stream" ->
String.downcase(Enum.join([uuid, ext], "."))
"audio/mpeg" ->
String.downcase(Enum.join([uuid, "mp3"], "."))
_ ->
String.downcase(Enum.join([uuid, List.last(String.split(type, "/"))], "."))
end
end
defp get_uuid(file, should_dedupe) do
if should_dedupe do
Base.encode16(:crypto.hash(:sha256, File.read!(file.path)))
else
UUID.generate()
end
end
defp get_name(file, uuid, type, should_dedupe) do
if should_dedupe do
create_name(uuid, List.last(String.split(file.filename, ".")), type)
else
unless String.contains?(file.filename, ".") do
case type do
"image/png" -> file.filename <> ".png"
"image/jpeg" -> file.filename <> ".jpg"
"image/gif" -> file.filename <> ".gif"
"video/webm" -> file.filename <> ".webm"
"video/mp4" -> file.filename <> ".mp4"
"audio/mpeg" -> file.filename <> ".mp3"
"audio/ogg" -> file.filename <> ".ogg"
"audio/wav" -> file.filename <> ".wav"
_ -> file.filename
end
else
file.filename
end
end
end
defp get_upload_path(uuid, should_dedupe) do
if should_dedupe do
upload_path()
else
Path.join(upload_path(), uuid)
end
end
defp get_url(name, uuid, should_dedupe) do
if should_dedupe do
url_for(:cow_uri.urlencode(name))
else
url_for(Path.join(uuid, :cow_uri.urlencode(name)))
end
end
defp url_for(file) do defp url_for(file) do
"#{Web.base_url()}/media/#{file}" "#{Web.base_url()}/media/#{file}"
end end
@ -89,6 +170,9 @@ defmodule Pleroma.Upload do
<<0x49, 0x44, 0x33, _, _, _, _, _>> -> <<0x49, 0x44, 0x33, _, _, _, _, _>> ->
"audio/mpeg" "audio/mpeg"
<<255, 251, _, 68, 0, 0, 0, 0>> ->
"audio/mpeg"
<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> -> <<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
"audio/ogg" "audio/ogg"

View file

@ -201,6 +201,14 @@ defmodule Pleroma.User do
end end
end end
def maybe_follow(%User{} = follower, %User{info: info} = followed) do
if not following?(follower, followed) do
follow(follower, followed)
else
{:ok, follower}
end
end
@user_config Application.get_env(:pleroma, :user) @user_config Application.get_env(:pleroma, :user)
@deny_follow_blocked Keyword.get(@user_config, :deny_follow_blocked) @deny_follow_blocked Keyword.get(@user_config, :deny_follow_blocked)
@ -259,6 +267,10 @@ defmodule Pleroma.User do
Enum.member?(follower.following, followed.follower_address) Enum.member?(follower.following, followed.follower_address)
end end
def locked?(%User{} = user) do
user.info["locked"] || false
end
def get_by_ap_id(ap_id) do def get_by_ap_id(ap_id) do
Repo.get_by(User, ap_id: ap_id) Repo.get_by(User, ap_id: ap_id)
end end
@ -356,6 +368,40 @@ defmodule Pleroma.User do
{:ok, Repo.all(q)} {:ok, Repo.all(q)}
end end
def get_follow_requests_query(%User{} = user) do
from(
a in Activity,
where:
fragment(
"? ->> 'type' = 'Follow'",
a.data
),
where:
fragment(
"? ->> 'state' = 'pending'",
a.data
),
where:
fragment(
"? @> ?",
a.data,
^%{"object" => user.ap_id}
)
)
end
def get_follow_requests(%User{} = user) do
q = get_follow_requests_query(user)
reqs = Repo.all(q)
users =
Enum.map(reqs, fn req -> req.actor end)
|> Enum.uniq()
|> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
{:ok, users}
end
def increase_note_count(%User{} = user) do def increase_note_count(%User{} = user) do
note_count = (user.info["note_count"] || 0) + 1 note_count = (user.info["note_count"] || 0) + 1
new_info = Map.put(user.info, "note_count", note_count) new_info = Map.put(user.info, "note_count", note_count)
@ -486,7 +532,31 @@ defmodule Pleroma.User do
def blocks?(user, %{ap_id: ap_id}) do def blocks?(user, %{ap_id: ap_id}) do
blocks = user.info["blocks"] || [] blocks = user.info["blocks"] || []
Enum.member?(blocks, ap_id) domain_blocks = user.info["domain_blocks"] || []
%{host: host} = URI.parse(ap_id)
Enum.member?(blocks, ap_id) ||
Enum.any?(domain_blocks, fn domain ->
host == domain
end)
end
def block_domain(user, domain) do
domain_blocks = user.info["domain_blocks"] || []
new_blocks = Enum.uniq([domain | domain_blocks])
new_info = Map.put(user.info, "domain_blocks", new_blocks)
cs = User.info_changeset(user, %{info: new_info})
update_and_set_cache(cs)
end
def unblock_domain(user, domain) do
blocks = user.info["domain_blocks"] || []
new_blocks = List.delete(blocks, domain)
new_info = Map.put(user.info, "domain_blocks", new_blocks)
cs = User.info_changeset(user, %{info: new_info})
update_and_set_cache(cs)
end end
def local_user_query() do def local_user_query() do

View file

@ -57,6 +57,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
if activity.data["type"] in ["Create", "Announce"] do if activity.data["type"] in ["Create", "Announce"] do
Pleroma.Web.Streamer.stream("user", activity) Pleroma.Web.Streamer.stream("user", activity)
Pleroma.Web.Streamer.stream("list", activity)
if Enum.member?(activity.data["to"], public) do if Enum.member?(activity.data["to"], public) do
Pleroma.Web.Streamer.stream("public", activity) Pleroma.Web.Streamer.stream("public", activity)
@ -198,7 +199,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
:ok <- maybe_federate(unannounce_activity), :ok <- maybe_federate(unannounce_activity),
{:ok, _activity} <- Repo.delete(announce_activity), {:ok, _activity} <- Repo.delete(announce_activity),
{:ok, object} <- remove_announce_from_object(announce_activity, object) do {:ok, object} <- remove_announce_from_object(announce_activity, object) do
{:ok, unannounce_activity, announce_activity, object} {:ok, unannounce_activity, object}
else else
_e -> {:ok, object} _e -> {:ok, object}
end end
@ -214,6 +215,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed), with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
{:ok, activity} <- insert(unfollow_data, local), {:ok, activity} <- insert(unfollow_data, local),
:ok <- maybe_federate(activity) do :ok <- maybe_federate(activity) do
@ -449,11 +451,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
blocks = info["blocks"] || [] blocks = info["blocks"] || []
domain_blocks = info["domain_blocks"] || []
from( from(
activity in query, activity in query,
where: fragment("not (? = ANY(?))", activity.actor, ^blocks), where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks) where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks),
where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
) )
end end
@ -502,7 +506,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end end
def upload(file) do def upload(file) do
data = Upload.store(file) data = Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media])
Repo.insert(%Object{data: data}) Repo.insert(%Object{data: data})
end end

View file

@ -2,6 +2,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
alias Pleroma.User alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF @behaviour Pleroma.Web.ActivityPub.MRF
@mrf_rejectnonpublic Application.get_env(:pleroma, :mrf_rejectnonpublic)
@allow_followersonly Keyword.get(@mrf_rejectnonpublic, :allow_followersonly)
@allow_direct Keyword.get(@mrf_rejectnonpublic, :allow_direct)
@impl true @impl true
def filter(object) do def filter(object) do
if object["type"] == "Create" do if object["type"] == "Create" do
@ -18,9 +22,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
end end
case visibility do case visibility do
"public" -> {:ok, object} "public" ->
"unlisted" -> {:ok, object} {:ok, object}
_ -> {:reject, nil}
"unlisted" ->
{:ok, object}
"followers" ->
with true <- @allow_followersonly do
{:ok, object}
else
_e -> {:reject, nil}
end
"direct" ->
with true <- @allow_direct do
{:ok, object}
else
_e -> {:reject, nil}
end
end end
else else
{:ok, object} {:ok, object}

View file

@ -30,14 +30,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
when not is_nil(in_reply_to_id) do when not is_nil(in_reply_to_id) do
case ActivityPub.fetch_object_from_id(in_reply_to_id) do case ActivityPub.fetch_object_from_id(in_reply_to_id) do
{:ok, replied_object} -> {:ok, replied_object} ->
activity = Activity.get_create_activity_by_object_ap_id(replied_object.data["id"]) with %Activity{} = activity <-
Activity.get_create_activity_by_object_ap_id(replied_object.data["id"]) do
object object
|> Map.put("inReplyTo", replied_object.data["id"]) |> Map.put("inReplyTo", replied_object.data["id"])
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id) |> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|> Map.put("inReplyToStatusId", activity.id) |> Map.put("inReplyToStatusId", activity.id)
|> Map.put("conversation", replied_object.data["context"] || object["conversation"]) |> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|> Map.put("context", replied_object.data["context"] || object["conversation"]) |> Map.put("context", replied_object.data["context"] || object["conversation"])
else
e ->
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
object
end
e -> e ->
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}") Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
@ -137,9 +142,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
%User{} = follower <- User.get_or_fetch_by_ap_id(follower), %User{} = follower <- User.get_or_fetch_by_ap_id(follower),
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
ActivityPub.accept(%{to: [follower.ap_id], actor: followed.ap_id, object: data, local: true}) if not User.locked?(followed) do
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed.ap_id,
object: data,
local: true
})
User.follow(follower, followed)
end
User.follow(follower, followed)
{:ok, activity} {:ok, activity}
else else
_e -> :error _e -> :error
@ -252,7 +265,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object) {:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
banner = new_user_data[:info]["banner"] banner = new_user_data[:info]["banner"]
locked = new_user_data[:info]["locked"] locked = new_user_data[:info]["locked"] || false
update_data = update_data =
new_user_data new_user_data
@ -304,7 +317,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor), with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <- {:ok, object} <-
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id), get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
{:ok, activity, _, _} <- ActivityPub.unannounce(actor, object, id, false) do {:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
{:ok, activity} {:ok, activity}
else else
_e -> :error _e -> :error
@ -432,6 +445,58 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, data} {:ok, data}
end end
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
# because of course it does.
def prepare_outgoing(%{"type" => "Accept"} = data) do
follow_activity_id =
if is_binary(data["object"]) do
data["object"]
else
data["object"]["id"]
end
with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
object = %{
"actor" => follow_activity.actor,
"object" => follow_activity.data["object"],
"id" => follow_activity.data["id"],
"type" => "Follow"
}
data =
data
|> Map.put("object", object)
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
{:ok, data}
end
end
def prepare_outgoing(%{"type" => "Reject"} = data) do
follow_activity_id =
if is_binary(data["object"]) do
data["object"]
else
data["object"]["id"]
end
with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
object = %{
"actor" => follow_activity.actor,
"object" => follow_activity.data["object"],
"id" => follow_activity.data["id"],
"type" => "Follow"
}
data =
data
|> Map.put("object", object)
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
{:ok, data}
end
end
def prepare_outgoing(%{"type" => _type} = data) do def prepare_outgoing(%{"type" => _type} = data) do
data = data =
data data

View file

@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
alias Ecto.{Changeset, UUID} alias Ecto.{Changeset, UUID}
import Ecto.Query import Ecto.Query
require Logger
# Some implementations send the actor URI as the actor field, others send the entire actor object, # Some implementations send the actor URI as the actor field, others send the entire actor object,
# so figure out what the actor's URI is based on what we have. # so figure out what the actor's URI is based on what we have.
@ -216,10 +217,27 @@ defmodule Pleroma.Web.ActivityPub.Utils do
#### Follow-related helpers #### Follow-related helpers
@doc """
Updates a follow activity's state (for locked accounts).
"""
def update_follow_state(%Activity{} = activity, state) do
with new_data <-
activity.data
|> Map.put("state", state),
changeset <- Changeset.change(activity, data: new_data),
{:ok, activity} <- Repo.update(changeset) do
{:ok, activity}
end
end
@doc """ @doc """
Makes a follow activity data for the given follower and followed Makes a follow activity data for the given follower and followed
""" """
def make_follow_data(%User{ap_id: follower_id}, %User{ap_id: followed_id}, activity_id) do def make_follow_data(
%User{ap_id: follower_id},
%User{ap_id: followed_id} = followed,
activity_id
) do
data = %{ data = %{
"type" => "Follow", "type" => "Follow",
"actor" => follower_id, "actor" => follower_id,
@ -228,7 +246,10 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"object" => followed_id "object" => followed_id
} }
if activity_id, do: Map.put(data, "id", activity_id), else: data data = if activity_id, do: Map.put(data, "id", activity_id), else: data
data = if User.locked?(followed), do: Map.put(data, "state", "pending"), else: data
data
end end
def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do

View file

@ -4,6 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView} alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView}
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.{CommonAPI, OStatus} alias Pleroma.Web.{CommonAPI, OStatus}
alias Pleroma.Web.OAuth.{Authorization, Token, App} alias Pleroma.Web.OAuth.{Authorization, Token, App}
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
@ -71,6 +72,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
user user
end end
user =
if locked = params["locked"] do
with locked <- locked == "true",
new_info <- Map.put(user.info, "locked", locked),
change <- User.info_changeset(user, %{info: new_info}),
{:ok, user} <- User.update_and_set_cache(change) do
user
else
_e -> user
end
else
user
end
with changeset <- User.update_changeset(user, params), with changeset <- User.update_changeset(user, params),
{:ok, user} <- User.update_and_set_cache(changeset) do {:ok, user} <- User.update_and_set_cache(changeset) do
if original_user != user do if original_user != user do
@ -345,7 +360,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do %Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity}) render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
end end
@ -476,6 +491,53 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
end end
def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
with {:ok, follow_requests} <- User.get_follow_requests(followed) do
render(conn, AccountView, "accounts.json", %{users: follow_requests, as: :user})
end
end
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
with %User{} = follower <- Repo.get(User, id),
{:ok, follower} <- User.maybe_follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
{:ok, _activity} <-
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed.ap_id,
object: follow_activity.data["id"],
type: "Accept"
}) do
render(conn, AccountView, "relationship.json", %{user: followed, target: follower})
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
end
end
def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
with %User{} = follower <- Repo.get(User, id),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
{:ok, _activity} <-
ActivityPub.reject(%{
to: [follower.ap_id],
actor: followed.ap_id,
object: follow_activity.data["id"],
type: "Reject"
}) do
render(conn, AccountView, "relationship.json", %{user: followed, target: follower})
else
{:error, message} ->
conn
|> put_resp_content_type("application/json")
|> send_resp(403, Jason.encode!(%{"error" => message}))
end
end
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
with %User{} = followed <- Repo.get(User, id), with %User{} = followed <- Repo.get(User, id),
{:ok, follower} <- User.maybe_direct_follow(follower, followed), {:ok, follower} <- User.maybe_direct_follow(follower, followed),
@ -545,6 +607,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end end
end end
def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do
json(conn, info["domain_blocks"] || [])
end
def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
User.block_domain(blocker, domain)
json(conn, %{})
end
def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
User.unblock_domain(blocker, domain)
json(conn, %{})
end
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, params["resolve"] == "true") accounts = User.search(query, params["resolve"] == "true")

View file

@ -15,10 +15,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
with token when not is_nil(token) <- params["access_token"], with token when not is_nil(token) <- params["access_token"],
%Token{user_id: user_id} <- Repo.get_by(Token, token: token), %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
%User{} = user <- Repo.get(User, user_id), %User{} = user <- Repo.get(User, user_id),
stream when stream in ["public", "public:local", "user", "direct"] <- params["stream"] do stream when stream in ["public", "public:local", "user", "direct", "list"] <-
params["stream"] do
topic = if stream == "list", do: "list:#{params["list"]}", else: stream
socket = socket =
socket socket
|> assign(:topic, params["stream"]) |> assign(:topic, topic)
|> assign(:user, user) |> assign(:user, user)
Pleroma.Web.Streamer.add_socket(params["stream"], socket) Pleroma.Web.Streamer.add_socket(params["stream"], socket)

View file

@ -125,8 +125,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
uri: object["id"], uri: object["id"],
url: object["external_url"] || object["id"], url: object["external_url"] || object["id"],
account: AccountView.render("account.json", %{user: user}), account: AccountView.render("account.json", %{user: user}),
in_reply_to_id: reply_to && reply_to.id, in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && reply_to_user.id, in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
reblog: nil, reblog: nil,
content: HtmlSanitizeEx.basic_html(object["content"]), content: HtmlSanitizeEx.basic_html(object["content"]),
created_at: created_at, created_at: created_at,

View file

@ -81,10 +81,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
# - investigate a way to verify the user wants to grant read/write/follow once scope handling is done # - investigate a way to verify the user wants to grant read/write/follow once scope handling is done
def token_exchange( def token_exchange(
conn, conn,
%{"grant_type" => "password", "name" => name, "password" => password} = params %{"grant_type" => "password", "username" => name, "password" => password} = params
) do ) do
with %App{} = app <- get_app_from_request(conn, params), with %App{} = app <- get_app_from_request(conn, params),
%User{} = user <- User.get_cached_by_nickname(name), %User{} = user <- User.get_by_nickname_or_email(name),
true <- Pbkdf2.checkpw(password, user.password_hash), true <- Pbkdf2.checkpw(password, user.password_hash),
{:ok, auth} <- Authorization.create_authorization(app, user), {:ok, auth} <- Authorization.create_authorization(app, user),
{:ok, token} <- Token.exchange_token(app, auth) do {:ok, token} <- Token.exchange_token(app, auth) do
@ -104,6 +104,18 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end end
end end
def token_exchange(
conn,
%{"grant_type" => "password", "name" => name, "password" => password} = params
) do
params =
params
|> Map.delete("name")
|> Map.put("username", name)
token_exchange(conn, params)
end
defp fix_padding(token) do defp fix_padding(token) do
token token
|> Base.url_decode64!(padding: false) |> Base.url_decode64!(padding: false)

View file

@ -41,7 +41,7 @@ defmodule Pleroma.Web.Router do
end end
pipeline :well_known do pipeline :well_known do
plug(:accepts, ["xml", "xrd+xml", "json", "jrd+json"]) plug(:accepts, ["json", "jrd+json", "xml", "xrd+xml"])
end end
pipeline :config do pipeline :config do
@ -97,12 +97,14 @@ defmodule Pleroma.Web.Router do
post("/accounts/:id/mute", MastodonAPIController, :relationship_noop) post("/accounts/:id/mute", MastodonAPIController, :relationship_noop)
post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop) post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop)
get("/follow_requests", MastodonAPIController, :follow_requests)
post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request)
post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request)
post("/follows", MastodonAPIController, :follow) post("/follows", MastodonAPIController, :follow)
get("/blocks", MastodonAPIController, :blocks) get("/blocks", MastodonAPIController, :blocks)
get("/domain_blocks", MastodonAPIController, :empty_array)
get("/follow_requests", MastodonAPIController, :empty_array)
get("/mutes", MastodonAPIController, :empty_array) get("/mutes", MastodonAPIController, :empty_array)
get("/timelines/home", MastodonAPIController, :home_timeline) get("/timelines/home", MastodonAPIController, :home_timeline)
@ -134,6 +136,10 @@ defmodule Pleroma.Web.Router do
get("/lists/:id/accounts", MastodonAPIController, :list_accounts) get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
post("/lists/:id/accounts", MastodonAPIController, :add_to_list) post("/lists/:id/accounts", MastodonAPIController, :add_to_list)
delete("/lists/:id/accounts", MastodonAPIController, :remove_from_list) delete("/lists/:id/accounts", MastodonAPIController, :remove_from_list)
get("/domain_blocks", MastodonAPIController, :domain_blocks)
post("/domain_blocks", MastodonAPIController, :block_domain)
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
end end
scope "/api/web", Pleroma.Web.MastodonAPI do scope "/api/web", Pleroma.Web.MastodonAPI do
@ -238,8 +244,13 @@ defmodule Pleroma.Web.Router do
post("/statuses/update", TwitterAPI.Controller, :status_update) post("/statuses/update", TwitterAPI.Controller, :status_update)
post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet) post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet)
post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet)
post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post) post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post)
get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests)
post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request)
post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request)
post("/friendships/create", TwitterAPI.Controller, :follow) post("/friendships/create", TwitterAPI.Controller, :follow)
post("/friendships/destroy", TwitterAPI.Controller, :unfollow) post("/friendships/destroy", TwitterAPI.Controller, :unfollow)
post("/blocks/create", TwitterAPI.Controller, :block) post("/blocks/create", TwitterAPI.Controller, :block)

View file

@ -1,7 +1,7 @@
defmodule Pleroma.Web.Streamer do defmodule Pleroma.Web.Streamer do
use GenServer use GenServer
require Logger require Logger
alias Pleroma.{User, Notification} alias Pleroma.{User, Notification, Activity, Object}
def init(args) do def init(args) do
{:ok, args} {:ok, args}
@ -59,6 +59,19 @@ defmodule Pleroma.Web.Streamer do
{:noreply, topics} {:noreply, topics}
end end
def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
recipient_topics =
Pleroma.List.get_lists_from_activity(item)
|> Enum.map(fn %{id: id} -> "list:#{id}" end)
Enum.each(recipient_topics || [], fn list_topic ->
Logger.debug("Trying to push message to #{list_topic}\n\n")
push_to_socket(topics, list_topic, item)
end)
{:noreply, topics}
end
def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
topic = "user:#{item.user_id}" topic = "user:#{item.user_id}"
@ -125,6 +138,34 @@ defmodule Pleroma.Web.Streamer do
{:noreply, state} {:noreply, state}
end end
defp represent_update(%Activity{} = activity, %User{} = user) do
%{
event: "update",
payload:
Pleroma.Web.MastodonAPI.StatusView.render(
"status.json",
activity: activity,
for: user
)
|> Jason.encode!()
}
|> Jason.encode!()
end
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
Enum.each(topics[topic] || [], fn socket ->
# Get the current user so we have up-to-date blocks etc.
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
blocks = user.info["blocks"] || []
parent = Object.get_by_ap_id(item.data["object"])
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
send(socket.transport_pid, {:text, represent_update(item, user)})
end
end)
end
def push_to_socket(topics, topic, item) do def push_to_socket(topics, topic, item) do
Enum.each(topics[topic] || [], fn socket -> Enum.each(topics[topic] || [], fn socket ->
# Get the current user so we have up-to-date blocks etc. # Get the current user so we have up-to-date blocks etc.
@ -132,20 +173,7 @@ defmodule Pleroma.Web.Streamer do
blocks = user.info["blocks"] || [] blocks = user.info["blocks"] || []
unless item.actor in blocks do unless item.actor in blocks do
json = send(socket.transport_pid, {:text, represent_update(item, user)})
%{
event: "update",
payload:
Pleroma.Web.MastodonAPI.StatusView.render(
"status.json",
activity: item,
for: user
)
|> Jason.encode!()
}
|> Jason.encode!()
send(socket.transport_pid, {:text, json})
end end
end) end)
end end

View file

@ -12,14 +12,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end end
def delete(%User{} = user, id) do def delete(%User{} = user, id) do
# TwitterAPI does not have an "unretweet" endpoint; instead this is done with %Activity{data: %{"type" => type}} <- Repo.get(Activity, id),
# via the "destroy" endpoint. Therefore, we need to handle {:ok, activity} <- CommonAPI.delete(id, user) do
# when the status to "delete" is actually an Announce (repeat) object. {:ok, activity}
with %Activity{data: %{"type" => type}} <- Repo.get(Activity, id) do
case type do
"Announce" -> unrepeat(user, id)
_ -> CommonAPI.delete(id, user)
end
end end
end end
@ -70,8 +65,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end end
end end
defp unrepeat(%User{} = user, ap_id_or_id) do def unrepeat(%User{} = user, ap_id_or_id) do
with {:ok, _unannounce, activity, _object} <- CommonAPI.unrepeat(ap_id_or_id, user) do with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
{:ok, activity} {:ok, activity}
end end
end end

View file

@ -4,6 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.{Repo, Activity, User, Notification} alias Pleroma.{Repo, Activity, User, Notification}
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Ecto.Changeset alias Ecto.Changeset
require Logger require Logger
@ -240,6 +241,13 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end end
end end
def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)},
{:ok, activity} <- TwitterAPI.unrepeat(user, id) do
render(conn, ActivityView, "activity.json", %{activity: activity, for: user})
end
end
def register(conn, params) do def register(conn, params) do
with {:ok, user} <- TwitterAPI.register_user(params) do with {:ok, user} <- TwitterAPI.register_user(params) do
render(conn, UserView, "show.json", %{user: user}) render(conn, UserView, "show.json", %{user: user})
@ -331,6 +339,54 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end end
end end
def friend_requests(conn, params) do
with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
{:ok, friend_requests} <- User.get_follow_requests(user) do
render(conn, UserView, "index.json", %{users: friend_requests, for: conn.assigns[:user]})
else
_e -> bad_request_reply(conn, "Can't get friend requests")
end
end
def approve_friend_request(conn, %{"user_id" => uid} = params) do
with followed <- conn.assigns[:user],
uid when is_number(uid) <- String.to_integer(uid),
%User{} = follower <- Repo.get(User, uid),
{:ok, follower} <- User.maybe_follow(follower, followed),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
{:ok, _activity} <-
ActivityPub.accept(%{
to: [follower.ap_id],
actor: followed.ap_id,
object: follow_activity.data["id"],
type: "Accept"
}) do
render(conn, UserView, "show.json", %{user: follower, for: followed})
else
e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}")
end
end
def deny_friend_request(conn, %{"user_id" => uid} = params) do
with followed <- conn.assigns[:user],
uid when is_number(uid) <- String.to_integer(uid),
%User{} = follower <- Repo.get(User, uid),
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
{:ok, _activity} <-
ActivityPub.reject(%{
to: [follower.ap_id],
actor: followed.ap_id,
object: follow_activity.data["id"],
type: "Reject"
}) do
render(conn, UserView, "show.json", %{user: follower, for: followed})
else
e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}")
end
end
def friends_ids(%{assigns: %{user: user}} = conn, _params) do def friends_ids(%{assigns: %{user: user}} = conn, _params) do
with {:ok, friends} <- User.get_friends(user) do with {:ok, friends} <- User.get_friends(user) do
ids = ids =
@ -357,6 +413,20 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
params params
end end
user =
if locked = params["locked"] do
with locked <- locked == "true",
new_info <- Map.put(user.info, "locked", locked),
change <- User.info_changeset(user, %{info: new_info}),
{:ok, user} <- User.update_and_set_cache(change) do
user
else
_e -> user
end
else
user
end
with changeset <- User.update_changeset(user, params), with changeset <- User.update_changeset(user, params),
{:ok, user} <- User.update_and_set_cache(changeset) do {:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user) CommonAPI.update(user)

View file

@ -51,7 +51,8 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
"statusnet_profile_url" => user.ap_id, "statusnet_profile_url" => user.ap_id,
"cover_photo" => User.banner_url(user) |> MediaProxy.url(), "cover_photo" => User.banner_url(user) |> MediaProxy.url(),
"background_image" => image_url(user.info["background"]) |> MediaProxy.url(), "background_image" => image_url(user.info["background"]) |> MediaProxy.url(),
"is_local" => user.local "is_local" => user.local,
"locked" => !!user.info["locked"]
} }
if assigns[:token] do if assigns[:token] do

View file

@ -25,35 +25,17 @@ defmodule Pleroma.Web.WebFinger do
|> XmlBuilder.to_doc() |> XmlBuilder.to_doc()
end end
def webfinger(resource, "JSON") do def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
host = Pleroma.Web.Endpoint.host() host = Pleroma.Web.Endpoint.host()
regex = ~r/(acct:)?(?<username>\w+)@#{host}/ regex = ~r/(acct:)?(?<username>\w+)@#{host}/
with %{"username" => username} <- Regex.named_captures(regex, resource) do with %{"username" => username} <- Regex.named_captures(regex, resource),
user = User.get_by_nickname(username) %User{} = user <- User.get_by_nickname(username) do
{:ok, represent_user(user, "JSON")} {:ok, represent_user(user, fmt)}
else else
_e -> _e ->
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do with %User{} = user <- User.get_cached_by_ap_id(resource) do
{:ok, represent_user(user, "JSON")} {:ok, represent_user(user, fmt)}
else
_e ->
{:error, "Couldn't find user"}
end
end
end
def webfinger(resource, "XML") do
host = Pleroma.Web.Endpoint.host()
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
with %{"username" => username} <- Regex.named_captures(regex, resource) do
user = User.get_by_nickname(username)
{:ok, represent_user(user, "XML")}
else
_e ->
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
{:ok, represent_user(user, "XML")}
else else
_e -> _e ->
{:error, "Couldn't find user"} {:error, "Couldn't find user"}

View file

@ -0,0 +1,7 @@
defmodule Pleroma.Repo.Migrations.AddListFollowIndex do
use Ecto.Migration
def change do
create index(:lists, [:following])
end
end

View file

@ -0,0 +1,8 @@
defmodule Pleroma.Repo.Migrations.CreateApidHostExtractionIndex do
use Ecto.Migration
@disable_ddl_transaction true
def change do
create index(:activities, ["(split_part(actor, '/', 3))"], concurrently: true, name: :activities_hosts)
end
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 997 B

View file

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.c0e1e1e1fcff94fd1e14fc44bfee9a1e.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.16ab7851cdbf730f9cbc.js></script><script type=text/javascript src=/static/js/vendor.56aa9f8c34786f6af6b7.js></script><script type=text/javascript src=/static/js/app.13c0bda10eb515cdf8ed.js></script></body></html> <!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.5d0189b6f119febde070b703869bbd06.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.f2341edd686e54ee9b4a.js></script><script type=text/javascript src=/static/js/vendor.a93310d51acbd9480094.js></script><script type=text/javascript src=/static/js/app.de965bb2a0a8bffbeafa.js></script></body></html>

View file

@ -10,5 +10,6 @@
"whoToFollowProviderDummy2": "https://followlink.osa-p.net/api/get_recommend.json?acct=@{{user}}@{{host}}", "whoToFollowProviderDummy2": "https://followlink.osa-p.net/api/get_recommend.json?acct=@{{user}}@{{host}}",
"whoToFollowLink": "https://vinayaka.distsn.org/?{{host}}+{{user}}", "whoToFollowLink": "https://vinayaka.distsn.org/?{{host}}+{{user}}",
"whoToFollowLinkDummy2": "https://followlink.osa-p.net/recommend.html", "whoToFollowLinkDummy2": "https://followlink.osa-p.net/recommend.html",
"showInstanceSpecificPanel": false "showInstanceSpecificPanel": false,
"scopeOptionsEnabled": false
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
!function(e){function t(a){if(r[a])return r[a].exports;var n=r[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,i=[];s<c.length;s++)l=c[s],n[l]&&i.push.apply(i,n[l]),n[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(a&&a(c,o);i.length;)i.shift().call(null,t);if(o[0])return r[0]=0,t(0)};var r={},n={0:0};t.e=function(e,a){if(0===n[e])return a.call(null,t);if(void 0!==n[e])n[e].push(a);else{n[e]=[a];var r=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.src=t.p+"static/js/"+e+"."+{1:"56aa9f8c34786f6af6b7",2:"13c0bda10eb515cdf8ed"}[e]+".js",r.appendChild(c)}},t.m=e,t.c=r,t.p="/"}([]);
//# sourceMappingURL=manifest.16ab7851cdbf730f9cbc.js.map

View file

@ -0,0 +1,2 @@
!function(e){function t(a){if(r[a])return r[a].exports;var n=r[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a=window.webpackJsonp;window.webpackJsonp=function(o,p){for(var c,l,s=0,i=[];s<o.length;s++)l=o[s],n[l]&&i.push.apply(i,n[l]),n[l]=0;for(c in p)Object.prototype.hasOwnProperty.call(p,c)&&(e[c]=p[c]);for(a&&a(o,p);i.length;)i.shift().call(null,t);if(p[0])return r[0]=0,t(0)};var r={},n={0:0};t.e=function(e,a){if(0===n[e])return a.call(null,t);if(void 0!==n[e])n[e].push(a);else{n[e]=[a];var r=document.getElementsByTagName("head")[0],o=document.createElement("script");o.type="text/javascript",o.charset="utf-8",o.async=!0,o.src=t.p+"static/js/"+e+"."+{1:"a93310d51acbd9480094",2:"de965bb2a0a8bffbeafa"}[e]+".js",r.appendChild(o)}},t.m=e,t.c=r,t.p="/"}([]);
//# sourceMappingURL=manifest.f2341edd686e54ee9b4a.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -74,4 +74,20 @@ defmodule Pleroma.ListTest do
assert list_two in lists assert list_two in lists
refute list_three in lists refute list_three in lists
end end
test "getting all lists the user is a member of" do
user = insert(:user)
other_user = insert(:user)
{:ok, list_one} = Pleroma.List.create("title", user)
{:ok, list_two} = Pleroma.List.create("other title", user)
{:ok, list_three} = Pleroma.List.create("third title", other_user)
{:ok, list_one} = Pleroma.List.follow(list_one, other_user)
{:ok, list_two} = Pleroma.List.follow(list_two, other_user)
{:ok, list_three} = Pleroma.List.follow(list_three, user)
lists = Pleroma.List.get_lists_from_activity(%Pleroma.Activity{actor: other_user.ap_id})
assert list_one in lists
assert list_two in lists
refute list_three in lists
end
end end

View file

@ -3,40 +3,58 @@ defmodule Pleroma.UploadTest do
use Pleroma.DataCase use Pleroma.DataCase
describe "Storing a file" do describe "Storing a file" do
test "copies the file to the configured folder" do test "copies the file to the configured folder with deduping" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
file = %Plug.Upload{ file = %Plug.Upload{
content_type: "image/jpg", content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"), path: Path.absname("test/fixtures/image_tmp.jpg"),
filename: "an [image.jpg" filename: "an [image.jpg"
} }
data = Upload.store(file) data = Upload.store(file, true)
assert data["name"] == "an [image.jpg"
assert List.first(data["url"])["href"] == assert data["name"] ==
"http://localhost:4001/media/#{data["uuid"]}/an%20%5Bimage.jpg" "e7a6d0cf595bff76f14c9a98b6c199539559e8b844e02e51e5efcfd1f614a2df.jpeg"
end end
test "fixes an incorrect content type" do test "copies the file to the configured folder without deduping" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
file = %Plug.Upload{ file = %Plug.Upload{
content_type: "application/octet-stream", content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"), path: Path.absname("test/fixtures/image_tmp.jpg"),
filename: "an [image.jpg" filename: "an [image.jpg"
} }
data = Upload.store(file) data = Upload.store(file, false)
assert data["name"] == "an [image.jpg"
end
test "fixes incorrect content type" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
file = %Plug.Upload{
content_type: "application/octet-stream",
path: Path.absname("test/fixtures/image_tmp.jpg"),
filename: "an [image.jpg"
}
data = Upload.store(file, true)
assert hd(data["url"])["mediaType"] == "image/jpeg" assert hd(data["url"])["mediaType"] == "image/jpeg"
end end
test "does not modify a valid content type" do test "adds missing extension" do
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
file = %Plug.Upload{ file = %Plug.Upload{
content_type: "image/png", content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"), path: Path.absname("test/fixtures/image_tmp.jpg"),
filename: "an [image.jpg" filename: "an [image"
} }
data = Upload.store(file) data = Upload.store(file, false)
assert hd(data["url"])["mediaType"] == "image/png" assert data["name"] == "an [image.jpg"
end end
end end
end end

View file

@ -361,6 +361,27 @@ defmodule Pleroma.UserTest do
end end
end end
describe "domain blocking" do
test "blocks domains" do
user = insert(:user)
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
{:ok, user} = User.block_domain(user, "awful-and-rude-instance.com")
assert User.blocks?(user, collateral_user)
end
test "unblocks domains" do
user = insert(:user)
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
{:ok, user} = User.block_domain(user, "awful-and-rude-instance.com")
{:ok, user} = User.unblock_domain(user, "awful-and-rude-instance.com")
refute User.blocks?(user, collateral_user)
end
end
test "get recipients from activity" do test "get recipients from activity" do
actor = insert(:user) actor = insert(:user)
user = insert(:user, local: true) user = insert(:user, local: true)

View file

@ -318,11 +318,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
{:ok, announce_activity, object} = ActivityPub.announce(user, object) {:ok, announce_activity, object} = ActivityPub.announce(user, object)
assert object.data["announcement_count"] == 1 assert object.data["announcement_count"] == 1
{:ok, unannounce_activity, activity, object} = ActivityPub.unannounce(user, object) {:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
assert object.data["announcement_count"] == 0 assert object.data["announcement_count"] == 0
assert activity == announce_activity
assert unannounce_activity.data["to"] == [ assert unannounce_activity.data["to"] == [
User.ap_followers(user), User.ap_followers(user),
announce_activity.data["actor"] announce_activity.data["actor"]

View file

@ -4,6 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
alias Pleroma.Web.TwitterAPI.TwitterAPI alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.{Repo, User, Activity, Notification} alias Pleroma.{Repo, User, Activity, Notification}
alias Pleroma.Web.{OStatus, CommonAPI} alias Pleroma.Web.{OStatus, CommonAPI}
alias Pleroma.Web.ActivityPub.ActivityPub
import Pleroma.Factory import Pleroma.Factory
import ExUnit.CaptureLog import ExUnit.CaptureLog
@ -644,6 +645,73 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
end end
end end
describe "locked accounts" do
test "/api/v1/follow_requests works" do
user = insert(:user, %{info: %{"locked" => true}})
other_user = insert(:user)
{:ok, activity} = ActivityPub.follow(other_user, user)
user = Repo.get(User, user.id)
other_user = Repo.get(User, other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> get("/api/v1/follow_requests")
assert [relationship] = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
end
test "/api/v1/follow_requests/:id/authorize works" do
user = insert(:user, %{info: %{"locked" => true}})
other_user = insert(:user)
{:ok, activity} = ActivityPub.follow(other_user, user)
user = Repo.get(User, user.id)
other_user = Repo.get(User, other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/follow_requests/#{other_user.id}/authorize")
assert relationship = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
user = Repo.get(User, user.id)
other_user = Repo.get(User, other_user.id)
assert User.following?(other_user, user) == true
end
test "/api/v1/follow_requests/:id/reject works" do
user = insert(:user, %{info: %{"locked" => true}})
other_user = insert(:user)
{:ok, activity} = ActivityPub.follow(other_user, user)
conn =
build_conn()
|> assign(:user, user)
|> post("/api/v1/follow_requests/#{other_user.id}/reject")
assert relationship = json_response(conn, 200)
assert to_string(other_user.id) == relationship["id"]
user = Repo.get(User, user.id)
other_user = Repo.get(User, other_user.id)
assert User.following?(other_user, user) == false
end
end
test "account fetching", %{conn: conn} do test "account fetching", %{conn: conn} do
user = insert(:user) user = insert(:user)
@ -792,6 +860,46 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
assert [%{"id" => ^other_user_id}] = json_response(conn, 200) assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
end end
test "blocking / unblocking a domain", %{conn: conn} do
user = insert(:user)
other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
conn =
conn
|> assign(:user, user)
|> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
assert %{} = json_response(conn, 200)
user = User.get_cached_by_ap_id(user.ap_id)
assert User.blocks?(user, other_user)
conn =
build_conn()
|> assign(:user, user)
|> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
assert %{} = json_response(conn, 200)
user = User.get_cached_by_ap_id(user.ap_id)
refute User.blocks?(user, other_user)
end
test "getting a list of domain blocks" do
user = insert(:user)
{:ok, user} = User.block_domain(user, "bad.site")
{:ok, user} = User.block_domain(user, "even.worse.site")
conn =
conn
|> assign(:user, user)
|> get("/api/v1/domain_blocks")
domain_blocks = json_response(conn, 200)
assert "bad.site" in domain_blocks
assert "even.worse.site" in domain_blocks
end
test "unimplemented mute endpoints" do test "unimplemented mute endpoints" do
user = insert(:user) user = insert(:user)
other_user = insert(:user) other_user = insert(:user)

View file

@ -64,11 +64,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do
status = StatusView.render("status.json", %{activity: activity}) status = StatusView.render("status.json", %{activity: activity})
assert status.in_reply_to_id == note.id assert status.in_reply_to_id == to_string(note.id)
[status] = StatusView.render("index.json", %{activities: [activity], as: :activity}) [status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
assert status.in_reply_to_id == note.id assert status.in_reply_to_id == to_string(note.id)
end end
test "contains mentions" do test "contains mentions" do

View file

@ -580,6 +580,40 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
end end
end end
describe "POST /api/statuses/unretweet/:id" do
setup [:valid_user]
test "without valid credentials", %{conn: conn} do
note_activity = insert(:note_activity)
conn = post(conn, "/api/statuses/unretweet/#{note_activity.id}.json")
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
end
test "with credentials", %{conn: conn, user: current_user} do
note_activity = insert(:note_activity)
request_path = "/api/statuses/retweet/#{note_activity.id}.json"
_response =
conn
|> with_credentials(current_user.nickname, "test")
|> post(request_path)
request_path = String.replace(request_path, "retweet", "unretweet")
response =
conn
|> with_credentials(current_user.nickname, "test")
|> post(request_path)
activity = Repo.get(Activity, note_activity.id)
activity_user = Repo.get_by(User, ap_id: note_activity.data["actor"])
assert json_response(response, 200) ==
ActivityRepresenter.to_map(activity, %{user: activity_user, for: current_user})
end
end
describe "POST /api/account/register" do describe "POST /api/account/register" do
test "it creates a new user", %{conn: conn} do test "it creates a new user", %{conn: conn} do
data = %{ data = %{
@ -762,6 +796,38 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user}) assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end end
test "it locks an account", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"locked" => "true"
})
user = Repo.get!(User, user.id)
assert user.info["locked"] == true
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
test "it unlocks an account", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"locked" => "false"
})
user = Repo.get!(User, user.id)
assert user.info["locked"] == false
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
end
end end
defp valid_user(_context) do defp valid_user(_context) do
@ -926,4 +992,72 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
:timer.sleep(1000) :timer.sleep(1000)
end end
end end
describe "GET /api/pleroma/friend_requests" do
test "it lists friend requests" do
user = insert(:user, %{info: %{"locked" => true}})
other_user = insert(:user)
{:ok, activity} = ActivityPub.follow(other_user, user)
user = Repo.get(User, user.id)
other_user = Repo.get(User, other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> get("/api/pleroma/friend_requests")
assert [relationship] = json_response(conn, 200)
assert other_user.id == relationship["id"]
end
end
describe "POST /api/pleroma/friendships/approve" do
test "it approves a friend request" do
user = insert(:user, %{info: %{"locked" => true}})
other_user = insert(:user)
{:ok, activity} = ActivityPub.follow(other_user, user)
user = Repo.get(User, user.id)
other_user = Repo.get(User, other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> post("/api/pleroma/friendships/approve", %{"user_id" => to_string(other_user.id)})
assert relationship = json_response(conn, 200)
assert other_user.id == relationship["id"]
assert relationship["follows_you"] == true
end
end
describe "POST /api/pleroma/friendships/deny" do
test "it denies a friend request" do
user = insert(:user, %{info: %{"locked" => true}})
other_user = insert(:user)
{:ok, activity} = ActivityPub.follow(other_user, user)
user = Repo.get(User, user.id)
other_user = Repo.get(User, other_user.id)
assert User.following?(other_user, user) == false
conn =
build_conn()
|> assign(:user, user)
|> post("/api/pleroma/friendships/deny", %{"user_id" => to_string(other_user.id)})
assert relationship = json_response(conn, 200)
assert other_user.id == relationship["id"]
assert relationship["follows_you"] == false
end
end
end end

View file

@ -228,6 +228,17 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
assert status == updated_activity assert status == updated_activity
end end
test "it unretweets an already retweeted status" do
user = insert(:user)
note_activity = insert(:note_activity)
{:ok, _status} = TwitterAPI.repeat(user, note_activity.id)
{:ok, status} = TwitterAPI.unrepeat(user, note_activity.id)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
assert status == updated_activity
end
test "it registers a new user and returns the user." do test "it registers a new user and returns the user." do
data = %{ data = %{
"nickname" => "lain", "nickname" => "lain",

View file

@ -59,7 +59,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"statusnet_profile_url" => user.ap_id, "statusnet_profile_url" => user.ap_id,
"cover_photo" => banner, "cover_photo" => banner,
"background_image" => nil, "background_image" => nil,
"is_local" => true "is_local" => true,
"locked" => false
} }
assert represented == UserView.render("show.json", %{user: user}) assert represented == UserView.render("show.json", %{user: user})
@ -94,7 +95,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"statusnet_profile_url" => user.ap_id, "statusnet_profile_url" => user.ap_id,
"cover_photo" => banner, "cover_photo" => banner,
"background_image" => nil, "background_image" => nil,
"is_local" => true "is_local" => true,
"locked" => false
} }
assert represented == UserView.render("show.json", %{user: user, for: follower}) assert represented == UserView.render("show.json", %{user: user, for: follower})
@ -130,7 +132,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"statusnet_profile_url" => follower.ap_id, "statusnet_profile_url" => follower.ap_id,
"cover_photo" => banner, "cover_photo" => banner,
"background_image" => nil, "background_image" => nil,
"is_local" => true "is_local" => true,
"locked" => false
} }
assert represented == UserView.render("show.json", %{user: follower, for: user}) assert represented == UserView.render("show.json", %{user: follower, for: user})
@ -173,7 +176,8 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
"statusnet_profile_url" => user.ap_id, "statusnet_profile_url" => user.ap_id,
"cover_photo" => banner, "cover_photo" => banner,
"background_image" => nil, "background_image" => nil,
"is_local" => true "is_local" => true,
"locked" => false
} }
blocker = Repo.get(User, blocker.id) blocker = Repo.get(User, blocker.id)