Resolve merge conflict

This commit is contained in:
Rin Toshaka 2018-12-30 21:00:40 +01:00
commit dec23500d8
56 changed files with 822 additions and 182 deletions

View file

@ -8,6 +8,7 @@ variables:
POSTGRES_USER: postgres POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
DB_HOST: postgres DB_HOST: postgres
MIX_ENV: test
cache: cache:
key: ${CI_COMMIT_REF_SLUG} key: ${CI_COMMIT_REF_SLUG}
@ -23,15 +24,15 @@ before_script:
- mix local.rebar --force - mix local.rebar --force
- mix deps.get - mix deps.get
- mix compile --force - mix compile --force
- MIX_ENV=test mix ecto.create - mix ecto.create
- MIX_ENV=test mix ecto.migrate - mix ecto.migrate
lint: lint:
stage: lint stage: lint
script: script:
- MIX_ENV=test mix format --check-formatted - mix format --check-formatted
unit-testing: unit-testing:
stage: test stage: test
script: script:
- MIX_ENV=test mix test --trace - mix test --trace

View file

@ -30,7 +30,7 @@ While we don't provide docker files, other people have written very good ones. T
### Dependencies ### Dependencies
* Postgresql version 9.6 or newer * 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 * Build-essential tools
### Configuration ### Configuration

View file

@ -98,7 +98,8 @@
name: "Pleroma", name: "Pleroma",
email: "example@example.com", email: "example@example.com",
description: "A Pleroma instance, an alternative fediverse server", description: "A Pleroma instance, an alternative fediverse server",
limit: 5000, limit: 5_000,
remote_limit: 100_000,
upload_limit: 16_000_000, upload_limit: 16_000_000,
avatar_upload_limit: 2_000_000, avatar_upload_limit: 2_000_000,
background_upload_limit: 4_000_000, background_upload_limit: 4_000_000,
@ -137,8 +138,8 @@
logo_mask: true, logo_mask: true,
logo_margin: "0.1em", logo_margin: "0.1em",
background: "/static/aurora_borealis.jpg", background: "/static/aurora_borealis.jpg",
redirect_root_no_login: "/~/main/all", redirect_root_no_login: "/main/all",
redirect_root_login: "/~/main/friends", redirect_root_login: "/main/friends",
show_instance_panel: true, show_instance_panel: true,
scope_options_enabled: false, scope_options_enabled: false,
formatting_options_enabled: false, formatting_options_enabled: false,
@ -220,6 +221,37 @@
credentials: true, credentials: true,
headers: ["Authorization", "Content-Type", "Idempotency-Key"] 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 # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs" import_config "#{Mix.env()}.exs"

View file

@ -63,6 +63,7 @@ config :pleroma, Pleroma.Mailer,
* `email`: Email used to reach an Administrator/Moderator of the instance * `email`: Email used to reach an Administrator/Moderator of the instance
* `description`: The instances description, can be seen in nodeinfo and ``/api/v1/instance`` * `description`: The instances description, can be seen in nodeinfo and ``/api/v1/instance``
* `limit`: Posts character limit (CW/Subject included in the counter) * `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) * `upload_limit`: File size limit of uploads (except for avatar, background, banner)
* `avatar_upload_limit`: File size limit of users profile avatars * `avatar_upload_limit`: File size limit of users profile avatars
* `background_upload_limit`: File size limit of users profile backgrounds * `background_upload_limit`: File size limit of users profile backgrounds

View file

@ -21,6 +21,8 @@ ProtectSystem=full
PrivateDevices=false PrivateDevices=false
; Ensures that the service process and all its children can never gain new privileges through execve(). ; Ensures that the service process and all its children can never gain new privileges through execve().
NoNewPrivileges=true NoNewPrivileges=true
; Drops the sysadmin capability from the daemon.
CapabilityBoundingSet=~CAP_SYS_ADMIN
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target

View file

@ -75,7 +75,7 @@ def run(["gen" | rest]) do
name = name =
Common.get_option( Common.get_option(
options, options,
:name, :instance_name,
"What is the name of your instance? (e.g. Pleroma/Soykaf)" "What is the name of your instance? (e.g. Pleroma/Soykaf)"
) )

View file

@ -7,6 +7,8 @@ defmodule Pleroma.Activity do
alias Pleroma.{Repo, Activity, Notification} alias Pleroma.{Repo, Activity, Notification}
import Ecto.Query import Ecto.Query
@type t :: %__MODULE__{}
# https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19 # https://github.com/tootsuite/mastodon/blob/master/app/models/notification.rb#L19
@mastodon_notification_types %{ @mastodon_notification_types %{
"Create" => "mention", "Create" => "mention",

View file

@ -10,6 +10,8 @@ defmodule Pleroma.HTTP do
alias Pleroma.HTTP.Connection alias Pleroma.HTTP.Connection
alias Pleroma.HTTP.RequestBuilder, as: Builder alias Pleroma.HTTP.RequestBuilder, as: Builder
@type t :: __MODULE__
@doc """ @doc """
Builds and perform http request. Builds and perform http request.

View file

@ -80,9 +80,8 @@ def get(%{id: user_id} = _user, id) do
end end
def clear(user) do def clear(user) do
query = from(n in Notification, where: n.user_id == ^user.id) from(n in Notification, where: n.user_id == ^user.id)
|> Repo.delete_all()
Repo.delete_all(query)
end end
def dismiss(%{id: user_id} = _user, id) do def dismiss(%{id: user_id} = _user, id) do

View file

@ -4,7 +4,8 @@
defmodule Pleroma.Object do defmodule Pleroma.Object do
use Ecto.Schema 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} import Ecto.{Query, Changeset}
schema "objects" do schema "objects" do
@ -66,8 +67,25 @@ def context_mapping(context) do
Object.change(%Object{}, %{data: %{"id" => context}}) Object.change(%Object{}, %{data: %{"id" => context}})
end 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 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)), Repo.delete_all(Activity.all_non_create_by_object_ap_id_q(id)),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
{:ok, object} {:ok, object}

View file

@ -0,0 +1,4 @@
defmodule Pleroma.ObjectTombstone do
@enforce_keys [:id, :formerType, :deleted]
defstruct [:id, :formerType, :deleted, type: "Tombstone"]
end

View file

@ -13,6 +13,8 @@ defmodule Pleroma.User do
alias Pleroma.Web.{OStatus, Websub, OAuth} alias Pleroma.Web.{OStatus, Websub, OAuth}
alias Pleroma.Web.ActivityPub.{Utils, ActivityPub} alias Pleroma.Web.ActivityPub.{Utils, ActivityPub}
require Logger
@type t :: %__MODULE__{} @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])?)*$/ @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]) !Pleroma.Config.get([:instance, :account_activation_required])
end 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 superuser?(%User{} = user), do: user.info && User.Info.superuser?(user.info)
def avatar_url(user) do def avatar_url(user) do
@ -197,6 +207,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|> validate_confirmation(:password) |> validate_confirmation(:password)
|> unique_constraint(:email) |> unique_constraint(:email)
|> unique_constraint(:nickname) |> unique_constraint(:nickname)
|> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
|> validate_format(:nickname, local_nickname_regex()) |> validate_format(:nickname, local_nickname_regex())
|> validate_format(:email, @email_regex) |> validate_format(:email, @email_regex)
|> validate_length(:bio, max: 1000) |> validate_length(:bio, max: 1000)
@ -330,6 +341,24 @@ def following?(%User{} = follower, %User{} = followed) do
Enum.member?(follower.following, followed.follower_address) Enum.member?(follower.following, followed.follower_address)
end 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 def locked?(%User{} = user) do
user.info.locked || false user.info.locked || false
end end
@ -366,7 +395,11 @@ def get_cached_by_nickname(nickname) do
end end
def get_by_nickname(nickname) do 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 end
def get_by_nickname_or_email(nickname_or_email) do def get_by_nickname_or_email(nickname_or_email) do
@ -595,6 +628,23 @@ def search(query, resolve \\ false) do
Repo.all(q) Repo.all(q)
end 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 def block(blocker, %User{ap_id: ap_id} = blocked) do
# sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213) # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
blocker = blocker =
@ -648,6 +698,9 @@ def blocks?(user, %{ap_id: ap_id}) do
end) end)
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 def block_domain(user, domain) do
info_cng = info_cng =
user.info user.info

View file

@ -56,10 +56,18 @@ defp check_actor_is_active(actor) do
end end
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 def insert(map, local \\ true) when is_map(map) do
with nil <- Activity.normalize(map), with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map), map <- lazy_put_activity_defaults(map),
:ok <- check_actor_is_active(map["actor"]), :ok <- check_actor_is_active(map["actor"]),
{_, true} <- {:remote_limit_error, check_remote_limit(map)},
{:ok, map} <- MRF.filter(map), {:ok, map} <- MRF.filter(map),
:ok <- insert_full_object(map) do :ok <- insert_full_object(map) do
{recipients, _, _} = get_recipients(map) {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_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 # Only search through last 100_000 activities by default
defp restrict_recent(query, %{"whole_db" => true}), do: query defp restrict_recent(query, %{"whole_db" => true}), do: query
@ -561,6 +575,7 @@ def fetch_activities_query(recipients, opts \\ %{}) do
|> restrict_media(opts) |> restrict_media(opts)
|> restrict_visibility(opts) |> restrict_visibility(opts)
|> restrict_replies(opts) |> restrict_replies(opts)
|> restrict_reblogs(opts)
end end
def fetch_activities(recipients, opts \\ %{}) do def fetch_activities(recipients, opts \\ %{}) do

View file

@ -4,11 +4,12 @@
defmodule Pleroma.Web.ActivityPub.ActivityPubController do defmodule Pleroma.Web.ActivityPub.ActivityPubController do
use Pleroma.Web, :controller use Pleroma.Web, :controller
alias Pleroma.{User, Object} alias Pleroma.{Activity, User, Object}
alias Pleroma.Web.ActivityPub.{ObjectView, UserView} alias Pleroma.Web.ActivityPub.{ObjectView, UserView}
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
require Logger require Logger
@ -93,19 +94,15 @@ def followers(conn, %{"nickname" => nickname}) do
end end
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), with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> 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
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 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
with %User{} = user <- User.get_cached_by_nickname(nickname), with %User{} = user <- User.get_cached_by_nickname(nickname),
true <- Utils.recipient_in_message(user.ap_id, params), true <- Utils.recipient_in_message(user.ap_id, params),
@ -156,6 +153,57 @@ def relay(conn, _params) do
end end
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 def errors(conn, {:error, :not_found}) do
conn conn
|> put_status(404) |> put_status(404)

View file

@ -176,6 +176,53 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do
end end
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 def collection(collection, iri, page, show_items \\ true, total \\ nil) do
offset = (page - 1) * 10 offset = (page - 1) * 10
items = Enum.slice(collection, offset, 10) items = Enum.slice(collection, offset, 10)

View file

@ -102,7 +102,7 @@ def post(user, %{"status" => status} = data) do
attachments, attachments,
tags, tags,
get_content_type(data["content_type"]), get_content_type(data["content_type"]),
data["no_attachment_links"] Enum.member?([true, "true"], data["no_attachment_links"])
), ),
context <- make_context(inReplyTo), context <- make_context(inReplyTo),
cw <- data["spoiler_text"], cw <- data["spoiler_text"],

View file

@ -704,11 +704,9 @@ def unblock(%{assigns: %{user: blocker}} = conn, %{"id" => id}) do
end end
end end
# TODO: Use proper query
def blocks(%{assigns: %{user: user}} = conn, _) do def blocks(%{assigns: %{user: user}} = conn, _) do
with blocked_users <- user.info.blocks || [], with blocked_accounts <- User.blocked_users(user) do
accounts <- Enum.map(blocked_users, fn ap_id -> User.get_cached_by_ap_id(ap_id) end) do res = AccountView.render("accounts.json", users: blocked_accounts, for: user, as: :user)
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
json(conn, res) json(conn, res)
end end
end end

View file

@ -11,10 +11,55 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
alias Pleroma.HTML alias Pleroma.HTML
def render("accounts.json", %{users: users} = opts) do 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 end
def render("account.json", %{user: user} = opts) do 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() image = User.avatar_url(user) |> MediaProxy.url()
header = User.banner_url(user) |> MediaProxy.url() header = User.banner_url(user) |> MediaProxy.url()
user_info = User.user_info(user) user_info = User.user_info(user)
@ -72,43 +117,6 @@ def render("account.json", %{user: user} = opts) do
} }
end 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 defp username_from_nickname(string) when is_binary(string) do
hd(String.split(string, "@")) hd(String.split(string, "@"))
end end

View file

@ -138,7 +138,8 @@ def nodeinfo(conn, %{"version" => "2.0"}) do
}, },
accountActivationRequired: Keyword.get(instance, :account_activation_required, false), accountActivationRequired: Keyword.get(instance, :account_activation_required, false),
invitesEnabled: Keyword.get(instance, :invites_enabled, false), invitesEnabled: Keyword.get(instance, :invites_enabled, false),
features: features features: features,
restrictedNicknames: Pleroma.Config.get([Pleroma.User, :restricted_nicknames])
} }
} }

View file

@ -137,6 +137,7 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma", Pleroma.Web.TwitterAPI do scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)
post("/blocks_import", UtilController, :blocks_import)
post("/follow_import", UtilController, :follow_import) post("/follow_import", UtilController, :follow_import)
post("/change_password", UtilController, :change_password) post("/change_password", UtilController, :change_password)
post("/delete_account", UtilController, :delete_account) post("/delete_account", UtilController, :delete_account)
@ -281,6 +282,7 @@ defmodule Pleroma.Web.Router do
get("/statuses/followers", TwitterAPI.Controller, :followers) get("/statuses/followers", TwitterAPI.Controller, :followers)
get("/statuses/friends", TwitterAPI.Controller, :friends) get("/statuses/friends", TwitterAPI.Controller, :friends)
get("/statuses/blocks", TwitterAPI.Controller, :blocks)
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status) get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation) get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
@ -410,6 +412,27 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname/outbox", ActivityPubController, :outbox) get("/users/:nickname/outbox", ActivityPubController, :outbox)
end end
pipeline :activitypub_client do
plug(:accepts, ["activity+json"])
plug(:fetch_session)
plug(Pleroma.Plugs.OAuthPlug)
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
plug(Pleroma.Plugs.UserFetcherPlug)
plug(Pleroma.Plugs.SessionAuthenticationPlug)
plug(Pleroma.Plugs.LegacyAuthenticationPlug)
plug(Pleroma.Plugs.AuthenticationPlug)
plug(Pleroma.Plugs.UserEnabledPlug)
plug(Pleroma.Plugs.SetUserSessionIdPlug)
plug(Pleroma.Plugs.EnsureUserKeyPlug)
end
scope "/", Pleroma.Web.ActivityPub do
pipe_through([:activitypub_client])
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
end
scope "/relay", Pleroma.Web.ActivityPub do scope "/relay", Pleroma.Web.ActivityPub do
pipe_through(:ap_relay) pipe_through(:ap_relay)
get("/", ActivityPubController, :relay) get("/", ActivityPubController, :relay)

View file

@ -161,16 +161,21 @@ def remote_users(%{data: %{"to" => to} = data}) do
|> Enum.filter(fn user -> user && !user.local end) |> Enum.filter(fn user -> user && !user.local end)
end end
defp send_to_user(%{info: %{salmon: salmon}}, feed, poster) do # push an activity to remote accounts
#
defp send_to_user(%{info: %{salmon: salmon}}, feed, poster),
do: send_to_user(salmon, feed, poster)
defp send_to_user(url, feed, poster) when is_binary(url) do
with {:ok, %{status: code}} <- with {:ok, %{status: code}} <-
poster.( poster.(
salmon, url,
feed, feed,
[{"Content-Type", "application/magic-envelope+xml"}] [{"Content-Type", "application/magic-envelope+xml"}]
) do ) do
Logger.debug(fn -> "Pushed to #{salmon}, code #{code}" end) Logger.debug(fn -> "Pushed to #{url}, code #{code}" end)
else else
e -> Logger.debug(fn -> "Pushing Salmon to #{salmon} failed, #{inspect(e)}" end) e -> Logger.debug(fn -> "Pushing Salmon to #{url} failed, #{inspect(e)}" end)
end end
end end
@ -184,6 +189,11 @@ defp send_to_user(_, _, _), do: nil
"Undo", "Undo",
"Delete" "Delete"
] ]
@doc """
Publishes an activity to remote accounts
"""
@spec publish(User.t(), Pleroma.Activity.t(), Pleroma.HTTP.t()) :: none
def publish(user, activity, poster \\ &@httpoison.post/3) def publish(user, activity, poster \\ &@httpoison.post/3)
def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity, poster) def publish(%{info: %{keys: keys}} = user, %{data: %{"type" => type}} = activity, poster)

View file

@ -240,21 +240,22 @@ def follow_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
follow_import(conn, %{"list" => File.read!(listfile.path)}) follow_import(conn, %{"list" => File.read!(listfile.path)})
end end
def follow_import(%{assigns: %{user: user}} = conn, %{"list" => list}) do def follow_import(%{assigns: %{user: follower}} = conn, %{"list" => list}) do
Task.start(fn -> with followed_identifiers <- String.split(list),
String.split(list) {:ok, _} = Task.start(fn -> User.follow_import(follower, followed_identifiers) end) do
|> Enum.map(fn account -> json(conn, "job started")
with %User{} = follower <- User.get_cached_by_ap_id(user.ap_id), end
%User{} = followed <- User.get_or_fetch(account), end
{:ok, follower} <- User.maybe_direct_follow(follower, followed) do
ActivityPub.follow(follower, followed)
else
err -> Logger.debug("follow_import: following #{account} failed with #{inspect(err)}")
end
end)
end)
json(conn, "job started") def blocks_import(conn, %{"list" => %Plug.Upload{} = listfile}) do
blocks_import(conn, %{"list" => File.read!(listfile.path)})
end
def blocks_import(%{assigns: %{user: blocker}} = conn, %{"list" => list}) do
with blocked_identifiers <- String.split(list),
{:ok, _} = Task.start(fn -> User.blocks_import(blocker, blocked_identifiers) end) do
json(conn, "job started")
end
end end
def change_password(%{assigns: %{user: user}} = conn, params) do def change_password(%{assigns: %{user: user}} = conn, params) do

View file

@ -130,6 +130,15 @@ def show_user(conn, params) do
def user_timeline(%{assigns: %{user: user}} = conn, params) do def user_timeline(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.get_user(user, params) do case TwitterAPI.get_user(user, params) do
{:ok, target_user} -> {:ok, target_user} ->
# Twitter and ActivityPub use a different name and sense for this parameter.
{include_rts, params} = Map.pop(params, "include_rts")
params =
case include_rts do
x when x == "false" or x == "0" -> Map.put(params, "exclude_reblogs", "true")
_ -> params
end
activities = ActivityPub.fetch_user_activities(target_user, user, params) activities = ActivityPub.fetch_user_activities(target_user, user, params)
conn conn
@ -498,6 +507,14 @@ def friends(%{assigns: %{user: for_user}} = conn, params) do
end end
end end
def blocks(%{assigns: %{user: user}} = conn, _params) do
with blocked_users <- User.blocked_users(user) do
conn
|> put_view(UserView)
|> render("index.json", %{users: blocked_users, for: user})
end
end
def friend_requests(conn, params) do def friend_requests(conn, params) do
with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
{:ok, friend_requests} <- User.get_follow_requests(user) do {:ok, friend_requests} <- User.get_follow_requests(user) do
@ -653,7 +670,7 @@ defp forbidden_json_reply(conn, error_message) do
json_reply(conn, 403, json) json_reply(conn, 403, json)
end end
def only_if_public_instance(conn = %{conn: %{assigns: %{user: _user}}}, _), do: conn def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
def only_if_public_instance(conn, _) do def only_if_public_instance(conn, _) do
if Keyword.get(Application.get_env(:pleroma, :instance), :public) do if Keyword.get(Application.get_env(:pleroma, :instance), :public) do

View file

@ -15,18 +15,44 @@ def render("show.json", %{user: user = %User{}} = assigns) do
end end
def render("index.json", %{users: users, for: user}) do def render("index.json", %{users: users, for: user}) do
render_many(users, Pleroma.Web.TwitterAPI.UserView, "user.json", for: user) users
|> render_many(Pleroma.Web.TwitterAPI.UserView, "user.json", for: user)
|> Enum.filter(&Enum.any?/1)
end end
def render("user.json", %{user: user = %User{}} = assigns) do def render("user.json", %{user: user = %User{}} = assigns) do
if User.visible_for?(user, assigns[:for]),
do: do_render("user.json", assigns),
else: %{}
end
def render("short.json", %{
user: %User{
nickname: nickname,
id: id,
ap_id: ap_id,
name: name
}
}) do
%{
"fullname" => name,
"id" => id,
"ostatus_uri" => ap_id,
"profile_url" => ap_id,
"screen_name" => nickname
}
end
defp do_render("user.json", %{user: user = %User{}} = assigns) do
for_user = assigns[:for]
image = User.avatar_url(user) |> MediaProxy.url() image = User.avatar_url(user) |> MediaProxy.url()
{following, follows_you, statusnet_blocking} = {following, follows_you, statusnet_blocking} =
if assigns[:for] do if for_user do
{ {
User.following?(assigns[:for], user), User.following?(for_user, user),
User.following?(user, assigns[:for]), User.following?(user, for_user),
User.blocks?(assigns[:for], user) User.blocks?(for_user, user)
} }
else else
{false, false, false} {false, false, false}
@ -51,7 +77,7 @@ def render("user.json", %{user: user = %User{}} = assigns) do
data = %{ data = %{
"created_at" => user.inserted_at |> Utils.format_naive_asctime(), "created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")), "description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
"description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(assigns[:for])), "description_html" => HTML.filter_tags(user.bio, User.html_filter_policy(for_user)),
"favourites_count" => 0, "favourites_count" => 0,
"followers_count" => user_info[:follower_count], "followers_count" => user_info[:follower_count],
"following" => following, "following" => following,
@ -97,23 +123,6 @@ def render("user.json", %{user: user = %User{}} = assigns) do
end end
end end
def render("short.json", %{
user: %User{
nickname: nickname,
id: id,
ap_id: ap_id,
name: name
}
}) do
%{
"fullname" => name,
"id" => id,
"ostatus_uri" => ap_id,
"profile_url" => ap_id,
"screen_name" => nickname
}
end
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil defp image_url(_), do: nil

View file

@ -5,7 +5,7 @@ def project do
[ [
app: :pleroma, app: :pleroma,
version: version("0.9.0"), version: version("0.9.0"),
elixir: "~> 1.4", elixir: "~> 1.7",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(), compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: true], elixirc_options: [warnings_as_errors: true],
@ -71,7 +71,7 @@ defp deps do
{:crypt, {:crypt,
git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"}, git: "https://github.com/msantos/crypt", ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"},
{:cors_plug, "~> 1.5"}, {:cors_plug, "~> 1.5"},
{:ex_doc, "> 0.18.3 and < 0.20.0", only: :dev, runtime: false}, {:ex_doc, "~> 0.19", only: :dev, runtime: false},
{:web_push_encryption, "~> 0.2.1"}, {:web_push_encryption, "~> 0.2.1"},
{:swoosh, "~> 0.20"}, {:swoosh, "~> 0.20"},
{:gen_smtp, "~> 0.13"}, {:gen_smtp, "~> 0.13"},

View file

@ -1 +1 @@
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><!--server-generated-meta--><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.285fa56c62b811bbd37880f7e2656b13.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.60e190da7cc4cc4711dc.js></script><script type=text/javascript src=/static/js/vendor.48d4753220bd83360796.js></script><script type=text/javascript src=/static/js/app.6cb7378f44092df9536a.js></script></body></html> <!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><!--server-generated-meta--><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.44bcebbab7b3203648fdb538eb16129b.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.18ee0a4963e1e9ec7ea6.js></script><script type=text/javascript src=/static/js/vendor.21f9327c919db89265c3.js></script><script type=text/javascript src=/static/js/app.f5ecd4e55f996aad6b8a.js></script></body></html>

View file

@ -4,8 +4,8 @@
"logo": "/static/logo.png", "logo": "/static/logo.png",
"logoMask": true, "logoMask": true,
"logoMargin": ".1em", "logoMargin": ".1em",
"redirectRootNoLogin": "/~/main/all", "redirectRootNoLogin": "/main/all",
"redirectRootLogin": "/~/main/friends", "redirectRootLogin": "/main/friends",
"chatDisabled": false, "chatDisabled": false,
"showInstanceSpecificPanel": false, "showInstanceSpecificPanel": false,
"scopeOptionsEnabled": false, "scopeOptionsEnabled": false,
@ -16,5 +16,7 @@
"alwaysShowSubjectInput": true, "alwaysShowSubjectInput": true,
"hidePostStats": false, "hidePostStats": false,
"hideUserStats": false, "hideUserStats": false,
"loginMethod": "password" "loginMethod": "password",
"webPushNotifications": false,
"noAttachmentLinks": false
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,2 @@
!function(e){function t(r){if(a[r])return a[r].exports;var n=a[r]={exports:{},id:r,loaded:!1};return e[r].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var r=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,i=[];s<c.length;s++)l=c[s],n[l]&&i.push.apply(i,n[l]),n[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(r&&r(c,o);i.length;)i.shift().call(null,t);if(o[0])return a[0]=0,t(0)};var a={},n={0:0};t.e=function(e,r){if(0===n[e])return r.call(null,t);if(void 0!==n[e])n[e].push(r);else{n[e]=[r];var a=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:"21f9327c919db89265c3",2:"f5ecd4e55f996aad6b8a"}[e]+".js",a.appendChild(c)}},t.m=e,t.c=a,t.p="/"}([]);
//# sourceMappingURL=manifest.18ee0a4963e1e9ec7ea6.js.map

View file

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,18 +4,19 @@
defmodule Pleroma.ActivityTest do defmodule Pleroma.ActivityTest do
use Pleroma.DataCase use Pleroma.DataCase
alias Pleroma.Activity
import Pleroma.Factory import Pleroma.Factory
test "returns an activity by it's AP id" do test "returns an activity by it's AP id" do
activity = insert(:note_activity) activity = insert(:note_activity)
found_activity = Pleroma.Activity.get_by_ap_id(activity.data["id"]) found_activity = Activity.get_by_ap_id(activity.data["id"])
assert activity == found_activity assert activity == found_activity
end end
test "returns activities by it's objects AP ids" do test "returns activities by it's objects AP ids" do
activity = insert(:note_activity) activity = insert(:note_activity)
[found_activity] = Pleroma.Activity.all_by_object_ap_id(activity.data["object"]["id"]) [found_activity] = Activity.all_by_object_ap_id(activity.data["object"]["id"])
assert activity == found_activity assert activity == found_activity
end end
@ -23,8 +24,7 @@ test "returns activities by it's objects AP ids" do
test "returns the activity that created an object" do test "returns the activity that created an object" do
activity = insert(:note_activity) activity = insert(:note_activity)
found_activity = found_activity = Activity.get_create_activity_by_object_ap_id(activity.data["object"]["id"])
Pleroma.Activity.get_create_activity_by_object_ap_id(activity.data["object"]["id"])
assert activity == found_activity assert activity == found_activity
end end

View file

@ -0,0 +1,9 @@
{
"@context": ["https://www.w3.org/ns/activitystreams", {"@language": "en-GB"}],
"type": "Create",
"object": {
"type": "Note",
"content": "It's a note"
},
"to": ["https://www.w3.org/ns/activitystreams#Public"]
}

View file

@ -36,6 +36,8 @@ test "deletes an object" do
found_object = Object.get_by_ap_id(object.data["id"]) found_object = Object.get_by_ap_id(object.data["id"])
refute object == found_object refute object == found_object
assert found_object.data["type"] == "Tombstone"
end end
test "ensures cache is cleared for the object" do test "ensures cache is cleared for the object" do
@ -51,6 +53,8 @@ test "ensures cache is cleared for the object" do
cached_object = Object.get_cached_by_ap_id(object.data["id"]) cached_object = Object.get_cached_by_ap_id(object.data["id"])
refute object == cached_object refute object == cached_object
assert cached_object.data["type"] == "Tombstone"
end end
end end
end end

View file

@ -153,6 +153,20 @@ test "it requires an email, name, nickname and password, bio is optional" do
end) end)
end end
test "it restricts certain nicknames" do
[restricted_name | _] = Pleroma.Config.get([Pleroma.User, :restricted_nicknames])
assert is_bitstring(restricted_name)
params =
@full_user_data
|> Map.put(:nickname, restricted_name)
changeset = User.register_changeset(%User{}, params)
refute changeset.valid?
end
test "it sets the password_hash, ap_id and following fields" do test "it sets the password_hash, ap_id and following fields" do
changeset = User.register_changeset(%User{}, @full_user_data) changeset = User.register_changeset(%User{}, @full_user_data)
@ -264,6 +278,24 @@ test "gets an existing user, case insensitive" do
assert user == fetched_user assert user == fetched_user
end end
test "gets an existing user by fully qualified nickname" do
user = insert(:user)
fetched_user =
User.get_or_fetch_by_nickname(user.nickname <> "@" <> Pleroma.Web.Endpoint.host())
assert user == fetched_user
end
test "gets an existing user by fully qualified nickname, case insensitive" do
user = insert(:user, nickname: "nick")
casing_altered_fqn = String.upcase(user.nickname <> "@" <> Pleroma.Web.Endpoint.host())
fetched_user = User.get_or_fetch_by_nickname(casing_altered_fqn)
assert user == fetched_user
end
test "fetches an external user via ostatus if no user exists" do test "fetches an external user via ostatus if no user exists" do
fetched_user = User.get_or_fetch_by_nickname("shp@social.heldscal.la") fetched_user = User.get_or_fetch_by_nickname("shp@social.heldscal.la")
assert fetched_user.nickname == "shp@social.heldscal.la" assert fetched_user.nickname == "shp@social.heldscal.la"
@ -471,6 +503,21 @@ test "it sets the info->follower_count property" do
end end
end end
describe "follow_import" do
test "it imports user followings from list" do
[user1, user2, user3] = insert_list(3, :user)
identifiers = [
user2.ap_id,
user3.nickname
]
result = User.follow_import(user1, identifiers)
assert is_list(result)
assert result == [user2, user3]
end
end
describe "blocks" do describe "blocks" do
test "it blocks people" do test "it blocks people" do
user = insert(:user) user = insert(:user)
@ -570,6 +617,21 @@ test "unblocks domains" do
end end
end end
describe "blocks_import" do
test "it imports user blocks from list" do
[user1, user2, user3] = insert_list(3, :user)
identifiers = [
user2.ap_id,
user3.nickname
]
result = User.blocks_import(user1, identifiers)
assert is_list(result)
assert result == [user2, user3]
end
end
test "get recipients from activity" do test "get recipients from activity" do
actor = insert(:user) actor = insert(:user)
user = insert(:user, local: true) user = insert(:user, local: true)

View file

@ -112,6 +112,32 @@ test "it inserts an incoming activity into the database", %{conn: conn} do
:timer.sleep(500) :timer.sleep(500)
assert Activity.get_by_ap_id(data["id"]) assert Activity.get_by_ap_id(data["id"])
end end
test "it rejects reads from other users", %{conn: conn} do
user = insert(:user)
otheruser = insert(:user)
conn =
conn
|> assign(:user, otheruser)
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/inbox")
assert json_response(conn, 403)
end
test "it returns a note activity in a collection", %{conn: conn} do
note_activity = insert(:direct_note_activity)
user = User.get_cached_by_ap_id(hd(note_activity.data["to"]))
conn =
conn
|> assign(:user, user)
|> put_req_header("accept", "application/activity+json")
|> get("/users/#{user.nickname}/inbox")
assert response(conn, 200) =~ note_activity.data["object"]["content"]
end
end end
describe "/users/:nickname/outbox" do describe "/users/:nickname/outbox" do
@ -138,6 +164,34 @@ test "it returns an announce activity in a collection", %{conn: conn} do
assert response(conn, 200) =~ announce_activity.data["object"] assert response(conn, 200) =~ announce_activity.data["object"]
end end
test "it rejects posts from other users", %{conn: conn} do
data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
user = insert(:user)
otheruser = insert(:user)
conn =
conn
|> assign(:user, otheruser)
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/outbox", data)
assert json_response(conn, 403)
end
test "it inserts an incoming activity into the database", %{conn: conn} do
data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!()
user = insert(:user)
conn =
conn
|> assign(:user, user)
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/outbox", data)
result = json_response(conn, 201)
assert Activity.get_by_ap_id(result["id"])
end
end end
describe "/users/:nickname/followers" do describe "/users/:nickname/followers" do

View file

@ -31,6 +31,24 @@ test "it returns a user" do
end end
describe "insertion" do describe "insertion" do
test "drops activities beyond a certain limit" do
limit = Pleroma.Config.get([:instance, :remote_limit])
random_text =
:crypto.strong_rand_bytes(limit + 1)
|> Base.encode64()
|> binary_part(0, limit + 1)
data = %{
"ok" => true,
"object" => %{
"content" => random_text
}
}
assert {:error, {:remote_limit_error, _}} = ActivityPub.insert(data)
end
test "returns the activity if one with the same id is already in" do test "returns the activity if one with the same id is already in" do
activity = insert(:note_activity) activity = insert(:note_activity)
{:ok, new_activity} = ActivityPub.insert(activity.data) {:ok, new_activity} = ActivityPub.insert(activity.data)
@ -180,6 +198,16 @@ test "doesn't return blocked activities" do
assert Enum.member?(activities, activity_one) assert Enum.member?(activities, activity_one)
end end
test "excludes reblogs on request" do
user = insert(:user)
{:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user})
{:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user})
[activity] = ActivityPub.fetch_user_activities(user, nil, %{"exclude_reblogs" => "true"})
assert activity == expected_activity
end
describe "public fetch activities" do describe "public fetch activities" do
test "doesn't retrieve unlisted activities" do test "doesn't retrieve unlisted activities" do
user = insert(:user) user = insert(:user)
@ -482,7 +510,7 @@ test "it creates a delete activity and deletes the original object" do
assert Repo.get(Activity, delete.id) != nil assert Repo.get(Activity, delete.id) != nil
assert Repo.get(Object, object.id) == nil assert Repo.get(Object, object.id).data["type"] == "Tombstone"
end end
end end

View file

@ -296,7 +296,7 @@ test "when you created it", %{conn: conn} do
assert %{} = json_response(conn, 200) assert %{} = json_response(conn, 200)
assert Repo.get(Activity, activity.id) == nil refute Repo.get(Activity, activity.id)
end end
test "when you didn't create it", %{conn: conn} do test "when you didn't create it", %{conn: conn} do
@ -840,6 +840,26 @@ test "gets an users media", %{conn: conn} do
assert [%{"id" => id}] = json_response(conn, 200) assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(image_post.id) assert id == to_string(image_post.id)
end end
test "gets a user's statuses without reblogs", %{conn: conn} do
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{"status" => "HI!!!"})
{:ok, _, _} = CommonAPI.repeat(post.id, user)
conn =
conn
|> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "true"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(post.id)
conn =
conn
|> get("/api/v1/accounts/#{user.id}/statuses", %{"exclude_reblogs" => "1"})
assert [%{"id" => id}] = json_response(conn, 200)
assert id == to_string(post.id)
end
end end
describe "user relationships" do describe "user relationships" do

View file

@ -19,6 +19,17 @@ test "nodeinfo shows staff accounts", %{conn: conn} do
assert user.ap_id in result["metadata"]["staffAccounts"] assert user.ap_id in result["metadata"]["staffAccounts"]
end end
test "nodeinfo shows restricted nicknames", %{conn: conn} do
conn =
conn
|> get("/nodeinfo/2.0.json")
assert result = json_response(conn, 200)
assert Pleroma.Config.get([Pleroma.User, :restricted_nicknames]) ==
result["metadata"]["restrictedNicknames"]
end
test "returns 404 when federation is disabled", %{conn: conn} do test "returns 404 when federation is disabled", %{conn: conn} do
instance = instance =
Application.get_env(:pleroma, :instance) Application.get_env(:pleroma, :instance)

View file

@ -25,7 +25,7 @@ test "it removes the mentioned activity" do
refute Repo.get(Activity, note.id) refute Repo.get(Activity, note.id)
refute Repo.get(Activity, like.id) refute Repo.get(Activity, like.id)
refute Object.get_by_ap_id(note.data["object"]["id"]) assert Object.get_by_ap_id(note.data["object"]["id"]).data["type"] == "Tombstone"
assert Repo.get(Activity, second_note.id) assert Repo.get(Activity, second_note.id)
assert Object.get_by_ap_id(second_note.data["object"]["id"]) assert Object.get_by_ap_id(second_note.data["object"]["id"])

View file

@ -5,7 +5,7 @@
defmodule Pleroma.Web.OStatus.OStatusControllerTest do defmodule Pleroma.Web.OStatus.OStatusControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.{User, Repo} alias Pleroma.{User, Repo, Object}
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OStatus.ActivityRepresenter alias Pleroma.Web.OStatus.ActivityRepresenter
@ -114,6 +114,22 @@ test "404s on nonexisting objects", %{conn: conn} do
|> response(404) |> response(404)
end end
test "404s on deleted objects", %{conn: conn} do
note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["object"]["id"]))
object = Object.get_by_ap_id(note_activity.data["object"]["id"])
conn
|> get("/objects/#{uuid}")
|> response(200)
Object.delete(object)
conn
|> get("/objects/#{uuid}")
|> response(404)
end
test "gets an activity", %{conn: conn} do test "gets an activity", %{conn: conn} do
note_activity = insert(:note_activity) note_activity = insert(:note_activity)
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"])) [_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))

View file

@ -112,6 +112,8 @@ test "with credentials", %{conn: conn, user: user} do
end end
describe "GET /statuses/public_timeline.json" do describe "GET /statuses/public_timeline.json" do
setup [:valid_user]
test "returns statuses", %{conn: conn} do test "returns statuses", %{conn: conn} do
user = insert(:user) user = insert(:user)
activities = ActivityBuilder.insert_list(30, %{}, %{user: user}) activities = ActivityBuilder.insert_list(30, %{}, %{user: user})
@ -145,14 +147,44 @@ test "returns 403 to unauthenticated request when the instance is not public", %
Application.put_env(:pleroma, :instance, instance) Application.put_env(:pleroma, :instance, instance)
end end
test "returns 200 to authenticated request when the instance is not public",
%{conn: conn, user: user} do
instance =
Application.get_env(:pleroma, :instance)
|> Keyword.put(:public, false)
Application.put_env(:pleroma, :instance, instance)
conn
|> with_credentials(user.nickname, "test")
|> get("/api/statuses/public_timeline.json")
|> json_response(200)
instance =
Application.get_env(:pleroma, :instance)
|> Keyword.put(:public, true)
Application.put_env(:pleroma, :instance, instance)
end
test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do
conn conn
|> get("/api/statuses/public_timeline.json") |> get("/api/statuses/public_timeline.json")
|> json_response(200) |> json_response(200)
end end
test "returns 200 to authenticated request when the instance is public",
%{conn: conn, user: user} do
conn
|> with_credentials(user.nickname, "test")
|> get("/api/statuses/public_timeline.json")
|> json_response(200)
end
end end
describe "GET /statuses/public_and_external_timeline.json" do describe "GET /statuses/public_and_external_timeline.json" do
setup [:valid_user]
test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do test "returns 403 to unauthenticated request when the instance is not public", %{conn: conn} do
instance = instance =
Application.get_env(:pleroma, :instance) Application.get_env(:pleroma, :instance)
@ -171,11 +203,39 @@ test "returns 403 to unauthenticated request when the instance is not public", %
Application.put_env(:pleroma, :instance, instance) Application.put_env(:pleroma, :instance, instance)
end end
test "returns 200 to authenticated request when the instance is not public",
%{conn: conn, user: user} do
instance =
Application.get_env(:pleroma, :instance)
|> Keyword.put(:public, false)
Application.put_env(:pleroma, :instance, instance)
conn
|> with_credentials(user.nickname, "test")
|> get("/api/statuses/public_and_external_timeline.json")
|> json_response(200)
instance =
Application.get_env(:pleroma, :instance)
|> Keyword.put(:public, true)
Application.put_env(:pleroma, :instance, instance)
end
test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do test "returns 200 to unauthenticated request when the instance is public", %{conn: conn} do
conn conn
|> get("/api/statuses/public_and_external_timeline.json") |> get("/api/statuses/public_and_external_timeline.json")
|> json_response(200) |> json_response(200)
end end
test "returns 200 to authenticated request when the instance is public",
%{conn: conn, user: user} do
conn
|> with_credentials(user.nickname, "test")
|> get("/api/statuses/public_and_external_timeline.json")
|> json_response(200)
end
end end
describe "GET /statuses/show/:id.json" do describe "GET /statuses/show/:id.json" do
@ -519,6 +579,34 @@ test "with credentials screen_name", %{conn: conn, user: current_user} do
assert length(response) == 1 assert length(response) == 1
assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user}) assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user})
end end
test "with credentials with user_id, excluding RTs", %{conn: conn, user: current_user} do
user = insert(:user)
{:ok, activity} = ActivityBuilder.insert(%{"id" => 1, "type" => "Create"}, %{user: user})
{:ok, _} = ActivityBuilder.insert(%{"id" => 2, "type" => "Announce"}, %{user: user})
conn =
conn
|> with_credentials(current_user.nickname, "test")
|> get("/api/statuses/user_timeline.json", %{
"user_id" => user.id,
"include_rts" => "false"
})
response = json_response(conn, 200)
assert length(response) == 1
assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user})
conn =
conn
|> get("/api/statuses/user_timeline.json", %{"user_id" => user.id, "include_rts" => "0"})
response = json_response(conn, 200)
assert length(response) == 1
assert Enum.at(response, 0) == ActivityRepresenter.to_map(activity, %{user: user})
end
end end
describe "POST /friendships/create.json" do describe "POST /friendships/create.json" do
@ -1057,6 +1145,24 @@ test "it returns the followers for a hidden network if requested by the user the
end end
end end
describe "GET /api/statuses/blocks" do
test "it returns the list of users blocked by requester", %{conn: conn} do
user = insert(:user)
other_user = insert(:user)
{:ok, user} = User.block(user, other_user)
conn =
conn
|> assign(:user, user)
|> get("/api/statuses/blocks")
expected = UserView.render("index.json", %{users: [other_user], for: user})
result = json_response(conn, 200)
assert Enum.sort(expected) == Enum.sort(result)
end
end
describe "GET /api/statuses/friends" do describe "GET /api/statuses/friends" do
test "it returns the logged in user's friends", %{conn: conn} do test "it returns the logged in user's friends", %{conn: conn} do
user = insert(:user) user = insert(:user)

View file

@ -0,0 +1,35 @@
defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
describe "POST /api/pleroma/follow_import" do
test "it returns HTTP 200", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
response =
conn
|> assign(:user, user1)
|> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"})
|> json_response(:ok)
assert response == "job started"
end
end
describe "POST /api/pleroma/blocks_import" do
test "it returns HTTP 200", %{conn: conn} do
user1 = insert(:user)
user2 = insert(:user)
response =
conn
|> assign(:user, user1)
|> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"})
|> json_response(:ok)
assert response == "job started"
end
end
end