Commit dec23500 authored by rinpatch's avatar rinpatch

Resolve merge conflict

parents ab3089d6 dfde2622
......@@ -8,6 +8,7 @@ variables:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
DB_HOST: postgres
MIX_ENV: test
cache:
key: ${CI_COMMIT_REF_SLUG}
......@@ -23,15 +24,15 @@ before_script:
- mix local.rebar --force
- mix deps.get
- mix compile --force
- MIX_ENV=test mix ecto.create
- MIX_ENV=test mix ecto.migrate
- mix ecto.create
- mix ecto.migrate
lint:
stage: lint
script:
- MIX_ENV=test mix format --check-formatted
- mix format --check-formatted
unit-testing:
stage: test
script:
- MIX_ENV=test mix test --trace
- mix test --trace
......@@ -30,7 +30,7 @@ While we don't provide docker files, other people have written very good ones. T
### Dependencies
* Postgresql version 9.6 or newer
* Elixir version 1.5 or newer. If your distribution only has an old version available, check [Elixir's install page](https://elixir-lang.org/install.html) or use a tool like [asdf](https://github.com/asdf-vm/asdf).
* Elixir version 1.7 or newer. If your distribution only has an old version available, check [Elixir's install page](https://elixir-lang.org/install.html) or use a tool like [asdf](https://github.com/asdf-vm/asdf).
* Build-essential tools
### Configuration
......
......@@ -98,7 +98,8 @@
name: "Pleroma",
email: "example@example.com",
description: "A Pleroma instance, an alternative fediverse server",
limit: 5000,
limit: 5_000,
remote_limit: 100_000,
upload_limit: 16_000_000,
avatar_upload_limit: 2_000_000,
background_upload_limit: 4_000_000,
......@@ -137,8 +138,8 @@
logo_mask: true,
logo_margin: "0.1em",
background: "/static/aurora_borealis.jpg",
redirect_root_no_login: "/~/main/all",
redirect_root_login: "/~/main/friends",
redirect_root_no_login: "/main/all",
redirect_root_login: "/main/friends",
show_instance_panel: true,
scope_options_enabled: false,
formatting_options_enabled: false,
......@@ -220,6 +221,37 @@
credentials: true,
headers: ["Authorization", "Content-Type", "Idempotency-Key"]
config :pleroma, Pleroma.User,
restricted_nicknames: [
"about",
"~",
"main",
"users",
"settings",
"objects",
"activities",
"web",
"registration",
"friend-requests",
"pleroma",
"api",
"tag",
"notice",
"status",
"user-search",
"ostatus_subscribe",
"oauth",
"push",
"relay",
"inbox",
".well-known",
"nodeinfo",
"auth",
"proxy",
"dev",
"internal"
]
# 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"
......@@ -63,6 +63,7 @@ config :pleroma, Pleroma.Mailer,
* `email`: Email used to reach an Administrator/Moderator of the instance
* `description`: The instance’s description, can be seen in nodeinfo and ``/api/v1/instance``
* `limit`: Posts character limit (CW/Subject included in the counter)
* `remote_limit`: Hard character limit beyond which remote posts will be dropped.
* `upload_limit`: File size limit of uploads (except for avatar, background, banner)
* `avatar_upload_limit`: File size limit of user’s profile avatars
* `background_upload_limit`: File size limit of user’s profile backgrounds
......
......@@ -21,6 +21,8 @@ ProtectSystem=full
PrivateDevices=false
; Ensures that the service process and all its children can never gain new privileges through execve().
NoNewPrivileges=true
; Drops the sysadmin capability from the daemon.
CapabilityBoundingSet=~CAP_SYS_ADMIN
[Install]
WantedBy=multi-user.target
......@@ -75,7 +75,7 @@ def run(["gen" | rest]) do
name =
Common.get_option(
options,
:name,
:instance_name,
"What is the name of your instance? (e.g. Pleroma/Soykaf)"
)
......
......@@ -7,6 +7,8 @@ defmodule Pleroma.Activity do
alias Pleroma.{Repo, Activity, Notification}
import Ecto.Query
@type t :: %__MODULE__{}
# https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
@mastodon_notification_types %{
"Create" => "mention",
......
......@@ -10,6 +10,8 @@ defmodule Pleroma.HTTP do
alias Pleroma.HTTP.Connection
alias Pleroma.HTTP.RequestBuilder, as: Builder
@type t :: __MODULE__
@doc """
Builds and perform http request.
......
......@@ -80,9 +80,8 @@ def get(%{id: user_id} = _user, id) do
end
def clear(user) do
query = from(n in Notification, where: n.user_id == ^user.id)
Repo.delete_all(query)
from(n in Notification, where: n.user_id == ^user.id)
|> Repo.delete_all()
end
def dismiss(%{id: user_id} = _user, id) do
......
......@@ -4,7 +4,8 @@
defmodule Pleroma.Object do
use Ecto.Schema
alias Pleroma.{Repo, Object, User, Activity, HTML}
alias Pleroma.{Repo, Object, User, Activity, HTML, ObjectTombstone}
alias Pleroma.{Repo, Object, User, Activity, ObjectTombstone}
import Ecto.{Query, Changeset}
schema "objects" do
......@@ -66,8 +67,25 @@ def context_mapping(context) do
Object.change(%Object{}, %{data: %{"id" => context}})
end
def make_tombstone(%Object{data: %{"id" => id, "type" => type}}, deleted \\ DateTime.utc_now()) do
%ObjectTombstone{
id: id,
formerType: type,
deleted: deleted
}
|> Map.from_struct()
end
def swap_object_with_tombstone(object) do
tombstone = make_tombstone(object)
object
|> Object.change(%{data: tombstone})
|> Repo.update()
end
def delete(%Object{data: %{"id" => id}} = object) do
with Repo.delete(object),
with {:ok, _obj} = swap_object_with_tombstone(object),
Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
{:ok, object}
......
defmodule Pleroma.ObjectTombstone do
@enforce_keys [:id, :formerType, :deleted]
defstruct [:id, :formerType, :deleted, type: "Tombstone"]
end
......@@ -13,6 +13,8 @@ defmodule Pleroma.User do
alias Pleroma.Web.{OStatus, Websub, OAuth}
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
require Logger
@type t :: %__MODULE__{}
@email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
......@@ -47,6 +49,14 @@ def auth_active?(%User{} = user) do
!Pleroma.Config.get([:instance, :account_activation_required])
end
def remote_or_auth_active?(%User{} = user), do: !user.local || auth_active?(user)
def visible_for?(%User{} = user, for_user \\ nil) do
User.remote_or_auth_active?(user) || (for_user && for_user.id == user.id) ||
User.superuser?(for_user)
end
def superuser?(nil), do: false
def superuser?(%User{} = user), do: user.info && User.Info.superuser?(user.info)
def avatar_url(user) do
......@@ -197,6 +207,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> unique_constraint(:nickname)
|> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
|> validate_format(:nickname, local_nickname_regex())
|> validate_format(:email, @email_regex)
|> validate_length(:bio, max: 1000)
......@@ -330,6 +341,24 @@ def following?(%User{} = follower, %User{} = followed) do
Enum.member?(follower.following, followed.follower_address)
end
def follow_import(%User{} = follower, followed_identifiers)
when is_list(followed_identifiers) do
Enum.map(
followed_identifiers,
fn followed_identifier ->
with %User{} = followed <- get_or_fetch(followed_identifier),
{:ok, follower} <- maybe_direct_follow(follower, followed),
{:ok, _} <- ActivityPub.follow(follower, followed) do
followed
else
err ->
Logger.debug("follow_import failed for #{followed_identifier} with: #{inspect(err)}")
err
end
end
)
end
def locked?(%User{} = user) do
user.info.locked || false
end
......@@ -366,7 +395,11 @@ def get_cached_by_nickname(nickname) do
end
def get_by_nickname(nickname) do
Repo.get_by(User, nickname: nickname)
Repo.get_by(User, nickname: nickname) ||
if Regex.match?(~r(@#{Pleroma.Web.Endpoint.host()})i, nickname) do
[local_nickname, _] = String.split(nickname, "@")
Repo.get_by(User, nickname: local_nickname)
end
end
def get_by_nickname_or_email(nickname_or_email) do
......@@ -595,6 +628,23 @@ def search(query, resolve \\ false) do
Repo.all(q)
end
def blocks_import(%User{} = blocker, blocked_identifiers) when is_list(blocked_identifiers) do
Enum.map(
blocked_identifiers,
fn blocked_identifier ->
with %User{} = blocked <- get_or_fetch(blocked_identifier),
{:ok, blocker} <- block(blocker, blocked),
{:ok, _} <- ActivityPub.block(blocker, blocked) do
blocked
else
err ->
Logger.debug("blocks_import failed for #{blocked_identifier} with: #{inspect(err)}")
err
end
end
)
end
def block(blocker, %User{ap_id: ap_id} = blocked) do
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
blocker =
......@@ -648,6 +698,9 @@ def blocks?(user, %{ap_id: ap_id}) do
end)
end
def blocked_users(user),
do: Repo.all(from(u in User, where: u.ap_id in ^user.info.blocks))
def block_domain(user, domain) do
info_cng =
user.info
......
......@@ -56,10 +56,18 @@ defp check_actor_is_active(actor) do
end
end
defp check_remote_limit(%{"object" => %{"content" => content}}) do
limit = Pleroma.Config.get([:instance, :remote_limit])
String.length(content) <= limit
end
defp check_remote_limit(_), do: true
def insert(map, local \\ true) when is_map(map) do
with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map),
:ok <- check_actor_is_active(map["actor"]),
{_, true} <- {:remote_limit_error, check_remote_limit(map)},
{:ok, map} <- MRF.filter(map),
:ok <- insert_full_object(map) do
{recipients, _, _} = get_recipients(map)
......@@ -503,6 +511,12 @@ defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or
defp restrict_replies(query, _), do: query
defp restrict_reblogs(query, %{"exclude_reblogs" => val}) when val == "true" or val == "1" do
from(activity in query, where: fragment("?->>'type' != 'Announce'", activity.data))
end
defp restrict_reblogs(query, _), do: query
# Only search through last 100_000 activities by default
defp restrict_recent(query, %{"whole_db" => true}), do: query
......@@ -561,6 +575,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_media(opts)
|> restrict_visibility(opts)
|> restrict_replies(opts)
|> restrict_reblogs(opts)
end
def fetch_activities(recipients, opts \\ %{}) do
......
......@@ -4,11 +4,12 @@
defmodule Pleroma.Web.ActivityPub.ActivityPubController do
use Pleroma.Web, :controller
alias Pleroma.{User, Object}
alias Pleroma.{Activity, User, Object}
alias Pleroma.Web.ActivityPub.{ObjectView, UserView}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.Federator
require Logger
......@@ -93,19 +94,15 @@ def followers(conn, %{"nickname" => nickname}) do
end
end
def outbox(conn, %{"nickname" => nickname, "max_id" => max_id}) do
def outbox(conn, %{"nickname" => nickname} = params) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("outbox.json", %{user: user, max_id: max_id}))
|> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
end
end
def outbox(conn, %{"nickname" => nickname}) do
outbox(conn, %{"nickname" => nickname, "max_id" => nil})
end
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
with %User{} = user <- User.get_cached_by_nickname(nickname),
true <- Utils.recipient_in_message(user.ap_id, params),
......@@ -156,6 +153,57 @@ def relay(conn, _params) do
end
end
def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
if nickname == user.nickname do
conn
|> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
else
conn
|> put_status(:forbidden)
|> json("can't read inbox of #{nickname} as #{user.nickname}")
end
end
def update_outbox(
%{assigns: %{user: user}} = conn,
%{"nickname" => nickname, "type" => "Create"} = params
) do
if nickname == user.nickname do
actor = user.ap_id()
params =
params
|> Map.drop(["id"])
|> Map.put("actor", actor)
|> Transmogrifier.fix_addressing()
object =
params["object"]
|> Map.merge(Map.take(params, ["to", "cc"]))
|> Map.put("attributedTo", actor)
|> Transmogrifier.fix_object()
with {:ok, %Activity{} = activity} <-
ActivityPub.create(%{
to: params["to"],
actor: user,
context: object["context"],
object: object,
additional: Map.take(params, ["cc"])
}) do
conn
|> put_status(:created)
|> put_resp_header("location", activity.data["id"])
|> json(activity.data)
end
else
conn
|> put_status(:forbidden)
|> json("can't update outbox of #{nickname} as #{user.nickname}")
end
end
def errors(conn, {:error, :not_found}) do
conn
|> put_status(404)
......
......@@ -176,6 +176,53 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
end
end
def render("inbox.json", %{user: user, max_id: max_qid}) do
params = %{
"limit" => "10"
}
params =
if max_qid != nil do
Map.put(params, "max_id", max_qid)
else
params
end
activities = ActivityPub.fetch_activities([user.ap_id | user.following], 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)
iri = "#{user.ap_id}/inbox"
page = %{
"id" => "#{iri}?max_id=#{max_id}",
"type" => "OrderedCollectionPage",
"partOf" => iri,
"totalItems" => -1,
"orderedItems" => collection,
"next" => "#{iri}?max_id=#{min_id - 1}"
}
if max_qid == nil do
%{
"id" => iri,
"type" => "OrderedCollection",
"totalItems" => -1,
"first" => page
}
|> Map.merge(Utils.make_json_ld_header())
else
page |> Map.merge(Utils.make_json_ld_header())
end
end
def collection(collection, iri, page, show_items \\ true, total \\ nil) do
offset = (page - 1) * 10
items = Enum.slice(collection, offset, 10)
......
......@@ -102,7 +102,7 @@ def post(user, %{"status" => status} = data) do
attachments,
tags,
get_content_type(data["content_type"]),
data["no_attachment_links"]
Enum.member?([true, "true"], data["no_attachment_links"])
),
context <- make_context(inReplyTo),
cw <- data["spoiler_text"],
......
......@@ -704,11 +704,9 @@ def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
end
end
# TODO: Use proper query
def blocks(%{assigns: %{user: user}} = conn, _) do
with blocked_users <- user.info.blocks || [],
accounts <- Enum.map(blocked_users, fn ap_id -> User.get_cached_by_ap_id(ap_id) end) do
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
with blocked_accounts <- User.blocked_users(user) do
res = AccountView.render("accounts.json", users: blocked_accounts, for: user, as: :user)
json(conn, res)
end
end
......
......@@ -11,10 +11,55 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
alias Pleroma.HTML
def render("accounts.json", %{users: users} = opts) do
render_many(users, AccountView, "account.json", opts)
users
|> render_many(AccountView, "account.json", opts)
|> Enum.filter(&Enum.any?/1)
end
def render("account.json", %{user: user} = opts) do
if User.visible_for?(user, opts[:for]),
do: do_render("account.json", opts),
else: %{}
end
def render("mention.json", %{user: user}) do
%{
id: to_string(user.id),
acct: user.nickname,
username: username_from_nickname(user.nickname),
url: user.ap_id
}
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),
followed_by: User.following?(target, user),
blocking: User.blocks?(user, target),
muting: false,
muting_notifications: false,
requested: requested,
domain_blocking: false,
showing_reblogs: false,
endorsed: false
}
end
def render("relationships.json", %{user: user, targets: targets}) do
render_many(targets, AccountView, "relationship.json", user: user, as: :target)
end
defp do_render("account.json", %{user: user} = opts) do
image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url()
user_info = User.user_info(user)
......@@ -72,43 +117,6 @@ def render("account.json", %{user: user} = opts) do
}
end
def render("mention.json", %{user: user}) do
%{
id: to_string(user.id),
acct: user.nickname,
username: username_from_nickname(user.nickname),
url: user.ap_id
}
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),
followed_by: User.following?(target, user),
blocking: User.blocks?(user, target),
muting: false,
muting_notifications: false,
requested: requested,
domain_blocking: false,
showing_reblogs: false,
endorsed: false
}
end
def render("relationships.json", %{user: user, targets: targets}) do
render_many(targets, AccountView, "relationship.json", user: user, as: :target)
end
defp username_from_nickname(string) when is_binary(string) do
hd(String.split(string, "@"))
end
......