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 472 additions and 194 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
}

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

@ -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,c){for(var p,l,s=0,f=[];s<o.length;s++)l=o[s],n[l]&&f.push.apply(f,n[l]),n[l]=0;for(p in c)Object.prototype.hasOwnProperty.call(c,p)&&(e[p]=c[p]);for(a&&a(o,c);f.length;)f.shift().call(null,t);if(c[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:"ef2aee0b2db579c3a86a",2:"af121efa5ff89725b4c6"}[e]+".js",r.appendChild(o)}},t.m=e,t.c=r,t.p="/"}([]);
//# sourceMappingURL=manifest.38e369a50eccc2857845.js.map

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:"ef2aee0b2db579c3a86a",2:"408cea515c3032097a51"}[e]+".js",r.appendChild(c)}},t.m=e,t.c=r,t.p="/"}([]);
//# sourceMappingURL=manifest.cdeff2a56af285544b89.js.map

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