@@ -19,7 +19,7 @@ | |||
# | |||
# You can give explicit globs or simply directories. | |||
# In the latter case `**/*.{ex,exs}` will be used. | |||
included: ["lib/", "src/", "web/", "apps/"], | |||
included: ["lib/", "src/", "web/", "apps/", "test/"], | |||
excluded: [~r"/_build/", ~r"/deps/"] | |||
}, | |||
# | |||
@@ -1,7 +1,8 @@ | |||
image: elixir:1.7.2 | |||
services: | |||
- postgres:9.6.2 | |||
- name: postgres:9.6.2 | |||
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] | |||
variables: | |||
POSTGRES_DB: pleroma_test | |||
@@ -35,4 +36,4 @@ lint: | |||
unit-testing: | |||
stage: test | |||
script: | |||
- mix test --trace | |||
- mix test --trace --preload-modules |
@@ -162,7 +162,9 @@ config :pleroma, :instance, | |||
mrf_transparency: true, | |||
autofollowed_nicknames: [], | |||
max_pinned_statuses: 1, | |||
no_attachment_links: false | |||
no_attachment_links: false, | |||
welcome_user_nickname: nil, | |||
welcome_message: nil | |||
config :pleroma, :markup, | |||
# XXX - unfortunately, inline images must be enabled by default right now, because | |||
@@ -228,8 +230,8 @@ config :pleroma, :mrf_rejectnonpublic, | |||
allow_direct: false | |||
config :pleroma, :mrf_hellthread, | |||
delist_threshold: 5, | |||
reject_threshold: 10 | |||
delist_threshold: 10, | |||
reject_threshold: 20 | |||
config :pleroma, :mrf_simple, | |||
media_removal: [], | |||
@@ -330,14 +332,16 @@ config :pleroma, Pleroma.User, | |||
"web" | |||
] | |||
config :pleroma, Pleroma.Web.Federator, max_jobs: 50 | |||
config :pleroma, Pleroma.Web.Federator.RetryQueue, | |||
enabled: false, | |||
max_jobs: 20, | |||
initial_timeout: 30, | |||
max_retries: 5 | |||
config :pleroma, Pleroma.Jobs, | |||
federator_incoming: [max_jobs: 50], | |||
federator_outgoing: [max_jobs: 50] | |||
# 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" |
@@ -16,7 +16,8 @@ config :pleroma, Pleroma.Web.Endpoint, | |||
debug_errors: true, | |||
code_reloader: true, | |||
check_origin: false, | |||
watchers: [] | |||
watchers: [], | |||
secure_cookie_flag: false | |||
config :pleroma, Pleroma.Mailer, adapter: Swoosh.Adapters.Local | |||
@@ -44,6 +44,8 @@ config :web_push_encryption, :vapid_details, | |||
"BLH1qVhJItRGCfxgTtONfsOKDc9VRAraXw-3NsmjMngWSh7NxOizN6bkuRA7iLTMPS82PjwJAr3UoK9EC1IFrz4", | |||
private_key: "_-XZ0iebPrRfZ_o0-IatTdszYa8VCH1yLN-JauK7HHA" | |||
config :pleroma, Pleroma.Jobs, testing: [max_jobs: 2] | |||
try do | |||
import_config "test.secret.exs" | |||
rescue | |||
@@ -7,6 +7,7 @@ Feel free to contact us to be added to this list! | |||
- Homepage: <http://www.pleroma.com/desktop-app/> | |||
- Source Code: ??? | |||
- Platforms: Windows, Mac, (Linux?) | |||
- Features: Streaming Ready | |||
### Social | |||
- Source Code: <https://gitlab.gnome.org/BrainBlasted/Social> | |||
@@ -19,6 +20,7 @@ Feel free to contact us to be added to this list! | |||
- Source Code: <https://github.com/h3poteto/whalebird-desktop> | |||
- Contact: [@h3poteto@pleroma.io](https://pleroma.io/users/h3poteto) | |||
- Platforms: Windows, Mac, Linux | |||
- Features: Streaming Ready | |||
## Handheld | |||
### Amaroq | |||
@@ -26,60 +28,71 @@ Feel free to contact us to be added to this list! | |||
- Source Code: <https://github.com/ReticentJohn/Amaroq> | |||
- Contact: [@eurasierboy@mastodon.social](https://mastodon.social/users/eurasierboy) | |||
- Platforms: iOS | |||
- Features: No Streaming | |||
### Nekonium | |||
- Homepage: [F-Droid Repository](https://repo.gdgd.jp.net/), [Google Play](https://play.google.com/store/apps/details?id=com.apps.nekonium), [Amazon](https://www.amazon.co.jp/dp/B076FXPRBC/) | |||
- Source: <https://git.gdgd.jp.net/lin/nekonium/> | |||
- Contact: [@lin@pleroma.gdgd.jp.net](https://pleroma.gdgd.jp.net/users/lin) | |||
- Platforms: Android | |||
- Features: Streaming Ready | |||
### Mastalab | |||
- Source Code: <https://gitlab.com/tom79/mastalab/> | |||
- Contact: [@tom79@mastodon.social](https://mastodon.social/users/tom79) | |||
- Platforms: Android | |||
- Features: Streaming Ready | |||
### Roma | |||
- Homepage: <http://www.pleroma.com/> | |||
- Source Code: ??? | |||
- Platforms: iOS, Android | |||
- Features: No Streaming | |||
### Tootdon | |||
- Homepage: <http://tootdon.club/>, <http://blog.mastodon-tootdon.com/> | |||
- Source Code: ??? | |||
- Contact: [@tootdon@mstdn.jp](https://mstdn.jp/users/tootdon) | |||
- Platforms: Android, iOS | |||
- Features: No Streaming | |||
### Tusky | |||
- Homepage: <https://tuskyapp.github.io/> | |||
- Source Code: <https://github.com/tuskyapp/Tusky> | |||
- Contact: [@ConnyDuck@mastodon.social](https://mastodon.social/users/ConnyDuck) | |||
- Platforms: Android | |||
- Features: No Streaming | |||
### Twidere | |||
- Homepage: <https://twidere.mariotaku.org/> | |||
- Source Code: <https://github.com/TwidereProject/Twidere-Android/>, <https://github.com/TwidereProject/Twidere-iOS/> | |||
- Contact: <me@mariotaku.org> | |||
- Platform: Android, iOS | |||
- Features: No Streaming | |||
## Alternative Web Interfaces | |||
### Brutaldon | |||
- Homepage: <https://jfm.carcosa.net/projects/software/brutaldon/> | |||
- Source Code: <https://github.com/jfmcbrayer/brutaldon> | |||
- Contact: [@gcupc@glitch.social](https://glitch.social/users/gcupc) | |||
- Features: No Streaming | |||
### Feather | |||
- Source Code: <https://github.com/kaniini/feather> | |||
- Contact: [@kaniini@pleroma.site](https://pleroma.site/kaniini) | |||
- Features: No Streaming | |||
### Halcyon | |||
- Source Code: <https://notabug.org/halcyon-suite/halcyon> | |||
- Contact: [@halcyon@social.csswg.org](https://social.csswg.org/users/halcyon) | |||
- Features: Streaming Ready | |||
### Pinafore | |||
- Homepage: <https://pinafore.social/> | |||
- Source Code: <https://github.com/nolanlawson/pinafore> | |||
- Contact: [@pinafore@mastodon.technology](https://mastodon.technology/users/pinafore) | |||
- Note: Pleroma support is a secondary goal | |||
- Features: No Streaming | |||
### Sengi | |||
- Source Code: <https://github.com/NicolasConstant/sengi> | |||
@@ -0,0 +1,11 @@ | |||
# Differences in Mastodon API responses from vanilla Mastodon | |||
A Pleroma instance can be identified by "<Mastodon version> (compatible; Pleroma <version>)" present in `version` field in response from `/api/v1/instance` | |||
## Flake IDs | |||
Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mastodon's ids they are sortable strings | |||
## Attachment cap | |||
Some apps operate under the assumption that no more than 4 attachments can be returned or uploaded. Pleroma however does not enforce any limits on attachment count neither when returning the status object nor when posting. |
@@ -94,3 +94,17 @@ Request parameters can be passed via [query strings](https://en.wikipedia.org/wi | |||
## `/api/pleroma/admin/`… | |||
See [Admin-API](Admin-API.md) | |||
## `/api/v1/pleroma/flavour/:flavour` | |||
* Method `POST` | |||
* Authentication: required | |||
* Response: JSON string. Returns the user flavour or the default one on success, otherwise returns `{"error": "error_msg"}` | |||
* Example response: "glitch" | |||
* Note: This is intended to be used only by mastofe | |||
## `/api/v1/pleroma/flavour` | |||
* Method `GET` | |||
* Authentication: required | |||
* Response: JSON string. Returns the user flavour or the default one. | |||
* Example response: "glitch" | |||
* Note: This is intended to be used only by mastofe |
@@ -36,14 +36,15 @@ This filter replaces the filename (not the path) of an upload. For complete obfu | |||
An example for Sendgrid adapter: | |||
``` | |||
```exs | |||
config :pleroma, Pleroma.Mailer, | |||
adapter: Swoosh.Adapters.Sendgrid, | |||
api_key: "YOUR_API_KEY" | |||
``` | |||
An example for SMTP adapter: | |||
``` | |||
```exs | |||
config :pleroma, Pleroma.Mailer, | |||
adapter: Swoosh.Adapters.SMTP, | |||
relay: "smtp.gmail.com", | |||
@@ -97,6 +98,8 @@ config :pleroma, Pleroma.Mailer, | |||
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature. | |||
* `autofollowed_nicknames`: Set to nicknames of (local) users that every new user should automatically follow. | |||
* `no_attachment_links`: Set to true to disable automatically adding attachment link text to statuses | |||
* `welcome_message`: A message that will be send to a newly registered users as a direct message. | |||
* `welcome_user_nickname`: The nickname of the local user that sends the welcome message. | |||
## :logger | |||
* `backends`: `:console` is used to send logs to stdout, `{ExSyslogger, :ex_syslogger}` to log to syslog | |||
@@ -207,7 +210,7 @@ their ActivityPub ID. | |||
An example: | |||
``` | |||
```exs | |||
config :pleroma, :mrf_user_allowlist, | |||
"example.org": ["https://example.org/users/admin"] | |||
``` | |||
@@ -236,18 +239,34 @@ the source code is here: https://github.com/koto-bank/kocaptcha. The default end | |||
Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the 'admin_token' parameter. Example: | |||
``` | |||
```exs | |||
config :pleroma, :admin_token, "somerandomtoken" | |||
``` | |||
You can then do | |||
``` | |||
```sh | |||
curl "http://localhost:4000/api/pleroma/admin/invite_token?admin_token=somerandomtoken" | |||
``` | |||
## Pleroma.Web.Federator | |||
## Pleroma.Jobs | |||
A list of job queues and their settings. | |||
Job queue settings: | |||
* `max_jobs`: The maximum amount of parallel jobs running at the same time. | |||
Example: | |||
```exs | |||
config :pleroma, Pleroma.Jobs, | |||
federator_incoming: [max_jobs: 50], | |||
federator_outgoing: [max_jobs: 50] | |||
``` | |||
This config contains two queues: `federator_incoming` and `federator_outgoing`. Both have the `max_jobs` set to `50`. | |||
* `max_jobs`: The maximum amount of parallel federation jobs running at the same time. | |||
## Pleroma.Web.Federator.RetryQueue | |||
@@ -108,9 +108,10 @@ defmodule Pleroma.Application do | |||
hackney_pool_children() ++ | |||
[ | |||
worker(Pleroma.Web.Federator.RetryQueue, []), | |||
worker(Pleroma.Web.Federator, []), | |||
worker(Pleroma.Stats, []), | |||
worker(Pleroma.Web.Push, []) | |||
worker(Pleroma.Web.Push, []), | |||
worker(Pleroma.Jobs, []), | |||
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary) | |||
] ++ | |||
streamer_child() ++ | |||
chat_child() ++ | |||
@@ -0,0 +1,152 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Jobs do | |||
@moduledoc """ | |||
A basic job queue | |||
""" | |||
use GenServer | |||
require Logger | |||
def init(args) do | |||
{:ok, args} | |||
end | |||
def start_link do | |||
queues = | |||
Pleroma.Config.get(Pleroma.Jobs) | |||
|> Enum.map(fn {name, _} -> create_queue(name) end) | |||
|> Enum.into(%{}) | |||
state = %{ | |||
queues: queues, | |||
refs: %{} | |||
} | |||
GenServer.start_link(__MODULE__, state, name: __MODULE__) | |||
end | |||
def create_queue(name) do | |||
{name, {:sets.new(), []}} | |||
end | |||
@doc """ | |||
Enqueues a job. | |||
Returns `:ok`. | |||
## Arguments | |||
- `queue_name` - a queue name(must be specified in the config). | |||
- `mod` - a worker module (must have `perform` function). | |||
- `args` - a list of arguments for the `perform` function of the worker module. | |||
- `priority` - a job priority (`0` by default). | |||
## Examples | |||
Enqueue `Module.perform/0` with `priority=1`: | |||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, []) | |||
:ok | |||
Enqueue `Module.perform(:job_name)` with `priority=5`: | |||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:job_name], 5) | |||
:ok | |||
Enqueue `Module.perform(:another_job, data)` with `priority=1`: | |||
iex> data = "foobar" | |||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:another_job, data]) | |||
:ok | |||
Enqueue `Module.perform(:foobar_job, :foo, :bar, 42)` with `priority=1`: | |||
iex> Pleroma.Jobs.enqueue(:example_queue, Module, [:foobar_job, :foo, :bar, 42]) | |||
:ok | |||
""" | |||
def enqueue(queue_name, mod, args, priority \\ 1) | |||
if Mix.env() == :test do | |||
def enqueue(_queue_name, mod, args, _priority) do | |||
apply(mod, :perform, args) | |||
end | |||
else | |||
@spec enqueue(atom(), atom(), [any()], integer()) :: :ok | |||
def enqueue(queue_name, mod, args, priority) do | |||
GenServer.cast(__MODULE__, {:enqueue, queue_name, mod, args, priority}) | |||
end | |||
end | |||
def handle_cast({:enqueue, queue_name, mod, args, priority}, state) do | |||
{running_jobs, queue} = state[:queues][queue_name] | |||
queue = enqueue_sorted(queue, {mod, args}, priority) | |||
state = | |||
state | |||
|> update_queue(queue_name, {running_jobs, queue}) | |||
|> maybe_start_job(queue_name, running_jobs, queue) | |||
{:noreply, state} | |||
end | |||
def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do | |||
queue_name = state.refs[ref] | |||
{running_jobs, queue} = state[:queues][queue_name] | |||
running_jobs = :sets.del_element(ref, running_jobs) | |||
state = | |||
state | |||
|> remove_ref(ref) | |||
|> update_queue(queue_name, {running_jobs, queue}) | |||
|> maybe_start_job(queue_name, running_jobs, queue) | |||
{:noreply, state} | |||
end | |||
def maybe_start_job(state, queue_name, running_jobs, queue) do | |||
if :sets.size(running_jobs) < Pleroma.Config.get([__MODULE__, queue_name, :max_jobs]) && | |||
queue != [] do | |||
{{mod, args}, queue} = queue_pop(queue) | |||
{:ok, pid} = Task.start(fn -> apply(mod, :perform, args) end) | |||
mref = Process.monitor(pid) | |||
state | |||
|> add_ref(queue_name, mref) | |||
|> update_queue(queue_name, {:sets.add_element(mref, running_jobs), queue}) | |||
else | |||
state | |||
end | |||
end | |||
def enqueue_sorted(queue, element, priority) do | |||
[%{item: element, priority: priority} | queue] | |||
|> Enum.sort_by(fn %{priority: priority} -> priority end) | |||
end | |||
def queue_pop([%{item: element} | queue]) do | |||
{element, queue} | |||
end | |||
defp add_ref(state, queue_name, ref) do | |||
refs = Map.put(state[:refs], ref, queue_name) | |||
Map.put(state, :refs, refs) | |||
end | |||
defp remove_ref(state, ref) do | |||
refs = Map.delete(state[:refs], ref) | |||
Map.put(state, :refs, refs) | |||
end | |||
defp update_queue(state, queue_name, data) do | |||
queues = Map.put(state[:queues], queue_name, data) | |||
Map.put(state, :queues, queues) | |||
end | |||
end |
@@ -33,7 +33,22 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do | |||
end | |||
defp csp_string do | |||
protocol = Config.get([Pleroma.Web.Endpoint, :protocol]) | |||
scheme = Config.get([Pleroma.Web.Endpoint, :url])[:scheme] | |||
websocket_url = String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws") | |||
connect_src = | |||
if Mix.env() == :dev do | |||
"connect-src 'self' http://localhost:3035/ " <> websocket_url | |||
else | |||
"connect-src 'self' " <> websocket_url | |||
end | |||
script_src = | |||
if Mix.env() == :dev do | |||
"script-src 'self' 'unsafe-eval'" | |||
else | |||
"script-src 'self'" | |||
end | |||
[ | |||
"default-src 'none'", | |||
@@ -43,10 +58,10 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do | |||
"media-src 'self' https:", | |||
"style-src 'self' 'unsafe-inline'", | |||
"font-src 'self'", | |||
"script-src 'self'", | |||
"connect-src 'self' " <> String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws"), | |||
"manifest-src 'self'", | |||
if protocol == "https" do | |||
connect_src, | |||
script_src, | |||
if scheme == "https" do | |||
"upgrade-insecure-requests" | |||
end | |||
] | |||
@@ -25,7 +25,7 @@ defmodule Pleroma.Uploaders.MDII do | |||
query = "#{cgi}?#{extension}" | |||
with {:ok, %{status: 200, body: body}} <- | |||
@httpoison.post(query, file_data, adapter: [pool: :default]) do | |||
@httpoison.post(query, file_data, [], adapter: [pool: :default]) do | |||
remote_file_name = String.split(body) |> List.first() | |||
public_url = "#{files}/#{remote_file_name}.#{extension}" | |||
{:ok, {:url, public_url}} | |||
@@ -237,6 +237,7 @@ defmodule Pleroma.User do | |||
changeset | |||
|> put_change(:password_hash, hashed) | |||
|> put_change(:ap_id, ap_id) | |||
|> unique_constraint(:ap_id) | |||
|> put_change(:following, [followers]) | |||
|> put_change(:follower_address, followers) | |||
else | |||
@@ -261,6 +262,7 @@ defmodule Pleroma.User do | |||
def register(%Ecto.Changeset{} = changeset) do | |||
with {:ok, user} <- Repo.insert(changeset), | |||
{:ok, user} <- autofollow_users(user), | |||
{:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user), | |||
{:ok, _} <- try_send_confirmation_email(user) do | |||
{:ok, user} | |||
end | |||
@@ -311,12 +313,12 @@ defmodule Pleroma.User do | |||
end | |||
end | |||
@doc "A mass follow for local users. Respects blocks but does not create activities." | |||
@doc "A mass follow for local users. Respects blocks in both directions but does not create activities." | |||
@spec follow_all(User.t(), list(User.t())) :: {atom(), User.t()} | |||
def follow_all(follower, followeds) do | |||
followed_addresses = | |||
followeds | |||
|> Enum.reject(fn %{ap_id: ap_id} -> ap_id in follower.info.blocks end) | |||
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end) | |||
|> Enum.map(fn %{follower_address: fa} -> fa end) | |||
q = | |||
@@ -618,6 +620,32 @@ defmodule Pleroma.User do | |||
) | |||
end | |||
def update_follow_request_count(%User{} = user) do | |||
subquery = | |||
user | |||
|> User.get_follow_requests_query() | |||
|> select([a], %{count: count(a.id)}) | |||
User | |||
|> where(id: ^user.id) | |||
|> join(:inner, [u], s in subquery(subquery)) | |||
|> update([u, s], | |||
set: [ | |||
info: | |||
fragment( | |||
"jsonb_set(?, '{follow_request_count}', ?::varchar::jsonb, true)", | |||
u.info, | |||
s.count | |||
) | |||
] | |||
) | |||
|> Repo.update_all([], returning: true) | |||
|> case do | |||
{1, [user]} -> {:ok, user} | |||
_ -> {:error, user} | |||
end | |||
end | |||
def get_follow_requests(%User{} = user) do | |||
q = get_follow_requests_query(user) | |||
reqs = Repo.all(q) | |||
@@ -731,7 +759,7 @@ defmodule Pleroma.User do | |||
# Strip the beginning @ off if there is a query | |||
query = String.trim_leading(query, "@") | |||
if resolve, do: User.get_or_fetch_by_nickname(query) | |||
if resolve, do: get_or_fetch(query) | |||
fts_results = do_search(fts_search_subquery(query), for_user) | |||
@@ -1173,7 +1201,7 @@ defmodule Pleroma.User do | |||
{:ok, updated_user} = | |||
user | |||
|> change(%{tags: new_tags}) | |||
|> Repo.update() | |||
|> update_and_set_cache() | |||
updated_user | |||
end | |||
@@ -12,6 +12,7 @@ defmodule Pleroma.User.Info do | |||
field(:source_data, :map, default: %{}) | |||
field(:note_count, :integer, default: 0) | |||
field(:follower_count, :integer, default: 0) | |||
field(:follow_request_count, :integer, default: 0) | |||
field(:locked, :boolean, default: false) | |||
field(:confirmation_pending, :boolean, default: false) | |||
field(:confirmation_token, :string, default: nil) | |||
@@ -34,6 +35,7 @@ defmodule Pleroma.User.Info do | |||
field(:hide_followers, :boolean, default: false) | |||
field(:hide_follows, :boolean, default: false) | |||
field(:pinned_activities, {:array, :string}, default: []) | |||
field(:flavour, :string, default: nil) | |||
# Found in the wild | |||
# ap_id -> Where is this used? | |||
@@ -186,6 +188,14 @@ defmodule Pleroma.User.Info do | |||
|> validate_required([:settings]) | |||
end | |||
def mastodon_flavour_update(info, flavour) do | |||
params = %{flavour: flavour} | |||
info | |||
|> cast(params, [:flavour]) | |||
|> validate_required([:flavour]) | |||
end | |||
def set_source_data(info, source_data) do | |||
params = %{source_data: source_data} | |||
@@ -0,0 +1,30 @@ | |||
defmodule Pleroma.User.WelcomeMessage do | |||
alias Pleroma.User | |||
alias Pleroma.Web.CommonAPI | |||
def post_welcome_message_to_user(user) do | |||
with %User{} = sender_user <- welcome_user(), | |||
message when is_binary(message) <- welcome_message() do | |||
CommonAPI.post(sender_user, %{ | |||
"visibility" => "direct", | |||
"status" => "@#{user.nickname}\n#{message}" | |||
}) | |||
else | |||
_ -> {:ok, nil} | |||
end | |||
end | |||
defp welcome_user() do | |||
with nickname when is_binary(nickname) <- | |||
Pleroma.Config.get([:instance, :welcome_user_nickname]), | |||
%User{local: true} = user <- User.get_cached_by_nickname(nickname) do | |||
user | |||
else | |||
_ -> nil | |||
end | |||
end | |||
defp welcome_message() do | |||
Pleroma.Config.get([:instance, :welcome_message]) | |||
end | |||
end |
@@ -172,9 +172,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
# only accept false as false value | |||
local = !(params[:local] == false) | |||
with data <- %{"to" => to, "type" => "Accept", "actor" => actor, "object" => object}, | |||
with data <- %{"to" => to, "type" => "Accept", "actor" => actor.ap_id, "object" => object}, | |||
{:ok, activity} <- insert(data, local), | |||
:ok <- maybe_federate(activity) do | |||
:ok <- maybe_federate(activity), | |||
_ <- User.update_follow_request_count(actor) do | |||
{:ok, activity} | |||
end | |||
end | |||
@@ -183,9 +184,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
# only accept false as false value | |||
local = !(params[:local] == false) | |||
with data <- %{"to" => to, "type" => "Reject", "actor" => actor, "object" => object}, | |||
with data <- %{"to" => to, "type" => "Reject", "actor" => actor.ap_id, "object" => object}, | |||
{:ok, activity} <- insert(data, local), | |||
:ok <- maybe_federate(activity) do | |||
:ok <- maybe_federate(activity), | |||
_ <- User.update_follow_request_count(actor) do | |||
{:ok, activity} | |||
end | |||
end | |||
@@ -283,7 +285,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
def follow(follower, followed, activity_id \\ nil, local \\ true) do | |||
with data <- make_follow_data(follower, followed, activity_id), | |||
{:ok, activity} <- insert(data, local), | |||
:ok <- maybe_federate(activity) do | |||
:ok <- maybe_federate(activity), | |||
_ <- User.update_follow_request_count(followed) do | |||
{:ok, activity} | |||
end | |||
end | |||
@@ -293,7 +296,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
{:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"), | |||
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), | |||
{:ok, activity} <- insert(unfollow_data, local), | |||
:ok <- maybe_federate(activity) do | |||
:ok <- maybe_federate(activity), | |||
_ <- User.update_follow_request_count(followed) do | |||
{:ok, activity} | |||
end | |||
end | |||
@@ -753,21 +757,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
public = is_public?(activity) | |||
reachable_inboxes_metadata = | |||
(Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers) | |||
|> Enum.filter(fn user -> User.ap_enabled?(user) end) | |||
|> Enum.map(fn %{info: %{source_data: data}} -> | |||
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"] | |||
end) | |||
|> Enum.uniq() | |||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end) | |||
|> Instances.filter_reachable() | |||
{:ok, data} = Transmogrifier.prepare_outgoing(activity.data) | |||
json = Jason.encode!(data) | |||
Enum.each(reachable_inboxes_metadata, fn {inbox, unreachable_since} -> | |||
Federator.enqueue(:publish_single_ap, %{ | |||
(Pleroma.Web.Salmon.remote_users(activity) ++ remote_followers) | |||
|> Enum.filter(fn user -> User.ap_enabled?(user) end) | |||
|> Enum.map(fn %{info: %{source_data: data}} -> | |||
(is_map(data["endpoints"]) && Map.get(data["endpoints"], "sharedInbox")) || data["inbox"] | |||
end) | |||
|> Enum.uniq() | |||
|> Enum.filter(fn inbox -> should_federate?(inbox, public) end) | |||
|> Instances.filter_reachable() | |||
|> Enum.each(fn {inbox, unreachable_since} -> | |||
Federator.publish_single_ap(%{ | |||
inbox: inbox, | |||
json: json, | |||
actor: actor, | |||
@@ -818,8 +820,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
if object = Object.get_cached_by_ap_id(id) do | |||
{:ok, object} | |||
else | |||
Logger.info("Fetching #{id} via AP") | |||
with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), | |||
nil <- Object.normalize(data), | |||
params <- %{ | |||
@@ -851,7 +851,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
end | |||
def fetch_and_contain_remote_object_from_id(id) do | |||
Logger.info("Fetching #{id} via AP") | |||
Logger.info("Fetching object #{id} via AP") | |||
with true <- String.starts_with?(id, "http"), | |||
{:ok, %{body: body, status: code}} when code in 200..299 <- | |||
@@ -878,7 +878,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
end | |||
def is_private?(activity) do | |||
!is_public?(activity) && Enum.any?(activity.data["to"], &String.contains?(&1, "/followers")) | |||
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 | |||
@@ -155,13 +155,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do | |||
with %User{} = user <- User.get_cached_by_nickname(nickname), | |||
true <- Utils.recipient_in_message(user.ap_id, params), | |||
params <- Utils.maybe_splice_recipient(user.ap_id, params) do | |||
Federator.enqueue(:incoming_ap_doc, params) | |||
Federator.incoming_ap_doc(params) | |||
json(conn, "ok") | |||
end | |||
end | |||
def inbox(%{assigns: %{valid_signature: true}} = conn, params) do | |||
Federator.enqueue(:incoming_ap_doc, params) | |||
Federator.incoming_ap_doc(params) | |||
json(conn, "ok") | |||
end | |||
@@ -6,40 +6,80 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do | |||
alias Pleroma.User | |||
@behaviour Pleroma.Web.ActivityPub.MRF | |||
defp delist_message(message) do | |||
defp delist_message(message, threshold) when threshold > 0 do | |||
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address | |||
message | |||
|> Map.put("to", [follower_collection]) | |||
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) | |||
follower_collection? = Enum.member?(message["to"] ++ message["cc"], follower_collection) | |||
message = | |||
case get_recipient_count(message) do | |||
{:public, recipients} | |||
when follower_collection? and recipients > threshold -> | |||
message | |||
|> Map.put("to", [follower_collection]) | |||
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) | |||
{:public, recipients} when recipients > threshold -> | |||
message | |||
|> Map.put("to", []) | |||
|> Map.put("cc", ["https://www.w3.org/ns/activitystreams#Public"]) | |||
_ -> | |||
message | |||
end | |||
{:ok, message} | |||
end | |||
defp delist_message(message, _threshold), do: {:ok, message} | |||
defp reject_message(message, threshold) when threshold > 0 do | |||
with {_, recipients} <- get_recipient_count(message) do | |||
if recipients > threshold do | |||
{:reject, nil} | |||
else | |||
{:ok, message} | |||
end | |||
end | |||
end | |||
defp reject_message(message, _threshold), do: {:ok, message} | |||
defp get_recipient_count(message) do | |||
recipients = (message["to"] || []) ++ (message["cc"] || []) | |||
follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address | |||
if Enum.member?(recipients, "https://www.w3.org/ns/activitystreams#Public") do | |||
recipients = | |||
recipients | |||
|> List.delete("https://www.w3.org/ns/activitystreams#Public") | |||
|> List.delete(follower_collection) | |||
{:public, length(recipients)} | |||
else | |||
recipients = | |||
recipients | |||
|> List.delete(follower_collection) | |||
{:not_public, length(recipients)} | |||
end | |||
end | |||
@impl true | |||
def filter(%{"type" => "Create"} = message) do | |||
delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold]) | |||
reject_threshold = | |||
Pleroma.Config.get( | |||
[:mrf_hellthread, :reject_threshold], | |||
Pleroma.Config.get([:mrf_hellthread, :threshold]) | |||
) | |||
recipients = (message["to"] || []) ++ (message["cc"] || []) | |||
cond do | |||
length(recipients) > reject_threshold and reject_threshold > 0 -> | |||
{:reject, nil} | |||
length(recipients) > delist_threshold and delist_threshold > 0 -> | |||
if Enum.member?(message["to"], "https://www.w3.org/ns/activitystreams#Public") or | |||
Enum.member?(message["cc"], "https://www.w3.org/ns/activitystreams#Public") do | |||
{:ok, delist_message(message)} | |||
else | |||
{:ok, message} | |||
end | |||
delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold]) | |||
true -> | |||
{:ok, message} | |||
with {:ok, message} <- reject_message(message, reject_threshold), | |||
{:ok, message} <- delist_message(message, delist_threshold) do | |||
{:ok, message} | |||
else | |||
_e -> {:reject, nil} | |||
end | |||
end | |||
@@ -406,7 +406,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
if not User.locked?(followed) do | |||
ActivityPub.accept(%{ | |||
to: [follower.ap_id], | |||
actor: followed.ap_id, | |||
actor: followed, | |||
object: data, | |||
local: true | |||
}) | |||
@@ -432,7 +432,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
ActivityPub.accept(%{ | |||
to: follow_activity.data["to"], | |||
type: "Accept", | |||
actor: followed.ap_id, | |||
actor: followed, | |||
object: follow_activity.data["id"], | |||
local: false | |||
}) do | |||
@@ -458,7 +458,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
ActivityPub.reject(%{ | |||
to: follow_activity.data["to"], | |||
type: "Reject", | |||
actor: followed.ap_id, | |||
actor: followed, | |||
object: follow_activity.data["id"], | |||
local: false | |||
}) do | |||
@@ -649,7 +649,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
if object = Object.normalize(id), do: {:ok, object}, else: nil | |||
end | |||
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do | |||
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) when is_binary(inReplyTo) do | |||
with false <- String.starts_with?(inReplyTo, "http"), | |||
{:ok, %{data: replied_to_object}} <- get_obj_helper(inReplyTo) do | |||
Map.put(object, "inReplyTo", replied_to_object["external_url"] || inReplyTo) | |||
@@ -765,12 +765,18 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||
def add_hashtags(object) do | |||
tags = | |||
(object["tag"] || []) | |||
|> Enum.map(fn tag -> | |||
%{ | |||
"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", | |||
"name" => "##{tag}", | |||
"type" => "Hashtag" | |||
} | |||
|> Enum.map(fn | |||
# Expand internal representation tags into AS2 tags. | |||
tag when is_binary(tag) -> | |||
%{ | |||
"href" => Pleroma.Web.Endpoint.url() <> "/tags/#{tag}", | |||
"name" => "##{tag}", | |||
"type" => "Hashtag" | |||
} | |||
# Do not process tags which are already AS2 tag objects. | |||
tag when is_map(tag) -> | |||
tag | |||
end) | |||
object | |||
@@ -164,7 +164,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do | |||
_ -> 5 | |||
end | |||
Pleroma.Web.Federator.enqueue(:publish, activity, priority) | |||
Pleroma.Web.Federator.publish(activity, priority) | |||
:ok | |||
end | |||
@@ -12,9 +12,26 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.ActivityPub.Transmogrifier | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.Router.Helpers | |||
alias Pleroma.Web.Endpoint | |||
import Ecto.Query | |||
def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do | |||
%{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)} | |||
end | |||
def render("endpoints.json", %{user: %User{local: true} = _user}) do | |||
%{ | |||
"oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize), | |||
"oauthRegistrationEndpoint" => Helpers.mastodon_api_url(Endpoint, :create_app), | |||
"oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange), | |||
"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox) | |||
} | |||
end | |||
def render("endpoints.json", _), do: %{} | |||
# the instance itself is not a Person, but instead an Application | |||
def render("user.json", %{user: %{nickname: nil} = user}) do | |||
{:ok, user} = WebFinger.ensure_keys_present(user) | |||
@@ -22,6 +39,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) | |||
public_key = :public_key.pem_encode([public_key]) | |||
endpoints = render("endpoints.json", %{user: user}) | |||
%{ | |||
"id" => user.ap_id, | |||
"type" => "Application", | |||
@@ -37,9 +56,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
"owner" => user.ap_id, | |||
"publicKeyPem" => public_key | |||
}, | |||
"endpoints" => %{ | |||
"sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox" | |||
} | |||
"endpoints" => endpoints | |||
} | |||
|> Map.merge(Utils.make_json_ld_header()) | |||
end | |||
@@ -50,6 +67,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) | |||
public_key = :public_key.pem_encode([public_key]) | |||
endpoints = render("endpoints.json", %{user: user}) | |||
%{ | |||
"id" => user.ap_id, | |||
"type" => "Person", | |||
@@ -67,9 +86,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
"owner" => user.ap_id, | |||
"publicKeyPem" => public_key | |||
}, | |||
"endpoints" => %{ | |||
"sharedInbox" => "#{Pleroma.Web.Endpoint.url()}/inbox" | |||
}, | |||
"endpoints" => endpoints, | |||
"icon" => %{ | |||
"type" => "Image", | |||
"url" => User.avatar_url(user) | |||
@@ -88,7 +105,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
query = from(user in query, select: [:ap_id]) | |||
following = Repo.all(query) | |||
collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows) | |||
total = | |||
if !user.info.hide_follows do | |||
length(following) | |||
else | |||
0 | |||
end | |||
collection(following, "#{user.ap_id}/following", page, !user.info.hide_follows, total) | |||
|> Map.merge(Utils.make_json_ld_header()) | |||
end | |||
@@ -97,10 +121,17 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
query = from(user in query, select: [:ap_id]) | |||
following = Repo.all(query) | |||
total = | |||
if !user.info.hide_follows do | |||
length(following) | |||
else | |||
0 | |||
end | |||
%{ | |||
"id" => "#{user.ap_id}/following", | |||
"type" => "OrderedCollection", | |||
"totalItems" => length(following), | |||
"totalItems" => total, | |||
"first" => collection(following, "#{user.ap_id}/following", 1, !user.info.hide_follows) | |||
} | |||
|> Map.merge(Utils.make_json_ld_header()) | |||
@@ -111,7 +142,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
query = from(user in query, select: [:ap_id]) | |||
followers = Repo.all(query) | |||
collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers) | |||
total = | |||
if !user.info.hide_followers do | |||
length(followers) | |||
else | |||
0 | |||
end | |||
collection(followers, "#{user.ap_id}/followers", page, !user.info.hide_followers, total) | |||
|> Map.merge(Utils.make_json_ld_header()) | |||
end | |||
@@ -120,19 +158,24 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
query = from(user in query, select: [:ap_id]) | |||
followers = Repo.all(query) | |||
total = | |||
if !user.info.hide_followers do | |||
length(followers) | |||
else | |||
0 | |||
end | |||
%{ | |||
"id" => "#{user.ap_id}/followers", | |||
"type" => "OrderedCollection", | |||
"totalItems" => length(followers), | |||
"first" => collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers) | |||
"totalItems" => total, | |||
"first" => | |||
collection(followers, "#{user.ap_id}/followers", 1, !user.info.hide_followers, total) | |||
} | |||
|> Map.merge(Utils.make_json_ld_header()) | |||
end | |||
def render("outbox.json", %{user: user, max_id: max_qid}) do | |||
# XXX: technically note_count is wrong for this, but it's better than nothing | |||
info = User.user_info(user) | |||
params = %{ | |||
"limit" => "10" | |||
} | |||
@@ -160,7 +203,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
"id" => "#{iri}?max_id=#{max_id}", | |||
"type" => "OrderedCollectionPage", | |||
"partOf" => iri, | |||
"totalItems" => info.note_count, | |||
"orderedItems" => collection, | |||
"next" => "#{iri}?max_id=#{min_id}" | |||
} | |||
@@ -169,7 +211,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
%{ | |||
"id" => iri, | |||
"type" => "OrderedCollection", | |||
"totalItems" => info.note_count, | |||
"first" => page | |||
} | |||
|> Map.merge(Utils.make_json_ld_header()) | |||
@@ -207,7 +248,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
"id" => "#{iri}?max_id=#{max_id}", | |||
"type" => "OrderedCollectionPage", | |||
"partOf" => iri, | |||
"totalItems" => -1, | |||
"orderedItems" => collection, | |||
"next" => "#{iri}?max_id=#{min_id}" | |||
} | |||
@@ -216,7 +256,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do | |||
%{ | |||
"id" => iri, | |||
"type" => "OrderedCollection", | |||
"totalItems" => -1, | |||
"first" => page | |||
} | |||
|> Map.merge(Utils.make_json_ld_header()) | |||
@@ -95,7 +95,7 @@ defmodule Pleroma.Web.CommonAPI do | |||
limit = Pleroma.Config.get([:instance, :limit]) | |||
with status <- String.trim(status), | |||
attachments <- attachments_from_ids(data["media_ids"]), | |||
attachments <- attachments_from_ids(data), | |||
mentions <- Formatter.parse_mentions(status), | |||
inReplyTo <- get_replied_to_activity(data["in_reply_to_status_id"]), | |||
{to, cc} <- to_for_user_and_mentions(user, mentions, inReplyTo, visibility), | |||
@@ -35,12 +35,28 @@ defmodule Pleroma.Web.CommonAPI.Utils do | |||
def get_replied_to_activity(_), do: nil | |||
def attachments_from_ids(ids) do | |||
def attachments_from_ids(data) do | |||
if Map.has_key?(data, "descriptions") do | |||
attachments_from_ids_descs(data["media_ids"], data["descriptions"]) | |||
else | |||
attachments_from_ids_no_descs(data["media_ids"]) | |||
end | |||
end | |||
def attachments_from_ids_no_descs(ids) do | |||
Enum.map(ids || [], fn media_id -> | |||
Repo.get(Object, media_id).data | |||
end) | |||
end | |||
def attachments_from_ids_descs(ids, descs_str) do | |||
{_, descs} = Jason.decode(descs_str) | |||
Enum.map(ids || [], fn media_id -> | |||
Map.put(Repo.get(Object, media_id).data, "name", descs[media_id]) | |||
end) | |||
end | |||
def to_for_user_and_mentions(user, mentions, inReplyTo, "public") do | |||
mentioned_users = Enum.map(mentions, fn {_, %{ap_id: ap_id}} -> ap_id end) | |||
@@ -3,8 +3,6 @@ | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.Federator do | |||
use GenServer | |||
alias Pleroma.Activity | |||
alias Pleroma.User | |||
alias Pleroma.Web.WebFinger | |||
@@ -16,45 +14,71 @@ defmodule Pleroma.Web.Federator do | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.Federator.RetryQueue | |||
alias Pleroma.Web.OStatus | |||
alias Pleroma.Jobs | |||
require Logger | |||
@websub Application.get_env(:pleroma, :websub) | |||
@ostatus Application.get_env(:pleroma, :ostatus) | |||
def init(args) do | |||
{:ok, args} | |||
def init() do | |||
# 1 minute | |||
Process.sleep(1000 * 60 * 1) | |||
refresh_subscriptions() | |||
end | |||
def start_link do | |||
spawn(fn -> | |||
# 1 minute | |||
Process.sleep(1000 * 60) | |||
enqueue(:refresh_subscriptions, nil) | |||
end) | |||
# Client API | |||
def incoming_doc(doc) do | |||
Jobs.enqueue(:federator_incoming, __MODULE__, [:incoming_doc, doc]) | |||
end | |||
def incoming_ap_doc(params) do | |||
Jobs.enqueue(:federator_incoming, __MODULE__, [:incoming_ap_doc, params]) | |||
end | |||
def publish(activity, priority \\ 1) do | |||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish, activity], priority) | |||
end | |||
def publish_single_ap(params) do | |||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_ap, params]) | |||
end | |||
GenServer.start_link( | |||
__MODULE__, | |||
%{ | |||
in: {:sets.new(), []}, | |||
out: {:sets.new(), []} | |||
}, | |||
name: __MODULE__ | |||
) | |||
def publish_single_websub(websub) do | |||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_websub, websub]) | |||
end | |||
def handle(:refresh_subscriptions, _) do | |||
def verify_websub(websub) do | |||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:verify_websub, websub]) | |||
end | |||
def request_subscription(sub) do | |||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:request_subscription, sub]) | |||
end | |||
def refresh_subscriptions() do | |||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:refresh_subscriptions]) | |||
end | |||
def publish_single_salmon(params) do | |||
Jobs.enqueue(:federator_outgoing, __MODULE__, [:publish_single_salmon, params]) | |||
end | |||
# Job Worker Callbacks | |||
def perform(:refresh_subscriptions) do | |||
Logger.debug("Federator running refresh subscriptions") | |||
Websub.refresh_subscriptions() | |||
spawn(fn -> | |||
# 6 hours | |||
Process.sleep(1000 * 60 * 60 * 6) | |||
enqueue(:refresh_subscriptions, nil) | |||
refresh_subscriptions() | |||
end) | |||
end | |||
def handle(:request_subscription, websub) do | |||
def perform(:request_subscription, websub) do | |||
Logger.debug("Refreshing #{websub.topic}") | |||
with {:ok, websub} <- Websub.request_subscription(websub) do | |||
@@ -64,7 +88,7 @@ defmodule Pleroma.Web.Federator do | |||
end | |||
end | |||
def handle(:publish, activity) do | |||
def perform(:publish, activity) do | |||
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) | |||
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do | |||
@@ -90,7 +114,7 @@ defmodule Pleroma.Web.Federator do | |||
end | |||
end | |||
def handle(:verify_websub, websub) do | |||
def perform(:verify_websub, websub) do | |||
Logger.debug(fn -> | |||
"Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})" | |||
end) | |||
@@ -98,12 +122,12 @@ defmodule Pleroma.Web.Federator do | |||
@websub.verify(websub) | |||
end | |||
def handle(:incoming_doc, doc) do | |||
def perform(:incoming_doc, doc) do | |||
Logger.info("Got document, trying to parse") | |||
@ostatus.handle_incoming(doc) | |||
end | |||
def handle(:incoming_ap_doc, params) do | |||
def perform(:incoming_ap_doc, params) do | |||
Logger.info("Handling incoming AP activity") | |||
params = Utils.normalize_params(params) | |||
@@ -128,11 +152,11 @@ defmodule Pleroma.Web.Federator do | |||
end | |||
end | |||
def handle(:publish_single_salmon, params) do | |||
def perform(:publish_single_salmon, params) do | |||
Salmon.send_to_user(params) | |||
end | |||
def handle(:publish_single_ap, params) do | |||
def perform(:publish_single_ap, params) do | |||
case ActivityPub.publish_one(params) do | |||
{:ok, _} -> | |||
:ok | |||
@@ -142,7 +166,7 @@ defmodule Pleroma.Web.Federator do | |||
end | |||
end | |||
def handle( | |||
def perform( | |||
:publish_single_websub, | |||
%{xml: _xml, topic: _topic, callback: _callback, secret: _secret} = params | |||
) do | |||
@@ -155,74 +179,11 @@ defmodule Pleroma.Web.Federator do | |||
end | |||
end | |||
def handle(type, _) do | |||
def perform(type, _) do | |||
Logger.debug(fn -> "Unknown task: #{type}" end) | |||
{:error, "Don't know what to do with this"} | |||
end | |||
if Mix.env() == :test do | |||
def enqueue(type, payload, _priority \\ 1) do | |||
if Pleroma.Config.get([:instance, :federating]) do | |||
handle(type, payload) | |||
end | |||
end | |||
else | |||
def enqueue(type, payload, priority \\ 1) do | |||
if Pleroma.Config.get([:instance, :federating]) do | |||
GenServer.cast(__MODULE__, {:enqueue, type, payload, priority}) | |||
end | |||
end | |||
end | |||
def maybe_start_job(running_jobs, queue) do | |||
if :sets.size(running_jobs) < Pleroma.Config.get([__MODULE__, :max_jobs]) && queue != [] do | |||
{{type, payload}, queue} = queue_pop(queue) | |||
{:ok, pid} = Task.start(fn -> handle(type, payload) end) | |||
mref = Process.monitor(pid) | |||
{:sets.add_element(mref, running_jobs), queue} | |||
else | |||
{running_jobs, queue} | |||
end | |||
end | |||
def handle_cast({:enqueue, type, payload, _priority}, state) | |||
when type in [:incoming_doc, :incoming_ap_doc] do | |||
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state | |||
i_queue = enqueue_sorted(i_queue, {type, payload}, 1) | |||
{i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue) | |||
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}} | |||
end | |||
def handle_cast({:enqueue, type, payload, _priority}, state) do | |||
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state | |||
o_queue = enqueue_sorted(o_queue, {type, payload}, 1) | |||
{o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue) | |||
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}} | |||
end | |||
def handle_cast(_, state) do | |||
{:noreply, state} | |||
end | |||
def handle_info({:DOWN, ref, :process, _pid, _reason}, state) do | |||
%{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}} = state | |||
i_running_jobs = :sets.del_element(ref, i_running_jobs) | |||
o_running_jobs = :sets.del_element(ref, o_running_jobs) | |||
{i_running_jobs, i_queue} = maybe_start_job(i_running_jobs, i_queue) | |||
{o_running_jobs, o_queue} = maybe_start_job(o_running_jobs, o_queue) | |||
{:noreply, %{in: {i_running_jobs, i_queue}, out: {o_running_jobs, o_queue}}} | |||
end | |||
def enqueue_sorted(queue, element, priority) do | |||
[%{item: element, priority: priority} | queue] | |||
|> Enum.sort_by(fn %{priority: priority} -> priority end) | |||
end | |||
def queue_pop([%{item: element} | queue]) do | |||
{element, queue} | |||
end | |||
def ap_enabled_actor(id) do | |||
user = User.get_by_ap_id(id) | |||
@@ -680,7 +680,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
{:ok, _activity} <- | |||
ActivityPub.accept(%{ | |||
to: [follower.ap_id], | |||
actor: followed.ap_id, | |||
actor: followed, | |||
object: follow_activity.data["id"], | |||
type: "Accept" | |||
}) do | |||
@@ -702,7 +702,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
{:ok, _activity} <- | |||
ActivityPub.reject(%{ | |||
to: [follower.ap_id], | |||
actor: followed.ap_id, | |||
actor: followed, | |||
object: follow_activity.data["id"], | |||
type: "Reject" | |||
}) do | |||
@@ -1051,6 +1051,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
accounts = | |||
Map.put(%{}, user.id, AccountView.render("account.json", %{user: user, for: user})) | |||
flavour = get_user_flavour(user) | |||
initial_state = | |||
%{ | |||
meta: %{ | |||
@@ -1135,7 +1137,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
conn | |||
|> put_layout(false) | |||
|> put_view(MastodonView) | |||
|> render("index.html", %{initial_state: initial_state}) | |||
|> render("index.html", %{initial_state: initial_state, flavour: flavour}) | |||
else | |||
conn | |||
|> redirect(to: "/web/login") | |||
@@ -1157,6 +1159,43 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do | |||
end | |||
end | |||
@supported_flavours ["glitch", "vanilla"] | |||
def set_flavour(%{assigns: %{user: user}} = conn, %{"flavour" => flavour} = _params) | |||
when flavour in @supported_flavours do | |||
flavour_cng = User.Info.mastodon_flavour_update(user.info, flavour) | |||
with changeset <- Ecto.Changeset.change(user), | |||
changeset <- Ecto.Changeset.put_embed(changeset, :info, flavour_cng), | |||
{:ok, user} <- User.update_and_set_cache(changeset), | |||
flavour <- user.info.flavour do | |||
json(conn, flavour) | |||
else | |||
e -> | |||
conn | |||
|> put_resp_content_type("application/json") | |||
|> send_resp(500, Jason.encode!(%{"error" => inspect(e)})) | |||
end | |||
end | |||
def set_flavour(conn, _params) do | |||
conn | |||
|> put_status(400) | |||
|> json(%{error: "Unsupported flavour"}) | |||
end | |||
def get_flavour(%{assigns: %{user: user}} = conn, _params) do | |||
json(conn, get_user_flavour(user)) | |||
end | |||
defp get_user_flavour(%User{info: %{flavour: flavour}}) when flavour in @supported_flavours do | |||
flavour | |||
end | |||
defp get_user_flavour(_) do | |||
"glitch" | |||
end | |||
def login(conn, %{"code" => code}) do | |||
with {:ok, app} <- get_or_make_app(), | |||
%Authorization{} = auth <- Repo.get_by(Authorization, token: code, app_id: app.id), | |||
@@ -166,7 +166,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do | |||
sensitive: sensitive, | |||
spoiler_text: object["summary"] || "", | |||
visibility: get_visibility(object), | |||
media_attachments: attachments |> Enum.take(4), | |||
media_attachments: attachments, | |||
mentions: mentions, | |||
tags: build_tags(tags), | |||
application: %{ | |||
@@ -19,11 +19,16 @@ defmodule Pleroma.Web.MediaProxy do | |||
else | |||
secret = Application.get_env(:pleroma, Pleroma.Web.Endpoint)[:secret_key_base] | |||
# Must preserve `%2F` for compatibility with S3 (https://git.pleroma.social/pleroma/pleroma/issues/580) | |||
replacement = get_replacement(url, ":2F:") | |||
# The URL is url-decoded and encoded again to ensure it is correctly encoded and not twice. | |||
base64 = | |||
url | |||
|> String.replace("%2F", replacement) | |||
|> URI.decode() | |||
|> URI.encode() | |||
|> String.replace(replacement, "%2F") | |||
|> Base.url_encode64(@base64_opts) | |||
sig = :crypto.hmac(:sha, secret, base64) | |||
@@ -60,4 +65,12 @@ defmodule Pleroma.Web.MediaProxy do | |||
|> Enum.filter(fn value -> value end) | |||
|> Path.join() | |||
end | |||
defp get_replacement(url, replacement) do | |||
if String.contains?(url, replacement) do | |||
get_replacement(url, replacement <> replacement) | |||
else | |||
replacement | |||
end | |||
end | |||
end |
@@ -25,8 +25,14 @@ defmodule Pleroma.Web.OAuth.App do | |||
if changeset.valid? do | |||
changeset | |||
|> put_change(:client_id, :crypto.strong_rand_bytes(32) |> Base.url_encode64()) | |||
|> put_change(:client_secret, :crypto.strong_rand_bytes(32) |> Base.url_encode64()) | |||
|> put_change( | |||
:client_id, | |||
:crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) | |||
) | |||
|> put_change( | |||
:client_secret, | |||
:crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) | |||
) | |||
else | |||
changeset | |||
end | |||
@@ -24,7 +24,7 @@ defmodule Pleroma.Web.OAuth.Authorization do | |||
end | |||
def create_authorization(%App{} = app, %User{} = user) do | |||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() | |||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) | |||
authorization = %Authorization{ | |||
token: token, | |||
@@ -173,7 +173,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do | |||
token | |||
|> URI.decode() | |||
|> Base.url_decode64!(padding: false) | |||
|> Base.url_encode64() | |||
|> Base.url_encode64(padding: false) | |||
end | |||
defp get_app_from_request(conn, params) do | |||
@@ -31,8 +31,8 @@ defmodule Pleroma.Web.OAuth.Token do | |||
end | |||
def create_token(%App{} = app, %User{} = user) do | |||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() | |||
refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() | |||
token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) | |||
refresh_token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false) | |||
token = %Token{ | |||
token: token, | |||
@@ -47,9 +47,27 @@ defmodule Pleroma.Web.OAuth.Token do | |||
def delete_user_tokens(%User{id: user_id}) do | |||
from( | |||
t in Pleroma.Web.OAuth.Token, | |||
t in Token, | |||
where: t.user_id == ^user_id | |||
) | |||
|> Repo.delete_all() | |||
end | |||
def delete_user_token(%User{id: user_id}, token_id) do | |||
from( | |||
t in Token, | |||
where: t.user_id == ^user_id, | |||
where: t.id == ^token_id | |||
) | |||
|> Repo.delete_all() | |||
end | |||
def get_user_tokens(%User{id: user_id}) do | |||
from( | |||
t in Token, | |||
where: t.user_id == ^user_id | |||
) | |||
|> Repo.all() | |||
|> Repo.preload(:app) | |||
end | |||
end |
@@ -87,7 +87,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do | |||
{:ok, body, _conn} = read_body(conn) | |||
{:ok, doc} = decode_or_retry(body) | |||
Federator.enqueue(:incoming_doc, doc) | |||
Federator.incoming_doc(doc) | |||
conn | |||
|> send_resp(200, "") | |||
@@ -236,6 +236,9 @@ defmodule Pleroma.Web.Router do | |||
get("/suggestions", MastodonAPIController, :suggestions) | |||
get("/endorsements", MastodonAPIController, :empty_array) | |||
post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour) | |||
get("/pleroma/flavour", MastodonAPIController, :get_flavour) | |||
end | |||
scope "/api/web", Pleroma.Web.MastodonAPI do | |||
@@ -389,6 +392,9 @@ defmodule Pleroma.Web.Router do | |||
get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array) | |||
get("/externalprofile/show", TwitterAPI.Controller, :external_profile) | |||
get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens) | |||
delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) | |||
end | |||
pipeline :ap_relay do | |||
@@ -469,8 +475,8 @@ defmodule Pleroma.Web.Router do | |||
scope "/", Pleroma.Web.ActivityPub do | |||
pipe_through(:activitypub) | |||
post("/users/:nickname/inbox", ActivityPubController, :inbox) | |||
post("/inbox", ActivityPubController, :inbox) | |||
post("/users/:nickname/inbox", ActivityPubController, :inbox) | |||
end | |||
scope "/.well-known", Pleroma.Web do | |||
@@ -229,7 +229,7 @@ defmodule Pleroma.Web.Salmon do | |||
|> Enum.each(fn remote_user -> | |||
Logger.debug(fn -> "Sending Salmon to #{remote_user.ap_id}" end) | |||
Pleroma.Web.Federator.enqueue(:publish_single_salmon, %{ | |||
Pleroma.Web.Federator.publish_single_salmon(%{ | |||
recipient: remote_user, | |||
feed: feed, | |||
poster: poster, | |||
@@ -67,6 +67,32 @@ | |||
font-weight: 500; | |||
font-size: 16px; | |||
} | |||
.alert-danger { | |||
box-sizing: border-box; | |||
width: 100%; | |||
color: #D8000C; | |||
background-color: #FFD2D2; | |||
border-radius: 4px; | |||
border: none; | |||
padding: 10px; | |||
margin-top: 20px; | |||
font-weight: 500; | |||
font-size: 16px; | |||
} | |||
.alert-info { | |||
box-sizing: border-box; | |||
width: 100%; | |||
color: #00529B; | |||
background-color: #BDE5F8; | |||
border-radius: 4px; | |||
border: none; | |||
padding: 10px; | |||
margin-top: 20px; | |||
font-weight: 500; | |||
font-size: 16px; | |||
} | |||
</style> | |||
</head> | |||
<body> | |||
@@ -8,7 +8,7 @@ | |||
</title> | |||
<link rel="icon" type="image/png" href="/favicon.png"/> | |||
<script crossorigin='anonymous' src="/packs/locales.js"></script> | |||
<script crossorigin='anonymous' src="/packs/locales/glitch/en.js"></script> | |||
<script crossorigin='anonymous' src="/packs/locales/<%= @flavour %>/en.js"></script> | |||
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/getting_started.js'> | |||
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/compose.js'> | |||
@@ -19,10 +19,10 @@ | |||
<script src="/packs/core/common.js"></script> | |||
<link rel="stylesheet" media="all" href="/packs/core/common.css" /> | |||
<script src="/packs/flavours/glitch/common.js"></script> | |||
<link rel="stylesheet" media="all" href="/packs/flavours/glitch/common.css" /> | |||
<script src="/packs/flavours/<%= @flavour %>/common.js"></script> | |||
<link rel="stylesheet" media="all" href="/packs/flavours/<%= @flavour %>/common.css" /> | |||
<script src="/packs/flavours/glitch/home.js"></script> | |||
<script src="/packs/flavours/<%= @flavour %>/home.js"></script> | |||
</head> | |||
<body class='app-body no-reduce-motion system-font'> | |||
<div class='app-holder' data-props='{"locale":"en"}' id='mastodon'> | |||
@@ -1,5 +1,9 @@ | |||
<%= if get_flash(@conn, :info) do %> | |||
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p> | |||
<% end %> | |||
<%= if get_flash(@conn, :error) do %> | |||
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p> | |||
<% end %> | |||
<h2>OAuth Authorization</h2> | |||
<%= form_for @conn, o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> | |||
<%= label f, :name, "Name or email" %> | |||
@@ -8,6 +8,10 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
import Pleroma.Web.ControllerHelper, only: [json_response: 3] | |||
alias Ecto.Changeset | |||
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView, TokenView} | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.{Repo, Activity, Object, User, Notification} | |||
alias Pleroma.Web.OAuth.Token | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.CommonAPI | |||
@@ -524,6 +528,9 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
def friends(%{assigns: %{user: for_user}} = conn, params) do | |||
{:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1) | |||
{:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false) | |||
page = if export, do: nil, else: page | |||
with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), | |||
{:ok, friends} <- User.get_friends(user, page) do | |||
@@ -542,6 +549,20 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
end | |||
end | |||
def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do | |||
with oauth_tokens <- Token.get_user_tokens(user) do | |||
conn | |||
|> put_view(TokenView) | |||
|> render("index.json", %{tokens: oauth_tokens}) | |||
end | |||
end | |||
def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do | |||
Token.delete_user_token(user, id) | |||
json_reply(conn, 201, "") | |||
end | |||
def blocks(%{assigns: %{user: user}} = conn, _params) do | |||
with blocked_users <- User.blocked_users(user) do | |||
conn | |||
@@ -570,7 +591,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
{:ok, _activity} <- | |||
ActivityPub.accept(%{ | |||
to: [follower.ap_id], | |||
actor: followed.ap_id, | |||
actor: followed, | |||
object: follow_activity.data["id"], | |||
type: "Accept" | |||
}) do | |||
@@ -590,7 +611,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do | |||
{:ok, _activity} <- | |||
ActivityPub.reject(%{ | |||
to: [follower.ap_id], | |||
actor: followed.ap_id, | |||
actor: followed, | |||
object: follow_activity.data["id"], | |||
type: "Reject" | |||
}) do | |||
@@ -0,0 +1,21 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.TwitterAPI.TokenView do | |||
use Pleroma.Web, :view | |||
def render("index.json", %{tokens: tokens}) do | |||
tokens | |||
|> render_many(Pleroma.Web.TwitterAPI.TokenView, "show.json") | |||
|> Enum.filter(&Enum.any?/1) | |||
end | |||
def render("show.json", %{token: token_entry}) do | |||
%{ | |||
id: token_entry.id, | |||
valid_until: token_entry.valid_until, | |||
app_name: token_entry.app.client_name | |||
} | |||
end | |||
end |
@@ -113,10 +113,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do | |||
"fields" => fields, | |||
# Pleroma extension | |||
"pleroma" => %{ | |||
"confirmation_pending" => user_info.confirmation_pending, | |||
"tags" => user.tags | |||
} | |||
"pleroma" => | |||
%{ | |||
"confirmation_pending" => user_info.confirmation_pending, | |||
"tags" => user.tags | |||
} | |||
|> maybe_with_follow_request_count(user, for_user) | |||
} | |||
data = | |||
@@ -132,6 +134,14 @@ defmodule Pleroma.Web.TwitterAPI.UserView do | |||
end | |||
end | |||
defp maybe_with_follow_request_count(data, %User{id: id, info: %{locked: true}} = user, %User{ | |||
id: id | |||
}) do | |||
Map.put(data, "follow_request_count", user.info.follow_request_count) | |||
end | |||
defp maybe_with_follow_request_count(data, _, _), do: data | |||
defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do | |||
Map.merge(data, %{"role" => role(user), "show_role" => user.info.show_role}) | |||
end | |||
@@ -13,6 +13,7 @@ defmodule Pleroma.Web.Websub do | |||
alias Pleroma.Web.Endpoint | |||
alias Pleroma.Web.OStatus | |||
alias Pleroma.Web.Router.Helpers | |||
alias Pleroma.Web.Federator | |||
require Logger | |||
import Ecto.Query | |||
@@ -87,7 +88,7 @@ defmodule Pleroma.Web.Websub do | |||
unreachable_since: reachable_callbacks_metadata[sub.callback] | |||
} | |||
Pleroma.Web.Federator.enqueue(:publish_single_websub, data) | |||
Federator.publish_single_websub(data) | |||
end) | |||
end | |||
@@ -119,7 +120,7 @@ defmodule Pleroma.Web.Websub do | |||
websub = Repo.update!(change) | |||
Pleroma.Web.Federator.enqueue(:verify_websub, websub) | |||
Federator.verify_websub(websub) | |||
{:ok, websub} | |||
else | |||
@@ -269,7 +270,7 @@ defmodule Pleroma.Web.Websub do | |||
subs = Repo.all(query) | |||
Enum.each(subs, fn sub -> | |||
Pleroma.Web.Federator.enqueue(:request_subscription, sub) | |||
Federator.request_subscription(sub) | |||
end) | |||
end | |||
@@ -84,7 +84,7 @@ defmodule Pleroma.Web.Websub.WebsubController do | |||
%WebsubClientSubscription{} = websub <- Repo.get(WebsubClientSubscription, id), | |||
{:ok, body, _conn} = read_body(conn), | |||
^signature <- Websub.sign(websub.secret, body) do | |||
Federator.enqueue(:incoming_doc, body) | |||
Federator.incoming_doc(body) | |||
conn | |||
|> send_resp(200, "OK") | |||
@@ -21,7 +21,14 @@ defmodule Pleroma.Mixfile do | |||
homepage_url: "https://pleroma.social/", | |||
docs: [ | |||
logo: "priv/static/static/logo.png", | |||
extras: ["README.md", "docs/config.md", "docs/Pleroma-API.md", "docs/Admin-API.md"], | |||
extras: [ | |||
"README.md", | |||
"docs/config.md", | |||
"docs/Pleroma-API.md", | |||
"docs/Admin-API.md", | |||
"docs/Clients.md", | |||
"docs/Differences-in-MastodonAPI-Responses.md" | |||
], | |||
main: "readme", | |||
output: "priv/static/doc" | |||
] | |||
@@ -19,7 +19,11 @@ | |||
"value": "schema:value", | |||
"sensitive": "as:sensitive", | |||
"litepub": "http://litepub.social/ns#", | |||
"directMessage": "litepub:directMessage" | |||
"directMessage": "litepub:directMessage", | |||
"oauthRegistrationEndpoint": { | |||
"@id": "litepub:oauthRegistrationEndpoint", | |||
"@type": "@id" | |||
} | |||
} | |||
] | |||
} |
@@ -197,7 +197,7 @@ defmodule Pleroma.FormatterTest do | |||
{subs, text} = Formatter.add_user_links({[], text}, mentions) | |||
assert length(subs) == 0 | |||
assert Enum.empty?(subs) | |||
Enum.each(subs, fn {uuid, _} -> assert String.contains?(text, uuid) end) | |||
expected_text = "@a hi" | |||
@@ -0,0 +1,83 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.JobsTest do | |||
use ExUnit.Case, async: true | |||
alias Pleroma.Jobs | |||
alias Jobs.WorkerMock | |||
setup do | |||
state = %{ | |||
queues: Enum.into([Jobs.create_queue(:testing)], %{}), | |||
refs: %{} | |||
} | |||
[state: state] | |||
end | |||
test "creates queue" do | |||
queue = Jobs.create_queue(:foobar) | |||
assert {:foobar, set} = queue | |||
assert :set == elem(set, 0) |> elem(0) | |||
end | |||
test "enqueues an element according to priority" do | |||
queue = [%{item: 1, priority: 2}] | |||
new_queue = Jobs.enqueue_sorted(queue, 2, 1) | |||
assert new_queue == [%{item: 2, priority: 1}, %{item: 1, priority: 2}] | |||
new_queue = Jobs.enqueue_sorted(queue, 2, 3) | |||
assert new_queue == [%{item: 1, priority: 2}, %{item: 2, priority: 3}] | |||
end | |||
test "pop first item" do | |||
queue = [%{item: 2, priority: 1}, %{item: 1, priority: 2}] | |||
assert {2, [%{item: 1, priority: 2}]} = Jobs.queue_pop(queue) | |||
end | |||
test "enqueue a job", %{state: state} do | |||
assert {:noreply, new_state} = | |||
Jobs.handle_cast({:enqueue, :testing, WorkerMock, [:test_job, :foo, :bar], 3}, state) | |||
assert %{queues: %{testing: {running_jobs, []}}, refs: _} = new_state | |||
assert :sets.size(running_jobs) == 1 | |||
assert [ref] = :sets.to_list(running_jobs) | |||
assert %{refs: %{^ref => :testing}} = new_state | |||
end | |||
test "max jobs setting", %{state: state} do | |||
max_jobs = Pleroma.Config.get([Jobs, :testing, :max_jobs]) | |||
{:noreply, state} = | |||
Enum.reduce(1..(max_jobs + 1), {:noreply, state}, fn _, {:noreply, state} -> | |||
Jobs.handle_cast({:enqueue, :testing, WorkerMock, [:test_job, :foo, :bar], 3}, state) | |||
end) | |||
assert %{ | |||
queues: %{ | |||
testing: | |||
{running_jobs, [%{item: {WorkerMock, [:test_job, :foo, :bar]}, priority: 3}]} | |||
} | |||
} = state | |||
assert :sets.size(running_jobs) == max_jobs | |||
end | |||
test "remove job after it finished", %{state: state} do | |||
{:noreply, new_state} = | |||
Jobs.handle_cast({:enqueue, :testing, WorkerMock, [:test_job, :foo, :bar], 3}, state) | |||
%{queues: %{testing: {running_jobs, []}}} = new_state | |||
[ref] = :sets.to_list(running_jobs) | |||
assert {:noreply, %{queues: %{testing: {running_jobs, []}}, refs: %{}}} = | |||
Jobs.handle_info({:DOWN, ref, :process, nil, nil}, new_state) | |||
assert :sets.size(running_jobs) == 0 | |||
end | |||
end |
@@ -140,6 +140,15 @@ defmodule Pleroma.MediaProxyTest do | |||
assert String.starts_with?(encoded, Pleroma.Config.get([:media_proxy, :base_url])) | |||
end | |||
# https://git.pleroma.social/pleroma/pleroma/issues/580 | |||
test "encoding S3 links (must preserve `%2F`)" do | |||
url = | |||
"https://s3.amazonaws.com/example/test.png?X-Amz-Credential=your-access-key-id%2F20130721%2Fus-east-1%2Fs3%2Faws4_request" | |||
encoded = url(url) | |||
assert decode_result(encoded) == url | |||
end | |||
end | |||
describe "when disabled" do | |||
@@ -6,7 +6,8 @@ defmodule Pleroma.NotificationTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.TwitterAPI.TwitterAPI | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.{User, Notification} | |||
alias Pleroma.User | |||
alias Pleroma.Notification | |||
alias Pleroma.Web.ActivityPub.Transmogrifier | |||
import Pleroma.Factory | |||
@@ -299,7 +300,7 @@ defmodule Pleroma.NotificationTest do | |||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user) | |||
@@ -307,7 +308,7 @@ defmodule Pleroma.NotificationTest do | |||
{:ok, _} = CommonAPI.delete(activity.id, user) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
end | |||
test "liking an activity results in 1 notification, then 0 if the activity is unliked" do | |||
@@ -316,7 +317,7 @@ defmodule Pleroma.NotificationTest do | |||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user) | |||
@@ -324,7 +325,7 @@ defmodule Pleroma.NotificationTest do | |||
{:ok, _, _, _} = CommonAPI.unfavorite(activity.id, other_user) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
end | |||
test "repeating an activity results in 1 notification, then 0 if the activity is deleted" do | |||
@@ -333,7 +334,7 @@ defmodule Pleroma.NotificationTest do | |||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user) | |||
@@ -341,7 +342,7 @@ defmodule Pleroma.NotificationTest do | |||
{:ok, _} = CommonAPI.delete(activity.id, user) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
end | |||
test "repeating an activity results in 1 notification, then 0 if the activity is unrepeated" do | |||
@@ -350,7 +351,7 @@ defmodule Pleroma.NotificationTest do | |||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
{:ok, _, _} = CommonAPI.repeat(activity.id, other_user) | |||
@@ -358,7 +359,7 @@ defmodule Pleroma.NotificationTest do | |||
{:ok, _, _} = CommonAPI.unrepeat(activity.id, other_user) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
end | |||
test "liking an activity which is already deleted does not generate a notification" do | |||
@@ -367,15 +368,15 @@ defmodule Pleroma.NotificationTest do | |||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
{:ok, _deletion_activity} = CommonAPI.delete(activity.id, user) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
{:error, _} = CommonAPI.favorite(activity.id, other_user) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
end | |||
test "repeating an activity which is already deleted does not generate a notification" do | |||
@@ -384,15 +385,15 @@ defmodule Pleroma.NotificationTest do | |||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
{:ok, _deletion_activity} = CommonAPI.delete(activity.id, user) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
{:error, _} = CommonAPI.repeat(activity.id, other_user) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
end | |||
test "replying to a deleted post without tagging does not generate a notification" do | |||
@@ -408,7 +409,7 @@ defmodule Pleroma.NotificationTest do | |||
"in_reply_to_status_id" => activity.id | |||
}) | |||
assert length(Notification.for_user(user)) == 0 | |||
assert Enum.empty?(Notification.for_user(user)) | |||
end | |||
end | |||
end |
@@ -5,7 +5,8 @@ | |||
defmodule Pleroma.ObjectTest do | |||
use Pleroma.DataCase | |||
import Pleroma.Factory | |||
alias Pleroma.{Repo, Object} | |||
alias Pleroma.Repo | |||
alias Pleroma.Object | |||
test "returns an object by it's AP id" do | |||
object = insert(:note) | |||
@@ -1,5 +1,6 @@ | |||
defmodule Pleroma.Builders.UserBuilder do | |||
alias Pleroma.{User, Repo} | |||
alias Pleroma.User | |||
alias Pleroma.Repo | |||
def build(data \\ %{}) do | |||
user = %User{ | |||
@@ -227,4 +227,17 @@ defmodule Pleroma.Factory do | |||
unreachable_since: nil | |||
} | |||
end | |||
def oauth_token_factory do | |||
user = insert(:user) | |||
oauth_app = insert(:oauth_app) | |||
%Pleroma.Web.OAuth.Token{ | |||
token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(), | |||
refresh_token: :crypto.strong_rand_bytes(32) |> Base.url_encode64(), | |||
user_id: user.id, | |||
app_id: oauth_app.id, | |||
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10) | |||
} | |||
end | |||
end |
@@ -0,0 +1,19 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Jobs.WorkerMock do | |||
require Logger | |||
def perform(:test_job, arg, arg2) do | |||
Logger.debug({:perform, :test_job, arg, arg2}) | |||
end | |||
def perform(:test_job, payload) do | |||
Logger.debug({:perform, :test_job, payload}) | |||
end | |||
def test_job(payload) do | |||
Pleroma.Jobs.enqueue(:testing, __MODULE__, [:test_job, payload]) | |||
end | |||
end |
@@ -4,7 +4,9 @@ | |||
defmodule Mix.Tasks.Pleroma.RelayTest do | |||
alias Pleroma.Activity | |||
alias Pleroma.Web.ActivityPub.{ActivityPub, Relay, Utils} | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.ActivityPub.Relay | |||
alias Pleroma.User | |||
use Pleroma.DataCase | |||
@@ -151,7 +151,7 @@ defmodule Mix.Tasks.Pleroma.UserTest do | |||
assert message =~ "Successfully unsubscribed" | |||
user = User.get_by_nickname(user.nickname) | |||
assert length(user.following) == 0 | |||
assert Enum.empty?(user.following) | |||
assert user.info.deactivated | |||
end | |||
@@ -4,7 +4,9 @@ | |||
defmodule Pleroma.UserTest do | |||
alias Pleroma.Builders.UserBuilder | |||
alias Pleroma.{User, Repo, Activity} | |||
alias Pleroma.Activity | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
alias Pleroma.Web.CommonAPI | |||
use Pleroma.DataCase | |||
@@ -55,18 +57,21 @@ defmodule Pleroma.UserTest do | |||
followed_two = insert(:user) | |||
blocked = insert(:user) | |||
not_followed = insert(:user) | |||
reverse_blocked = insert(:user) | |||
{:ok, user} = User.block(user, blocked) | |||
{:ok, reverse_blocked} = User.block(reverse_blocked, user) | |||
{:ok, user} = User.follow(user, followed_zero) | |||
{:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked]) | |||
{:ok, user} = User.follow_all(user, [followed_one, followed_two, blocked, reverse_blocked]) | |||
assert User.following?(user, followed_one) | |||
assert User.following?(user, followed_two) | |||
assert User.following?(user, followed_zero) | |||
refute User.following?(user, not_followed) | |||
refute User.following?(user, blocked) | |||
refute User.following?(user, reverse_blocked) | |||
end | |||
test "follow_all follows mutliple users without duplicating" do | |||
@@ -191,6 +196,26 @@ defmodule Pleroma.UserTest do | |||
assert User.following?(registered_user, user) | |||
refute User.following?(registered_user, remote_user) | |||
Pleroma.Config.put([:instance, :autofollowed_nicknames], []) | |||
end | |||
test "it sends a welcome message if it is set" do | |||
welcome_user = insert(:user) | |||
Pleroma.Config.put([:instance, :welcome_user_nickname], welcome_user.nickname) | |||
Pleroma.Config.put([:instance, :welcome_message], "Hello, this is a cool site") | |||
cng = User.register_changeset(%User{}, @full_user_data) | |||
{:ok, registered_user} = User.register(cng) | |||
activity = Repo.one(Pleroma.Activity) | |||
assert registered_user.ap_id in activity.recipients | |||
assert activity.data["object"]["content"] =~ "cool site" | |||
assert activity.actor == welcome_user.ap_id | |||
Pleroma.Config.put([:instance, :welcome_user_nickname], nil) | |||
Pleroma.Config.put([:instance, :welcome_message], nil) | |||
end | |||
test "it requires an email, name, nickname and password, bio is optional" do | |||
@@ -873,6 +898,16 @@ defmodule Pleroma.UserTest do | |||
assert [] == User.search(query) | |||
end) | |||
end | |||
test "works with URIs" do | |||
results = User.search("http://mastodon.example.org/users/admin", true) | |||
result = results |> List.first() | |||
user = User.get_by_ap_id("http://mastodon.example.org/users/admin") | |||
assert length(results) == 1 | |||
assert user == result |> Map.put(:search_rank, nil) | |||
end | |||
end | |||
test "auth_active?/1 works correctly" do | |||
@@ -5,8 +5,13 @@ | |||
defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do | |||
use Pleroma.Web.ConnCase | |||
import Pleroma.Factory | |||
alias Pleroma.Web.ActivityPub.{UserView, ObjectView} | |||
alias Pleroma.{Object, Repo, Activity, User, Instances} | |||
alias Pleroma.Web.ActivityPub.UserView | |||
alias Pleroma.Web.ActivityPub.ObjectView | |||
alias Pleroma.Object | |||
alias Pleroma.Repo | |||
alias Pleroma.Activity | |||
alias Pleroma.User | |||
alias Pleroma.Instances | |||
setup_all do | |||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) | |||
@@ -397,7 +402,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do | |||
|> json_response(200) | |||
assert result["first"]["orderedItems"] == [] | |||
assert result["totalItems"] == 1 | |||
assert result["totalItems"] == 0 | |||
end | |||
test "it works for more than 10 users", %{conn: conn} do | |||
@@ -452,7 +457,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do | |||
|> json_response(200) | |||
assert result["first"]["orderedItems"] == [] | |||
assert result["totalItems"] == 1 | |||
assert result["totalItems"] == 0 | |||
end | |||
test "it works for more than 10 users", %{conn: conn} do | |||
@@ -7,7 +7,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.ActivityPub.Utils | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.{Activity, Object, User, Instances} | |||
alias Pleroma.Activity | |||
alias Pleroma.Object | |||
alias Pleroma.User | |||
alias Pleroma.Instances | |||
alias Pleroma.Builders.ActivityBuilder | |||
import Pleroma.Factory | |||
@@ -0,0 +1,73 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do | |||
use Pleroma.DataCase | |||
import Pleroma.Factory | |||
import Pleroma.Web.ActivityPub.MRF.HellthreadPolicy | |||
setup do | |||
user = insert(:user) | |||
message = %{ | |||
"actor" => user.ap_id, | |||
"cc" => [user.follower_address], | |||
"type" => "Create", | |||
"to" => [ | |||
"https://www.w3.org/ns/activitystreams#Public", | |||
"https://instance.tld/users/user1", | |||
"https://instance.tld/users/user2", | |||
"https://instance.tld/users/user3" | |||
] | |||
} | |||
[user: user, message: message] | |||
end | |||
describe "reject" do | |||
test "rejects the message if the recipient count is above reject_threshold", %{ | |||
message: message | |||
} do | |||
Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 2}) | |||
{:reject, nil} = filter(message) | |||
end | |||
test "does not reject the message if the recipient count is below reject_threshold", %{ | |||
message: message | |||
} do | |||
Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 3}) | |||
assert {:ok, ^message} = filter(message) | |||
end | |||
end | |||
describe "delist" do | |||
test "delists the message if the recipient count is above delist_threshold", %{ | |||
user: user, | |||
message: message | |||
} do | |||
Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 2, reject_threshold: 0}) | |||
{:ok, message} = filter(message) | |||
assert user.follower_address in message["to"] | |||
assert "https://www.w3.org/ns/activitystreams#Public" in message["cc"] | |||
end | |||
test "does not delist the message if the recipient count is below delist_threshold", %{ | |||
message: message | |||
} do | |||
Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 4, reject_threshold: 0}) | |||
assert {:ok, ^message} = filter(message) | |||
end | |||
end | |||
test "excludes follower collection and public URI from threshold count", %{message: message} do | |||
Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 3}) | |||
assert {:ok, ^message} = filter(message) | |||
end | |||
end |
@@ -1128,4 +1128,58 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do | |||
) | |||
end | |||
end | |||
describe "reserialization" do | |||
test "successfully reserializes a message with inReplyTo == nil" do | |||
user = insert(:user) | |||
message = %{ | |||
"@context" => "https://www.w3.org/ns/activitystreams", | |||
"to" => ["https://www.w3.org/ns/activitystreams#Public"], | |||
"cc" => [], | |||
"type" => "Create", | |||
"object" => %{ | |||
"to" => ["https://www.w3.org/ns/activitystreams#Public"], | |||
"cc" => [], | |||
"type" => "Note", | |||
"content" => "Hi", | |||
"inReplyTo" => nil, | |||
"attributedTo" => user.ap_id | |||
}, | |||
"actor" => user.ap_id | |||
} | |||
{:ok, activity} = Transmogrifier.handle_incoming(message) | |||
{:ok, _} = Transmogrifier.prepare_outgoing(activity.data) | |||
end | |||
test "successfully reserializes a message with AS2 objects in IR" do | |||
user = insert(:user) | |||
message = %{ | |||
"@context" => "https://www.w3.org/ns/activitystreams", | |||
"to" => ["https://www.w3.org/ns/activitystreams#Public"], | |||
"cc" => [], | |||
"type" => "Create", | |||
"object" => %{ | |||
"to" => ["https://www.w3.org/ns/activitystreams#Public"], | |||
"cc" => [], | |||
"type" => "Note", | |||
"content" => "Hi", | |||
"inReplyTo" => nil, | |||
"attributedTo" => user.ap_id, | |||
"tag" => [ | |||
%{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"}, | |||
%{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"} | |||
] | |||
}, | |||
"actor" => user.ap_id | |||
} | |||
{:ok, activity} = Transmogrifier.handle_incoming(message) | |||
{:ok, _} = Transmogrifier.prepare_outgoing(activity.data) | |||
end | |||
end | |||
end |
@@ -15,4 +15,43 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do | |||
assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY") | |||
end | |||
describe "endpoints" do | |||
test "local users have a usable endpoints structure" do | |||
user = insert(:user) | |||
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) | |||
result = UserView.render("user.json", %{user: user}) | |||
assert result["id"] == user.ap_id | |||
%{ | |||
"sharedInbox" => _, | |||
"oauthAuthorizationEndpoint" => _, | |||
"oauthRegistrationEndpoint" => _, | |||
"oauthTokenEndpoint" => _ | |||
} = result["endpoints"] | |||
end | |||
test "remote users have an empty endpoints structure" do | |||
user = insert(:user, local: false) | |||
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) | |||
result = UserView.render("user.json", %{user: user}) | |||
assert result["id"] == user.ap_id | |||
assert result["endpoints"] == %{} | |||
end | |||
test "instance users do not expose oAuth endpoints" do | |||
user = insert(:user, nickname: nil, local: true) | |||
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) | |||
result = UserView.render("user.json", %{user: user}) | |||
refute result["endpoints"]["oauthAuthorizationEndpoint"] | |||
refute result["endpoints"]["oauthRegistrationEndpoint"] | |||
refute result["endpoints"]["oauthTokenEndpoint"] | |||
end | |||
end | |||
end |
@@ -5,7 +5,8 @@ | |||
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do | |||
use Pleroma.Web.ConnCase | |||
alias Pleroma.{Repo, User} | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
import Pleroma.Factory | |||
describe "/api/pleroma/admin/user" do | |||
@@ -2,7 +2,7 @@ | |||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.CommonAPI.Test do | |||
defmodule Pleroma.Web.CommonAPITest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.User | |||
@@ -5,7 +5,7 @@ | |||
defmodule Pleroma.Web.CommonAPI.UtilsTest do | |||
alias Pleroma.Web.CommonAPI.Utils | |||
alias Pleroma.Web.Endpoint | |||
alias Pleroma.Builders.{UserBuilder} | |||
alias Pleroma.Builders.UserBuilder | |||
use Pleroma.DataCase | |||
test "it adds attachment links to a given text and attachment set" do | |||
@@ -3,7 +3,8 @@ | |||
# SPDX-License-Identifier: AGPL-3.0-only | |||
defmodule Pleroma.Web.FederatorTest do | |||
alias Pleroma.Web.{CommonAPI, Federator} | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.Web.Federator | |||
alias Pleroma.Instances | |||
use Pleroma.DataCase | |||
import Pleroma.Factory | |||
@@ -14,22 +15,6 @@ defmodule Pleroma.Web.FederatorTest do | |||
:ok | |||
end | |||
test "enqueues an element according to priority" do | |||
queue = [%{item: 1, priority: 2}] | |||
new_queue = Federator.enqueue_sorted(queue, 2, 1) | |||
assert new_queue == [%{item: 2, priority: 1}, %{item: 1, priority: 2}] | |||
new_queue = Federator.enqueue_sorted(queue, 2, 3) | |||
assert new_queue == [%{item: 1, priority: 2}, %{item: 2, priority: 3}] | |||
end | |||
test "pop first item" do | |||
queue = [%{item: 2, priority: 1}, %{item: 1, priority: 2}] | |||
assert {2, [%{item: 1, priority: 2}]} = Federator.queue_pop(queue) | |||
end | |||
describe "Publish an activity" do | |||
setup do | |||
user = insert(:user) | |||
@@ -49,7 +34,7 @@ defmodule Pleroma.Web.FederatorTest do | |||
relay_mock: relay_mock | |||
} do | |||
with_mocks([relay_mock]) do | |||
Federator.handle(:publish, activity) | |||
Federator.publish(activity) | |||
end | |||
assert_received :relay_publish | |||
@@ -62,7 +47,7 @@ defmodule Pleroma.Web.FederatorTest do | |||
Pleroma.Config.put([:instance, :allow_relay], false) | |||
with_mocks([relay_mock]) do | |||
Federator.handle(:publish, activity) | |||
Federator.publish(activity) | |||
end | |||
refute_received :relay_publish | |||
@@ -103,11 +88,9 @@ defmodule Pleroma.Web.FederatorTest do | |||
{:ok, _activity} = | |||
CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"}) | |||
assert called( | |||
Federator.enqueue(:publish_single_ap, %{inbox: inbox1, unreachable_since: dt}) | |||
) | |||
assert called(Federator.publish_single_ap(%{inbox: inbox1, unreachable_since: dt})) | |||
refute called(Federator.enqueue(:publish_single_ap, %{inbox: inbox2})) | |||
refute called(Federator.publish_single_ap(%{inbox: inbox2})) | |||
end | |||
test_with_mock "it federates only to reachable instances via Websub", | |||
@@ -139,13 +122,13 @@ defmodule Pleroma.Web.FederatorTest do | |||
{:ok, _activity} = CommonAPI.post(user, %{"status" => "HI"}) | |||
assert called( | |||
Federator.enqueue(:publish_single_websub, %{ | |||
Federator.publish_single_websub(%{ | |||
callback: sub2.callback, | |||
unreachable_since: dt | |||
}) | |||
) | |||
refute called(Federator.enqueue(:publish_single_websub, %{callback: sub1.callback})) | |||
refute called(Federator.publish_single_websub(%{callback: sub1.callback})) | |||
end | |||
test_with_mock "it federates only to reachable instances via Salmon", | |||
@@ -179,13 +162,13 @@ defmodule Pleroma.Web.FederatorTest do | |||
CommonAPI.post(user, %{"status" => "HI @nick1@domain.com, @nick2@domain2.com!"}) | |||
assert called( | |||
Federator.enqueue(:publish_single_salmon, %{ | |||
Federator.publish_single_salmon(%{ | |||
recipient: remote_user2, | |||
unreachable_since: dt | |||
}) | |||
) | |||
refute called(Federator.enqueue(:publish_single_websub, %{recipient: remote_user1})) | |||
refute called(Federator.publish_single_websub(%{recipient: remote_user1})) | |||
end | |||
end | |||
@@ -205,7 +188,7 @@ defmodule Pleroma.Web.FederatorTest do | |||
"to" => ["https://www.w3.org/ns/activitystreams#Public"] | |||
} | |||
{:ok, _activity} = Federator.handle(:incoming_ap_doc, params) | |||
{:ok, _activity} = Federator.incoming_ap_doc(params) | |||
end | |||
test "rejects incoming AP docs with incorrect origin" do | |||
@@ -223,7 +206,7 @@ defmodule Pleroma.Web.FederatorTest do | |||
"to" => ["https://www.w3.org/ns/activitystreams#Public"] | |||
} | |||
:error = Federator.handle(:incoming_ap_doc, params) | |||
:error = Federator.incoming_ap_doc(params) | |||
end | |||
end | |||
end |
@@ -6,8 +6,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
use Pleroma.Web.ConnCase | |||
alias Pleroma.Web.TwitterAPI.TwitterAPI | |||
alias Pleroma.{Repo, User, Object, Activity, Notification} | |||
alias Pleroma.Web.{OStatus, CommonAPI} | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
alias Pleroma.Object | |||
alias Pleroma.Activity | |||
alias Pleroma.Notification | |||
alias Pleroma.Web.OStatus | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.MastodonAPI.FilterView | |||
alias Ecto.Changeset | |||
@@ -31,7 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
|> assign(:user, user) | |||
|> get("/api/v1/timelines/home") | |||
assert length(json_response(conn, 200)) == 0 | |||
assert Enum.empty?(json_response(conn, 200)) | |||
{:ok, user} = User.follow(user, following) | |||
@@ -932,7 +937,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
end | |||
test "/api/v1/follow_requests/:id/authorize works" do | |||
user = insert(:user, %{info: %Pleroma.User.Info{locked: true}}) | |||
user = insert(:user, %{info: %User.Info{locked: true}}) | |||
other_user = insert(:user) | |||
{:ok, _activity} = ActivityPub.follow(other_user, user) | |||
@@ -941,6 +946,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
other_user = Repo.get(User, other_user.id) | |||
assert User.following?(other_user, user) == false | |||
assert user.info.follow_request_count == 1 | |||
conn = | |||
build_conn() | |||
@@ -954,6 +960,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
other_user = Repo.get(User, other_user.id) | |||
assert User.following?(other_user, user) == true | |||
assert user.info.follow_request_count == 0 | |||
end | |||
test "verify_credentials", %{conn: conn} do | |||
@@ -974,6 +981,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
{:ok, _activity} = ActivityPub.follow(other_user, user) | |||
user = Repo.get(User, user.id) | |||
assert user.info.follow_request_count == 1 | |||
conn = | |||
build_conn() | |||
|> assign(:user, user) | |||
@@ -986,6 +996,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
other_user = Repo.get(User, other_user.id) | |||
assert User.following?(other_user, user) == false | |||
assert user.info.follow_request_count == 0 | |||
end | |||
end | |||
@@ -1781,4 +1792,29 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do | |||
|> json_response(200) | |||
end | |||
end | |||
test "flavours switching (Pleroma Extension)", %{conn: conn} do | |||
user = insert(:user) | |||
get_old_flavour = | |||
conn | |||
|> assign(:user, user) | |||
|> get("/api/v1/pleroma/flavour") | |||
assert "glitch" == json_response(get_old_flavour, 200) | |||
set_flavour = | |||
conn | |||
|> assign(:user, user) | |||
|> post("/api/v1/pleroma/flavour/vanilla") | |||
assert "vanilla" == json_response(set_flavour, 200) | |||
get_new_flavour = | |||
conn | |||
|> assign(:user, user) | |||
|> post("/api/v1/pleroma/flavour/vanilla") | |||
assert json_response(set_flavour, 200) == json_response(get_new_flavour, 200) | |||
end | |||
end |
@@ -5,7 +5,8 @@ | |||
defmodule Pleroma.Web.MastodonAPI.StatusViewTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView} | |||
alias Pleroma.Web.MastodonAPI.AccountView | |||
alias Pleroma.Web.MastodonAPI.StatusView | |||
alias Pleroma.User | |||
alias Pleroma.Web.OStatus | |||
alias Pleroma.Web.CommonAPI | |||
@@ -4,7 +4,8 @@ | |||
defmodule Pleroma.Web.OAuth.AuthorizationTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.OAuth.{Authorization, App} | |||
alias Pleroma.Web.OAuth.Authorization | |||
alias Pleroma.Web.OAuth.App | |||
import Pleroma.Factory | |||
test "create an authorization token for a valid app" do | |||
@@ -7,7 +7,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do | |||
import Pleroma.Factory | |||
alias Pleroma.Repo | |||
alias Pleroma.Web.OAuth.{Authorization, Token} | |||
alias Pleroma.Web.OAuth.Authorization | |||
alias Pleroma.Web.OAuth.Token | |||
test "redirects with oauth authorization" do | |||
user = insert(:user) | |||
@@ -4,7 +4,9 @@ | |||
defmodule Pleroma.Web.OAuth.TokenTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.OAuth.{App, Token, Authorization} | |||
alias Pleroma.Web.OAuth.App | |||
alias Pleroma.Web.OAuth.Authorization | |||
alias Pleroma.Web.OAuth.Token | |||
alias Pleroma.Repo | |||
import Pleroma.Factory | |||
@@ -6,7 +6,9 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenterTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.OStatus.ActivityRepresenter | |||
alias Pleroma.{User, Activity, Object} | |||
alias Pleroma.Activity | |||
alias Pleroma.User | |||
alias Pleroma.Object | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.OStatus | |||
@@ -6,7 +6,9 @@ defmodule Pleroma.Web.OStatus.FeedRepresenterTest do | |||
use Pleroma.DataCase | |||
import Pleroma.Factory | |||
alias Pleroma.User | |||
alias Pleroma.Web.OStatus.{FeedRepresenter, UserRepresenter, ActivityRepresenter} | |||
alias Pleroma.Web.OStatus.ActivityRepresenter | |||
alias Pleroma.Web.OStatus.FeedRepresenter | |||
alias Pleroma.Web.OStatus.UserRepresenter | |||
alias Pleroma.Web.OStatus | |||
test "returns a feed of the last 20 items of the user" do | |||
@@ -4,7 +4,9 @@ defmodule Pleroma.Web.OStatus.DeleteHandlingTest do | |||
import Pleroma.Factory | |||
import Tesla.Mock | |||
alias Pleroma.{Repo, Activity, Object} | |||
alias Pleroma.Repo | |||
alias Pleroma.Activity | |||
alias Pleroma.Object | |||
alias Pleroma.Web.OStatus | |||
setup do | |||
@@ -5,7 +5,9 @@ | |||
defmodule Pleroma.Web.OStatus.OStatusControllerTest do | |||
use Pleroma.Web.ConnCase | |||
import Pleroma.Factory | |||
alias Pleroma.{User, Repo, Object} | |||
alias Pleroma.User | |||
alias Pleroma.Repo | |||
alias Pleroma.Object | |||
alias Pleroma.Web.CommonAPI | |||
alias Pleroma.Web.OStatus.ActivityRepresenter | |||
@@ -6,7 +6,11 @@ defmodule Pleroma.Web.OStatusTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.OStatus | |||
alias Pleroma.Web.XML | |||
alias Pleroma.{Object, Repo, User, Activity, Instances} | |||
alias Pleroma.Object | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
alias Pleroma.Activity | |||
alias Pleroma.Instances | |||
import Pleroma.Factory | |||
import ExUnit.CaptureLog | |||
@@ -5,7 +5,9 @@ | |||
defmodule Pleroma.Web.Salmon.SalmonTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.Salmon | |||
alias Pleroma.{Repo, Activity, User} | |||
alias Pleroma.Activity | |||
alias Pleroma.Repo | |||
alias Pleroma.User | |||
import Pleroma.Factory | |||
@magickey "RSA.pu0s-halox4tu7wmES1FVSx6u-4wc0YrUFXcqWXZG4-27UmbCOpMQftRCldNRfyA-qLbz-eqiwQhh-1EwUvjsD4cYbAHNGHwTvDOyx5AKthQUP44ykPv7kjKGh3DWKySJvcs9tlUG87hlo7AvnMo9pwRS_Zz2CacQ-MKaXyDepk=.AQAB" | |||
@@ -4,8 +4,11 @@ | |||
defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenterTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.{User, Activity, Object} | |||
alias Pleroma.Web.TwitterAPI.Representers.{ActivityRepresenter, ObjectRepresenter} | |||
alias Pleroma.User | |||
alias Pleroma.Activity | |||
alias Pleroma.Object | |||
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter | |||
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.TwitterAPI.UserView | |||
import Pleroma.Factory | |||
@@ -5,9 +5,15 @@ | |||
defmodule Pleroma.Web.TwitterAPI.ControllerTest do | |||
use Pleroma.Web.ConnCase | |||
alias Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter | |||
alias Pleroma.Builders.{ActivityBuilder, UserBuilder} | |||
alias Pleroma.{Repo, Activity, User, Object, Notification} | |||
alias Pleroma.Builders.ActivityBuilder | |||
alias Pleroma.Builders.UserBuilder | |||
alias Pleroma.Repo | |||
alias Pleroma.Activity | |||
alias Pleroma.User | |||
alias Pleroma.Object | |||
alias Pleroma.Notification | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.OAuth.Token | |||
alias Pleroma.Web.TwitterAPI.UserView | |||
alias Pleroma.Web.TwitterAPI.NotificationView | |||
alias Pleroma.Web.CommonAPI | |||
@@ -635,6 +641,24 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do | |||
assert json_response(conn, 200) == | |||
UserView.render("show.json", %{user: followed, for: current_user}) | |||
end | |||
test "for restricted account", %{conn: conn, user: current_user} do | |||
followed = insert(:user, info: %User.Info{locked: true}) | |||
conn = | |||
conn | |||
|> with_credentials(current_user.nickname, "test") | |||
|> post("/api/friendships/create.json", %{user_id: followed.id}) | |||
current_user = Repo.get(User, current_user.id) | |||
followed = Repo.get(User, followed.id) | |||
refute User.ap_followers(followed) in current_user.following | |||
assert followed.info.follow_request_count == 1 | |||
assert json_response(conn, 200) == | |||
UserView.render("show.json", %{user: followed, for: current_user}) | |||
end | |||
end | |||
describe "POST /friendships/destroy.json" do | |||
@@ -1213,7 +1237,7 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do | |||
assert Enum.sort(expected) == Enum.sort(result) | |||
end | |||
test "it returns 20 friends per page", %{conn: conn} do | |||
test "it returns 20 friends per page, except if 'export' is set to true", %{conn: conn} do | |||
user = insert(:user) | |||
followeds = insert_list(21, :user) | |||
@@ -1237,6 +1261,14 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do | |||
result = json_response(res_conn, 200) | |||
assert length(result) == 1 | |||
res_conn = | |||
conn | |||
|> assign(:user, user) | |||
|> get("/api/statuses/friends", %{all: true}) | |||
result = json_response(res_conn, 200) | |||
assert length(result) == 21 | |||
end | |||
test "it returns a given user's friends with user_id", %{conn: conn} do | |||
@@ -1671,15 +1703,19 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do | |||
other_user = Repo.get(User, other_user.id) | |||
assert User.following?(other_user, user) == false | |||
assert user.info.follow_request_count == 1 | |||
conn = | |||
build_conn() | |||
|> assign(:user, user) | |||
|> post("/api/pleroma/friendships/approve", %{"user_id" => other_user.id}) | |||
user = Repo.get(User, user.id) | |||
assert relationship = json_response(conn, 200) | |||
assert other_user.id == relationship["id"] | |||
assert relationship["follows_you"] == true | |||
assert user.info.follow_request_count == 0 | |||
end | |||
end | |||
@@ -1694,15 +1730,19 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do | |||
other_user = Repo.get(User, other_user.id) | |||
assert User.following?(other_user, user) == false | |||
assert user.info.follow_request_count == 1 | |||
conn = | |||
build_conn() | |||
|> assign(:user, user) | |||
|> post("/api/pleroma/friendships/deny", %{"user_id" => other_user.id}) | |||
user = Repo.get(User, user.id) | |||
assert relationship = json_response(conn, 200) | |||
assert other_user.id == relationship["id"] | |||
assert relationship["follows_you"] == false | |||
assert user.info.follow_request_count == 0 | |||
end | |||
end | |||
@@ -1876,4 +1916,38 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do | |||
ActivityRepresenter.to_map(activity, %{user: user, for: user}) | |||
end | |||
end | |||
describe "GET /api/oauth_tokens" do | |||
setup do | |||
token = insert(:oauth_token) |> Repo.preload(:user) | |||
%{token: token} | |||
end | |||
test "renders list", %{token: token} do | |||
response = | |||
build_conn() | |||
|> assign(:user, token.user) | |||
|> get("/api/oauth_tokens") | |||
keys = | |||
json_response(response, 200) | |||
|> hd() | |||
|> Map.keys() | |||
assert keys -- ["id", "app_name", "valid_until"] == [] | |||
end | |||
test "revoke token", %{token: token} do | |||
response = | |||
build_conn() | |||
|> assign(:user, token.user) | |||
|> delete("/api/oauth_tokens/#{token.id}") | |||
tokens = Token.get_user_tokens(token.user) | |||
assert tokens == [] | |||
assert response.status == 201 | |||
end | |||
end | |||
end |
@@ -4,8 +4,13 @@ | |||
defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView} | |||
alias Pleroma.{Activity, User, Object, Repo, UserInviteToken} | |||
alias Pleroma.Web.TwitterAPI.TwitterAPI | |||
alias Pleroma.Web.TwitterAPI.UserView | |||
alias Pleroma.Activity | |||
alias Pleroma.User | |||
alias Pleroma.Object | |||
alias Pleroma.Repo | |||
alias Pleroma.UserInviteToken | |||
alias Pleroma.Web.ActivityPub.ActivityPub | |||
alias Pleroma.Web.TwitterAPI.ActivityView | |||
@@ -5,7 +5,8 @@ | |||
defmodule Pleroma.Web.TwitterAPI.NotificationViewTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.{User, Notification} | |||
alias Pleroma.User | |||
alias Pleroma.Notification | |||
alias Pleroma.Web.TwitterAPI.TwitterAPI | |||
alias Pleroma.Web.TwitterAPI.NotificationView | |||
alias Pleroma.Web.TwitterAPI.UserView | |||
@@ -6,7 +6,8 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do | |||
use Pleroma.Web.ConnCase | |||
import Pleroma.Factory | |||
alias Pleroma.Web.Websub.WebsubClientSubscription | |||
alias Pleroma.{Repo, Activity} | |||
alias Pleroma.Activity | |||
alias Pleroma.Repo | |||
alias Pleroma.Web.Websub | |||
test "websub subscription request", %{conn: conn} do | |||
@@ -80,7 +81,7 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do | |||
assert response(conn, 500) == "Error" | |||
assert length(Repo.all(Activity)) == 0 | |||
assert Enum.empty?(Repo.all(Activity)) | |||
end | |||
end | |||
end |
@@ -5,7 +5,8 @@ | |||
defmodule Pleroma.Web.WebsubTest do | |||
use Pleroma.DataCase | |||
alias Pleroma.Web.Websub | |||
alias Pleroma.Web.Websub.{WebsubServerSubscription, WebsubClientSubscription} | |||
alias Pleroma.Web.Websub.WebsubServerSubscription | |||
alias Pleroma.Web.Websub.WebsubClientSubscription | |||
import Pleroma.Factory | |||
alias Pleroma.Web.Router.Helpers | |||
import Tesla.Mock | |||