Merge branch 'develop' into kaniini/pleroma-bugfix/unlisted-statuses

This commit is contained in:
lain 2018-05-13 10:56:11 +02:00
commit 76722ea9c8
59 changed files with 461 additions and 183 deletions

View File

@ -26,3 +26,12 @@
config :pleroma, :websub, Pleroma.Web.WebsubMock
config :pleroma, :ostatus, Pleroma.Web.OStatusMock
config :pleroma, :httpoison, HTTPoisonMock
try do
import_config "test.secret.exs"
rescue
_ ->
IO.puts(
"You may want to create test.secret.exs to declare custom database connection parameters."
)
end

View File

@ -59,6 +59,16 @@ server {
}
# stop removing lines here.
add_header X-XSS-Protection "1; mode=block";
add_header X-Permitted-Cross-Domain-Policies none;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header Referrer-Policy same-origin;
add_header X-Download-Options noopen;
# Uncomment this only after you get HTTPS working.
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";

View File

@ -13,4 +13,3 @@ Restart=on-failure
[Install]
WantedBy=multi-user.target
Alias=pleroma.service

View File

@ -39,15 +39,9 @@ sub vcl_recv {
return (hash);
}
# Hack to enable a Terms of Service page missing from Pleroma
if (req.url ~ "^/about/more$") {
set req.http.x-redir = "https://" + req.http.host + "/static/terms-of-service.html";
return (synth(750, ""));
}
# Strip headers that will affect caching from all other static content
# This also permits caching of individual toots and AP Activities
if ((req.url ~ "^/(media|notice|objects|static)/") ||
if ((req.url ~ "^/(media|static)/") ||
(req.url ~ "(?i)\.(html|js|css|jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|svg|swf|ttf|pdf|woff|woff2)$"))
{
unset req.http.Cookie;
@ -99,8 +93,7 @@ sub vcl_backend_response {
# Strip cache-restricting headers from Pleroma on static content that we want to cache
# Also enable streaming of cached content to clients (no waiting for Varnish to complete backend fetch)
if ((bereq.url ~ "^/(notice|objects)/") ||
(bereq.url ~ "(?i)\.(js|css|jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|svg|swf|ttf|pdf|woff|woff2)$"))
if (bereq.url ~ "(?i)\.(js|css|jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|ogg|svg|swf|ttf|pdf|woff|woff2)$")
{
unset beresp.http.set-cookie;
unset beresp.http.Cache-Control;

View File

@ -1,6 +1,5 @@
defmodule Mix.Tasks.FixApUsers do
use Mix.Task
import Mix.Ecto
import Ecto.Query
alias Pleroma.{Repo, User}

View File

@ -1,7 +1,6 @@
defmodule Mix.Tasks.GeneratePasswordReset do
use Mix.Task
import Mix.Ecto
alias Pleroma.{Repo, User}
alias Pleroma.User
@shortdoc "Generate password reset link for user"
def run([nickname]) do

View File

@ -1,6 +1,5 @@
defmodule Mix.Tasks.RegisterUser do
use Mix.Task
import Mix.Ecto
alias Pleroma.{Repo, User}
@shortdoc "Register user"

View File

@ -1,7 +1,6 @@
defmodule Mix.Tasks.RmUser do
use Mix.Task
import Mix.Ecto
alias Pleroma.{User, Repo}
alias Pleroma.User
@shortdoc "Permanently delete a user"
def run([nickname]) do

View File

@ -23,6 +23,18 @@ def start(_type, _args) do
limit: 2500
]
]),
worker(
Cachex,
[
:idempotency_cache,
[
default_ttl: :timer.seconds(6 * 60 * 60),
ttl_interval: :timer.seconds(60),
limit: 2500
]
],
id: :cachex_idem
),
worker(Pleroma.Web.Federator, []),
worker(Pleroma.Gopher.Server, []),
worker(Pleroma.Stats, [])

View File

@ -65,12 +65,6 @@ def link(name, selector, type \\ 1) do
"#{type}#{name}\t#{selector}\t#{address}\t#{port}\r\n"
end
def response("") do
info("Welcome to #{Keyword.get(@instance, :name, "Pleroma")}!") <>
link("Public Timeline", "/main/public") <>
link("Federated Timeline", "/main/all") <> ".\r\n"
end
def render_activities(activities) do
activities
|> Enum.reverse()
@ -93,6 +87,12 @@ def render_activities(activities) do
|> Enum.join("\r\n")
end
def response("") do
info("Welcome to #{Keyword.get(@instance, :name, "Pleroma")}!") <>
link("Public Timeline", "/main/public") <>
link("Federated Timeline", "/main/all") <> ".\r\n"
end
def response("/main/public") do
posts =
ActivityPub.fetch_public_activities(%{"type" => ["Create"], "local_only" => true})

View File

@ -91,7 +91,8 @@ def create_notifications(_), do: {:ok, []}
# TODO move to sql, too.
def create_notification(%Activity{} = activity, %User{} = user) do
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) do
unless User.blocks?(user, %{ap_id: activity.data["actor"]}) or
user.ap_id == activity.data["actor"] do
notification = %Notification{user_id: user.id, activity: activity}
{:ok, notification} = Repo.insert(notification)
Pleroma.Web.Streamer.stream("user", notification)

View File

@ -7,11 +7,11 @@ def init(options) do
options
end
def call(%{assigns: %{valid_signature: true}} = conn, opts) do
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
conn
end
def call(conn, opts) do
def call(conn, _opts) do
user = conn.params["actor"]
Logger.debug("Checking sig for #{user}")
[signature | _] = get_req_header(conn, "signature")

View File

@ -1,6 +1,6 @@
defmodule Pleroma.Stats do
import Ecto.Query
alias Pleroma.{User, Repo, Activity}
alias Pleroma.{User, Repo}
def start_link do
agent = Agent.start_link(fn -> {[], %{}} end, name: __MODULE__)

View File

@ -1,6 +1,6 @@
defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.{Activity, Repo, Object, Upload, User, Notification}
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.{Transmogrifier, MRF}
alias Pleroma.Web.WebFinger
alias Pleroma.Web.Federator
alias Pleroma.Web.OStatus
@ -11,7 +11,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
@httpoison Application.get_env(:pleroma, :httpoison)
@instance Application.get_env(:pleroma, :instance)
@rewrite_policy Keyword.get(@instance, :rewrite_policy)
def get_recipients(data) do
(data["to"] || []) ++ (data["cc"] || [])
@ -20,8 +19,8 @@ def get_recipients(data) do
def insert(map, local \\ true) when is_map(map) do
with nil <- Activity.get_by_ap_id(map["id"]),
map <- lazy_put_activity_defaults(map),
:ok <- insert_full_object(map),
{:ok, map} <- @rewrite_policy.filter(map) do
{:ok, map} <- MRF.filter(map),
:ok <- insert_full_object(map) do
{:ok, activity} =
Repo.insert(%Activity{
data: map,
@ -66,7 +65,7 @@ def create(%{to: to, actor: actor, context: context, object: object} = params) d
),
{:ok, activity} <- insert(create_data, local),
:ok <- maybe_federate(activity),
{:ok, actor} <- User.increase_note_count(actor) do
{:ok, _actor} <- User.increase_note_count(actor) do
{:ok, activity}
end
end
@ -177,7 +176,7 @@ def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ tru
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
{:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity),
{:ok, actor} <- User.decrease_note_count(user) do
{:ok, _actor} <- User.decrease_note_count(user) do
{:ok, activity}
end
end
@ -236,7 +235,7 @@ defp restrict_tag(query, %{"tag" => tag}) do
defp restrict_tag(query, _), do: query
defp restrict_recipients(query, [], user), do: query
defp restrict_recipients(query, [], _user), do: query
defp restrict_recipients(query, recipients, nil) do
from(activity in query, where: fragment("? && ?", ^recipients, activity.recipients))
@ -313,7 +312,9 @@ defp restrict_recent(query, _) do
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
blocks = info["blocks"] || []
from(activity in query,
from(
activity in query,
where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks)
)
@ -405,7 +406,7 @@ def fetch_and_prepare_user_from_ap_id(ap_id) do
end
def make_user_from_ap_id(ap_id) do
if user = User.get_by_ap_id(ap_id) do
if _user = User.get_by_ap_id(ap_id) do
Transmogrifier.upgrade_user_from_ap_id(ap_id)
else
with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
@ -501,7 +502,7 @@ def fetch_object_from_id(id) do
object = %Object{} ->
{:ok, object}
e ->
_e ->
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
case OStatus.fetch_activity_from_url(id) do

View File

@ -1,7 +1,7 @@
defmodule Pleroma.Web.ActivityPub.ActivityPubController do
use Pleroma.Web, :controller
alias Pleroma.{User, Repo, Object, Activity}
alias Pleroma.Web.ActivityPub.{ObjectView, UserView, Transmogrifier}
alias Pleroma.{User, Object}
alias Pleroma.Web.ActivityPub.{ObjectView, UserView}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.Federator

View File

@ -0,0 +1,24 @@
defmodule Pleroma.Web.ActivityPub.MRF do
@callback filter(Map.t()) :: {:ok | :reject, Map.t()}
def filter(object) do
get_policies()
|> Enum.reduce({:ok, object}, fn
policy, {:ok, object} ->
policy.filter(object)
_, error ->
error
end)
end
def get_policies() do
Application.get_env(:pleroma, :instance, [])
|> Keyword.get(:rewrite_policy, [])
|> get_policies()
end
defp get_policies(policy) when is_atom(policy), do: [policy]
defp get_policies(policies) when is_list(policies), do: policies
defp get_policies(_), do: []
end

View File

@ -1,6 +1,8 @@
defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
require Logger
@behaviour Pleroma.Web.ActivityPub.MRF
@impl true
def filter(object) do
Logger.info("REJECTING #{inspect(object)}")
{:reject, object}

View File

@ -1,4 +1,7 @@
defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do
@behaviour Pleroma.Web.ActivityPub.MRF
@impl true
def filter(object) do
{:ok, object}
end

View File

@ -1,5 +1,6 @@
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF
@mrf_policy Application.get_env(:pleroma, :mrf_simple)
@ -17,9 +18,10 @@ defp check_media_removal(actor_info, object) do
if actor_info.host in @media_removal do
child_object = Map.delete(object["object"], "attachment")
object = Map.put(object, "object", child_object)
{:ok, object}
else
{:ok, object}
end
{:ok, object}
end
@media_nsfw Keyword.get(@mrf_policy, :media_nsfw)
@ -32,9 +34,10 @@ defp check_media_nsfw(actor_info, object) do
child_object = Map.put(child_object, "tags", tags)
child_object = Map.put(child_object, "sensitive", true)
object = Map.put(object, "object", child_object)
{:ok, object}
else
{:ok, object}
end
{:ok, object}
end
@ftl_removal Keyword.get(@mrf_policy, :federated_timeline_removal)
@ -43,24 +46,31 @@ defp check_ftl_removal(actor_info, object) do
user = User.get_by_ap_id(object["actor"])
# flip to/cc relationship to make the post unlisted
if "https://www.w3.org/ns/activitystreams#Public" in object["to"] and
user.follower_address in object["cc"] do
to =
List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++
[user.follower_address]
object =
if "https://www.w3.org/ns/activitystreams#Public" in object["to"] and
user.follower_address in object["cc"] do
to =
List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++
[user.follower_address]
cc =
List.delete(object["cc"], user.follower_address) ++
["https://www.w3.org/ns/activitystreams#Public"]
cc =
List.delete(object["cc"], user.follower_address) ++
["https://www.w3.org/ns/activitystreams#Public"]
object = Map.put(object, "to", to)
object = Map.put(object, "cc", cc)
end
object
|> Map.put("to", to)
|> Map.put("cc", cc)
else
object
end
{:ok, object}
else
{:ok, object}
end
{:ok, object}
end
@impl true
def filter(object) do
actor_info = URI.parse(object["actor"])
@ -70,7 +80,7 @@ def filter(object) do
{:ok, object} <- check_ftl_removal(actor_info, object) do
{:ok, object}
else
e -> {:reject, nil}
_e -> {:reject, nil}
end
end
end

View File

@ -72,9 +72,12 @@ def fix_emoji(object) do
|> Enum.reduce(%{}, fn data, mapping ->
name = data["name"]
if String.starts_with?(name, ":") do
name = name |> String.slice(1..-2)
end
name =
if String.starts_with?(name, ":") do
name |> String.slice(1..-2)
else
name
end
mapping |> Map.put(name, data["icon"]["url"])
end)
@ -143,12 +146,12 @@ def handle_incoming(
end
def handle_incoming(
%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = data
%{"type" => "Like", "object" => object_id, "actor" => actor, "id" => id} = _data
) do
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <-
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
{:ok, activity, object} <- ActivityPub.like(actor, object, id, false) do
{:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
{:ok, activity}
else
_e -> :error
@ -156,12 +159,12 @@ def handle_incoming(
end
def handle_incoming(
%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data
%{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = _data
) do
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <-
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
{:ok, activity, object} <- ActivityPub.announce(actor, object, id, false) do
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false) do
{:ok, activity}
else
_e -> :error
@ -202,7 +205,7 @@ def handle_incoming(
# TODO: Make secure.
def handle_incoming(
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = _data
) do
object_id =
case object_id do
@ -210,13 +213,13 @@ def handle_incoming(
id -> id
end
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
with %User{} = _actor <- User.get_or_fetch_by_ap_id(actor),
{:ok, object} <-
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
{:ok, activity} <- ActivityPub.delete(object, false) do
{:ok, activity}
else
e -> :error
_e -> :error
end
end
@ -254,10 +257,10 @@ def prepare_object(object) do
|> set_reply_to_uri
end
@doc
"""
internal -> Mastodon
"""
# @doc
# """
# internal -> Mastodon
# """
def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
object =
@ -272,7 +275,7 @@ def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = obj
{:ok, data}
end
def prepare_outgoing(%{"type" => type} = data) do
def prepare_outgoing(%{"type" => _type} = data) do
data =
data
|> maybe_fix_object_url
@ -286,7 +289,7 @@ def maybe_fix_object_url(data) do
case ActivityPub.fetch_object_from_id(data["object"]) do
{:ok, relative_object} ->
if relative_object.data["external_url"] do
data =
_data =
data
|> Map.put("object", relative_object.data["external_url"])
else

View File

@ -47,25 +47,6 @@ def render("user.json", %{user: user}) do
|> Map.merge(Utils.make_json_ld_header())
end
def collection(collection, iri, page, total \\ nil) do
offset = (page - 1) * 10
items = Enum.slice(collection, offset, 10)
items = Enum.map(items, fn user -> user.ap_id end)
total = total || length(collection)
map = %{
"id" => "#{iri}?page=#{page}",
"type" => "OrderedCollectionPage",
"partOf" => iri,
"totalItems" => length(collection),
"orderedItems" => items
}
if offset < length(collection) do
Map.put(map, "next", "#{iri}?page=#{page + 1}")
end
end
def render("following.json", %{user: user, page: page}) do
query = User.get_friends_query(user)
query = from(user in query, select: [:ap_id])
@ -123,9 +104,12 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
"limit" => "10"
}
if max_qid != nil do
params = Map.put(params, "max_id", max_qid)
end
params =
if max_qid != nil do
Map.put(params, "max_id", max_qid)
else
params
end
activities = ActivityPub.fetch_public_activities(params)
min_id = Enum.at(activities, 0).id
@ -162,4 +146,23 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
page |> Map.merge(Utils.make_json_ld_header())
end
end
def collection(collection, iri, page, total \\ nil) do
offset = (page - 1) * 10
items = Enum.slice(collection, offset, 10)
items = Enum.map(items, fn user -> user.ap_id end)
total = total || length(collection)
map = %{
"id" => "#{iri}?page=#{page}",
"type" => "OrderedCollectionPage",
"partOf" => iri,
"totalItems" => total,
"orderedItems" => items
}
if offset < total do
Map.put(map, "next", "#{iri}?page=#{page + 1}")
end
end
end

View File

@ -1,7 +1,6 @@
defmodule Pleroma.Web.UserSocket do
use Phoenix.Socket
alias Pleroma.User
alias Comeonin.Pbkdf2
## Channels
# channel "room:*", Pleroma.Web.RoomChannel

View File

@ -1,5 +1,5 @@
defmodule Pleroma.Web.CommonAPI do
alias Pleroma.{Repo, Activity, Object, User}
alias Pleroma.{Repo, Activity, Object}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Formatter

View File

@ -1,5 +1,5 @@
defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.{Repo, Object, Formatter, User, Activity}
alias Pleroma.{Repo, Object, Formatter, Activity}
alias Pleroma.Web.ActivityPub.Utils
alias Calendar.Strftime
@ -49,7 +49,7 @@ def to_for_user_and_mentions(user, mentions, inReplyTo, "private") do
{[user.follower_address | to], cc}
end
def to_for_user_and_mentions(user, mentions, inReplyTo, "direct") do
def to_for_user_and_mentions(_user, mentions, inReplyTo, "direct") do
mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end)
if inReplyTo do
@ -69,7 +69,7 @@ def make_content_html(status, mentions, attachments, tags, no_attachment_links \
def make_context(%Activity{data: %{"context" => context}}), do: context
def make_context(_), do: Utils.generate_context_id()
def maybe_add_attachments(text, attachments, _no_links = true), do: text
def maybe_add_attachments(text, _attachments, _no_links = true), do: text
def maybe_add_attachments(text, attachments, _no_links) do
add_attachments(text, attachments)

View File

@ -14,6 +14,10 @@ defmodule Pleroma.Web.Federator do
@federating Keyword.get(@instance, :federating)
@max_jobs 20
def init(args) do
{:ok, args}
end
def start_link do
spawn(fn ->
# 1 minute
@ -89,12 +93,12 @@ def handle(:incoming_ap_doc, params) do
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
nil <- Activity.get_by_ap_id(params["id"]),
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
{:ok, _activity} <- Transmogrifier.handle_incoming(params) do
else
%Activity{} ->
Logger.info("Already had #{params["id"]}")
e ->
_e ->
# Just drop those for now
Logger.info("Unhandled activity")
Logger.info(Poison.encode!(params, pretty: 2))
@ -154,7 +158,7 @@ def maybe_start_job(running_jobs, queue) do
end
end
def handle_cast({:enqueue, type, payload, priority}, state)
def handle_cast({:enqueue, type, payload, _priority}, state)
when type in [:incoming_doc, :incoming_ap_doc] do
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
i_queue = enqueue_sorted(i_queue, {type, payload}, 1)
@ -162,7 +166,7 @@ def handle_cast({:enqueue, type, payload, priority}, state)
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}}
end
def handle_cast({:enqueue, type, payload, priority}, state) do
def handle_cast({:enqueue, type, payload, _priority}, state) do
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state
o_queue = enqueue_sorted(o_queue, {type, payload}, 1)
{o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue)

View File

@ -45,7 +45,7 @@ def validate_conn(conn) do
end
end
else
e ->
_e ->
Logger.debug("Could not public key!")
false
end

View File

@ -112,7 +112,7 @@ def masto_instance(conn, _params) do
version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})",
email: Keyword.get(@instance, :email),
urls: %{
streaming_api: String.replace(Web.base_url(), ["http", "https"], "wss")
streaming_api: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws")
},
stats: Stats.get_stats(),
thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
@ -212,14 +212,14 @@ def user_statuses(%{assigns: %{user: user}} = conn, params) do
|> Map.put("actor_id", ap_id)
|> Map.put("whole_db", true)
if params["pinned"] == "true" do
# Since Pleroma has no "pinned" posts feature, we'll just set an empty list here
activities = []
else
activities =
activities =
if params["pinned"] == "true" do
# Since Pleroma has no "pinned" posts feature, we'll just set an empty list here
[]
else
ActivityPub.fetch_public_activities(params)
|> Enum.reverse()
end
end
conn
|> add_link_headers(:user_statuses, activities, params["id"])
@ -275,7 +275,19 @@ def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|> Map.put("in_reply_to_status_id", params["in_reply_to_id"])
|> Map.put("no_attachment_links", true)
{:ok, activity} = CommonAPI.post(user, params)
idempotency_key =
case get_req_header(conn, "idempotency-key") do
[key] -> key
_ -> Ecto.UUID.generate()
end
{:ok, activity} =
Cachex.get!(
:idempotency_cache,
idempotency_key,
fallback: fn _ -> CommonAPI.post(user, params) end
)
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
end

View File

@ -82,19 +82,6 @@ def render(
}
end
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
id = activity.data["object"]["inReplyTo"]
replied_to_activities[activity.data["object"]["inReplyTo"]]
end
def get_reply_to(%{data: %{"object" => object}}, _) do
if object["inReplyTo"] && object["inReplyTo"] != "" do
Activity.get_create_activity_by_object_ap_id(object["inReplyTo"])
else
nil
end
end
def render("status.json", %{activity: %{data: %{"object" => object}} = activity} = opts) do
user = User.get_cached_by_ap_id(activity.data["actor"])
@ -164,19 +151,6 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
}
end
def get_visibility(object) do
public = "https://www.w3.org/ns/activitystreams#Public"
to = object["to"] || []
cc = object["cc"] || []
cond do
public in to -> "public"
public in cc -> "unlisted"
Enum.any?(to, &String.contains?(&1, "/followers")) -> "private"
true -> "direct"
end
end
def render("attachment.json", %{attachment: attachment}) do
[%{"mediaType" => media_type, "href" => href} | _] = attachment["url"]
@ -199,4 +173,30 @@ def render("attachment.json", %{attachment: attachment}) do
type: type
}
end
def get_reply_to(activity, %{replied_to_activities: replied_to_activities}) do
_id = activity.data["object"]["inReplyTo"]
replied_to_activities[activity.data["object"]["inReplyTo"]]
end
def get_reply_to(%{data: %{"object" => object}}, _) do
if object["inReplyTo"] && object["inReplyTo"] != "" do
Activity.get_create_activity_by_object_ap_id(object["inReplyTo"])
else
nil
end
end
def get_visibility(object) do
public = "https://www.w3.org/ns/activitystreams#Public"
to = object["to"] || []
cc = object["cc"] || []
cond do
public in to -> "public"
public in cc -> "unlisted"
Enum.any?(to, &String.contains?(&1, "/followers")) -> "private"
true -> "direct"
end
end
end

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,62 @@
defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
use Pleroma.Web, :controller
alias Pleroma.Stats
alias Pleroma.Web
@instance Application.get_env(:pleroma, :instance)
def schemas(conn, _params) do
response = %{
links: [
%{
rel: "http://nodeinfo.diaspora.software/ns/schema/2.0",
href: Web.base_url() <> "/nodeinfo/2.0.json"
}
]
}
json(conn, response)
end
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
def nodeinfo(conn, %{"version" => "2.0"}) do
stats = Stats.get_stats()
response = %{
version: "2.0",
software: %{
name: "pleroma",
version: Keyword.get(@instance, :version)
},
protocols: ["ostatus", "activitypub"],
services: %{
inbound: [],
outbound: []
},
openRegistrations: Keyword.get(@instance, :registrations_open),
usage: %{
users: %{
total: stats.user_count || 0
},
localPosts: stats.status_count || 0
},
metadata: %{
nodeName: Keyword.get(@instance, :name)
}
}
conn
|> put_resp_header(
"content-type",
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
)
|> json(response)
end
def nodeinfo(conn, _) do
conn
|> put_status(404)
|> json(%{error: "Nodeinfo schema version not handled"})
end
end

View File

@ -11,7 +11,7 @@ defmodule Pleroma.Web.OAuth.Authorization do
field(:valid_until, :naive_datetime)
field(:used, :boolean, default: false)
belongs_to(:user, Pleroma.User)
belongs_to(:app, Pleroma.App)
belongs_to(:app, App)
timestamps()
end

View File

@ -9,7 +9,7 @@ defmodule Pleroma.Web.OAuth.Token do
field(:refresh_token, :string)
field(:valid_until, :naive_datetime)
belongs_to(:user, Pleroma.User)
belongs_to(:app, Pleroma.App)
belongs_to(:app, App)
timestamps()
end

View File

@ -1,7 +1,6 @@
defmodule Pleroma.Web.OStatus.ActivityRepresenter do
alias Pleroma.{Activity, User, Object}
alias Pleroma.Web.OStatus.UserRepresenter
alias Pleroma.Formatter
require Logger
defp get_href(id) do

View File

@ -1,7 +1,7 @@
defmodule Pleroma.Web.OStatus.NoteHandler do
require Logger
alias Pleroma.Web.{XML, OStatus}
alias Pleroma.{Object, User, Activity}
alias Pleroma.{Object, Activity}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI

View File

@ -8,7 +8,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Web.XML
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ActivityPub
import Ecto.Query
def feed_redirect(conn, %{"nickname" => nickname} = params) do
user = User.get_cached_by_nickname(nickname)

View File

@ -295,6 +295,11 @@ def user_fetcher(username) do
get("/host-meta", WebFinger.WebFingerController, :host_meta)
get("/webfinger", WebFinger.WebFingerController, :webfinger)
get("/nodeinfo", Nodeinfo.NodeinfoController, :schemas)
end
scope "/nodeinfo", Pleroma.Web do
get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)
end
end

View File

@ -3,6 +3,10 @@ defmodule Pleroma.Web.Streamer do
require Logger
alias Pleroma.{User, Notification}
def init(args) do
{:ok, args}
end
def start_link do
spawn(fn ->
# 30 seconds
@ -110,20 +114,26 @@ def handle_cast(m, state) do
def push_to_socket(topics, topic, item) do
Enum.each(topics[topic] || [], fn socket ->
json =
%{
event: "update",
payload:
Pleroma.Web.MastodonAPI.StatusView.render(
"status.json",
activity: item,
for: socket.assigns[:user]
)
|> Jason.encode!()
}
|> Jason.encode!()
# 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"] || []
send(socket.transport_pid, {:text, json})
unless item.actor in blocks do
json =
%{
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

View File

@ -92,7 +92,7 @@ def do_remote_follow(conn, %{
with %User{} = user <- User.get_cached_by_nickname(username),
true <- Pbkdf2.checkpw(password, user.password_hash),
%User{} = followed <- Repo.get(User, id),
%User{} = _followed <- Repo.get(User, id),
{:ok, follower} <- User.follow(user, followee),
{:ok, _activity} <- ActivityPub.follow(follower, followee) do
conn

View File

@ -1,7 +1,6 @@
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
alias Pleroma.{User, Activity, Repo, Object}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.{OStatus, CommonAPI}
import Ecto.Query
@ -184,7 +183,7 @@ defp parse_int(string, default) when is_binary(string) do
defp parse_int(_, default), do: default
def search(user, %{"q" => query} = params) do
def search(_user, %{"q" => query} = params) do
limit = parse_int(params["rpp"], 20)
page = parse_int(params["page"], 1)
offset = (page - 1) * limit
@ -206,7 +205,7 @@ def search(user, %{"q" => query} = params) do
order_by: [desc: :inserted_at]
)
activities = Repo.all(q)
_activities = Repo.all(q)
end
defp make_date do

View File

@ -347,7 +347,8 @@ def empty_array(conn, _params) do
def update_profile(%{assigns: %{user: user}} = conn, params) do
params =
if bio = params["description"] do
Map.put(params, "bio", bio)
bio_brs = Regex.replace(~r/\r?\n/, bio, "<br>")
Map.put(params, "bio", bio_brs)
else
params
end

View File

@ -31,7 +31,7 @@ defp query_users(user_ids) do
end
defp collect_context_ids(activities) do
contexts =
_contexts =
activities
|> Enum.reject(& &1.data["context_id"])
|> Enum.map(fn %{data: data} ->

View File

@ -2,7 +2,6 @@ defmodule Pleroma.Web.TwitterAPI.NotificationView do
use Pleroma.Web, :view
alias Pleroma.{Notification, User}
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.TwitterAPI.ActivityView

View File

@ -1,7 +1,7 @@
defmodule Pleroma.Web.WebFinger do
@httpoison Application.get_env(:pleroma, :httpoison)
alias Pleroma.{Repo, User, XmlBuilder}
alias Pleroma.{User, XmlBuilder}
alias Pleroma.Web
alias Pleroma.Web.{XML, Salmon, OStatus}
require Jason
@ -239,13 +239,14 @@ def finger(account) do
URI.parse(account).host
end
case find_lrdd_template(domain) do
{:ok, template} ->
address = String.replace(template, "{uri}", URI.encode(account))
address =
case find_lrdd_template(domain) do
{:ok, template} ->
String.replace(template, "{uri}", URI.encode(account))
_ ->
address = "http://#{domain}/.well-known/webfinger?resource=acct:#{account}"
end
_ ->
"http://#{domain}/.well-known/webfinger?resource=acct:#{account}"
end
with response <-
@httpoison.get(

View File

@ -14,7 +14,7 @@ def string_from_xpath(xpath, doc) do
if res == "", do: nil, else: res
catch
e ->
_e ->
Logger.debug("Couldn't find xpath #{xpath} in XML doc")
nil
end

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=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.6c8da1b0ace79ad8881a0e5e716ec818.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.cdeff2a56af285544b89.js></script><script type=text/javascript src=/static/js/vendor.ef2aee0b2db579c3a86a.js></script><script type=text/javascript src=/static/js/app.408cea515c3032097a51.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.6c8da1b0ace79ad8881a0e5e716ec818.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.38e369a50eccc2857845.js></script><script type=text/javascript src=/static/js/vendor.ef2aee0b2db579c3a86a.js></script><script type=text/javascript src=/static/js/app.af121efa5ff89725b4c6.js></script></body></html>

View File

@ -2,7 +2,8 @@
"theme": "pleroma-dark",
"background": "/static/aurora_borealis.jpg",
"logo": "/static/logo.png",
"defaultPath": "/main/all",
"redirectRootNoLogin": "/main/all",
"redirectRootLogin": "/main/friends",
"chatDisabled": false,
"showInstanceSpecificPanel": true
"showInstanceSpecificPanel": false
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -33,6 +33,13 @@ test "it doesn't create a notification for user if the user blocks the activity
assert nil == Notification.create_notification(activity, user)
end
test "it doesn't create a notification for user if he is the activity author" do
activity = insert(:note_activity)
author = User.get_by_ap_id(activity.data["actor"])
assert nil == Notification.create_notification(activity, author)
end
end
describe "get notification" do

View File

@ -26,7 +26,7 @@ def insert(data \\ %{}, opts \\ %{}) do
end
def insert_list(times, data \\ %{}, opts \\ %{}) do
Enum.map(1..times, fn n ->
Enum.map(1..times, fn _n ->
{:ok, activity} = insert(data, opts)
activity
end)

View File

@ -367,7 +367,7 @@ def get("https://shitposter.club/api/statuses/user_timeline/1.atom", _body, _hea
def post(
"https://social.heldscal.la/main/push/hub",
{:form, data},
{:form, _data},
"Content-type": "application/x-www-form-urlencoded"
) do
{:ok,
@ -711,11 +711,11 @@ def get(url, body, headers) do
}"}
end
def post(url, body, headers) do
def post(url, _body, _headers) do
{:error, "Not implemented the mock response for post #{inspect(url)}"}
end
def post(url, body, headers, options) do
def post(url, _body, _headers, _options) do
{:error, "Not implemented the mock response for post #{inspect(url)}"}
end
end

View File

@ -63,7 +63,42 @@ test "the public timeline", %{conn: conn} do
test "posting a status", %{conn: conn} do
user = insert(:user)
conn =
idempotency_key = "Pikachu rocks!"
conn_one =
conn
|> assign(:user, user)
|> put_req_header("idempotency-key", idempotency_key)
|> post("/api/v1/statuses", %{
"status" => "cofe",
"spoiler_text" => "2hu",
"sensitive" => "false"
})
{:ok, ttl} = Cachex.ttl(:idempotency_cache, idempotency_key)
# Six hours
assert ttl > :timer.seconds(6 * 60 * 60 - 1)
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
json_response(conn_one, 200)
assert Repo.get(Activity, id)
conn_two =
conn
|> assign(:user, user)
|> put_req_header("idempotency-key", idempotency_key)
|> post("/api/v1/statuses", %{
"status" => "cofe",
"spoiler_text" => "2hu",
"sensitive" => "false"
})
assert %{"id" => second_id} = json_response(conn_two, 200)
assert id == second_id
conn_three =
conn
|> assign(:user, user)
|> post("/api/v1/statuses", %{
@ -72,10 +107,9 @@ test "posting a status", %{conn: conn} do
"sensitive" => "false"
})
assert %{"content" => "cofe", "id" => id, "spoiler_text" => "2hu", "sensitive" => false} =
json_response(conn, 200)
assert %{"id" => third_id} = json_response(conn_three, 200)
assert Repo.get(Activity, id)
refute id == third_id
end
test "posting a sensitive status", %{conn: conn} do

View File

@ -0,0 +1,63 @@
defmodule Pleroma.Web.StreamerTest do
use Pleroma.DataCase
alias Pleroma.Web.Streamer
alias Pleroma.User
alias Pleroma.Web.CommonAPI
import Pleroma.Factory
test "it sends to public" do
user = insert(:user)
other_user = insert(:user)
task =
Task.async(fn ->
assert_receive {:text, _}, 4_000
end)
fake_socket = %{
transport_pid: task.pid,
assigns: %{
user: user
}
}
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "Test"})
topics = %{
"public" => [fake_socket]
}
Streamer.push_to_socket(topics, "public", activity)
Task.await(task)
end
test "it doesn't send to blocked users" do
user = insert(:user)
blocked_user = insert(:user)
{:ok, user} = User.block(user, blocked_user)
task =
Task.async(fn ->
refute_receive {:text, _}, 1_000
end)
fake_socket = %{
transport_pid: task.pid,
assigns: %{
user: user
}
}
{:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"})
topics = %{
"public" => [fake_socket]
}
Streamer.push_to_socket(topics, "public", activity)
Task.await(task)
end
end

View File

@ -257,8 +257,10 @@ test "without valid credentials", %{conn: conn} do
end
test "with credentials", %{conn: conn, user: current_user} do
other_user = insert(:user)
{:ok, activity} =
ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: current_user})
ActivityBuilder.insert(%{"to" => [current_user.ap_id]}, %{user: other_user})
conn =
conn
@ -784,4 +786,18 @@ test "it returns the tags timeline", %{conn: conn} do
assert status["id"] == activity.id
end
end
test "Convert newlines to <br> in bio", %{conn: conn} do
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> post("/api/account/update_profile.json", %{
"description" => "Hello,\r\nWorld! I\n am a test."
})
user = Repo.get!(User, user.id)
assert user.bio == "Hello,<br>World! I<br> am a test."
end
end