Commit f2fa09c5 authored by kaniini's avatar kaniini

Merge branch 'develop' into 'patch-2'

# Conflicts:
#   mix.exs
parents 718882b3 5fbb14f5
......@@ -13,6 +13,21 @@ Instead, overload the settings by editing the following files:
* `dev.secret.exs`: custom additional configuration for `MIX_ENV=dev`
* `prod.secret.exs`: custom additional configuration for `MIX_ENV=prod`
## Uploads configuration
To configure where to upload files, and wether or not
you want to remove automatically EXIF data from pictures
being uploaded.
config :pleroma, Pleroma.Upload,
uploads: "uploads",
strip_exif: false
* `uploads`: where to put the uploaded files, relative to pleroma's main directory.
* `strip_exif`: whether or not to remove EXIF data from uploaded pics automatically.
This needs Imagemagick installed on the system ( apt install imagemagick ).
## Block functionality
config :pleroma, :activitypub,
......
......@@ -10,7 +10,11 @@ config :pleroma, ecto_repos: [Pleroma.Repo]
config :pleroma, Pleroma.Repo, types: Pleroma.PostgresTypes
config :pleroma, Pleroma.Upload, uploads: "uploads"
config :pleroma, Pleroma.Upload,
uploads: "uploads",
strip_exif: false
config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"]
# Configures the endpoint
config :pleroma, Pleroma.Web.Endpoint,
......@@ -50,6 +54,7 @@ config :pleroma, :instance,
version: version,
name: "Pleroma",
email: "example@example.com",
description: "A Pleroma instance, an alternative fediverse server",
limit: 5000,
upload_limit: 16_000_000,
registrations_open: true,
......@@ -58,6 +63,19 @@ config :pleroma, :instance,
public: true,
quarantined_instances: []
config :pleroma, :fe,
theme: "pleroma-dark",
logo: "/static/logo.png",
background: "/static/aurora_borealis.jpg",
redirect_root_no_login: "/main/all",
redirect_root_login: "/main/friends",
show_instance_panel: true,
show_who_to_follow_panel: false,
who_to_follow_provider:
"https://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-osa-api.cgi?{{host}}+{{user}}",
who_to_follow_link: "https://vinayaka.distsn.org/?{{host}}+{{user}}",
scope_options_enabled: false
config :pleroma, :activitypub,
accept_blocks: true,
unfollow_blocked: true,
......@@ -93,6 +111,13 @@ config :pleroma, :gopher,
ip: {0, 0, 0, 0},
port: 9999
config :pleroma, :suggestions,
enabled: false,
third_party_engine:
"http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}",
timeout: 300_000,
web: "https://vinayaka.distsn.org/?{{host}}+{{user}}"
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"
defmodule Mix.Tasks.GenerateInviteToken do
use Mix.Task
@shortdoc "Generate invite token for user"
def run([]) do
Mix.Task.run("app.start")
with {:ok, token} <- Pleroma.UserInviteToken.create_token() do
IO.puts("Generated user invite token")
IO.puts(
"Url: #{
Pleroma.Web.Router.Helpers.redirect_url(
Pleroma.Web.Endpoint,
:registration_page,
token.token
)
}"
)
else
_ ->
IO.puts("Error creating token")
end
end
end
......@@ -78,4 +78,8 @@ defmodule Pleroma.Activity do
end
def get_create_activity_by_object_ap_id(_), do: nil
def normalize(obj) when is_map(obj), do: Activity.get_by_ap_id(obj["id"])
def normalize(ap_id) when is_binary(ap_id), do: Activity.get_by_ap_id(ap_id)
def normalize(_), do: nil
end
......@@ -116,7 +116,28 @@ defmodule Pleroma.Formatter do
_ -> []
end)
@emoji @finmoji_with_filenames ++ @emoji_from_file
@emoji_from_globs (
static_path = Path.join(:code.priv_dir(:pleroma), "static")
globs =
Application.get_env(:pleroma, :emoji, [])
|> Keyword.get(:shortcode_globs, [])
paths =
Enum.map(globs, fn glob ->
Path.join(static_path, glob)
|> Path.wildcard()
end)
|> Enum.concat()
Enum.map(paths, fn path ->
shortcode = Path.basename(path, Path.extname(path))
external_path = Path.join("/", Path.relative_to(path, static_path))
{shortcode, external_path}
end)
)
@emoji @finmoji_with_filenames ++ @emoji_from_globs ++ @emoji_from_file
def emojify(text, emoji \\ @emoji)
def emojify(text, nil), do: text
......@@ -223,8 +244,8 @@ defmodule Pleroma.Formatter do
subs =
subs ++
Enum.map(tags, fn {_, tag, uuid} ->
url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>"
Enum.map(tags, fn {tag_text, tag, uuid} ->
url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{tag_text}</a>"
{uuid, url}
end)
......
......@@ -54,7 +54,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
String.split(text, "\r")
|> Enum.map(fn text ->
"i#{text}\tfake\(NULL)\t0\r\n"
"i#{text}\tfake\t(NULL)\t0\r\n"
end)
|> Enum.join("")
end
......@@ -77,14 +77,14 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
info("#{like_count} likes, #{announcement_count} repeats") <>
"\r\n" <>
"i\tfake\t(NULL)\t0\r\n" <>
info(
HtmlSanitizeEx.strip_tags(
String.replace(activity.data["object"]["content"], "<br>", "\r")
)
)
end)
|> Enum.join("\r\n")
|> Enum.join("i\tfake\t(NULL)\t0\r\n")
end
def response("") do
......
......@@ -27,6 +27,10 @@ defmodule Pleroma.Object do
Repo.one(from(object in Object, where: fragment("(?)->>'id' = ?", object.data, ^ap_id)))
end
def normalize(obj) when is_map(obj), do: Object.get_by_ap_id(obj["id"])
def normalize(ap_id) when is_binary(ap_id), do: Object.get_by_ap_id(ap_id)
def normalize(_), do: nil
def get_cached_by_ap_id(ap_id) do
if Mix.env() == :test do
get_by_ap_id(ap_id)
......
defmodule Pleroma.Web.Plugs.DigestPlug do
alias Plug.Conn
require Logger
def read_body(conn, opts) do
{:ok, body, conn} = Conn.read_body(conn, opts)
digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64())
{:ok, body, Conn.assign(conn, :digest, digest)}
end
end
......@@ -19,6 +19,8 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
cond do
signature && String.contains?(signature, user) ->
# set (request-target) header to the appropriate value
# we also replace the digest header with the one we computed
conn =
conn
|> put_req_header(
......@@ -26,6 +28,14 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
String.downcase("#{conn.method}") <> " #{conn.request_path}"
)
conn =
if conn.assigns[:digest] do
conn
|> put_req_header("digest", conn.assigns[:digest])
else
conn
end
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
signature ->
......
......@@ -18,8 +18,10 @@ defmodule Pleroma.Upload do
File.cp!(file.path, result_file)
end
strip_exif_data(content_type, result_file)
%{
"type" => "Image",
"type" => "Document",
"url" => [
%{
"type" => "Link",
......@@ -67,6 +69,8 @@ defmodule Pleroma.Upload do
File.rename(uuidpath, result_file)
end
strip_exif_data(content_type, result_file)
%{
"type" => "Image",
"url" => [
......@@ -80,6 +84,16 @@ defmodule Pleroma.Upload do
}
end
def strip_exif_data(content_type, file) do
settings = Application.get_env(:pleroma, Pleroma.Upload)
do_strip = Keyword.fetch!(settings, :strip_exif)
[filetype, ext] = String.split(content_type, "/")
if filetype == "image" and do_strip == true do
Mogrify.open(file) |> Mogrify.custom("strip") |> Mogrify.save(in_place: true)
end
end
def upload_path do
settings = Application.get_env(:pleroma, Pleroma.Upload)
Keyword.fetch!(settings, :uploads)
......@@ -110,20 +124,20 @@ defmodule Pleroma.Upload do
if should_dedupe do
create_name(uuid, List.last(String.split(file.filename, ".")), type)
else
unless String.contains?(file.filename, ".") do
case type do
"image/png" -> file.filename <> ".png"
"image/jpeg" -> file.filename <> ".jpg"
"image/gif" -> file.filename <> ".gif"
"video/webm" -> file.filename <> ".webm"
"video/mp4" -> file.filename <> ".mp4"
"audio/mpeg" -> file.filename <> ".mp3"
"audio/ogg" -> file.filename <> ".ogg"
"audio/wav" -> file.filename <> ".wav"
_ -> file.filename
parts = String.split(file.filename, ".")
new_filename =
if length(parts) > 1 do
Enum.drop(parts, -1) |> Enum.join(".")
else
Enum.join(parts)
end
else
file.filename
case type do
"application/octet-stream" -> file.filename
"audio/mpeg" -> new_filename <> ".mp3"
"image/jpeg" -> new_filename <> ".jpg"
_ -> Enum.join([new_filename, String.split(type, "/") |> List.last()], ".")
end
end
end
......
......@@ -398,6 +398,7 @@ defmodule Pleroma.User do
Enum.map(reqs, fn req -> req.actor end)
|> Enum.uniq()
|> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
|> Enum.filter(fn u -> !following?(u, user) end)
{:ok, users}
end
......@@ -607,7 +608,7 @@ defmodule Pleroma.User do
|> Enum.each(fn activity ->
case activity.data["type"] do
"Create" ->
ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
ActivityPub.delete(Object.normalize(activity.data["object"]))
# TODO: Do something with likes, follows, repeats.
_ ->
......
defmodule Pleroma.UserInviteToken do
use Ecto.Schema
import Ecto.Changeset
alias Pleroma.{User, UserInviteToken, Repo}
schema "user_invite_tokens" do
field(:token, :string)
field(:used, :boolean, default: false)
timestamps()
end
def create_token do
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
token = %UserInviteToken{
used: false,
token: token
}
Repo.insert(token)
end
def used_changeset(struct) do
struct
|> cast(%{}, [])
|> put_change(:used, true)
end
def mark_as_used(token) do
with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}),
{:ok, token} <- Repo.update(used_changeset(token)) do
{:ok, token}
else
_e -> {:error, token}
end
end
end
......@@ -30,7 +30,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def insert(map, local \\ true) when is_map(map) do
with nil <- Activity.get_by_ap_id(map["id"]),
with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map),
:ok <- check_actor_is_active(map["actor"]),
{:ok, map} <- MRF.filter(map),
......@@ -641,13 +641,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Logger.info("Federating #{id} to #{inbox}")
host = URI.parse(inbox).host
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
signature =
Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
Pleroma.Web.HTTPSignatures.sign(actor, %{
host: host,
"content-length": byte_size(json),
digest: digest
})
@httpoison.post(
inbox,
json,
[{"Content-Type", "application/activity+json"}, {"signature", signature}],
[
{"Content-Type", "application/activity+json"},
{"signature", signature},
{"digest", digest}
],
hackney: [pool: :default]
)
end
......@@ -670,7 +680,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
recv_timeout: 20000
),
{:ok, data} <- Jason.decode(body),
nil <- Object.get_by_ap_id(data["id"]),
nil <- Object.normalize(data),
params <- %{
"type" => "Create",
"to" => data["to"],
......@@ -679,7 +689,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"object" => data
},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
{:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
{:ok, Object.normalize(activity.data["object"])}
else
object = %Object{} ->
{:ok, object}
......@@ -688,7 +698,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
case OStatus.fetch_activity_from_url(id) do
{:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
{:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
e -> e
end
end
......
......@@ -13,18 +13,58 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
require Logger
def get_actor(%{"actor" => actor}) when is_binary(actor) do
actor
end
def get_actor(%{"actor" => actor}) when is_list(actor) do
Enum.at(actor, 0)
end
def get_actor(%{"actor" => actor}) when is_map(actor) do
actor["id"]
end
def get_actor(%{"actor" => actor_list}) do
Enum.find(actor_list, fn %{"type" => type} -> type == "Person" end)
|> Map.get("id")
end
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
"""
def fix_object(object) do
object
|> Map.put("actor", object["attributedTo"])
|> fix_actor
|> fix_attachments
|> fix_context
|> fix_in_reply_to
|> fix_emoji
|> fix_tag
|> fix_content_map
|> fix_addressing
end
def fix_addressing_list(map, field) do
if is_binary(map[field]) do
map
|> Map.put(field, [map[field]])
else
map
end
end
def fix_addressing(map) do
map
|> fix_addressing_list("to")
|> fix_addressing_list("cc")
|> fix_addressing_list("bto")
|> fix_addressing_list("bcc")
end
def fix_actor(%{"attributedTo" => actor} = object) do
object
|> Map.put("actor", get_actor(%{"actor" => actor}))
end
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
......@@ -122,7 +162,14 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# TODO: validate those with a Ecto scheme
# - tags
# - emoji
def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
when objtype in ["Article", "Note"] do
actor = get_actor(data)
data =
Map.put(data, "actor", actor)
|> fix_addressing
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
object = fix_object(data["object"])
......@@ -412,7 +459,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(_), do: :error
def get_obj_helper(id) do
if object = Object.get_by_ap_id(id), do: {:ok, object}, else: nil
if object = Object.normalize(id), do: {:ok, object}, else: nil
end
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
......@@ -460,14 +507,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
# because of course it does.
def prepare_outgoing(%{"type" => "Accept"} = data) do
follow_activity_id =
if is_binary(data["object"]) do
data["object"]
else
data["object"]["id"]
end
with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
with follow_activity <- Activity.normalize(data["object"]) do
object = %{
"actor" => follow_activity.actor,
"object" => follow_activity.data["object"],
......@@ -485,14 +525,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def prepare_outgoing(%{"type" => "Reject"} = data) do
follow_activity_id =
if is_binary(data["object"]) do
data["object"]
else
data["object"]["id"]
end
with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
with follow_activity <- Activity.normalize(data["object"]) do
object = %{
"actor" => follow_activity.actor,
"object" => follow_activity.data["object"],
......
......@@ -128,7 +128,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Inserts a full object if it is contained in an activity.
"""
def insert_full_object(%{"object" => %{"type" => type} = object_data})
when is_map(object_data) and type in ["Note"] do
when is_map(object_data) and type in ["Article", "Note"] do
with {:ok, _} <- Object.create(object_data) do
:ok
end
......
......@@ -12,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("user.json", %{user: user}) do
{:ok, user} = WebFinger.ensure_keys_present(user)
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
%{
......@@ -98,9 +98,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
info = User.user_info(user)
params = %{
"type" => ["Create", "Announce"],
"actor_id" => user.ap_id,
"whole_db" => true,
"limit" => "10"
}
......@@ -111,10 +108,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
params
end
activities = ActivityPub.fetch_public_activities(params)
min_id = Enum.at(activities, 0).id
activities = Enum.reverse(activities)
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 =
......
......@@ -7,7 +7,7 @@ defmodule Pleroma.Web.CommonAPI do
def delete(activity_id, user) do
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
%Object{} = object <- Object.get_by_ap_id(object_id),
%Object{} = object <- Object.normalize(object_id),
true <- user.info["is_moderator"] || user.ap_id == object.data["actor"],
{:ok, delete} <- ActivityPub.delete(object) do