@@ -1,6 +1,7 @@ | |||||
# default Apache site config for Pleroma | # default Apache site config for Pleroma | ||||
# | # | ||||
# needed modules: define headers proxy proxy_http proxy_wstunnel rewrite ssl | # needed modules: define headers proxy proxy_http proxy_wstunnel rewrite ssl | ||||
# optional modules: cache cache_disk | |||||
# | # | ||||
# Simple installation instructions: | # Simple installation instructions: | ||||
# 1. Install your TLS certificate, possibly using Let's Encrypt. | # 1. Install your TLS certificate, possibly using Let's Encrypt. | ||||
@@ -8,6 +9,14 @@ | |||||
# 3. This assumes a Debian style Apache config. Copy this file to | # 3. This assumes a Debian style Apache config. Copy this file to | ||||
# /etc/apache2/sites-available/ and then add a symlink to it in | # /etc/apache2/sites-available/ and then add a symlink to it in | ||||
# /etc/apache2/sites-enabled/ by running 'a2ensite pleroma-apache.conf', then restart Apache. | # /etc/apache2/sites-enabled/ by running 'a2ensite pleroma-apache.conf', then restart Apache. | ||||
# | |||||
# Optional: enable disk-based caching for the media proxy | |||||
# For details, see https://git.pleroma.social/pleroma/pleroma/wikis/How%20to%20activate%20mediaproxy | |||||
# | |||||
# 1. Create the directory listed below as the CacheRoot, and make sure | |||||
# the Apache user can write to it. | |||||
# 2. Configure Apache's htcacheclean to clean the directory periodically. | |||||
# 3. Run 'a2enmod cache cache_disk' and restart Apache. | |||||
Define servername example.tld | Define servername example.tld | ||||
@@ -34,6 +43,15 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined | |||||
SSLCompression off | SSLCompression off | ||||
SSLSessionTickets off | SSLSessionTickets off | ||||
# uncomment the following to enable mediaproxy caching on disk | |||||
# <IfModule mod_cache_disk.c> | |||||
# CacheRoot /var/cache/apache2/mod_cache_disk | |||||
# CacheDirLevels 1 | |||||
# CacheDirLength 2 | |||||
# CacheEnable disk /proxy | |||||
# CacheLock on | |||||
# </IfModule> | |||||
RewriteEngine On | RewriteEngine On | ||||
RewriteCond %{HTTP:Connection} Upgrade [NC] | RewriteCond %{HTTP:Connection} Upgrade [NC] | ||||
RewriteCond %{HTTP:Upgrade} websocket [NC] | RewriteCond %{HTTP:Upgrade} websocket [NC] | ||||
@@ -11,7 +11,7 @@ proxy_cache_path /tmp/pleroma-media-cache levels=1:2 keys_zone=pleroma_media_cac | |||||
server { | server { | ||||
server_name example.tld; | server_name example.tld; | ||||
listen 80; | |||||
listen [::]:80; | |||||
return 301 https://$server_name$request_uri; | return 301 https://$server_name$request_uri; | ||||
# Uncomment this if you need to use the 'webroot' method with certbot. Make sure | # Uncomment this if you need to use the 'webroot' method with certbot. Make sure | ||||
@@ -29,7 +29,7 @@ server { | |||||
ssl_session_cache shared:ssl_session_cache:10m; | ssl_session_cache shared:ssl_session_cache:10m; | ||||
server { | server { | ||||
listen 443 ssl http2; | |||||
listen [::]:443 ssl http2; | |||||
ssl_session_timeout 5m; | ssl_session_timeout 5m; | ||||
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/fullchain.pem; | ssl_trusted_certificate /etc/letsencrypt/live/example.tld/fullchain.pem; | ||||
@@ -37,6 +37,7 @@ end | |||||
defmodule Pleroma.Gopher.Server.ProtocolHandler do | defmodule Pleroma.Gopher.Server.ProtocolHandler do | ||||
alias Pleroma.Web.ActivityPub.ActivityPub | alias Pleroma.Web.ActivityPub.ActivityPub | ||||
alias Pleroma.Web.ActivityPub.Visibility | |||||
alias Pleroma.Activity | alias Pleroma.Activity | ||||
alias Pleroma.HTML | alias Pleroma.HTML | ||||
alias Pleroma.User | alias Pleroma.User | ||||
@@ -110,7 +111,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do | |||||
def response("/notices/" <> id) do | def response("/notices/" <> id) do | ||||
with %Activity{} = activity <- Repo.get(Activity, id), | with %Activity{} = activity <- Repo.get(Activity, id), | ||||
true <- ActivityPub.is_public?(activity) do | |||||
true <- Visibility.is_public?(activity) do | |||||
activities = | activities = | ||||
ActivityPub.fetch_activities_for_context(activity.data["context"]) | ActivityPub.fetch_activities_for_context(activity.data["context"]) | ||||
|> render_activities | |> render_activities | ||||
@@ -18,6 +18,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||||
import Ecto.Query | import Ecto.Query | ||||
import Pleroma.Web.ActivityPub.Utils | import Pleroma.Web.ActivityPub.Utils | ||||
import Pleroma.Web.ActivityPub.Visibility | |||||
require Logger | require Logger | ||||
@@ -912,57 +913,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||||
end | end | ||||
end | end | ||||
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false | |||||
def is_public?(%Object{data: data}), do: is_public?(data) | |||||
def is_public?(%Activity{data: data}), do: is_public?(data) | |||||
def is_public?(%{"directMessage" => true}), do: false | |||||
def is_public?(data) do | |||||
"https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || [])) | |||||
end | |||||
def is_private?(activity) do | |||||
unless is_public?(activity) do | |||||
follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address | |||||
Enum.any?(activity.data["to"], &(&1 == follower_address)) | |||||
else | |||||
false | |||||
end | |||||
end | |||||
def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true | |||||
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true | |||||
def is_direct?(activity) do | |||||
!is_public?(activity) && !is_private?(activity) | |||||
end | |||||
def visible_for_user?(activity, nil) do | |||||
is_public?(activity) | |||||
end | |||||
def visible_for_user?(activity, user) do | |||||
x = [user.ap_id | user.following] | |||||
y = activity.data["to"] ++ (activity.data["cc"] || []) | |||||
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y)) | |||||
end | |||||
# guard | |||||
def entire_thread_visible_for_user?(nil, _user), do: false | |||||
# child | |||||
def entire_thread_visible_for_user?( | |||||
%Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail, | |||||
user | |||||
) | |||||
when is_binary(parent_id) do | |||||
parent = Activity.get_in_reply_to_activity(tail) | |||||
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user) | |||||
end | |||||
# root | |||||
def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user) | |||||
# filter out broken threads | # filter out broken threads | ||||
def contain_broken_threads(%Activity{} = activity, %User{} = user) do | def contain_broken_threads(%Activity{} = activity, %User{} = user) do | ||||
entire_thread_visible_for_user?(activity, user) | entire_thread_visible_for_user?(activity, user) | ||||
@@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do | |||||
alias Pleroma.Web.ActivityPub.ObjectView | alias Pleroma.Web.ActivityPub.ObjectView | ||||
alias Pleroma.Web.ActivityPub.UserView | alias Pleroma.Web.ActivityPub.UserView | ||||
alias Pleroma.Web.ActivityPub.ActivityPub | alias Pleroma.Web.ActivityPub.ActivityPub | ||||
alias Pleroma.Web.ActivityPub.Visibility | |||||
alias Pleroma.Web.ActivityPub.Relay | alias Pleroma.Web.ActivityPub.Relay | ||||
alias Pleroma.Web.ActivityPub.Transmogrifier | alias Pleroma.Web.ActivityPub.Transmogrifier | ||||
alias Pleroma.Web.ActivityPub.Utils | alias Pleroma.Web.ActivityPub.Utils | ||||
@@ -49,7 +50,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do | |||||
def object(conn, %{"uuid" => uuid}) do | def object(conn, %{"uuid" => uuid}) do | ||||
with ap_id <- o_status_url(conn, :object, uuid), | with ap_id <- o_status_url(conn, :object, uuid), | ||||
%Object{} = object <- Object.get_cached_by_ap_id(ap_id), | %Object{} = object <- Object.get_cached_by_ap_id(ap_id), | ||||
{_, true} <- {:public?, ActivityPub.is_public?(object)} do | |||||
{_, true} <- {:public?, Visibility.is_public?(object)} do | |||||
conn | conn | ||||
|> put_resp_header("content-type", "application/activity+json") | |> put_resp_header("content-type", "application/activity+json") | ||||
|> json(ObjectView.render("object.json", %{object: object})) | |> json(ObjectView.render("object.json", %{object: object})) | ||||
@@ -62,7 +63,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do | |||||
def object_likes(conn, %{"uuid" => uuid, "page" => page}) do | def object_likes(conn, %{"uuid" => uuid, "page" => page}) do | ||||
with ap_id <- o_status_url(conn, :object, uuid), | with ap_id <- o_status_url(conn, :object, uuid), | ||||
%Object{} = object <- Object.get_cached_by_ap_id(ap_id), | %Object{} = object <- Object.get_cached_by_ap_id(ap_id), | ||||
{_, true} <- {:public?, ActivityPub.is_public?(object)}, | |||||
{_, true} <- {:public?, Visibility.is_public?(object)}, | |||||
likes <- Utils.get_object_likes(object) do | likes <- Utils.get_object_likes(object) do | ||||
{page, _} = Integer.parse(page) | {page, _} = Integer.parse(page) | ||||
@@ -78,7 +79,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do | |||||
def object_likes(conn, %{"uuid" => uuid}) do | def object_likes(conn, %{"uuid" => uuid}) do | ||||
with ap_id <- o_status_url(conn, :object, uuid), | with ap_id <- o_status_url(conn, :object, uuid), | ||||
%Object{} = object <- Object.get_cached_by_ap_id(ap_id), | %Object{} = object <- Object.get_cached_by_ap_id(ap_id), | ||||
{_, true} <- {:public?, ActivityPub.is_public?(object)}, | |||||
{_, true} <- {:public?, Visibility.is_public?(object)}, | |||||
likes <- Utils.get_object_likes(object) do | likes <- Utils.get_object_likes(object) do | ||||
conn | conn | ||||
|> put_resp_header("content-type", "application/activity+json") | |> put_resp_header("content-type", "application/activity+json") | ||||
@@ -92,7 +93,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do | |||||
def activity(conn, %{"uuid" => uuid}) do | def activity(conn, %{"uuid" => uuid}) do | ||||
with ap_id <- o_status_url(conn, :activity, uuid), | with ap_id <- o_status_url(conn, :activity, uuid), | ||||
%Activity{} = activity <- Activity.normalize(ap_id), | %Activity{} = activity <- Activity.normalize(ap_id), | ||||
{_, true} <- {:public?, ActivityPub.is_public?(activity)} do | |||||
{_, true} <- {:public?, Visibility.is_public?(activity)} do | |||||
conn | conn | ||||
|> put_resp_header("content-type", "application/activity+json") | |> put_resp_header("content-type", "application/activity+json") | ||||
|> json(ObjectView.render("object.json", %{object: activity})) | |> json(ObjectView.render("object.json", %{object: activity})) | ||||
@@ -12,6 +12,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||||
alias Pleroma.Repo | alias Pleroma.Repo | ||||
alias Pleroma.Web.ActivityPub.ActivityPub | alias Pleroma.Web.ActivityPub.ActivityPub | ||||
alias Pleroma.Web.ActivityPub.Utils | alias Pleroma.Web.ActivityPub.Utils | ||||
alias Pleroma.Web.ActivityPub.Visibility | |||||
import Ecto.Query | import Ecto.Query | ||||
@@ -489,7 +490,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||||
with actor <- get_actor(data), | with actor <- get_actor(data), | ||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor), | %User{} = actor <- User.get_or_fetch_by_ap_id(actor), | ||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), | {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id), | ||||
public <- ActivityPub.is_public?(data), | |||||
public <- Visibility.is_public?(data), | |||||
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do | {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do | ||||
{:ok, activity} | {:ok, activity} | ||||
else | else | ||||
@@ -188,14 +188,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||||
end | end | ||||
activities = ActivityPub.fetch_user_activities(user, nil, params) | activities = ActivityPub.fetch_user_activities(user, nil, params) | ||||
min_id = Enum.at(Enum.reverse(activities), 0).id | |||||
max_id = Enum.at(activities, 0).id | |||||
collection = | |||||
Enum.map(activities, fn act -> | |||||
{:ok, data} = Transmogrifier.prepare_outgoing(act.data) | |||||
data | |||||
end) | |||||
{max_id, min_id, collection} = | |||||
if length(activities) > 0 do | |||||
{ | |||||
Enum.at(Enum.reverse(activities), 0).id, | |||||
Enum.at(activities, 0).id, | |||||
Enum.map(activities, fn act -> | |||||
{:ok, data} = Transmogrifier.prepare_outgoing(act.data) | |||||
data | |||||
end) | |||||
} | |||||
else | |||||
{ | |||||
0, | |||||
0, | |||||
[] | |||||
} | |||||
end | |||||
iri = "#{user.ap_id}/outbox" | iri = "#{user.ap_id}/outbox" | ||||
@@ -0,0 +1,56 @@ | |||||
defmodule Pleroma.Web.ActivityPub.Visibility do | |||||
alias Pleroma.Activity | |||||
alias Pleroma.Object | |||||
alias Pleroma.User | |||||
def is_public?(%Object{data: %{"type" => "Tombstone"}}), do: false | |||||
def is_public?(%Object{data: data}), do: is_public?(data) | |||||
def is_public?(%Activity{data: data}), do: is_public?(data) | |||||
def is_public?(%{"directMessage" => true}), do: false | |||||
def is_public?(data) do | |||||
"https://www.w3.org/ns/activitystreams#Public" in (data["to"] ++ (data["cc"] || [])) | |||||
end | |||||
def is_private?(activity) do | |||||
unless is_public?(activity) do | |||||
follower_address = User.get_cached_by_ap_id(activity.data["actor"]).follower_address | |||||
Enum.any?(activity.data["to"], &(&1 == follower_address)) | |||||
else | |||||
false | |||||
end | |||||
end | |||||
def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true | |||||
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true | |||||
def is_direct?(activity) do | |||||
!is_public?(activity) && !is_private?(activity) | |||||
end | |||||
def visible_for_user?(activity, nil) do | |||||
is_public?(activity) | |||||
end | |||||
def visible_for_user?(activity, user) do | |||||
x = [user.ap_id | user.following] | |||||
y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || []) | |||||
visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y)) | |||||
end | |||||
# guard | |||||
def entire_thread_visible_for_user?(nil, _user), do: false | |||||
# child | |||||
def entire_thread_visible_for_user?( | |||||
%Activity{data: %{"object" => %{"inReplyTo" => parent_id}}} = tail, | |||||
user | |||||
) | |||||
when is_binary(parent_id) do | |||||
parent = Activity.get_in_reply_to_activity(tail) | |||||
visible_for_user?(tail, user) && entire_thread_visible_for_user?(parent, user) | |||||
end | |||||
# root | |||||
def entire_thread_visible_for_user?(tail, user), do: visible_for_user?(tail, user) | |||||
end |
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.Federator do | |||||
alias Pleroma.Web.Websub | alias Pleroma.Web.Websub | ||||
alias Pleroma.Web.Salmon | alias Pleroma.Web.Salmon | ||||
alias Pleroma.Web.ActivityPub.ActivityPub | alias Pleroma.Web.ActivityPub.ActivityPub | ||||
alias Pleroma.Web.ActivityPub.Visibility | |||||
alias Pleroma.Web.ActivityPub.Relay | alias Pleroma.Web.ActivityPub.Relay | ||||
alias Pleroma.Web.ActivityPub.Transmogrifier | alias Pleroma.Web.ActivityPub.Transmogrifier | ||||
alias Pleroma.Web.ActivityPub.Utils | alias Pleroma.Web.ActivityPub.Utils | ||||
@@ -94,7 +95,7 @@ defmodule Pleroma.Web.Federator do | |||||
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do | with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do | ||||
{:ok, actor} = WebFinger.ensure_keys_present(actor) | {:ok, actor} = WebFinger.ensure_keys_present(actor) | ||||
if ActivityPub.is_public?(activity) do | |||||
if Visibility.is_public?(activity) do | |||||
if OStatus.is_representable?(activity) do | if OStatus.is_representable?(activity) do | ||||
Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end) | Logger.info(fn -> "Sending #{activity.data["id"]} out via WebSub" end) | ||||
Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity) | Websub.publish(Pleroma.Web.OStatus.feed_path(actor), actor, activity) | ||||
@@ -27,6 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
alias Pleroma.Web.MastodonAPI.ReportView | alias Pleroma.Web.MastodonAPI.ReportView | ||||
alias Pleroma.Web.ActivityPub.ActivityPub | alias Pleroma.Web.ActivityPub.ActivityPub | ||||
alias Pleroma.Web.ActivityPub.Utils | alias Pleroma.Web.ActivityPub.Utils | ||||
alias Pleroma.Web.ActivityPub.Visibility | |||||
alias Pleroma.Web.OAuth.App | alias Pleroma.Web.OAuth.App | ||||
alias Pleroma.Web.OAuth.Authorization | alias Pleroma.Web.OAuth.Authorization | ||||
alias Pleroma.Web.OAuth.Token | alias Pleroma.Web.OAuth.Token | ||||
@@ -307,7 +308,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do | def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do | ||||
with %Activity{} = activity <- Repo.get(Activity, id), | with %Activity{} = activity <- Repo.get(Activity, id), | ||||
true <- ActivityPub.visible_for_user?(activity, user) do | |||||
true <- Visibility.visible_for_user?(activity, user) do | |||||
conn | conn | ||||
|> put_view(StatusView) | |> put_view(StatusView) | ||||
|> try_render("status.json", %{activity: activity, for: user}) | |> try_render("status.json", %{activity: activity, for: user}) | ||||
@@ -449,7 +450,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do | def bookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do | ||||
with %Activity{} = activity <- Repo.get(Activity, id), | with %Activity{} = activity <- Repo.get(Activity, id), | ||||
%User{} = user <- User.get_by_nickname(user.nickname), | %User{} = user <- User.get_by_nickname(user.nickname), | ||||
true <- ActivityPub.visible_for_user?(activity, user), | |||||
true <- Visibility.visible_for_user?(activity, user), | |||||
{:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do | {:ok, user} <- User.bookmark(user, activity.data["object"]["id"]) do | ||||
conn | conn | ||||
|> put_view(StatusView) | |> put_view(StatusView) | ||||
@@ -460,7 +461,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do | def unbookmark_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do | ||||
with %Activity{} = activity <- Repo.get(Activity, id), | with %Activity{} = activity <- Repo.get(Activity, id), | ||||
%User{} = user <- User.get_by_nickname(user.nickname), | %User{} = user <- User.get_by_nickname(user.nickname), | ||||
true <- ActivityPub.visible_for_user?(activity, user), | |||||
true <- Visibility.visible_for_user?(activity, user), | |||||
{:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do | {:ok, user} <- User.unbookmark(user, activity.data["object"]["id"]) do | ||||
conn | conn | ||||
|> put_view(StatusView) | |> put_view(StatusView) | ||||
@@ -867,7 +868,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
if Regex.match?(~r/https?:/, query) do | if Regex.match?(~r/https?:/, query) do | ||||
with {:ok, object} <- ActivityPub.fetch_object_from_id(query), | with {:ok, object} <- ActivityPub.fetch_object_from_id(query), | ||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), | %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), | ||||
true <- ActivityPub.visible_for_user?(activity, user) do | |||||
true <- Visibility.visible_for_user?(activity, user) do | |||||
[activity] | [activity] | ||||
else | else | ||||
_e -> [] | _e -> [] | ||||
@@ -1518,9 +1519,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||||
end | end | ||||
end | end | ||||
def status_card(conn, %{"id" => status_id}) do | |||||
def status_card(%{assigns: %{user: user}} = conn, %{"id" => status_id}) do | |||||
with %Activity{} = activity <- Repo.get(Activity, status_id), | with %Activity{} = activity <- Repo.get(Activity, status_id), | ||||
true <- ActivityPub.is_public?(activity) do | |||||
true <- Visibility.visible_for_user?(activity, user) do | |||||
data = | data = | ||||
StatusView.render( | StatusView.render( | ||||
"card.json", | "card.json", | ||||
@@ -3,12 +3,10 @@ | |||||
# SPDX-License-Identifier: AGPL-3.0-only | # SPDX-License-Identifier: AGPL-3.0-only | ||||
defmodule Pleroma.Web.Metadata.Providers.OpenGraph do | defmodule Pleroma.Web.Metadata.Providers.OpenGraph do | ||||
alias Pleroma.HTML | |||||
alias Pleroma.Formatter | |||||
alias Pleroma.User | alias Pleroma.User | ||||
alias Pleroma.Web.Metadata | alias Pleroma.Web.Metadata | ||||
alias Pleroma.Web.MediaProxy | |||||
alias Pleroma.Web.Metadata.Providers.Provider | alias Pleroma.Web.Metadata.Providers.Provider | ||||
alias Pleroma.Web.Metadata.Utils | |||||
@behaviour Provider | @behaviour Provider | ||||
@@ -19,7 +17,7 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do | |||||
user: user | user: user | ||||
}) do | }) do | ||||
attachments = build_attachments(object) | attachments = build_attachments(object) | ||||
scrubbed_content = scrub_html_and_truncate(object) | |||||
scrubbed_content = Utils.scrub_html_and_truncate(object) | |||||
# Zero width space | # Zero width space | ||||
content = | content = | ||||
if scrubbed_content != "" and scrubbed_content != "\u200B" do | if scrubbed_content != "" and scrubbed_content != "\u200B" do | ||||
@@ -44,13 +42,14 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do | |||||
{:meta, | {:meta, | ||||
[ | [ | ||||
property: "og:description", | property: "og:description", | ||||
content: "#{user_name_string(user)}" <> content | |||||
content: "#{Utils.user_name_string(user)}" <> content | |||||
], []}, | ], []}, | ||||
{:meta, [property: "og:type", content: "website"], []} | {:meta, [property: "og:type", content: "website"], []} | ||||
] ++ | ] ++ | ||||
if attachments == [] or Metadata.activity_nsfw?(object) do | if attachments == [] or Metadata.activity_nsfw?(object) do | ||||
[ | [ | ||||
{:meta, [property: "og:image", content: attachment_url(User.avatar_url(user))], []}, | |||||
{:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], | |||||
[]}, | |||||
{:meta, [property: "og:image:width", content: 150], []}, | {:meta, [property: "og:image:width", content: 150], []}, | ||||
{:meta, [property: "og:image:height", content: 150], []} | {:meta, [property: "og:image:height", content: 150], []} | ||||
] | ] | ||||
@@ -61,17 +60,17 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do | |||||
@impl Provider | @impl Provider | ||||
def build_tags(%{user: user}) do | def build_tags(%{user: user}) do | ||||
with truncated_bio = scrub_html_and_truncate(user.bio || "") do | |||||
with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do | |||||
[ | [ | ||||
{:meta, | {:meta, | ||||
[ | [ | ||||
property: "og:title", | property: "og:title", | ||||
content: user_name_string(user) | |||||
content: Utils.user_name_string(user) | |||||
], []}, | ], []}, | ||||
{:meta, [property: "og:url", content: User.profile_url(user)], []}, | {:meta, [property: "og:url", content: User.profile_url(user)], []}, | ||||
{:meta, [property: "og:description", content: truncated_bio], []}, | {:meta, [property: "og:description", content: truncated_bio], []}, | ||||
{:meta, [property: "og:type", content: "website"], []}, | {:meta, [property: "og:type", content: "website"], []}, | ||||
{:meta, [property: "og:image", content: attachment_url(User.avatar_url(user))], []}, | |||||
{:meta, [property: "og:image", content: Utils.attachment_url(User.avatar_url(user))], []}, | |||||
{:meta, [property: "og:image:width", content: 150], []}, | {:meta, [property: "og:image:width", content: 150], []}, | ||||
{:meta, [property: "og:image:height", content: 150], []} | {:meta, [property: "og:image:height", content: 150], []} | ||||
] | ] | ||||
@@ -93,14 +92,15 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do | |||||
case media_type do | case media_type do | ||||
"audio" -> | "audio" -> | ||||
[ | [ | ||||
{:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])], []} | |||||
{:meta, | |||||
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []} | |||||
| acc | | acc | ||||
] | ] | ||||
"image" -> | "image" -> | ||||
[ | [ | ||||
{:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])], | |||||
[]}, | |||||
{:meta, | |||||
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []}, | |||||
{:meta, [property: "og:image:width", content: 150], []}, | {:meta, [property: "og:image:width", content: 150], []}, | ||||
{:meta, [property: "og:image:height", content: 150], []} | {:meta, [property: "og:image:height", content: 150], []} | ||||
| acc | | acc | ||||
@@ -108,7 +108,8 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do | |||||
"video" -> | "video" -> | ||||
[ | [ | ||||
{:meta, [property: "og:" <> media_type, content: attachment_url(url["href"])], []} | |||||
{:meta, | |||||
[property: "og:" <> media_type, content: Utils.attachment_url(url["href"])], []} | |||||
| acc | | acc | ||||
] | ] | ||||
@@ -120,37 +121,4 @@ defmodule Pleroma.Web.Metadata.Providers.OpenGraph do | |||||
acc ++ rendered_tags | acc ++ rendered_tags | ||||
end) | end) | ||||
end | end | ||||
defp scrub_html_and_truncate(%{data: %{"content" => content}} = object) do | |||||
content | |||||
# html content comes from DB already encoded, decode first and scrub after | |||||
|> HtmlEntities.decode() | |||||
|> String.replace(~r/<br\s?\/?>/, " ") | |||||
|> HTML.get_cached_stripped_html_for_object(object, __MODULE__) | |||||
|> Formatter.demojify() | |||||
|> Formatter.truncate() | |||||
end | |||||
defp scrub_html_and_truncate(content) when is_binary(content) do | |||||
content | |||||
# html content comes from DB already encoded, decode first and scrub after | |||||
|> HtmlEntities.decode() | |||||
|> String.replace(~r/<br\s?\/?>/, " ") | |||||
|> HTML.strip_tags() | |||||
|> Formatter.demojify() | |||||
|> Formatter.truncate() | |||||
end | |||||
defp attachment_url(url) do | |||||
MediaProxy.url(url) | |||||
end | |||||
defp user_name_string(user) do | |||||
"#{user.name} " <> | |||||
if user.local do | |||||
"(@#{user.nickname}@#{Pleroma.Web.Endpoint.host()})" | |||||
else | |||||
"(@#{user.nickname})" | |||||
end | |||||
end | |||||
end | end |
@@ -0,0 +1,21 @@ | |||||
defmodule Pleroma.Web.Metadata.PlayerView do | |||||
use Pleroma.Web, :view | |||||
import Phoenix.HTML.Tag, only: [content_tag: 3, tag: 2] | |||||
def render("player.html", %{"mediaType" => type, "href" => href}) do | |||||
{tag_type, tag_attrs} = | |||||
case type do | |||||
"audio" <> _ -> {:audio, []} | |||||
"video" <> _ -> {:video, [loop: true]} | |||||
end | |||||
content_tag( | |||||
tag_type, | |||||
[ | |||||
tag(:source, src: href, type: type), | |||||
"Your browser does not support #{type} playback." | |||||
], | |||||
[controls: true] ++ tag_attrs | |||||
) | |||||
end | |||||
end |
@@ -3,44 +3,120 @@ | |||||
# SPDX-License-Identifier: AGPL-3.0-only | # SPDX-License-Identifier: AGPL-3.0-only | ||||
defmodule Pleroma.Web.Metadata.Providers.TwitterCard do | defmodule Pleroma.Web.Metadata.Providers.TwitterCard do | ||||
alias Pleroma.Web.Metadata.Providers.Provider | |||||
alias Pleroma.User | |||||
alias Pleroma.Web.Metadata | alias Pleroma.Web.Metadata | ||||
alias Pleroma.Web.Metadata.Providers.Provider | |||||
alias Pleroma.Web.Metadata.Utils | |||||
@behaviour Provider | @behaviour Provider | ||||
@impl Provider | @impl Provider | ||||
def build_tags(%{object: object}) do | |||||
if Metadata.activity_nsfw?(object) or object.data["attachment"] == [] do | |||||
build_tags(nil) | |||||
else | |||||
case find_first_acceptable_media_type(object) do | |||||
"image" -> | |||||
[{:meta, [property: "twitter:card", content: "summary_large_image"], []}] | |||||
"audio" -> | |||||
[{:meta, [property: "twitter:card", content: "player"], []}] | |||||
"video" -> | |||||
[{:meta, [property: "twitter:card", content: "player"], []}] | |||||
_ -> | |||||
build_tags(nil) | |||||
def build_tags(%{ | |||||
activity_id: id, | |||||
object: object, | |||||
user: user | |||||
}) do | |||||
attachments = build_attachments(id, object) | |||||
scrubbed_content = Utils.scrub_html_and_truncate(object) | |||||
# Zero width space | |||||
content = | |||||
if scrubbed_content != "" and scrubbed_content != "\u200B" do | |||||
"“" <> scrubbed_content <> "”" | |||||
else | |||||
"" | |||||
end | |||||
[ | |||||
{:meta, | |||||
[ | |||||
property: "twitter:title", | |||||
content: Utils.user_name_string(user) | |||||
], []}, | |||||
{:meta, | |||||
[ | |||||
property: "twitter:description", | |||||
content: content | |||||
], []} | |||||
] ++ | |||||
if attachments == [] or Metadata.activity_nsfw?(object) do | |||||
[ | |||||
{:meta, | |||||
[property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], []}, | |||||
{:meta, [property: "twitter:card", content: "summary_large_image"], []} | |||||
] | |||||
else | |||||
attachments | |||||
end | end | ||||
end | |||||
end | end | ||||
@impl Provider | @impl Provider | ||||
def build_tags(_) do | |||||
[{:meta, [property: "twitter:card", content: "summary"], []}] | |||||
def build_tags(%{user: user}) do | |||||
with truncated_bio = Utils.scrub_html_and_truncate(user.bio || "") do | |||||
[ | |||||
{:meta, | |||||
[ | |||||
property: "twitter:title", | |||||
content: Utils.user_name_string(user) | |||||
], []}, | |||||
{:meta, [property: "twitter:description", content: truncated_bio], []}, | |||||
{:meta, [property: "twitter:image", content: Utils.attachment_url(User.avatar_url(user))], | |||||
[]}, | |||||
{:meta, [property: "twitter:card", content: "summary"], []} | |||||
] | |||||
end | |||||
end | end | ||||
def find_first_acceptable_media_type(%{data: %{"attachment" => attachment}}) do | |||||
Enum.find_value(attachment, fn attachment -> | |||||
Enum.find_value(attachment["url"], fn url -> | |||||
Enum.find(["image", "audio", "video"], fn media_type -> | |||||
String.starts_with?(url["mediaType"], media_type) | |||||
defp build_attachments(id, %{data: %{"attachment" => attachments}}) do | |||||
Enum.reduce(attachments, [], fn attachment, acc -> | |||||
rendered_tags = | |||||
Enum.reduce(attachment["url"], [], fn url, acc -> | |||||
media_type = | |||||
Enum.find(["image", "audio", "video"], fn media_type -> | |||||
String.starts_with?(url["mediaType"], media_type) | |||||
end) | |||||
# TODO: Add additional properties to objects when we have the data available. | |||||
case media_type do | |||||
"audio" -> | |||||
[ | |||||
{:meta, [property: "twitter:card", content: "player"], []}, | |||||
{:meta, [property: "twitter:player:width", content: "480"], []}, | |||||
{:meta, [property: "twitter:player:height", content: "80"], []}, | |||||
{:meta, [property: "twitter:player", content: player_url(id)], []} | |||||
| acc | |||||
] | |||||
"image" -> | |||||
[ | |||||
{:meta, [property: "twitter:card", content: "summary_large_image"], []}, | |||||
{:meta, | |||||
[ | |||||
property: "twitter:player", | |||||
content: Utils.attachment_url(url["href"]) | |||||
], []} | |||||
| acc | |||||
] | |||||
# TODO: Need the true width and height values here or Twitter renders an iFrame with a bad aspect ratio | |||||
"video" -> | |||||
[ | |||||
{:meta, [property: "twitter:card", content: "player"], []}, | |||||
{:meta, [property: "twitter:player", content: player_url(id)], []}, | |||||
{:meta, [property: "twitter:player:width", content: "480"], []}, | |||||
{:meta, [property: "twitter:player:height", content: "480"], []} | |||||
| acc | |||||
] | |||||
_ -> | |||||
acc | |||||
end | |||||
end) | end) | ||||
end) | |||||
acc ++ rendered_tags | |||||
end) | end) | ||||
end | end | ||||
defp player_url(id) do | |||||
Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id) | |||||
end | |||||
end | end |
@@ -0,0 +1,42 @@ | |||||
# Pleroma: A lightweight social networking server | |||||
# Copyright \xc2\xa9 2017-2019 Pleroma Authors <https://pleroma.social/> | |||||
# SPDX-License-Identifier: AGPL-3.0-only | |||||
defmodule Pleroma.Web.Metadata.Utils do | |||||
alias Pleroma.HTML | |||||
alias Pleroma.Formatter | |||||
alias Pleroma.Web.MediaProxy | |||||
def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do | |||||
content | |||||
# html content comes from DB already encoded, decode first and scrub after | |||||
|> HtmlEntities.decode() | |||||
|> String.replace(~r/<br\s?\/?>/, " ") | |||||
|> HTML.get_cached_stripped_html_for_object(object, __MODULE__) | |||||
|> Formatter.demojify() | |||||
|> Formatter.truncate() | |||||
end | |||||
def scrub_html_and_truncate(content) when is_binary(content) do | |||||
content | |||||
# html content comes from DB already encoded, decode first and scrub after | |||||
|> HtmlEntities.decode() | |||||
|> String.replace(~r/<br\s?\/?>/, " ") | |||||
|> HTML.strip_tags() | |||||
|> Formatter.demojify() | |||||
|> Formatter.truncate() | |||||
end | |||||
def attachment_url(url) do | |||||
MediaProxy.url(url) | |||||
end | |||||
def user_name_string(user) do | |||||
"#{user.name} " <> | |||||
if user.local do | |||||
"(@#{user.nickname}@#{Pleroma.Web.Endpoint.host()})" | |||||
else | |||||
"(@#{user.nickname})" | |||||
end | |||||
end | |||||
end |
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do | |||||
alias Pleroma.Object | alias Pleroma.Object | ||||
alias Pleroma.User | alias Pleroma.User | ||||
alias Pleroma.Web.ActivityPub.ActivityPub | alias Pleroma.Web.ActivityPub.ActivityPub | ||||
alias Pleroma.Web.ActivityPub.Visibility | |||||
alias Pleroma.Web.ActivityPub.ActivityPubController | alias Pleroma.Web.ActivityPub.ActivityPubController | ||||
alias Pleroma.Web.ActivityPub.ObjectView | alias Pleroma.Web.ActivityPub.ObjectView | ||||
alias Pleroma.Web.OStatus.ActivityRepresenter | alias Pleroma.Web.OStatus.ActivityRepresenter | ||||
@@ -102,7 +103,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do | |||||
else | else | ||||
with id <- o_status_url(conn, :object, uuid), | with id <- o_status_url(conn, :object, uuid), | ||||
{_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id(id)}, | {_, %Activity{} = activity} <- {:activity, Activity.get_create_by_object_ap_id(id)}, | ||||
{_, true} <- {:public?, ActivityPub.is_public?(activity)}, | |||||
{_, true} <- {:public?, Visibility.is_public?(activity)}, | |||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do | %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do | ||||
case get_format(conn) do | case get_format(conn) do | ||||
"html" -> redirect(conn, to: "/notice/#{activity.id}") | "html" -> redirect(conn, to: "/notice/#{activity.id}") | ||||
@@ -127,7 +128,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do | |||||
else | else | ||||
with id <- o_status_url(conn, :activity, uuid), | with id <- o_status_url(conn, :activity, uuid), | ||||
{_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, | {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)}, | ||||
{_, true} <- {:public?, ActivityPub.is_public?(activity)}, | |||||
{_, true} <- {:public?, Visibility.is_public?(activity)}, | |||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do | %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do | ||||
case format = get_format(conn) do | case format = get_format(conn) do | ||||
"html" -> redirect(conn, to: "/notice/#{activity.id}") | "html" -> redirect(conn, to: "/notice/#{activity.id}") | ||||
@@ -148,7 +149,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do | |||||
def notice(conn, %{"id" => id}) do | def notice(conn, %{"id" => id}) do | ||||
with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(id)}, | with {_, %Activity{} = activity} <- {:activity, Activity.get_by_id(id)}, | ||||
{_, true} <- {:public?, ActivityPub.is_public?(activity)}, | |||||
{_, true} <- {:public?, Visibility.is_public?(activity)}, | |||||
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do | %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do | ||||
case format = get_format(conn) do | case format = get_format(conn) do | ||||
"html" -> | "html" -> | ||||
@@ -156,6 +157,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do | |||||
%Object{} = object = Object.normalize(activity.data["object"]) | %Object{} = object = Object.normalize(activity.data["object"]) | ||||
Fallback.RedirectController.redirector_with_meta(conn, %{ | Fallback.RedirectController.redirector_with_meta(conn, %{ | ||||
activity_id: activity.id, | |||||
object: object, | object: object, | ||||
url: | url: | ||||
Pleroma.Web.Router.Helpers.o_status_url( | Pleroma.Web.Router.Helpers.o_status_url( | ||||
@@ -187,6 +189,30 @@ defmodule Pleroma.Web.OStatus.OStatusController do | |||||
end | end | ||||
end | end | ||||
# Returns an HTML embedded <audio> or <video> player suitable for embed iframes. | |||||
def notice_player(conn, %{"id" => id}) do | |||||
with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id), | |||||
true <- Visibility.is_public?(activity), | |||||
%Object{} = object <- Object.normalize(activity.data["object"]), | |||||
%{data: %{"attachment" => [%{"url" => [url | _]} | _]}} <- object, | |||||
true <- String.starts_with?(url["mediaType"], ["audio", "video"]) do | |||||
conn | |||||
|> put_layout(:metadata_player) | |||||
|> put_resp_header("x-frame-options", "ALLOW") | |||||
|> put_resp_header( | |||||
"content-security-policy", | |||||
"default-src 'none';style-src 'self' 'unsafe-inline';img-src 'self' data: https:; media-src 'self' https:;" | |||||
) | |||||
|> put_view(Pleroma.Web.Metadata.PlayerView) | |||||
|> render("player.html", url) | |||||
else | |||||
_error -> | |||||
conn | |||||
|> put_status(404) | |||||
|> Fallback.RedirectController.redirector(nil, 404) | |||||
end | |||||
end | |||||
defp represent_activity( | defp represent_activity( | ||||
conn, | conn, | ||||
"activity+json", | "activity+json", | ||||
@@ -505,6 +505,7 @@ defmodule Pleroma.Web.Router do | |||||
get("/objects/:uuid", OStatus.OStatusController, :object) | get("/objects/:uuid", OStatus.OStatusController, :object) | ||||
get("/activities/:uuid", OStatus.OStatusController, :activity) | get("/activities/:uuid", OStatus.OStatusController, :activity) | ||||
get("/notice/:id", OStatus.OStatusController, :notice) | get("/notice/:id", OStatus.OStatusController, :notice) | ||||
get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player) | |||||
get("/users/:nickname/feed", OStatus.OStatusController, :feed) | get("/users/:nickname/feed", OStatus.OStatusController, :feed) | ||||
get("/users/:nickname", OStatus.OStatusController, :feed_redirect) | get("/users/:nickname", OStatus.OStatusController, :feed_redirect) | ||||
@@ -10,7 +10,7 @@ defmodule Pleroma.Web.Streamer do | |||||
alias Pleroma.Activity | alias Pleroma.Activity | ||||
alias Pleroma.Object | alias Pleroma.Object | ||||
alias Pleroma.Repo | alias Pleroma.Repo | ||||
alias Pleroma.Web.ActivityPub.ActivityPub | |||||
alias Pleroma.Web.ActivityPub.Visibility | |||||
@keepalive_interval :timer.seconds(30) | @keepalive_interval :timer.seconds(30) | ||||
@@ -73,7 +73,7 @@ defmodule Pleroma.Web.Streamer do | |||||
def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do | def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do | ||||
# filter the recipient list if the activity is not public, see #270. | # filter the recipient list if the activity is not public, see #270. | ||||
recipient_lists = | recipient_lists = | ||||
case ActivityPub.is_public?(item) do | |||||
case Visibility.is_public?(item) do | |||||
true -> | true -> | ||||
Pleroma.List.get_lists_from_activity(item) | Pleroma.List.get_lists_from_activity(item) | ||||
@@ -82,7 +82,7 @@ defmodule Pleroma.Web.Streamer do | |||||
|> Enum.filter(fn list -> | |> Enum.filter(fn list -> | ||||
owner = Repo.get(User, list.user_id) | owner = Repo.get(User, list.user_id) | ||||
ActivityPub.visible_for_user?(item, owner) | |||||
Visibility.visible_for_user?(item, owner) | |||||
end) | end) | ||||
end | end | ||||
@@ -0,0 +1,16 @@ | |||||
<!DOCTYPE html> | |||||
<html> | |||||
<body> | |||||
<style type="text/css"> | |||||
video, audio { | |||||
width:100%; | |||||
max-width:600px; | |||||
height: auto; | |||||
} | |||||
</style> | |||||
<%= render @view_module, @view_template, assigns %> | |||||
</body> | |||||
</html> |
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||||
alias Pleroma.{Repo, Activity, Object, User, Notification} | alias Pleroma.{Repo, Activity, Object, User, Notification} | ||||
alias Pleroma.Web.OAuth.Token | alias Pleroma.Web.OAuth.Token | ||||
alias Pleroma.Web.ActivityPub.ActivityPub | alias Pleroma.Web.ActivityPub.ActivityPub | ||||
alias Pleroma.Web.ActivityPub.Visibility | |||||
alias Pleroma.Web.ActivityPub.Utils | alias Pleroma.Web.ActivityPub.Utils | ||||
alias Pleroma.Web.CommonAPI | alias Pleroma.Web.CommonAPI | ||||
alias Pleroma.Web.TwitterAPI.ActivityView | alias Pleroma.Web.TwitterAPI.ActivityView | ||||
@@ -268,7 +269,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||||
def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do | def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do | ||||
with %Activity{} = activity <- Repo.get(Activity, id), | with %Activity{} = activity <- Repo.get(Activity, id), | ||||
true <- ActivityPub.visible_for_user?(activity, user) do | |||||
true <- Visibility.visible_for_user?(activity, user) do | |||||
conn | conn | ||||
|> put_view(ActivityView) | |> put_view(ActivityView) | ||||
|> render("activity.json", %{activity: activity, for: user}) | |> render("activity.json", %{activity: activity, for: user}) | ||||
@@ -34,7 +34,7 @@ | |||||
"jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, | "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, | ||||
"makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, | "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, | ||||
"makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, | "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, | ||||
"meck": {:hex, :meck, "0.8.9", "64c5c0bd8bcca3a180b44196265c8ed7594e16bcc845d0698ec6b4e577f48188", [:rebar3], [], "hexpm"}, | |||||
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm"}, | |||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm"}, | ||||
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, | "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"}, | ||||
"mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], [], "hexpm"}, | ||||
@@ -0,0 +1,11 @@ | |||||
defmodule Pleroma.Repo.Migrations.DataMigrationNormalizeScopes do | |||||
use Ecto.Migration | |||||
def up do | |||||
for t <- [:apps, :oauth_authorizations, :oauth_tokens] do | |||||
execute "UPDATE #{t} SET scopes = string_to_array(array_to_string(scopes, ' '), ' ');" | |||||
end | |||||
end | |||||
def down, do: :noop | |||||
end |
@@ -304,6 +304,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do | |||||
end | end | ||||
describe "/users/:nickname/outbox" do | describe "/users/:nickname/outbox" do | ||||
test "it will not bomb when there is no activity", %{conn: conn} do | |||||
user = insert(:user) | |||||
conn = | |||||
conn | |||||
|> put_req_header("accept", "application/activity+json") | |||||
|> get("/users/#{user.nickname}/outbox") | |||||
result = json_response(conn, 200) | |||||
assert user.ap_id <> "/outbox" == result["id"] | |||||
end | |||||
test "it returns a note activity in a collection", %{conn: conn} do | test "it returns a note activity in a collection", %{conn: conn} do | ||||
note_activity = insert(:note_activity) | note_activity = insert(:note_activity) | ||||
user = User.get_cached_by_ap_id(note_activity.data["actor"]) | user = User.get_cached_by_ap_id(note_activity.data["actor"]) | ||||
@@ -0,0 +1,98 @@ | |||||
defmodule Pleroma.Web.ActivityPub.VisibilityTest do | |||||
use Pleroma.DataCase | |||||
alias Pleroma.Web.CommonAPI | |||||
alias Pleroma.Web.ActivityPub.Visibility | |||||
import Pleroma.Factory | |||||
setup do | |||||
user = insert(:user) | |||||
mentioned = insert(:user) | |||||
following = insert(:user) | |||||
unrelated = insert(:user) | |||||
{:ok, following} = Pleroma.User.follow(following, user) | |||||
{:ok, public} = | |||||
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "public"}) | |||||
{:ok, private} = | |||||
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "private"}) | |||||
{:ok, direct} = | |||||
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "direct"}) | |||||
{:ok, unlisted} = | |||||
CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "unlisted"}) | |||||
%{ | |||||
public: public, | |||||
private: private, | |||||
direct: direct, | |||||
unlisted: unlisted, | |||||
user: user, | |||||
mentioned: mentioned, | |||||
following: following, | |||||
unrelated: unrelated | |||||
} | |||||
end | |||||
test "is_direct?", %{public: public, private: private, direct: direct, unlisted: unlisted} do | |||||
assert Visibility.is_direct?(direct) | |||||
refute Visibility.is_direct?(public) | |||||
refute Visibility.is_direct?(private) | |||||
refute Visibility.is_direct?(unlisted) | |||||
end | |||||
test "is_public?", %{public: public, private: private, direct: direct, unlisted: unlisted} do | |||||
refute Visibility.is_public?(direct) | |||||
assert Visibility.is_public?(public) | |||||
refute Visibility.is_public?(private) | |||||
assert Visibility.is_public?(unlisted) | |||||
end | |||||
test "is_private?", %{public: public, private: private, direct: direct, unlisted: unlisted} do | |||||
refute Visibility.is_private?(direct) | |||||
refute Visibility.is_private?(public) | |||||
assert Visibility.is_private?(private) | |||||
refute Visibility.is_private?(unlisted) | |||||
end | |||||
test "visible_for_user?", %{ | |||||
public: public, | |||||
private: private, | |||||
direct: direct, | |||||
unlisted: unlisted, | |||||
user: user, | |||||
mentioned: mentioned, | |||||
following: following, | |||||
unrelated: unrelated | |||||
} do | |||||
# All visible to author | |||||
assert Visibility.visible_for_user?(public, user) | |||||
assert Visibility.visible_for_user?(private, user) | |||||
assert Visibility.visible_for_user?(unlisted, user) | |||||
assert Visibility.visible_for_user?(direct, user) | |||||
# All visible to a mentioned user | |||||
assert Visibility.visible_for_user?(public, mentioned) | |||||
assert Visibility.visible_for_user?(private, mentioned) | |||||
assert Visibility.visible_for_user?(unlisted, mentioned) | |||||
assert Visibility.visible_for_user?(direct, mentioned) | |||||
# DM not visible for just follower | |||||
assert Visibility.visible_for_user?(public, following) | |||||
assert Visibility.visible_for_user?(private, following) | |||||
assert Visibility.visible_for_user?(unlisted, following) | |||||
refute Visibility.visible_for_user?(direct, following) | |||||
# Public and unlisted visible for unrelated user | |||||
assert Visibility.visible_for_user?(public, unrelated) | |||||
assert Visibility.visible_for_user?(unlisted, unrelated) | |||||
refute Visibility.visible_for_user?(private, unrelated) | |||||
refute Visibility.visible_for_user?(direct, unrelated) | |||||
end | |||||
end |
@@ -1744,6 +1744,18 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||||
} | } | ||||
} | } | ||||
# works with private posts | |||||
{:ok, activity} = | |||||
CommonAPI.post(user, %{"status" => "http://example.com/ogp", "visibility" => "direct"}) | |||||
response_two = | |||||
conn | |||||
|> assign(:user, user) | |||||
|> get("/api/v1/statuses/#{activity.id}/card") | |||||
|> json_response(200) | |||||
assert response_two == response | |||||
Pleroma.Config.put([:rich_media, :enabled], false) | Pleroma.Config.put([:rich_media, :enabled], false) | ||||
end | end | ||||
end | end | ||||