...
 
Commits (27)
......@@ -75,7 +75,13 @@ config :pleroma, :instance,
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
public: true,
quarantined_instances: [],
managed_config: true
managed_config: true,
allowed_post_formats: [
"text/plain",
"text/html",
"text/markdown"
],
mrf_transparency: true
config :pleroma, :markup,
# XXX - unfortunately, inline images must be enabled by default right now, because
......@@ -99,12 +105,14 @@ config :pleroma, :fe,
redirect_root_login: "/main/friends",
show_instance_panel: true,
scope_options_enabled: false,
formatting_options_enabled: false,
collapse_message_with_subject: false
config :pleroma, :activitypub,
accept_blocks: true,
unfollow_blocked: true,
outgoing_blocks: true
outgoing_blocks: true,
follow_handshake_timeout: 500
config :pleroma, :user, deny_follow_blocked: true
......
......@@ -77,7 +77,7 @@ server {
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "same-origin" always;
add_header X-Download-Options "noopen" always;
add_header Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://example.tld; upgrade-insecure-requests;" always;
add_header Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action *; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://example.tld; upgrade-insecure-requests;" always;
# Uncomment this only after you get HTTPS working.
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
......
......@@ -192,7 +192,11 @@ defmodule Pleroma.Formatter do
]
# TODO: make it use something other than @link_regex
def html_escape(text) do
def html_escape(text, "text/html") do
HTML.filter_tags(text)
end
def html_escape(text, "text/plain") do
Regex.split(@link_regex, text, include_captures: true)
|> Enum.map_every(2, fn chunk ->
{:safe, part} = Phoenix.HTML.html_escape(chunk)
......
......@@ -185,32 +185,7 @@ defmodule Pleroma.User do
def needs_update?(_), do: true
def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do
user_config = Application.get_env(:pleroma, :user)
deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
user_info = user_info(followed)
should_direct_follow =
cond do
# if the account is locked, don't pre-create the relationship
user_info[:locked] == true ->
false
# if the users are blocking each other, we shouldn't even be here, but check for it anyway
deny_follow_blocked and
(User.blocks?(follower, followed) or User.blocks?(followed, follower)) ->
false
# if OStatus, then there is no three-way handshake to follow
User.ap_enabled?(followed) != true ->
true
# if there are no other reasons not to, just pre-create the relationship
true ->
true
end
if should_direct_follow do
if !User.ap_enabled?(followed) do
follow(follower, followed)
else
{:ok, follower}
......@@ -763,4 +738,28 @@ defmodule Pleroma.User do
get_or_fetch_by_nickname(uri_or_nickname)
end
end
# wait a period of time and return newest version of the User structs
# this is because we have synchronous follow APIs and need to simulate them
# with an async handshake
def wait_and_refresh(_, %User{local: true} = a, %User{local: true} = b) do
with %User{} = a <- Repo.get(User, a.id),
%User{} = b <- Repo.get(User, b.id) do
{:ok, a, b}
else
_e ->
:error
end
end
def wait_and_refresh(timeout, %User{} = a, %User{} = b) do
with :ok <- :timer.sleep(timeout),
%User{} = a <- Repo.get(User, a.id),
%User{} = b <- Repo.get(User, b.id) do
{:ok, a, b}
else
_e ->
:error
end
end
end
......@@ -326,6 +326,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with actor <- get_actor(data),
%User{} = followed <- User.get_or_fetch_by_ap_id(actor),
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, activity} <-
ActivityPub.accept(%{
......@@ -351,6 +352,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
with actor <- get_actor(data),
%User{} = followed <- User.get_or_fetch_by_ap_id(actor),
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, activity} <-
ActivityPub.accept(%{
......
......@@ -247,11 +247,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"actor" => follower_id,
"to" => [followed_id],
"cc" => ["https://www.w3.org/ns/activitystreams#Public"],
"object" => followed_id
"object" => followed_id,
"state" => "pending"
}
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
......
......@@ -73,6 +73,11 @@ defmodule Pleroma.Web.CommonAPI do
def get_visibility(_), do: "public"
@instance Application.get_env(:pleroma, :instance)
@allowed_post_formats Keyword.get(@instance, :allowed_post_formats)
defp get_content_type(content_type) when content_type in @allowed_post_formats, do: content_type
defp get_content_type(_), do: "text/plain"
@limit Keyword.get(@instance, :limit)
def post(user, %{"status" => status} = data) do
visibility = get_visibility(data)
......@@ -85,7 +90,14 @@ defmodule Pleroma.Web.CommonAPI do
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility),
tags <- Formatter.parse_tags(status, data),
content_html <-
make_content_html(status, mentions, attachments, tags, data["no_attachment_links"]),
make_content_html(
status,
mentions,
attachments,
tags,
get_content_type(data["content_type"]),
data["no_attachment_links"]
),
context <- make_context(inReplyTo),
cw <- data["spoiler_text"],
object <-
......
......@@ -63,9 +63,16 @@ defmodule Pleroma.Web.CommonAPI.Utils do
end
end
def make_content_html(status, mentions, attachments, tags, no_attachment_links \\ false) do
def make_content_html(
status,
mentions,
attachments,
tags,
content_type,
no_attachment_links \\ false
) do
status
|> format_input(mentions, tags)
|> format_input(mentions, tags, content_type)
|> maybe_add_attachments(attachments, no_attachment_links)
end
......@@ -92,9 +99,9 @@ defmodule Pleroma.Web.CommonAPI.Utils do
Enum.join([text | attachment_text], "<br>")
end
def format_input(text, mentions, tags) do
def format_input(text, mentions, tags, "text/plain") do
text
|> Formatter.html_escape()
|> Formatter.html_escape("text/plain")
|> String.replace(~r/\r?\n/, "<br>")
|> (&{[], &1}).()
|> Formatter.add_links()
......@@ -103,6 +110,25 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Formatter.finalize()
end
def format_input(text, mentions, tags, "text/html") do
text
|> Formatter.html_escape("text/html")
|> String.replace(~r/\r?\n/, "<br>")
|> (&{[], &1}).()
|> Formatter.add_user_links(mentions)
|> Formatter.finalize()
end
def format_input(text, mentions, tags, "text/markdown") do
text
|> Earmark.as_html!()
|> Formatter.html_escape("text/html")
|> String.replace(~r/\r?\n/, "")
|> (&{[], &1}).()
|> Formatter.add_user_links(mentions)
|> Formatter.finalize()
end
def add_tag_links(text, tags) do
tags =
tags
......
......@@ -571,10 +571,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
@activitypub Application.get_env(:pleroma, :activitypub)
@follow_handshake_timeout Keyword.get(@activitypub, :follow_handshake_timeout)
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
with %User{} = followed <- Repo.get(User, id),
{:ok, follower} <- User.maybe_direct_follow(follower, followed),
{:ok, _activity} <- ActivityPub.follow(follower, followed) do
{:ok, _activity} <- ActivityPub.follow(follower, followed),
{:ok, follower, followed} <-
User.wait_and_refresh(@follow_handshake_timeout, follower, followed) do
render(conn, AccountView, "relationship.json", %{user: follower, target: followed})
else
{:error, message} ->
......
......@@ -72,6 +72,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
end
def render("relationship.json", %{user: user, target: target}) do
follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target)
requested =
if follow_activity do
follow_activity.data["state"] == "pending"
else
false
end
%{
id: to_string(target.id),
following: User.following?(user, target),
......@@ -79,7 +88,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
blocking: User.blocks?(user, target),
muting: false,
muting_notifications: false,
requested: false,
requested: requested,
domain_blocking: false,
showing_reblogs: false,
endorsed: false
......
......@@ -4,6 +4,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
alias Pleroma.Stats
alias Pleroma.Web
alias Pleroma.{User, Repo}
alias Pleroma.Web.ActivityPub.MRF
def schemas(conn, _params) do
response = %{
......@@ -27,11 +28,41 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
gopher = Application.get_env(:pleroma, :gopher)
stats = Stats.get_stats()
mrf_simple =
Application.get_env(:pleroma, :mrf_simple)
|> Enum.into(%{})
mrf_policies =
MRF.get_policies()
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
quarantined = Keyword.get(instance, :quarantined_instances)
quarantined =
if is_list(quarantined) do
quarantined
else
[]
end
staff_accounts =
User.moderator_user_query()
|> Repo.all()
|> Enum.map(fn u -> u.ap_id end)
mrf_transparency = Keyword.get(instance, :mrf_transparency)
federation_response =
if mrf_transparency do
%{
mrf_policies: mrf_policies,
mrf_simple: mrf_simple,
quarantined_instances: quarantined
}
else
%{}
end
response = %{
version: "2.0",
software: %{
......@@ -64,7 +95,9 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
},
staffAccounts: staff_accounts,
chat: Keyword.get(chat, :enabled),
gopher: Keyword.get(gopher, :enabled)
gopher: Keyword.get(gopher, :enabled),
federation: federation_response,
postFormats: Keyword.get(instance, :allowed_post_formats)
}
}
......
......@@ -176,6 +176,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
chatDisabled: !Keyword.get(@instance_chat, :enabled),
showInstanceSpecificPanel: Keyword.get(@instance_fe, :show_instance_panel),
scopeOptionsEnabled: Keyword.get(@instance_fe, :scope_options_enabled),
formattingOptionsEnabled: Keyword.get(@instance_fe, :formatting_options_enabled),
collapseMessageWithSubject: Keyword.get(@instance_fe, :collapse_message_with_subject)
}
......
......@@ -20,10 +20,15 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end
end
@activitypub Application.get_env(:pleroma, :activitypub)
@follow_handshake_timeout Keyword.get(@activitypub, :follow_handshake_timeout)
def follow(%User{} = follower, params) do
with {:ok, %User{} = followed} <- get_user(params),
{:ok, follower} <- User.maybe_direct_follow(follower, followed),
{:ok, activity} <- ActivityPub.follow(follower, followed) do
{:ok, activity} <- ActivityPub.follow(follower, followed),
{:ok, follower, followed} <-
User.wait_and_refresh(@follow_handshake_timeout, follower, followed) do
{:ok, follower, followed, activity}
else
err -> err
......
......@@ -423,7 +423,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
{String.trim(name, ":"), url}
end)
bio_html = CommonUtils.format_input(bio, mentions, tags)
bio_html = CommonUtils.format_input(bio, mentions, tags, "text/plain")
Map.put(params, "bio", bio_html |> Formatter.emojify(emoji))
else
params
......
......@@ -48,6 +48,7 @@ defmodule Pleroma.Mixfile do
{:mogrify, "~> 0.6.1"},
{:ex_aws, "~> 2.0"},
{:ex_aws_s3, "~> 2.0"},
{:earmark, "~> 1.2"},
{:ex_machina, "~> 2.2", only: :test},
{:credo, "~> 0.9.3", only: [:dev, :test]},
{:mock, "~> 0.3.1", only: :test},
......
......@@ -11,6 +11,7 @@
"crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]},
"db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "2.2.10", "e7366dc82f48f8dd78fcbf3ab50985ceeb11cb3dc93435147c6e13f2cda0992e", [:mix], [{:db_connection, "~> 1.1", [hex: :db_connection, repo: "hexpm", optional: true]}, {:decimal, "~> 1.2", [hex: :decimal, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.8.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"},
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
"ex_aws": {:hex, :ex_aws, "2.1.0", "b92651527d6c09c479f9013caa9c7331f19cba38a650590d82ebf2c6c16a1d8a", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, repo: "hexpm", optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, repo: "hexpm", optional: true]}], "hexpm"},
......
<!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.0808aeafc6252b3050ea95b17dcaff1a.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.fba176349cf0e390101c.js></script><script type=text/javascript src=/static/js/vendor.f9957f6e91d33ea4ed05.js></script><script type=text/javascript src=/static/js/app.3bdb8bed25e3fcc77b06.js></script></body></html>
\ No newline at end of file
<!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.0808aeafc6252b3050ea95b17dcaff1a.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.eee3fdcefdfdc13b7a9a.js></script><script type=text/javascript src=/static/js/vendor.1729433de915d5fd6a8e.js></script><script type=text/javascript src=/static/js/app.4687cd74d92cc8396de6.js></script></body></html>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var r=window.webpackJsonp;window.webpackJsonp=function(o,c){for(var p,l,s=0,i=[];s<o.length;s++)l=o[s],a[l]&&i.push.apply(i,a[l]),a[l]=0;for(p in c)Object.prototype.hasOwnProperty.call(c,p)&&(e[p]=c[p]);for(r&&r(o,c);i.length;)i.shift().call(null,t);if(c[0])return n[0]=0,t(0)};var n={},a={0:0};t.e=function(e,r){if(0===a[e])return r.call(null,t);if(void 0!==a[e])a[e].push(r);else{a[e]=[r];var n=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:"f9957f6e91d33ea4ed05",2:"3bdb8bed25e3fcc77b06"}[e]+".js",n.appendChild(o)}},t.m=e,t.c=n,t.p="/"}([]);
//# sourceMappingURL=manifest.fba176349cf0e390101c.js.map
\ No newline at end of file
!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var r=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,d=[];s<c.length;s++)l=c[s],a[l]&&d.push.apply(d,a[l]),a[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(r&&r(c,o);d.length;)d.shift().call(null,t);if(o[0])return n[0]=0,t(0)};var n={},a={0:0};t.e=function(e,r){if(0===a[e])return r.call(null,t);if(void 0!==a[e])a[e].push(r);else{a[e]=[r];var n=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:"1729433de915d5fd6a8e",2:"4687cd74d92cc8396de6"}[e]+".js",n.appendChild(c)}},t.m=e,t.c=n,t.p="/"}([]);
//# sourceMappingURL=manifest.eee3fdcefdfdc13b7a9a.js.map
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -21,4 +21,36 @@ defmodule Pleroma.Web.CommonAPI.Test do
assert karjalanpiirakka["name"] == ":karjalanpiirakka:"
end
describe "posting" do
test "it filters out obviously bad tags when accepting a post as HTML" do
user = insert(:user)
post = "<p><b>2hu</b></p><script>alert('xss')</script>"
{:ok, activity} =
CommonAPI.post(user, %{
"status" => post,
"content_type" => "text/html"
})
content = activity.data["object"]["content"]
assert content == "<p><b>2hu</b></p>alert('xss')"
end
test "it filters out obviously bad tags when accepting a post as Markdown" do
user = insert(:user)
post = "<p><b>2hu</b></p><script>alert('xss')</script>"
{:ok, activity} =
CommonAPI.post(user, %{
"status" => post,
"content_type" => "text/markdown"
})
content = activity.data["object"]["content"]
assert content == "<p><b>2hu</b></p>alert('xss')"
end
end
end