Merge remote-tracking branch 'origin/develop' into shigusegubu
* origin/develop: (92 commits)
Web.MastodonApi.MastodonSocketTest: Add test for unauthenticated websocket
Web.Streamer: Get unauthenticated statuses representation
Web.MastodonAPI.MastodonSocket: Put access_token at function-level
Web.MastodonAPI.MastodonSocket: Add unauthentified websocket endpoints
Improved version string
mediaproxy: fix empty url & add some tests
RetryQueue: tiny refractor, add tests
Various runtime configuration fixes
update pleroma frontend
Federator: add retry queue.
activitypub: object view: avoid leaking private details
ostatus controller: respond with AS2 objects instead of activities to notice URIs
tests: federator: fix formatting
activitypub: transmogrifier: make deletes secure
Web.AdminAPI.AdminAPIControllerTest: New Test
Web.AdminAPI.AdminAPIController: Fixes bugs found with ExUnit
test/plugs/user_is_admin_plug_test: New test
lib/mix/tasks/relay*: Use a with block
Change Relay from `status` to `{status, message}`
Web.Router: Change right to permission group (except for function names)
...
This commit is contained in:
commit
f077d41b12
75 changed files with 1678 additions and 228 deletions
|
|
@ -8,7 +8,7 @@ Pleroma is written in Elixir, high-performance and can run on small devices like
|
|||
|
||||
For clients it supports both the [GNU Social API with Qvitter extensions](https://twitter-api.readthedocs.io/en/latest/index.html) and the [Mastodon client API](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md).
|
||||
|
||||
Mobile clients that are known to work well:
|
||||
Client applications that are known to work well:
|
||||
|
||||
* Twidere
|
||||
* Tusky
|
||||
|
|
@ -17,6 +17,7 @@ Mobile clients that are known to work well:
|
|||
* Amaroq (iOS)
|
||||
* Tootdon (Android + iOS)
|
||||
* Tootle (iOS)
|
||||
* Whalebird (Windows + Mac + Linux)
|
||||
|
||||
No release has been made yet, but several servers have been online for months already. If you want to run your own server, feel free to contact us at @lain@pleroma.soykaf.com or in our dev chat at #pleroma on freenode or via matrix at https://matrix.heldscal.la/#/room/#freenode_#pleroma:matrix.org.
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ config :pleroma, Pleroma.Uploaders.S3,
|
|||
public_endpoint: "https://s3.amazonaws.com",
|
||||
force_media_proxy: false
|
||||
|
||||
config :pleroma, Pleroma.Uploaders.MDII,
|
||||
cgi: "https://mdii.sakura.ne.jp/mdii-post.cgi",
|
||||
files: "https://mdii.sakura.ne.jp"
|
||||
|
||||
config :pleroma, :emoji, shortcode_globs: ["/emoji/custom/**/*.png"]
|
||||
|
||||
config :pleroma, :uri_schemes,
|
||||
|
|
@ -48,6 +52,7 @@ config :pleroma, Pleroma.Web.Endpoint,
|
|||
url: [host: "localhost"],
|
||||
protocol: "https",
|
||||
secret_key_base: "aK4Abxf29xU9TTDKre9coZPUgevcVCFQJe/5xP/7Lt4BEif6idBIbjupVbOrbKxl",
|
||||
signing_salt: "CqaoopA2",
|
||||
render_errors: [view: Pleroma.Web.ErrorView, accepts: ~w(json)],
|
||||
pubsub: [name: Pleroma.PubSub, adapter: Phoenix.PubSub.PG2],
|
||||
secure_cookie_flag: true
|
||||
|
|
@ -68,18 +73,10 @@ config :pleroma, :websub, Pleroma.Web.Websub
|
|||
config :pleroma, :ostatus, Pleroma.Web.OStatus
|
||||
config :pleroma, :httpoison, Pleroma.HTTP
|
||||
|
||||
version =
|
||||
with {version, 0} <- System.cmd("git", ["rev-parse", "HEAD"]) do
|
||||
"Pleroma #{Mix.Project.config()[:version]} #{String.trim(version)}"
|
||||
else
|
||||
_ -> "Pleroma #{Mix.Project.config()[:version]} dev"
|
||||
end
|
||||
|
||||
# Configures http settings, upstream proxy etc.
|
||||
config :pleroma, :http, proxy_url: nil
|
||||
|
||||
config :pleroma, :instance,
|
||||
version: version,
|
||||
name: "Shigusegubu",
|
||||
email: "pleroma@hjkos.com",
|
||||
description: "SigSegV, a pleroma instance",
|
||||
|
|
@ -176,6 +173,13 @@ config :pleroma, :suggestions,
|
|||
limit: 23,
|
||||
web: "https://vinayaka.distsn.org/?{{host}}+{{user}}"
|
||||
|
||||
config :pleroma, :http_security,
|
||||
enabled: true,
|
||||
sts: false,
|
||||
sts_max_age: 31_536_000,
|
||||
ct_max_age: 2_592_000,
|
||||
referrer_policy: "same-origin"
|
||||
|
||||
config :cors_plug,
|
||||
max_age: 86_400,
|
||||
methods: ["POST", "PUT", "DELETE", "GET", "PATCH", "OPTIONS"],
|
||||
|
|
|
|||
|
|
@ -80,3 +80,10 @@ This section is used to configure Pleroma-FE, unless ``:managed_config`` in ``:i
|
|||
* ``unfollow_blocked``: Whether blocks result in people getting unfollowed
|
||||
* ``outgoing_blocks``: Whether to federate blocks to other instances
|
||||
* ``deny_follow_blocked``: Whether to disallow following an account that has blocked the user in question
|
||||
|
||||
## :http_security
|
||||
* ``enabled``: Whether the managed content security policy is enabled
|
||||
* ``sts``: Whether to additionally send a `Strict-Transport-Security` header
|
||||
* ``sts_max_age``: The maximum age for the `Strict-Transport-Security` header if sent
|
||||
* ``ct_max_age``: The maximum age for the `Expect-CT` header if sent
|
||||
* ``referrer_policy``: The referrer policy to use, either `"same-origin"` or `"no-referrer"`.
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use Mix.Config
|
|||
# manifest is generated by the mix phoenix.digest task
|
||||
# which you typically run after static files are built.
|
||||
config :pleroma, Pleroma.Web.Endpoint,
|
||||
server: true,
|
||||
http: [port: 4000],
|
||||
protocol: "http"
|
||||
|
||||
|
|
|
|||
|
|
@ -21,28 +21,6 @@ example.tld {
|
|||
ciphers ECDHE-ECDSA-WITH-CHACHA20-POLY1305 ECDHE-RSA-WITH-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256
|
||||
}
|
||||
|
||||
header / {
|
||||
X-XSS-Protection "1; mode=block"
|
||||
X-Frame-Options "DENY"
|
||||
X-Content-Type-Options "nosniff"
|
||||
Referrer-Policy "same-origin"
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains;"
|
||||
Expect-CT "enforce, max-age=2592000"
|
||||
Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://{host}; upgrade-insecure-requests;"
|
||||
}
|
||||
|
||||
# If you do not want remote frontends to be able to access your Pleroma backend server, remove these lines.
|
||||
# If you want to allow all origins access, remove the origin lines.
|
||||
# To use this directive, you need the http.cors plugin for Caddy.
|
||||
cors / {
|
||||
origin https://halcyon.example.tld
|
||||
origin https://pinafore.example.tld
|
||||
methods POST,PUT,DELETE,GET,PATCH,OPTIONS
|
||||
allowed_headers Authorization,Content-Type,Idempotency-Key
|
||||
exposed_headers Link,X-RateLimit-Reset,X-RateLimit-Limit,X-RateLimit-Remaining,X-Request-Id
|
||||
}
|
||||
# Stop removing lines here.
|
||||
|
||||
# If you do not want to use the mediaproxy function, remove these lines.
|
||||
# To use this directive, you need the http.cache plugin for Caddy.
|
||||
cache {
|
||||
|
|
|
|||
|
|
@ -34,15 +34,6 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined
|
|||
SSLCompression off
|
||||
SSLSessionTickets off
|
||||
|
||||
Header always set X-Xss-Protection "1; mode=block"
|
||||
Header always set X-Frame-Options "DENY"
|
||||
Header always set X-Content-Type-Options "nosniff"
|
||||
Header always set Referrer-Policy same-origin
|
||||
Header always set Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://${servername}; upgrade-insecure-requests;"
|
||||
|
||||
# Uncomment this only after you get HTTPS working.
|
||||
# Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
|
||||
RewriteEngine On
|
||||
RewriteCond %{HTTP:Connection} Upgrade [NC]
|
||||
RewriteCond %{HTTP:Upgrade} websocket [NC]
|
||||
|
|
|
|||
|
|
@ -60,17 +60,6 @@ server {
|
|||
client_max_body_size 16m;
|
||||
|
||||
location / {
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
||||
add_header X-Frame-Options "DENY" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header Referrer-Policy "same-origin" always;
|
||||
add_header X-Download-Options "noopen" always;
|
||||
add_header Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action *; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://$server_name; upgrade-insecure-requests;" always;
|
||||
|
||||
# Uncomment this only after you get HTTPS working.
|
||||
# add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
|
|
|||
|
|
@ -119,13 +119,3 @@ sub vcl_pipe {
|
|||
set bereq.http.connection = req.http.connection;
|
||||
}
|
||||
}
|
||||
|
||||
sub vcl_deliver {
|
||||
set resp.http.X-Frame-Options = "DENY";
|
||||
set resp.http.X-XSS-Protection = "1; mode=block";
|
||||
set resp.http.X-Content-Type-Options = "nosniff";
|
||||
set resp.http.Referrer-Policy = "same-origin";
|
||||
set resp.http.Content-Security-Policy = "default-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'none'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://" + req.http.host + "; upgrade-insecure-requests;";
|
||||
# Uncomment this only after you get HTTPS working.
|
||||
# set resp.http.Strict-Transport-Security= "max-age=31536000; includeSubDomains";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ defmodule Mix.Tasks.RelayFollow do
|
|||
def run([target]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
:ok = Relay.follow(target)
|
||||
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
with {:ok, activity} <- Relay.follow(target) do
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
else
|
||||
{:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,9 +13,11 @@ defmodule Mix.Tasks.RelayUnfollow do
|
|||
def run([target]) do
|
||||
Mix.Task.run("app.start")
|
||||
|
||||
:ok = Relay.unfollow(target)
|
||||
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
with {:ok, activity} <- Relay.follow(target) do
|
||||
# put this task to sleep to allow the genserver to push out the messages
|
||||
:timer.sleep(500)
|
||||
else
|
||||
{:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ config :pleroma, Pleroma.Repo,
|
|||
hostname: "localhost",
|
||||
pool_size: 10
|
||||
|
||||
# Enable Strict-Transport-Security once SSL is working:
|
||||
# config :pleroma, :http_security,
|
||||
# sts: true
|
||||
|
||||
# Configure S3 support if desired.
|
||||
# The public S3 endpoint is different depending on region and provider,
|
||||
# consult your S3 provider's documentation for details on what to use.
|
||||
|
|
|
|||
32
lib/mix/tasks/set_admin.ex
Normal file
32
lib/mix/tasks/set_admin.ex
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
defmodule Mix.Tasks.SetAdmin do
|
||||
use Mix.Task
|
||||
alias Pleroma.User
|
||||
|
||||
@doc """
|
||||
Sets admin status
|
||||
Usage: set_admin nickname [true|false]
|
||||
"""
|
||||
def run([nickname | rest]) do
|
||||
Application.ensure_all_started(:pleroma)
|
||||
|
||||
status =
|
||||
case rest do
|
||||
[status] -> status == "true"
|
||||
_ -> true
|
||||
end
|
||||
|
||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
||||
info =
|
||||
user.info
|
||||
|> Map.put("is_admin", !!status)
|
||||
|
||||
cng = User.info_changeset(user, %{info: info})
|
||||
{:ok, user} = User.update_and_set_cache(cng)
|
||||
|
||||
IO.puts("Admin status of #{nickname}: #{user.info["is_admin"]}")
|
||||
else
|
||||
_ ->
|
||||
IO.puts("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,8 +1,15 @@
|
|||
defmodule Pleroma.Application do
|
||||
use Application
|
||||
|
||||
@name "Pleroma"
|
||||
@version Mix.Project.config()[:version]
|
||||
def name, do: @name
|
||||
def version, do: @version
|
||||
def named_version(), do: @name <> " " <> @version
|
||||
|
||||
# See http://elixir-lang.org/docs/stable/elixir/Application.html
|
||||
# for more information on OTP Applications
|
||||
@env Mix.env()
|
||||
def start(_type, _args) do
|
||||
import Supervisor.Spec
|
||||
import Cachex.Spec
|
||||
|
|
@ -57,10 +64,11 @@ defmodule Pleroma.Application do
|
|||
id: :cachex_idem
|
||||
),
|
||||
worker(Pleroma.Web.Federator, []),
|
||||
worker(Pleroma.Stats, []),
|
||||
worker(Pleroma.Gopher.Server, [])
|
||||
worker(Pleroma.Web.Federator.RetryQueue, []),
|
||||
worker(Pleroma.Gopher.Server, []),
|
||||
worker(Pleroma.Stats, [])
|
||||
] ++
|
||||
if Mix.env() == :test,
|
||||
if @env == :test,
|
||||
do: [],
|
||||
else:
|
||||
[worker(Pleroma.Web.Streamer, [])] ++
|
||||
|
|
|
|||
|
|
@ -31,10 +31,12 @@ defmodule Pleroma.Object do
|
|||
def normalize(ap_id) when is_binary(ap_id), do: Object.get_by_ap_id(ap_id)
|
||||
def normalize(_), do: nil
|
||||
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
if Mix.env() == :test do
|
||||
if Mix.env() == :test do
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
get_by_ap_id(ap_id)
|
||||
else
|
||||
end
|
||||
else
|
||||
def get_cached_by_ap_id(ap_id) do
|
||||
key = "object:#{ap_id}"
|
||||
|
||||
Cachex.fetch!(:object_cache, key, fn _ ->
|
||||
|
|
|
|||
58
lib/pleroma/plugs/http_security_plug.ex
Normal file
58
lib/pleroma/plugs/http_security_plug.ex
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
||||
alias Pleroma.Config
|
||||
import Plug.Conn
|
||||
|
||||
def init(opts), do: opts
|
||||
|
||||
def call(conn, options) do
|
||||
if Config.get([:http_security, :enabled]) do
|
||||
conn =
|
||||
merge_resp_headers(conn, headers())
|
||||
|> maybe_send_sts_header(Config.get([:http_security, :sts]))
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
defp headers do
|
||||
referrer_policy = Config.get([:http_security, :referrer_policy])
|
||||
|
||||
[
|
||||
{"x-xss-protection", "1; mode=block"},
|
||||
{"x-permitted-cross-domain-policies", "none"},
|
||||
{"x-frame-options", "DENY"},
|
||||
{"x-content-type-options", "nosniff"},
|
||||
{"referrer-policy", referrer_policy},
|
||||
{"x-download-options", "noopen"},
|
||||
{"content-security-policy", csp_string() <> ";"}
|
||||
]
|
||||
end
|
||||
|
||||
defp csp_string do
|
||||
[
|
||||
"default-src 'none'",
|
||||
"base-uri 'self'",
|
||||
"frame-ancestors 'none'",
|
||||
"img-src 'self' data: https:",
|
||||
"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"),
|
||||
"upgrade-insecure-requests"
|
||||
]
|
||||
|> Enum.join("; ")
|
||||
end
|
||||
|
||||
defp maybe_send_sts_header(conn, true) do
|
||||
max_age_sts = Config.get([:http_security, :sts_max_age])
|
||||
max_age_ct = Config.get([:http_security, :ct_max_age])
|
||||
|
||||
merge_resp_headers(conn, [
|
||||
{"strict-transport-security", "max-age=#{max_age_sts}; includeSubDomains"},
|
||||
{"expect-ct", "enforce, max-age=#{max_age_ct}"}
|
||||
])
|
||||
end
|
||||
|
||||
defp maybe_send_sts_header(conn, _), do: conn
|
||||
end
|
||||
19
lib/pleroma/plugs/user_is_admin_plug.ex
Normal file
19
lib/pleroma/plugs/user_is_admin_plug.ex
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
defmodule Pleroma.Plugs.UserIsAdminPlug do
|
||||
import Plug.Conn
|
||||
alias Pleroma.User
|
||||
|
||||
def init(options) do
|
||||
options
|
||||
end
|
||||
|
||||
def call(%{assigns: %{user: %User{info: %{"is_admin" => true}}}} = conn, _) do
|
||||
conn
|
||||
end
|
||||
|
||||
def call(conn, _) do
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Jason.encode!(%{error: "User is not admin."}))
|
||||
|> halt
|
||||
end
|
||||
end
|
||||
26
lib/pleroma/uploaders/mdii.ex
Normal file
26
lib/pleroma/uploaders/mdii.ex
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
defmodule Pleroma.Uploaders.MDII do
|
||||
alias Pleroma.Config
|
||||
|
||||
@behaviour Pleroma.Uploaders.Uploader
|
||||
|
||||
@httpoison Application.get_env(:pleroma, :httpoison)
|
||||
|
||||
def put_file(name, uuid, path, content_type, should_dedupe) do
|
||||
cgi = Pleroma.Config.get([Pleroma.Uploaders.MDII, :cgi])
|
||||
files = Pleroma.Config.get([Pleroma.Uploaders.MDII, :files])
|
||||
|
||||
{:ok, file_data} = File.read(path)
|
||||
|
||||
extension = String.split(name, ".") |> List.last()
|
||||
query = "#{cgi}?#{extension}"
|
||||
|
||||
with {:ok, %{status_code: 200, body: body}} <- @httpoison.post(query, file_data) do
|
||||
File.rm!(path)
|
||||
remote_file_name = String.split(body) |> List.first()
|
||||
public_url = "#{files}/#{remote_file_name}.#{extension}"
|
||||
{:ok, public_url}
|
||||
else
|
||||
_ -> Pleroma.Uploaders.Local.put_file(name, uuid, path, content_type, should_dedupe)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -498,7 +498,7 @@ defmodule Pleroma.User do
|
|||
Repo.all(query)
|
||||
end
|
||||
|
||||
def search(query, resolve) do
|
||||
def search(query, resolve \\ false) do
|
||||
# strip the beginning @ off if there is a query
|
||||
query = String.trim_leading(query, "@")
|
||||
|
||||
|
|
|
|||
|
|
@ -628,9 +628,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
|
||||
def fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||
with {:ok, %{status_code: 200, body: body}} <-
|
||||
@httpoison.get(ap_id, [Accept: "application/activity+json"], follow_redirect: true),
|
||||
{:ok, data} <- Jason.decode(body) do
|
||||
with {:ok, data} <- fetch_and_contain_remote_object_from_id(ap_id) do
|
||||
user_data_from_user_object(data)
|
||||
else
|
||||
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
|
||||
|
|
@ -732,22 +730,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
else
|
||||
Logger.info("Fetching #{id} via AP")
|
||||
|
||||
with true <- String.starts_with?(id, "http"),
|
||||
{:ok, %{body: body, status_code: code}} when code in 200..299 <-
|
||||
@httpoison.get(
|
||||
id,
|
||||
[Accept: "application/activity+json"],
|
||||
follow_redirect: true,
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000
|
||||
),
|
||||
{:ok, data} <- Jason.decode(body),
|
||||
with {:ok, data} <- fetch_and_contain_remote_object_from_id(id),
|
||||
nil <- Object.normalize(data),
|
||||
params <- %{
|
||||
"type" => "Create",
|
||||
"to" => data["to"],
|
||||
"cc" => data["cc"],
|
||||
"actor" => data["attributedTo"],
|
||||
"actor" => data["actor"] || data["attributedTo"],
|
||||
"object" => data
|
||||
},
|
||||
:ok <- Transmogrifier.contain_origin(id, params),
|
||||
|
|
@ -771,6 +760,27 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end
|
||||
end
|
||||
|
||||
def fetch_and_contain_remote_object_from_id(id) do
|
||||
Logger.info("Fetching #{id} via AP")
|
||||
|
||||
with true <- String.starts_with?(id, "http"),
|
||||
{:ok, %{body: body, status_code: code}} when code in 200..299 <-
|
||||
@httpoison.get(
|
||||
id,
|
||||
[Accept: "application/activity+json"],
|
||||
follow_redirect: true,
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000
|
||||
),
|
||||
{:ok, data} <- Jason.decode(body),
|
||||
:ok <- Transmogrifier.contain_origin_from_id(id, data) do
|
||||
{:ok, data}
|
||||
else
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
def is_public?(activity) do
|
||||
"https://www.w3.org/ns/activitystreams#Public" in (activity.data["to"] ++
|
||||
(activity.data["cc"] || []))
|
||||
|
|
|
|||
|
|
@ -12,11 +12,12 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|
|||
%User{} = target_user <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, activity} <- ActivityPub.follow(local_user, target_user) do
|
||||
Logger.info("relay: followed instance: #{target_instance}; id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
else
|
||||
e -> Logger.error("error: #{inspect(e)}")
|
||||
e ->
|
||||
Logger.error("error: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def unfollow(target_instance) do
|
||||
|
|
@ -24,11 +25,12 @@ defmodule Pleroma.Web.ActivityPub.Relay do
|
|||
%User{} = target_user <- User.get_or_fetch_by_ap_id(target_instance),
|
||||
{:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do
|
||||
Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
|
||||
{:ok, activity}
|
||||
else
|
||||
e -> Logger.error("error: #{inspect(e)}")
|
||||
e ->
|
||||
Logger.error("error: #{inspect(e)}")
|
||||
{:error, e}
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
def publish(%Activity{data: %{"type" => "Create"}} = activity) do
|
||||
|
|
|
|||
|
|
@ -50,6 +50,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
def contain_origin_from_id(id, %{"id" => nil}), do: :error
|
||||
|
||||
def contain_origin_from_id(id, %{"id" => other_id} = params) do
|
||||
id_uri = URI.parse(id)
|
||||
other_uri = URI.parse(other_id)
|
||||
|
||||
if id_uri.host == other_uri.host do
|
||||
:ok
|
||||
else
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Modifies an incoming AP object (mastodon format) to our internal format.
|
||||
"""
|
||||
|
|
@ -454,15 +467,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: Make secure.
|
||||
# TODO: We presently assume that any actor on the same origin domain as the object being
|
||||
# deleted has the rights to delete that object. A better way to validate whether or not
|
||||
# the object should be deleted is to refetch the object URI, which should return either
|
||||
# an error or a tombstone. This would allow us to verify that a deletion actually took
|
||||
# place.
|
||||
def handle_incoming(
|
||||
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data
|
||||
%{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data
|
||||
) do
|
||||
object_id = Utils.get_ap_id(object_id)
|
||||
|
||||
with actor <- get_actor(data),
|
||||
%User{} = _actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
%User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
|
||||
:ok <- contain_origin(actor.ap_id, object.data),
|
||||
{:ok, activity} <- ActivityPub.delete(object, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
|
|||
Map.merge(base, additional)
|
||||
end
|
||||
|
||||
def render("object.json", %{object: %Activity{} = activity}) do
|
||||
def render("object.json", %{object: %Activity{data: %{"type" => "Create"}} = activity}) do
|
||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
|
||||
object = Object.normalize(activity.data["object"])
|
||||
|
||||
|
|
@ -20,4 +20,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
|
|||
|
||||
Map.merge(base, additional)
|
||||
end
|
||||
|
||||
def render("object.json", %{object: %Activity{} = activity}) do
|
||||
base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header()
|
||||
object = Object.normalize(activity.data["object"])
|
||||
|
||||
additional =
|
||||
Transmogrifier.prepare_object(activity.data)
|
||||
|> Map.put("object", object.data["id"])
|
||||
|
||||
Map.merge(base, additional)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
158
lib/pleroma/web/admin_api/admin_api_controller.ex
Normal file
158
lib/pleroma/web/admin_api/admin_api_controller.ex
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.{User, Repo}
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
|
||||
require Logger
|
||||
|
||||
action_fallback(:errors)
|
||||
|
||||
def user_delete(conn, %{"nickname" => nickname}) do
|
||||
user = User.get_by_nickname(nickname)
|
||||
|
||||
if user.local == true do
|
||||
User.delete(user)
|
||||
else
|
||||
User.delete(user)
|
||||
end
|
||||
|
||||
conn
|
||||
|> json(nickname)
|
||||
end
|
||||
|
||||
def user_create(
|
||||
conn,
|
||||
%{"nickname" => nickname, "email" => email, "password" => password}
|
||||
) do
|
||||
new_user = %{
|
||||
nickname: nickname,
|
||||
name: nickname,
|
||||
email: email,
|
||||
password: password,
|
||||
password_confirmation: password,
|
||||
bio: "."
|
||||
}
|
||||
|
||||
User.register_changeset(%User{}, new_user)
|
||||
|> Repo.insert!()
|
||||
|
||||
conn
|
||||
|> json(new_user.nickname)
|
||||
end
|
||||
|
||||
def right_add(conn, %{"permission_group" => permission_group, "nickname" => nickname})
|
||||
when permission_group in ["moderator", "admin"] do
|
||||
user = User.get_by_nickname(nickname)
|
||||
|
||||
info =
|
||||
user.info
|
||||
|> Map.put("is_" <> permission_group, true)
|
||||
|
||||
cng = User.info_changeset(user, %{info: info})
|
||||
{:ok, user} = User.update_and_set_cache(cng)
|
||||
|
||||
conn
|
||||
|> json(user.info)
|
||||
end
|
||||
|
||||
def right_get(conn, %{"nickname" => nickname}) do
|
||||
user = User.get_by_nickname(nickname)
|
||||
|
||||
conn
|
||||
|> json(user.info)
|
||||
end
|
||||
|
||||
def right_add(conn, _) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "No such permission_group"})
|
||||
end
|
||||
|
||||
def right_delete(
|
||||
%{assigns: %{user: %User{:nickname => admin_nickname}}} = conn,
|
||||
%{
|
||||
"permission_group" => permission_group,
|
||||
"nickname" => nickname
|
||||
}
|
||||
)
|
||||
when permission_group in ["moderator", "admin"] do
|
||||
if admin_nickname == nickname do
|
||||
conn
|
||||
|> put_status(403)
|
||||
|> json(%{error: "You can't revoke your own admin status."})
|
||||
else
|
||||
user = User.get_by_nickname(nickname)
|
||||
|
||||
info =
|
||||
user.info
|
||||
|> Map.put("is_" <> permission_group, false)
|
||||
|
||||
cng = User.info_changeset(user, %{info: info})
|
||||
{:ok, user} = User.update_and_set_cache(cng)
|
||||
|
||||
conn
|
||||
|> json(user.info)
|
||||
end
|
||||
end
|
||||
|
||||
def right_delete(conn, _) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|> json(%{error: "No such permission_group"})
|
||||
end
|
||||
|
||||
def relay_follow(conn, %{"relay_url" => target}) do
|
||||
{status, message} = Relay.follow(target)
|
||||
|
||||
if status == :ok do
|
||||
conn
|
||||
|> json(target)
|
||||
else
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json(target)
|
||||
end
|
||||
end
|
||||
|
||||
def relay_unfollow(conn, %{"relay_url" => target}) do
|
||||
{status, message} = Relay.unfollow(target)
|
||||
|
||||
if status == :ok do
|
||||
conn
|
||||
|> json(target)
|
||||
else
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json(target)
|
||||
end
|
||||
end
|
||||
|
||||
@shortdoc "Get a account registeration invite token (base64 string)"
|
||||
def get_invite_token(conn, _params) do
|
||||
{:ok, token} = Pleroma.UserInviteToken.create_token()
|
||||
|
||||
conn
|
||||
|> json(token.token)
|
||||
end
|
||||
|
||||
@shortdoc "Get a password reset token (base64 string) for given nickname"
|
||||
def get_password_reset(conn, %{"nickname" => nickname}) do
|
||||
(%User{local: true} = user) = User.get_by_nickname(nickname)
|
||||
{:ok, token} = Pleroma.PasswordResetToken.create_token(user)
|
||||
|
||||
conn
|
||||
|> json(token.token)
|
||||
end
|
||||
|
||||
def errors(conn, {:param_cast, _}) do
|
||||
conn
|
||||
|> put_status(400)
|
||||
|> json("Invalid parameters")
|
||||
end
|
||||
|
||||
def errors(conn, _) do
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json("Something went wrong")
|
||||
end
|
||||
end
|
||||
|
|
@ -4,9 +4,7 @@ defmodule Pleroma.Web.UserSocket do
|
|||
|
||||
## Channels
|
||||
# channel "room:*", Pleroma.Web.RoomChannel
|
||||
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
|
||||
channel("chat:*", Pleroma.Web.ChatChannel)
|
||||
end
|
||||
channel("chat:*", Pleroma.Web.ChatChannel)
|
||||
|
||||
## Transports
|
||||
transport(:websocket, Phoenix.Transports.WebSocket)
|
||||
|
|
@ -24,7 +22,8 @@ defmodule Pleroma.Web.UserSocket do
|
|||
# See `Phoenix.Token` documentation for examples in
|
||||
# performing token verification on connect.
|
||||
def connect(%{"token" => token}, socket) do
|
||||
with {:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84600),
|
||||
with true <- Pleroma.Config.get([:chat, :enabled]),
|
||||
{:ok, user_id} <- Phoenix.Token.verify(socket, "user socket", token, max_age: 84600),
|
||||
%User{} = user <- Pleroma.Repo.get(User, user_id) do
|
||||
{:ok, assign(socket, :user_name, user.nickname)}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
defmodule Pleroma.Web.Endpoint do
|
||||
use Phoenix.Endpoint, otp_app: :pleroma
|
||||
|
||||
if Application.get_env(:pleroma, :chat) |> Keyword.get(:enabled) do
|
||||
socket("/socket", Pleroma.Web.UserSocket)
|
||||
end
|
||||
socket("/socket", Pleroma.Web.UserSocket)
|
||||
|
||||
socket("/api/v1", Pleroma.Web.MastodonAPI.MastodonSocket)
|
||||
|
||||
|
|
@ -12,6 +10,7 @@ defmodule Pleroma.Web.Endpoint do
|
|||
# You should set gzip to true if you are running phoenix.digest
|
||||
# when deploying your static files in production.
|
||||
plug(CORSPlug)
|
||||
plug(Pleroma.Plugs.HTTPSecurityPlug)
|
||||
|
||||
plug(Plug.Static, at: "/media", from: Pleroma.Uploaders.Local.upload_path(), gzip: false)
|
||||
|
||||
|
|
@ -45,14 +44,19 @@ defmodule Pleroma.Web.Endpoint do
|
|||
plug(Plug.MethodOverride)
|
||||
plug(Plug.Head)
|
||||
|
||||
cookie_name =
|
||||
if Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
|
||||
do: "__Host-pleroma_key",
|
||||
else: "pleroma_key"
|
||||
|
||||
# The session will be stored in the cookie and signed,
|
||||
# this means its contents can be read but not tampered with.
|
||||
# Set :encryption_salt if you would also like to encrypt it.
|
||||
plug(
|
||||
Plug.Session,
|
||||
store: :cookie,
|
||||
key: "_pleroma_key",
|
||||
signing_salt: "CqaoopA2",
|
||||
key: cookie_name,
|
||||
signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
|
||||
http_only: true,
|
||||
secure:
|
||||
Application.get_env(:pleroma, Pleroma.Web.Endpoint) |> Keyword.get(:secure_cookie_flag),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ defmodule Pleroma.Web.Federator do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Web.{WebFinger, Websub}
|
||||
alias Pleroma.Web.Federator.RetryQueue
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||
|
|
@ -101,44 +102,46 @@ defmodule Pleroma.Web.Federator do
|
|||
|
||||
params = Utils.normalize_params(params)
|
||||
|
||||
# NOTE: we use the actor ID to do the containment, this is fine because an
|
||||
# actor shouldn't be acting on objects outside their own AP server.
|
||||
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
|
||||
nil <- Activity.normalize(params["id"]),
|
||||
{:ok, _activity} <- Transmogrifier.handle_incoming(params) do
|
||||
:ok <- Transmogrifier.contain_origin_from_id(params["actor"], params),
|
||||
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
|
||||
{:ok, activity}
|
||||
else
|
||||
%Activity{} ->
|
||||
Logger.info("Already had #{params["id"]}")
|
||||
:error
|
||||
|
||||
_e ->
|
||||
# Just drop those for now
|
||||
Logger.info("Unhandled activity")
|
||||
Logger.info(Poison.encode!(params, pretty: 2))
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
def handle(:publish_single_ap, params) do
|
||||
ActivityPub.publish_one(params)
|
||||
case ActivityPub.publish_one(params) do
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
|
||||
{:error, _} ->
|
||||
RetryQueue.enqueue(params, ActivityPub)
|
||||
end
|
||||
end
|
||||
|
||||
def handle(:publish_single_websub, %{xml: xml, topic: topic, callback: callback, secret: secret}) do
|
||||
signature = @websub.sign(secret || "", xml)
|
||||
Logger.debug(fn -> "Pushing #{topic} to #{callback}" end)
|
||||
def handle(
|
||||
:publish_single_websub,
|
||||
%{xml: xml, topic: topic, callback: callback, secret: secret} = params
|
||||
) do
|
||||
case Websub.publish_one(params) do
|
||||
{:ok, _} ->
|
||||
:ok
|
||||
|
||||
with {:ok, %{status_code: code}} <-
|
||||
@httpoison.post(
|
||||
callback,
|
||||
xml,
|
||||
[
|
||||
{"Content-Type", "application/atom+xml"},
|
||||
{"X-Hub-Signature", "sha1=#{signature}"}
|
||||
],
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000,
|
||||
hackney: [pool: :default]
|
||||
) do
|
||||
Logger.debug(fn -> "Pushed to #{callback}, code #{code}" end)
|
||||
else
|
||||
e ->
|
||||
Logger.debug(fn -> "Couldn't push to #{callback}, #{inspect(e)}" end)
|
||||
{:error, _} ->
|
||||
RetryQueue.enqueue(params, Websub)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -147,11 +150,15 @@ defmodule Pleroma.Web.Federator do
|
|||
{:error, "Don't know what to do with this"}
|
||||
end
|
||||
|
||||
def enqueue(type, payload, priority \\ 1) do
|
||||
if Pleroma.Config.get([:instance, :federating]) do
|
||||
if Mix.env() == :test do
|
||||
if Mix.env() == :test do
|
||||
def enqueue(type, payload, priority \\ 1) do
|
||||
if Pleroma.Config.get([:instance, :federating]) do
|
||||
handle(type, payload)
|
||||
else
|
||||
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
|
||||
|
|
|
|||
71
lib/pleroma/web/federator/retry_queue.ex
Normal file
71
lib/pleroma/web/federator/retry_queue.ex
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
defmodule Pleroma.Web.Federator.RetryQueue do
|
||||
use GenServer
|
||||
alias Pleroma.Web.{WebFinger, Websub}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
require Logger
|
||||
|
||||
@websub Application.get_env(:pleroma, :websub)
|
||||
@ostatus Application.get_env(:pleroma, :websub)
|
||||
@httpoison Application.get_env(:pleroma, :websub)
|
||||
@instance Application.get_env(:pleroma, :websub)
|
||||
# initial timeout, 5 min
|
||||
@initial_timeout 30_000
|
||||
@max_retries 5
|
||||
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
end
|
||||
|
||||
def start_link() do
|
||||
GenServer.start_link(__MODULE__, %{delivered: 0, dropped: 0}, name: __MODULE__)
|
||||
end
|
||||
|
||||
def enqueue(data, transport, retries \\ 0) do
|
||||
GenServer.cast(__MODULE__, {:maybe_enqueue, data, transport, retries + 1})
|
||||
end
|
||||
|
||||
def get_retry_params(retries) do
|
||||
if retries > @max_retries do
|
||||
{:drop, "Max retries reached"}
|
||||
else
|
||||
{:retry, growth_function(retries)}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_cast({:maybe_enqueue, data, transport, retries}, %{dropped: drop_count} = state) do
|
||||
case get_retry_params(retries) do
|
||||
{:retry, timeout} ->
|
||||
Process.send_after(
|
||||
__MODULE__,
|
||||
{:send, data, transport, retries},
|
||||
growth_function(retries)
|
||||
)
|
||||
|
||||
{:noreply, state}
|
||||
|
||||
{:drop, message} ->
|
||||
Logger.debug(message)
|
||||
{:noreply, %{state | dropped: drop_count + 1}}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info({:send, data, transport, retries}, %{delivered: delivery_count} = state) do
|
||||
case transport.publish_one(data) do
|
||||
{:ok, _} ->
|
||||
{:noreply, %{state | delivered: delivery_count + 1}}
|
||||
|
||||
{:error, reason} ->
|
||||
enqueue(data, transport, retries)
|
||||
{:noreply, state}
|
||||
end
|
||||
end
|
||||
|
||||
def handle_info(unknown, state) do
|
||||
Logger.debug("RetryQueue: don't know what to do with #{inspect(unknown)}, ignoring")
|
||||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp growth_function(retries) do
|
||||
round(@initial_timeout * :math.pow(retries, 3))
|
||||
end
|
||||
end
|
||||
|
|
@ -141,7 +141,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
uri: Web.base_url(),
|
||||
title: Keyword.get(instance, :name),
|
||||
description: Keyword.get(instance, :description),
|
||||
version: "#{@mastodon_api_level} (compatible; #{Keyword.get(instance, :version)})",
|
||||
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
|
||||
email: Keyword.get(instance, :email),
|
||||
urls: %{
|
||||
streaming_api: String.replace(Pleroma.Web.Endpoint.static_url(), "http", "ws")
|
||||
|
|
@ -278,9 +278,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def dm_timeline(%{assigns: %{user: user}} = conn, _params) do
|
||||
def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||
query =
|
||||
ActivityPub.fetch_activities_query([user.ap_id], %{"type" => "Create", visibility: "direct"})
|
||||
ActivityPub.fetch_activities_query(
|
||||
[user.ap_id],
|
||||
Map.merge(params, %{"type" => "Create", visibility: "direct"})
|
||||
)
|
||||
|
||||
activities = Repo.all(query)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,9 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
|
|||
timeout: :infinity
|
||||
)
|
||||
|
||||
def connect(params, socket) do
|
||||
with token when not is_nil(token) <- params["access_token"],
|
||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||
def connect(%{"access_token" => token} = params, socket) do
|
||||
with %Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||
%User{} = user <- Repo.get(User, user_id),
|
||||
stream
|
||||
when stream in [
|
||||
|
|
@ -45,6 +44,24 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
|
|||
end
|
||||
end
|
||||
|
||||
def connect(%{"stream" => stream} = params, socket)
|
||||
when stream in ["public", "public:local", "hashtag"] do
|
||||
topic =
|
||||
case stream do
|
||||
"hashtag" -> "hashtag:#{params["tag"]}"
|
||||
_ -> stream
|
||||
end
|
||||
|
||||
with socket =
|
||||
socket
|
||||
|> assign(:topic, topic) do
|
||||
Pleroma.Web.Streamer.add_socket(topic, socket)
|
||||
{:ok, socket}
|
||||
else
|
||||
_e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def id(_), do: nil
|
||||
|
||||
def handle(:text, message, _state) do
|
||||
|
|
|
|||
|
|
@ -11,15 +11,47 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
error: "public, must-revalidate, max-age=160"
|
||||
}
|
||||
|
||||
def remote(conn, %{"sig" => sig, "url" => url}) do
|
||||
# Content-types that will not be returned as content-disposition attachments
|
||||
# Override with :media_proxy, :safe_content_types in the configuration
|
||||
@safe_content_types [
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/jpg",
|
||||
"image/png",
|
||||
"image/svg+xml",
|
||||
"audio/mpeg",
|
||||
"audio/mp3",
|
||||
"video/webm",
|
||||
"video/mp4"
|
||||
]
|
||||
|
||||
def remote(conn, params = %{"sig" => sig, "url" => url}) do
|
||||
config = Application.get_env(:pleroma, :media_proxy, [])
|
||||
|
||||
with true <- Keyword.get(config, :enabled, false),
|
||||
{:ok, url} <- Pleroma.Web.MediaProxy.decode_url(sig, url),
|
||||
{:ok, content_type, body} <- proxy_request(url) do
|
||||
filename <- Path.basename(URI.parse(url).path),
|
||||
true <-
|
||||
if(Map.get(params, "filename"),
|
||||
do: filename == Path.basename(conn.request_path),
|
||||
else: true
|
||||
),
|
||||
{:ok, content_type, body} <- proxy_request(url),
|
||||
safe_content_type <-
|
||||
Enum.member?(
|
||||
Keyword.get(config, :safe_content_types, @safe_content_types),
|
||||
content_type
|
||||
) do
|
||||
conn
|
||||
|> put_resp_content_type(content_type)
|
||||
|> set_cache_header(:default)
|
||||
|> put_resp_header(
|
||||
"content-security-policy",
|
||||
"default-src 'none'; style-src 'unsafe-inline'; media-src data:; img-src 'self' data:"
|
||||
)
|
||||
|> put_resp_header("x-xss-protection", "1; mode=block")
|
||||
|> put_resp_header("x-content-type-options", "nosniff")
|
||||
|> put_attachement_header(safe_content_type, filename)
|
||||
|> send_resp(200, body)
|
||||
else
|
||||
false ->
|
||||
|
|
@ -92,6 +124,12 @@ defmodule Pleroma.Web.MediaProxy.MediaProxyController do
|
|||
# TODO: the body is passed here as well because some hosts do not provide a content-type.
|
||||
# At some point we may want to use magic numbers to discover the content-type and reply a proper one.
|
||||
defp proxy_request_content_type(headers, _body) do
|
||||
headers["Content-Type"] || headers["content-type"] || "image/jpeg"
|
||||
headers["Content-Type"] || headers["content-type"] || "application/octet-stream"
|
||||
end
|
||||
|
||||
defp put_attachement_header(conn, true, _), do: conn
|
||||
|
||||
defp put_attachement_header(conn, false, filename) do
|
||||
put_resp_header(conn, "content-disposition", "attachment; filename='#{filename}'")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ defmodule Pleroma.Web.MediaProxy do
|
|||
|
||||
def url(nil), do: nil
|
||||
|
||||
def url(""), do: nil
|
||||
|
||||
def url(url = "/" <> _), do: url
|
||||
|
||||
def url(url) do
|
||||
|
|
@ -15,7 +17,10 @@ defmodule Pleroma.Web.MediaProxy do
|
|||
base64 = Base.url_encode64(url, @base64_opts)
|
||||
sig = :crypto.hmac(:sha, secret, base64)
|
||||
sig64 = sig |> Base.url_encode64(@base64_opts)
|
||||
Keyword.get(config, :base_url, Pleroma.Web.base_url()) <> "/proxy/#{sig64}/#{base64}"
|
||||
filename = if path = URI.parse(url).path, do: "/" <> Path.basename(path), else: ""
|
||||
|
||||
Keyword.get(config, :base_url, Pleroma.Web.base_url()) <>
|
||||
"/proxy/#{sig64}/#{base64}#{filename}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -86,8 +86,8 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
|||
response = %{
|
||||
version: "2.0",
|
||||
software: %{
|
||||
name: "pleroma",
|
||||
version: Keyword.get(instance, :version)
|
||||
name: Pleroma.Application.name(),
|
||||
version: Pleroma.Application.version()
|
||||
},
|
||||
protocols: ["ostatus", "activitypub"],
|
||||
services: %{
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Pleroma.Web.OStatus.OStatusController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.{User, Activity}
|
||||
alias Pleroma.{User, Activity, Object}
|
||||
alias Pleroma.Web.OStatus.{FeedRepresenter, ActivityRepresenter}
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.{OStatus, Federator}
|
||||
|
|
@ -136,7 +136,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
|||
"html" ->
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(200, "priv_sid/static/index.html")
|
||||
|> send_file(200, Application.app_dir(:pleroma, "priv_sid/static/index.html"))
|
||||
|
||||
_ ->
|
||||
represent_activity(conn, format, activity, user)
|
||||
|
|
@ -153,10 +153,21 @@ defmodule Pleroma.Web.OStatus.OStatusController do
|
|||
end
|
||||
end
|
||||
|
||||
defp represent_activity(conn, "activity+json", activity, user) do
|
||||
defp represent_activity(
|
||||
conn,
|
||||
"activity+json",
|
||||
%Activity{data: %{"type" => "Create"}} = activity,
|
||||
user
|
||||
) do
|
||||
object = Object.normalize(activity.data["object"])
|
||||
|
||||
conn
|
||||
|> put_resp_header("content-type", "application/activity+json")
|
||||
|> json(ObjectView.render("object.json", %{object: activity}))
|
||||
|> json(ObjectView.render("object.json", %{object: object}))
|
||||
end
|
||||
|
||||
defp represent_activity(conn, "activity+json", _, _) do
|
||||
{:error, :not_found}
|
||||
end
|
||||
|
||||
defp represent_activity(conn, _, activity, user) do
|
||||
|
|
|
|||
|
|
@ -31,6 +31,21 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
|
||||
end
|
||||
|
||||
pipeline :admin_api do
|
||||
plug(:accepts, ["json"])
|
||||
plug(:fetch_session)
|
||||
plug(Pleroma.Plugs.OAuthPlug)
|
||||
plug(Pleroma.Plugs.BasicAuthDecoderPlug)
|
||||
plug(Pleroma.Plugs.UserFetcherPlug)
|
||||
plug(Pleroma.Plugs.SessionAuthenticationPlug)
|
||||
plug(Pleroma.Plugs.LegacyAuthenticationPlug)
|
||||
plug(Pleroma.Plugs.AuthenticationPlug)
|
||||
plug(Pleroma.Plugs.UserEnabledPlug)
|
||||
plug(Pleroma.Plugs.SetUserSessionIdPlug)
|
||||
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
|
||||
plug(Pleroma.Plugs.UserIsAdminPlug)
|
||||
end
|
||||
|
||||
pipeline :mastodon_html do
|
||||
plug(:accepts, ["html"])
|
||||
plug(:fetch_session)
|
||||
|
|
@ -79,6 +94,23 @@ defmodule Pleroma.Web.Router do
|
|||
get("/emoji", UtilController, :emoji)
|
||||
end
|
||||
|
||||
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
|
||||
pipe_through(:admin_api)
|
||||
delete("/user", AdminAPIController, :user_delete)
|
||||
post("/user", AdminAPIController, :user_create)
|
||||
|
||||
get("/permission_group/:nickname", AdminAPIController, :right_get)
|
||||
get("/permission_group/:nickname/:permission_group", AdminAPIController, :right_get)
|
||||
post("/permission_group/:nickname/:permission_group", AdminAPIController, :right_add)
|
||||
delete("/permission_group/:nickname/:permission_group", AdminAPIController, :right_delete)
|
||||
|
||||
post("/relay", AdminAPIController, :relay_follow)
|
||||
delete("/relay", AdminAPIController, :relay_unfollow)
|
||||
|
||||
get("/invite_token", AdminAPIController, :get_invite_token)
|
||||
get("/password_reset", AdminAPIController, :get_password_reset)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.TwitterAPI do
|
||||
pipe_through(:pleroma_html)
|
||||
get("/ostatus_subscribe", UtilController, :remote_follow)
|
||||
|
|
@ -250,7 +282,12 @@ defmodule Pleroma.Web.Router do
|
|||
get("/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline)
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
scope "/api", Pleroma.Web, as: :twitter_api_search do
|
||||
pipe_through(:api)
|
||||
get("/pleroma/search_user", TwitterAPI.Controller, :search_user)
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
|
||||
pipe_through(:authenticated_api)
|
||||
|
||||
get("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
|
||||
|
|
@ -270,6 +307,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline)
|
||||
get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline)
|
||||
get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline)
|
||||
get("/statuses/dm_timeline", TwitterAPI.Controller, :dm_timeline)
|
||||
get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications)
|
||||
|
||||
# XXX: this is really a pleroma API, but we want to keep the pleroma namespace clean
|
||||
|
|
@ -378,12 +416,12 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
pipeline :remote_media do
|
||||
plug(:accepts, ["html"])
|
||||
end
|
||||
|
||||
scope "/proxy/", Pleroma.Web.MediaProxy do
|
||||
pipe_through(:remote_media)
|
||||
get("/:sig/:url", MediaProxyController, :remote)
|
||||
get("/:sig/:url/:filename", MediaProxyController, :remote)
|
||||
end
|
||||
|
||||
scope "/", Fallback do
|
||||
|
|
@ -398,11 +436,9 @@ defmodule Fallback.RedirectController do
|
|||
use Pleroma.Web, :controller
|
||||
|
||||
def redirector(conn, _params) do
|
||||
if Mix.env() != :test do
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(200, "priv_sid/static/index.html")
|
||||
end
|
||||
conn
|
||||
|> put_resp_content_type("text/html")
|
||||
|> send_file(200, Application.app_dir(:pleroma, "priv_sid/static/index.html"))
|
||||
end
|
||||
|
||||
def registration_page(conn, params) do
|
||||
|
|
|
|||
|
|
@ -169,16 +169,33 @@ defmodule Pleroma.Web.Streamer do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
defp represent_update(%Activity{} = activity) do
|
||||
%{
|
||||
event: "update",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||
"status.json",
|
||||
activity: activity
|
||||
)
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
# Get the current user so we have up-to-date blocks etc.
|
||||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
||||
blocks = user.info["blocks"] || []
|
||||
if socket.assigns[:user] do
|
||||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
||||
blocks = user.info["blocks"] || []
|
||||
|
||||
parent = Object.normalize(item.data["object"])
|
||||
parent = Object.normalize(item.data["object"])
|
||||
|
||||
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
|
||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
|
||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||
end
|
||||
else
|
||||
send(socket.transport_pid, {:text, represent_update(item)})
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
|
@ -186,11 +203,15 @@ defmodule Pleroma.Web.Streamer do
|
|||
def push_to_socket(topics, topic, item) do
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
# Get the current user so we have up-to-date blocks etc.
|
||||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
||||
blocks = user.info["blocks"] || []
|
||||
if socket.assigns[:user] do
|
||||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
||||
blocks = user.info["blocks"] || []
|
||||
|
||||
unless item.actor in blocks do
|
||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||
unless item.actor in blocks do
|
||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||
end
|
||||
else
|
||||
send(socket.transport_pid, {:text, represent_update(item)})
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
end
|
||||
|
||||
def version(conn, _params) do
|
||||
version = Pleroma.Config.get([:instance, :version])
|
||||
version = Pleroma.Application.named_version()
|
||||
|
||||
case get_format(conn) do
|
||||
"xml" ->
|
||||
|
|
|
|||
|
|
@ -126,6 +126,19 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|> render(ActivityView, "index.json", %{activities: activities, for: user})
|
||||
end
|
||||
|
||||
def dm_timeline(%{assigns: %{user: user}} = conn, params) do
|
||||
query =
|
||||
ActivityPub.fetch_activities_query(
|
||||
[user.ap_id],
|
||||
Map.merge(params, %{"type" => "Create", "user" => user, visibility: "direct"})
|
||||
)
|
||||
|
||||
activities = Repo.all(query)
|
||||
|
||||
conn
|
||||
|> render(ActivityView, "index.json", %{activities: activities, for: user})
|
||||
end
|
||||
|
||||
def notifications(%{assigns: %{user: user}} = conn, params) do
|
||||
notifications = Notification.for_user(user, params)
|
||||
|
||||
|
|
@ -516,6 +529,13 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
|> render(ActivityView, "index.json", %{activities: activities, for: user})
|
||||
end
|
||||
|
||||
def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
|
||||
users = User.search(query, true)
|
||||
|
||||
conn
|
||||
|> render(UserView, "index.json", %{users: users, for: user})
|
||||
end
|
||||
|
||||
defp bad_request_reply(conn, error_message) do
|
||||
json = error_json(conn, error_message)
|
||||
json_reply(conn, 400, json)
|
||||
|
|
|
|||
|
|
@ -55,8 +55,12 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
|
|||
"statusnet_blocking" => statusnet_blocking,
|
||||
"friends_count" => user_info[:following_count],
|
||||
"id" => user.id,
|
||||
"name" => user.name,
|
||||
"name_html" => HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
|
||||
"name" => user.name || user.nickname,
|
||||
"name_html" =>
|
||||
if(user.name,
|
||||
do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
|
||||
else: user.nickname
|
||||
),
|
||||
"profile_image_url" => image,
|
||||
"profile_image_url_https" => image,
|
||||
"profile_image_url_profile_size" => image,
|
||||
|
|
|
|||
|
|
@ -252,4 +252,29 @@ defmodule Pleroma.Web.Websub do
|
|||
Pleroma.Web.Federator.enqueue(:request_subscription, sub)
|
||||
end)
|
||||
end
|
||||
|
||||
def publish_one(%{xml: xml, topic: topic, callback: callback, secret: secret}) do
|
||||
signature = sign(secret || "", xml)
|
||||
Logger.info(fn -> "Pushing #{topic} to #{callback}" end)
|
||||
|
||||
with {:ok, %{status_code: code}} <-
|
||||
@httpoison.post(
|
||||
callback,
|
||||
xml,
|
||||
[
|
||||
{"Content-Type", "application/atom+xml"},
|
||||
{"X-Hub-Signature", "sha1=#{signature}"}
|
||||
],
|
||||
timeout: 10000,
|
||||
recv_timeout: 20000,
|
||||
hackney: [pool: :default]
|
||||
) do
|
||||
Logger.info(fn -> "Pushed to #{callback}, code #{code}" end)
|
||||
{:ok, code}
|
||||
else
|
||||
e ->
|
||||
Logger.debug(fn -> "Couldn't push to #{callback}, #{inspect(e)}" end)
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
49
mix.exs
49
mix.exs
|
|
@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
|
|||
def project do
|
||||
[
|
||||
app: :pleroma,
|
||||
version: "0.9.0",
|
||||
version: version("0.9.0"),
|
||||
elixir: "~> 1.4",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
|
||||
|
|
@ -84,4 +84,51 @@ defmodule Pleroma.Mixfile do
|
|||
test: ["ecto.create --quiet", "ecto.migrate", "test"]
|
||||
]
|
||||
end
|
||||
|
||||
# Builds a version string made of:
|
||||
# * the application version
|
||||
# * a pre-release if ahead of the tag: the describe string (-count-commithash)
|
||||
# * build info:
|
||||
# * a build name if `PLEROMA_BUILD_NAME` or `:pleroma, :build_name` is defined
|
||||
# * the mix environment if different than prod
|
||||
defp version(version) do
|
||||
{git_tag, git_pre_release} =
|
||||
with {tag, 0} <- System.cmd("git", ["describe", "--tags", "--abbrev=0"]),
|
||||
tag = String.trim(tag),
|
||||
{describe, 0} <- System.cmd("git", ["describe", "--tags"]),
|
||||
describe = String.trim(describe),
|
||||
ahead <- String.replace(describe, tag, "") do
|
||||
{String.replace_prefix(tag, "v", ""), if(ahead != "", do: String.trim(ahead))}
|
||||
else
|
||||
_ -> {nil, nil}
|
||||
end
|
||||
|
||||
if git_tag && version != git_tag do
|
||||
Mix.shell().error(
|
||||
"Application version #{inspect(version)} does not match git tag #{inspect(git_tag)}"
|
||||
)
|
||||
end
|
||||
|
||||
build_name =
|
||||
cond do
|
||||
name = Application.get_env(:pleroma, :build_name) -> name
|
||||
name = System.get_env("PLEROMA_BUILD_NAME") -> name
|
||||
true -> nil
|
||||
end
|
||||
|
||||
env_name = if Mix.env() != :prod, do: to_string(Mix.env())
|
||||
|
||||
build =
|
||||
[build_name, env_name]
|
||||
|> Enum.filter(fn string -> string && string != "" end)
|
||||
|> Enum.join("-")
|
||||
|> (fn
|
||||
"" -> nil
|
||||
string -> "+" <> string
|
||||
end).()
|
||||
|
||||
[version, git_pre_release, build]
|
||||
|> Enum.filter(fn string -> string && string != "" end)
|
||||
|> Enum.join()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.0808aeafc6252b3050ea95b17dcaff1a.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.0acc0743ff1587a4dde3.js></script><script type=text/javascript src=/static/js/vendor.26ecdac76eb1b8aebbe9.js></script><script type=text/javascript src=/static/js/app.36f6378df4aa596c1419.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.0808aeafc6252b3050ea95b17dcaff1a.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.34667c2817916147413f.js></script><script type=text/javascript src=/static/js/vendor.32c621c7157f34c20923.js></script><script type=text/javascript src=/static/js/app.065638d22ade92dea420.js></script></body></html>
|
||||
|
|
@ -12,5 +12,6 @@
|
|||
"formattingOptionsEnabled": false,
|
||||
"collapseMessageWithSubject": false,
|
||||
"hidePostStats": false,
|
||||
"hideUserStats": false
|
||||
"hideUserStats": false,
|
||||
"loginMethod": "password"
|
||||
}
|
||||
|
|
|
|||
11
priv/static/static/js/app.065638d22ade92dea420.js
Normal file
11
priv/static/static/js/app.065638d22ade92dea420.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/static/js/app.065638d22ade92dea420.js.map
Normal file
1
priv/static/static/js/app.065638d22ade92dea420.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,2 +0,0 @@
|
|||
!function(e){function t(a){if(r[a])return r[a].exports;var n=r[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,i=[];s<c.length;s++)l=c[s],n[l]&&i.push.apply(i,n[l]),n[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(a&&a(c,o);i.length;)i.shift().call(null,t);if(o[0])return r[0]=0,t(0)};var r={},n={0:0};t.e=function(e,a){if(0===n[e])return a.call(null,t);if(void 0!==n[e])n[e].push(a);else{n[e]=[a];var r=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.src=t.p+"static/js/"+e+"."+{1:"26ecdac76eb1b8aebbe9",2:"36f6378df4aa596c1419"}[e]+".js",r.appendChild(c)}},t.m=e,t.c=r,t.p="/"}([]);
|
||||
//# sourceMappingURL=manifest.0acc0743ff1587a4dde3.js.map
|
||||
2
priv/static/static/js/manifest.34667c2817916147413f.js
Normal file
2
priv/static/static/js/manifest.34667c2817916147413f.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var r=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,i=[];s<c.length;s++)l=c[s],a[l]&&i.push.apply(i,a[l]),a[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(r&&r(c,o);i.length;)i.shift().call(null,t);if(o[0])return n[0]=0,t(0)};var n={},a={0:0};t.e=function(e,r){if(0===a[e])return r.call(null,t);if(void 0!==a[e])a[e].push(r);else{a[e]=[r];var n=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.src=t.p+"static/js/"+e+"."+{1:"32c621c7157f34c20923",2:"065638d22ade92dea420"}[e]+".js",n.appendChild(c)}},t.m=e,t.c=n,t.p="/"}([]);
|
||||
//# sourceMappingURL=manifest.34667c2817916147413f.js.map
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
34
priv/static/static/js/vendor.32c621c7157f34c20923.js
Normal file
34
priv/static/static/js/vendor.32c621c7157f34c20923.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/static/js/vendor.32c621c7157f34c20923.js.map
Normal file
1
priv/static/static/js/vendor.32c621c7157f34c20923.js.map
Normal file
File diff suppressed because one or more lines are too long
10
priv/static/static/timeago-ga.json
Normal file
10
priv/static/static/timeago-ga.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
[
|
||||
"Anois",
|
||||
["%s s", "%s s"],
|
||||
["%s n", "%s nóimeád"],
|
||||
["%s u", "%s uair"],
|
||||
["%s l", "%s lá"],
|
||||
["%s se", "%s seachtaine"],
|
||||
["%s m", "%s mí"],
|
||||
["%s b", "%s bliainta"]
|
||||
]
|
||||
|
|
@ -3,8 +3,8 @@
|
|||
["fa %s s", "fa %s s"],
|
||||
["fa %s min", "fa %s min"],
|
||||
["fa %s h", "fa %s h"],
|
||||
["fa %s dia", "fa %s jorns"],
|
||||
["fa %s jorn", "fa %s jorns"],
|
||||
["fa %s setm.", "fa %s setm."],
|
||||
["fa %s mes", "fa %s meses"],
|
||||
["fa %s any", "fa %s ans"]
|
||||
["fa %s an", "fa %s ans"]
|
||||
]
|
||||
|
|
|
|||
17
test/fixtures/httpoison_mock/https___info.pleroma.site_actor.json
vendored
Normal file
17
test/fixtures/httpoison_mock/https___info.pleroma.site_actor.json
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"id": "https://info.pleroma.site/actor.json",
|
||||
"type": "Person",
|
||||
"following": "https://info.pleroma.site/following.json",
|
||||
"followers": "https://info.pleroma.site/followers.json",
|
||||
"inbox": "https://info.pleroma.site/inbox.json",
|
||||
"outbox": "https://info.pleroma.site/outbox.json",
|
||||
"preferredUsername": "admin",
|
||||
"name": null,
|
||||
"summary": "<p></p>",
|
||||
"publicKey": {
|
||||
"id": "https://info.pleroma.site/actor.json#main-key",
|
||||
"owner": "https://info.pleroma.site/actor.json",
|
||||
"publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtc4Tir+3ADhSNF6VKrtW\nOU32T01w7V0yshmQei38YyiVwVvFu8XOP6ACchkdxbJ+C9mZud8qWaRJKVbFTMUG\nNX4+6Q+FobyuKrwN7CEwhDALZtaN2IPbaPd6uG1B7QhWorrY+yFa8f2TBM3BxnUy\nI4T+bMIZIEYG7KtljCBoQXuTQmGtuffO0UwJksidg2ffCF5Q+K//JfQagJ3UzrR+\nZXbKMJdAw4bCVJYs4Z5EhHYBwQWiXCyMGTd7BGlmMkY6Av7ZqHKC/owp3/0EWDNz\nNqF09Wcpr3y3e8nA10X40MJqp/wR+1xtxp+YGbq/Cj5hZGBG7etFOmIpVBrDOhry\nBwIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"actor": "https://mastodon.example.org/users/admin",
|
||||
"actor": "http://mastodon.example.org/users/admin",
|
||||
"attachment": [],
|
||||
"attributedTo": "https://mastodon.example.org/users/admin",
|
||||
"attributedTo": "http://mastodon.example.org/users/admin",
|
||||
"content": "<p>this post was not actually written by Haelwenn</p>",
|
||||
"id": "https://info.pleroma.site/activity.json",
|
||||
"published": "2018-09-01T22:15:00Z",
|
||||
|
|
|
|||
14
test/fixtures/httpoison_mock/https__info.pleroma.site_activity2.json
vendored
Normal file
14
test/fixtures/httpoison_mock/https__info.pleroma.site_activity2.json
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"attributedTo": "https://info.pleroma.site/actor.json",
|
||||
"attachment": [],
|
||||
"actor": "http://mastodon.example.org/users/admin",
|
||||
"content": "<p>this post was not actually written by Haelwenn</p>",
|
||||
"id": "https://info.pleroma.site/activity2.json",
|
||||
"published": "2018-09-01T22:15:00Z",
|
||||
"tag": [],
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Note"
|
||||
}
|
||||
13
test/fixtures/httpoison_mock/https__info.pleroma.site_activity3.json
vendored
Normal file
13
test/fixtures/httpoison_mock/https__info.pleroma.site_activity3.json
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"attributedTo": "http://mastodon.example.org/users/admin",
|
||||
"attachment": [],
|
||||
"content": "<p>this post was not actually written by Haelwenn</p>",
|
||||
"id": "https://info.pleroma.site/activity2.json",
|
||||
"published": "2018-09-01T22:15:00Z",
|
||||
"tag": [],
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Note"
|
||||
}
|
||||
13
test/fixtures/httpoison_mock/https__info.pleroma.site_activity4.json
vendored
Normal file
13
test/fixtures/httpoison_mock/https__info.pleroma.site_activity4.json
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"@context": "https://www.w3.org/ns/activitystreams",
|
||||
"attributedTo": "http://mastodon.example.org/users/admin",
|
||||
"attachment": [],
|
||||
"content": "<p>this post was not actually written by Haelwenn</p>",
|
||||
"id": "http://mastodon.example.org/users/admin/activities/1234",
|
||||
"published": "2018-09-01T22:15:00Z",
|
||||
"tag": [],
|
||||
"to": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"type": "Note"
|
||||
}
|
||||
113
test/media_proxy_test.exs
Normal file
113
test/media_proxy_test.exs
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
defmodule Pleroma.MediaProxyTest do
|
||||
use ExUnit.Case
|
||||
import Pleroma.Web.MediaProxy
|
||||
|
||||
describe "when enabled" do
|
||||
setup do
|
||||
enabled = Pleroma.Config.get([:media_proxy, :enabled])
|
||||
|
||||
unless enabled do
|
||||
Pleroma.Config.put([:media_proxy, :enabled], true)
|
||||
on_exit(fn -> Pleroma.Config.put([:media_proxy, :enabled], enabled) end)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "ignores invalid url" do
|
||||
assert url(nil) == nil
|
||||
assert url("") == nil
|
||||
end
|
||||
|
||||
test "ignores relative url" do
|
||||
assert url("/local") == "/local"
|
||||
assert url("/") == "/"
|
||||
end
|
||||
|
||||
test "ignores local url" do
|
||||
local_url = Pleroma.Web.Endpoint.url() <> "/hello"
|
||||
local_root = Pleroma.Web.Endpoint.url()
|
||||
assert url(local_url) == local_url
|
||||
assert url(local_root) == local_root
|
||||
end
|
||||
|
||||
test "encodes and decodes URL" do
|
||||
url = "https://pleroma.soykaf.com/static/logo.png"
|
||||
encoded = url(url)
|
||||
|
||||
assert String.starts_with?(
|
||||
encoded,
|
||||
Pleroma.Config.get([:media_proxy, :base_url], Pleroma.Web.base_url())
|
||||
)
|
||||
|
||||
assert String.ends_with?(encoded, "/logo.png")
|
||||
|
||||
assert decode_result(encoded) == url
|
||||
end
|
||||
|
||||
test "encodes and decodes URL without a path" do
|
||||
url = "https://pleroma.soykaf.com"
|
||||
encoded = url(url)
|
||||
assert decode_result(encoded) == url
|
||||
end
|
||||
|
||||
test "encodes and decodes URL without an extension" do
|
||||
url = "https://pleroma.soykaf.com/path/"
|
||||
encoded = url(url)
|
||||
assert String.ends_with?(encoded, "/path")
|
||||
assert decode_result(encoded) == url
|
||||
end
|
||||
|
||||
test "encodes and decodes URL and ignores query params for the path" do
|
||||
url = "https://pleroma.soykaf.com/static/logo.png?93939393939&bunny=true"
|
||||
encoded = url(url)
|
||||
assert String.ends_with?(encoded, "/logo.png")
|
||||
assert decode_result(encoded) == url
|
||||
end
|
||||
|
||||
test "validates signature" do
|
||||
secret_key_base = Pleroma.Config.get([Pleroma.Web.Endpoint, :secret_key_base])
|
||||
|
||||
on_exit(fn ->
|
||||
Pleroma.Config.put([Pleroma.Web.Endpoint, :secret_key_base], secret_key_base)
|
||||
end)
|
||||
|
||||
encoded = url("https://pleroma.social")
|
||||
|
||||
Pleroma.Config.put(
|
||||
[Pleroma.Web.Endpoint, :secret_key_base],
|
||||
"00000000000000000000000000000000000000000000000"
|
||||
)
|
||||
|
||||
[_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
|
||||
assert decode_url(sig, base64) == {:error, :invalid_signature}
|
||||
end
|
||||
end
|
||||
|
||||
describe "when disabled" do
|
||||
setup do
|
||||
enabled = Pleroma.Config.get([:media_proxy, :enabled])
|
||||
|
||||
if enabled do
|
||||
Pleroma.Config.put([:media_proxy, :enabled], false)
|
||||
|
||||
on_exit(fn ->
|
||||
Pleroma.Config.put([:media_proxy, :enabled], enabled)
|
||||
:ok
|
||||
end)
|
||||
end
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
test "does not encode remote urls" do
|
||||
assert url("https://google.fr") == "https://google.fr"
|
||||
end
|
||||
end
|
||||
|
||||
defp decode_result(encoded) do
|
||||
[_, "proxy", sig, base64 | _] = URI.parse(encoded).path |> String.split("/")
|
||||
{:ok, decoded} = decode_url(sig, base64)
|
||||
decoded
|
||||
end
|
||||
end
|
||||
79
test/plugs/http_security_plug_test.exs
Normal file
79
test/plugs/http_security_plug_test.exs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
defmodule Pleroma.Web.Plugs.HTTPSecurityPlugTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
alias Pleroma.Config
|
||||
alias Plug.Conn
|
||||
|
||||
test "it sends CSP headers when enabled", %{conn: conn} do
|
||||
Config.put([:http_security, :enabled], true)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
|
||||
refute Conn.get_resp_header(conn, "x-xss-protection") == []
|
||||
refute Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
|
||||
refute Conn.get_resp_header(conn, "x-frame-options") == []
|
||||
refute Conn.get_resp_header(conn, "x-content-type-options") == []
|
||||
refute Conn.get_resp_header(conn, "x-download-options") == []
|
||||
refute Conn.get_resp_header(conn, "referrer-policy") == []
|
||||
refute Conn.get_resp_header(conn, "content-security-policy") == []
|
||||
end
|
||||
|
||||
test "it does not send CSP headers when disabled", %{conn: conn} do
|
||||
Config.put([:http_security, :enabled], false)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
|
||||
assert Conn.get_resp_header(conn, "x-xss-protection") == []
|
||||
assert Conn.get_resp_header(conn, "x-permitted-cross-domain-policies") == []
|
||||
assert Conn.get_resp_header(conn, "x-frame-options") == []
|
||||
assert Conn.get_resp_header(conn, "x-content-type-options") == []
|
||||
assert Conn.get_resp_header(conn, "x-download-options") == []
|
||||
assert Conn.get_resp_header(conn, "referrer-policy") == []
|
||||
assert Conn.get_resp_header(conn, "content-security-policy") == []
|
||||
end
|
||||
|
||||
test "it sends STS headers when enabled", %{conn: conn} do
|
||||
Config.put([:http_security, :enabled], true)
|
||||
Config.put([:http_security, :sts], true)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
|
||||
refute Conn.get_resp_header(conn, "strict-transport-security") == []
|
||||
refute Conn.get_resp_header(conn, "expect-ct") == []
|
||||
end
|
||||
|
||||
test "it does not send STS headers when disabled", %{conn: conn} do
|
||||
Config.put([:http_security, :enabled], true)
|
||||
Config.put([:http_security, :sts], false)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
|
||||
assert Conn.get_resp_header(conn, "strict-transport-security") == []
|
||||
assert Conn.get_resp_header(conn, "expect-ct") == []
|
||||
end
|
||||
|
||||
test "referrer-policy header reflects configured value", %{conn: conn} do
|
||||
Config.put([:http_security, :enabled], true)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> get("/api/v1/instance")
|
||||
|
||||
assert Conn.get_resp_header(conn, "referrer-policy") == ["same-origin"]
|
||||
|
||||
Config.put([:http_security, :referrer_policy], "no-referrer")
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> get("/api/v1/instance")
|
||||
|
||||
assert Conn.get_resp_header(conn, "referrer-policy") == ["no-referrer"]
|
||||
end
|
||||
end
|
||||
39
test/plugs/user_is_admin_plug_test.exs
Normal file
39
test/plugs/user_is_admin_plug_test.exs
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
defmodule Pleroma.Plugs.UserIsAdminPlugTest do
|
||||
use Pleroma.Web.ConnCase, async: true
|
||||
|
||||
alias Pleroma.Plugs.UserIsAdminPlug
|
||||
import Pleroma.Factory
|
||||
|
||||
test "accepts a user that is admin", %{conn: conn} do
|
||||
user = insert(:user, info: %{"is_admin" => true})
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|
||||
ret_conn =
|
||||
conn
|
||||
|> UserIsAdminPlug.call(%{})
|
||||
|
||||
assert conn == ret_conn
|
||||
end
|
||||
|
||||
test "denies a user that isn't admin", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> UserIsAdminPlug.call(%{})
|
||||
|
||||
assert conn.status == 403
|
||||
end
|
||||
|
||||
test "denies when a user isn't set", %{conn: conn} do
|
||||
conn =
|
||||
build_conn()
|
||||
|> UserIsAdminPlug.call(%{})
|
||||
|
||||
assert conn.status == 403
|
||||
end
|
||||
end
|
||||
|
|
@ -40,6 +40,38 @@ defmodule HTTPoisonMock do
|
|||
}}
|
||||
end
|
||||
|
||||
def get("https://info.pleroma.site/activity2.json", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity2.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://info.pleroma.site/activity3.json", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity3.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://info.pleroma.site/activity4.json", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https__info.pleroma.site_activity4.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://info.pleroma.site/actor.json", _, _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/https___info.pleroma.site_actor.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get("https://puckipedia.com/", [Accept: "application/activity+json"], _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
|
|
@ -732,6 +764,14 @@ defmodule HTTPoisonMock do
|
|||
}}
|
||||
end
|
||||
|
||||
def get("https://n1u.moe/users/rye", [Accept: "application/activity+json"], _) do
|
||||
{:ok,
|
||||
%Response{
|
||||
status_code: 200,
|
||||
body: File.read!("test/fixtures/httpoison_mock/rye.json")
|
||||
}}
|
||||
end
|
||||
|
||||
def get(
|
||||
"https://mst3k.interlinked.me/users/luciferMysticus",
|
||||
[Accept: "application/activity+json"],
|
||||
|
|
|
|||
|
|
@ -578,4 +578,16 @@ defmodule Pleroma.UserTest do
|
|||
assert cached_user != user
|
||||
end
|
||||
end
|
||||
|
||||
describe "User.search" do
|
||||
test "finds a user, ranking by similarity" do
|
||||
user = insert(:user, %{name: "lain"})
|
||||
user_two = insert(:user, %{name: "ean"})
|
||||
user_three = insert(:user, %{name: "ebn", nickname: "lain@mastodon.social"})
|
||||
user_four = insert(:user, %{nickname: "lain@pleroma.soykaf.com"})
|
||||
|
||||
assert user_four ==
|
||||
User.search("lain@ple") |> List.first() |> Map.put(:search_distance, nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -361,6 +361,26 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
refute Repo.get(Activity, activity.id)
|
||||
end
|
||||
|
||||
test "it fails for incoming deletes with spoofed origin" do
|
||||
activity = insert(:note_activity)
|
||||
|
||||
data =
|
||||
File.read!("test/fixtures/mastodon-delete.json")
|
||||
|> Poison.decode!()
|
||||
|
||||
object =
|
||||
data["object"]
|
||||
|> Map.put("id", activity.data["object"]["id"])
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|
||||
:error = Transmogrifier.handle_incoming(data)
|
||||
|
||||
assert Repo.get(Activity, activity.id)
|
||||
end
|
||||
|
||||
test "it works for incoming unannounces with an existing notice" do
|
||||
user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||
|
|
@ -872,12 +892,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
end
|
||||
|
||||
test "it rejects activities which reference objects with bogus origins" do
|
||||
user = insert(:user, %{local: false})
|
||||
|
||||
data = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => user.ap_id <> "/activities/1234",
|
||||
"actor" => user.ap_id,
|
||||
"id" => "http://mastodon.example.org/users/admin/activities/1234",
|
||||
"actor" => "http://mastodon.example.org/users/admin",
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"object" => "https://info.pleroma.site/activity.json",
|
||||
"type" => "Announce"
|
||||
|
|
@ -885,5 +903,96 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
|
|||
|
||||
:error = Transmogrifier.handle_incoming(data)
|
||||
end
|
||||
|
||||
test "it rejects objects when attributedTo is wrong (variant 1)" do
|
||||
{:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity2.json")
|
||||
end
|
||||
|
||||
test "it rejects activities which reference objects that have an incorrect attribution (variant 1)" do
|
||||
data = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "http://mastodon.example.org/users/admin/activities/1234",
|
||||
"actor" => "http://mastodon.example.org/users/admin",
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"object" => "https://info.pleroma.site/activity2.json",
|
||||
"type" => "Announce"
|
||||
}
|
||||
|
||||
:error = Transmogrifier.handle_incoming(data)
|
||||
end
|
||||
|
||||
test "it rejects objects when attributedTo is wrong (variant 2)" do
|
||||
{:error, _} = ActivityPub.fetch_object_from_id("https://info.pleroma.site/activity3.json")
|
||||
end
|
||||
|
||||
test "it rejects activities which reference objects that have an incorrect attribution (variant 2)" do
|
||||
data = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"id" => "http://mastodon.example.org/users/admin/activities/1234",
|
||||
"actor" => "http://mastodon.example.org/users/admin",
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
|
||||
"object" => "https://info.pleroma.site/activity3.json",
|
||||
"type" => "Announce"
|
||||
}
|
||||
|
||||
:error = Transmogrifier.handle_incoming(data)
|
||||
end
|
||||
end
|
||||
|
||||
describe "general origin containment" do
|
||||
test "contain_origin_from_id() catches obvious spoofing attempts" do
|
||||
data = %{
|
||||
"id" => "http://example.com/~alyssa/activities/1234.json"
|
||||
}
|
||||
|
||||
:error =
|
||||
Transmogrifier.contain_origin_from_id(
|
||||
"http://example.org/~alyssa/activities/1234.json",
|
||||
data
|
||||
)
|
||||
end
|
||||
|
||||
test "contain_origin_from_id() allows alternate IDs within the same origin domain" do
|
||||
data = %{
|
||||
"id" => "http://example.com/~alyssa/activities/1234.json"
|
||||
}
|
||||
|
||||
:ok =
|
||||
Transmogrifier.contain_origin_from_id(
|
||||
"http://example.com/~alyssa/activities/1234",
|
||||
data
|
||||
)
|
||||
end
|
||||
|
||||
test "contain_origin_from_id() allows matching IDs" do
|
||||
data = %{
|
||||
"id" => "http://example.com/~alyssa/activities/1234.json"
|
||||
}
|
||||
|
||||
:ok =
|
||||
Transmogrifier.contain_origin_from_id(
|
||||
"http://example.com/~alyssa/activities/1234.json",
|
||||
data
|
||||
)
|
||||
end
|
||||
|
||||
test "users cannot be collided through fake direction spoofing attempts" do
|
||||
user =
|
||||
insert(:user, %{
|
||||
nickname: "rye@niu.moe",
|
||||
local: false,
|
||||
ap_id: "https://niu.moe/users/rye",
|
||||
follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
|
||||
})
|
||||
|
||||
{:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
|
||||
end
|
||||
|
||||
test "all objects with fake directions are rejected by the object fetcher" do
|
||||
{:error, _} =
|
||||
ActivityPub.fetch_and_contain_remote_object_from_id(
|
||||
"https://info.pleroma.site/activity4.json"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
|
|||
use Pleroma.DataCase
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.ActivityPub.ObjectView
|
||||
|
||||
test "renders a note object" do
|
||||
|
|
@ -15,4 +16,43 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do
|
|||
assert result["type"] == "Note"
|
||||
assert result["@context"]
|
||||
end
|
||||
|
||||
test "renders a note activity" do
|
||||
note = insert(:note_activity)
|
||||
|
||||
result = ObjectView.render("object.json", %{object: note})
|
||||
|
||||
assert result["id"] == note.data["id"]
|
||||
assert result["to"] == note.data["to"]
|
||||
assert result["object"]["type"] == "Note"
|
||||
assert result["object"]["content"] == note.data["object"]["content"]
|
||||
assert result["type"] == "Create"
|
||||
assert result["@context"]
|
||||
end
|
||||
|
||||
test "renders a like activity" do
|
||||
note = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, like_activity, _} = CommonAPI.favorite(note.id, user)
|
||||
|
||||
result = ObjectView.render("object.json", %{object: like_activity})
|
||||
|
||||
assert result["id"] == like_activity.data["id"]
|
||||
assert result["object"] == note.data["object"]["id"]
|
||||
assert result["type"] == "Like"
|
||||
end
|
||||
|
||||
test "renders an announce activity" do
|
||||
note = insert(:note_activity)
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, announce_activity, _} = CommonAPI.repeat(note.id, user)
|
||||
|
||||
result = ObjectView.render("object.json", %{object: announce_activity})
|
||||
|
||||
assert result["id"] == announce_activity.data["id"]
|
||||
assert result["object"] == note.data["object"]["id"]
|
||||
assert result["type"] == "Announce"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
112
test/web/admin_api/admin_api_controller_test.exs
Normal file
112
test/web/admin_api/admin_api_controller_test.exs
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
||||
use Pleroma.Web.ConnCase
|
||||
|
||||
alias Pleroma.{Repo, User}
|
||||
|
||||
import Pleroma.Factory
|
||||
import ExUnit.CaptureLog
|
||||
|
||||
describe "/api/pleroma/admin/user" do
|
||||
test "Delete" do
|
||||
admin = insert(:user, info: %{"is_admin" => true})
|
||||
user = insert(:user)
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> delete("/api/pleroma/admin/user?nickname=#{user.nickname}")
|
||||
|
||||
assert json_response(conn, 200) == user.nickname
|
||||
end
|
||||
|
||||
test "Create" do
|
||||
admin = insert(:user, info: %{"is_admin" => true})
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> post("/api/pleroma/admin/user", %{
|
||||
"nickname" => "lain",
|
||||
"email" => "lain@example.org",
|
||||
"password" => "test"
|
||||
})
|
||||
|
||||
assert json_response(conn, 200) == "lain"
|
||||
end
|
||||
end
|
||||
|
||||
describe "/api/pleroma/admin/permission_group" do
|
||||
test "GET is giving user_info" do
|
||||
admin = insert(:user, info: %{"is_admin" => true})
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> get("/api/pleroma/admin/permission_group/#{admin.nickname}")
|
||||
|
||||
assert json_response(conn, 200) == admin.info
|
||||
end
|
||||
|
||||
test "/:right POST, can add to a permission group" do
|
||||
admin = insert(:user, info: %{"is_admin" => true})
|
||||
user = insert(:user)
|
||||
|
||||
user_info =
|
||||
user.info
|
||||
|> Map.put("is_admin", true)
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> post("/api/pleroma/admin/permission_group/#{user.nickname}/admin")
|
||||
|
||||
assert json_response(conn, 200) == user_info
|
||||
end
|
||||
|
||||
test "/:right DELETE, can remove from a permission group" do
|
||||
admin = insert(:user, info: %{"is_admin" => true})
|
||||
user = insert(:user, info: %{"is_admin" => true})
|
||||
|
||||
user_info =
|
||||
user.info
|
||||
|> Map.put("is_admin", false)
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> delete("/api/pleroma/admin/permission_group/#{user.nickname}/admin")
|
||||
|
||||
assert json_response(conn, 200) == user_info
|
||||
end
|
||||
end
|
||||
|
||||
test "/api/pleroma/admin/invite_token" do
|
||||
admin = insert(:user, info: %{"is_admin" => true})
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> get("/api/pleroma/admin/invite_token")
|
||||
|
||||
assert conn.status == 200
|
||||
end
|
||||
|
||||
test "/api/pleroma/admin/password_reset" do
|
||||
admin = insert(:user, info: %{"is_admin" => true})
|
||||
user = insert(:user, info: %{"is_admin" => true})
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, admin)
|
||||
|> put_req_header("accept", "application/json")
|
||||
|> get("/api/pleroma/admin/password_reset?nickname=#{user.nickname}")
|
||||
|
||||
assert conn.status == 200
|
||||
end
|
||||
end
|
||||
|
|
@ -61,4 +61,42 @@ defmodule Pleroma.Web.FederatorTest do
|
|||
Pleroma.Config.put([:instance, :allow_relay], true)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Receive an activity" do
|
||||
test "successfully processes incoming AP docs with correct origin" do
|
||||
params = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"actor" => "http://mastodon.example.org/users/admin",
|
||||
"type" => "Create",
|
||||
"id" => "http://mastodon.example.org/users/admin/activities/1",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "hi world!",
|
||||
"id" => "http://mastodon.example.org/users/admin/objects/1",
|
||||
"attributedTo" => "http://mastodon.example.org/users/admin"
|
||||
},
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
}
|
||||
|
||||
{:ok, _activity} = Federator.handle(:incoming_ap_doc, params)
|
||||
end
|
||||
|
||||
test "rejects incoming AP docs with incorrect origin" do
|
||||
params = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"actor" => "https://niu.moe/users/rye",
|
||||
"type" => "Create",
|
||||
"id" => "http://mastodon.example.org/users/admin/activities/1",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "hi world!",
|
||||
"id" => "http://mastodon.example.org/users/admin/objects/1",
|
||||
"attributedTo" => "http://mastodon.example.org/users/admin"
|
||||
},
|
||||
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
|
||||
}
|
||||
|
||||
:error = Federator.handle(:incoming_ap_doc, params)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -178,6 +178,32 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
|||
|> get("api/v1/timelines/home")
|
||||
|
||||
[_s1, _s2] = json_response(res_conn, 200)
|
||||
|
||||
# Test pagination
|
||||
Enum.each(1..20, fn _ ->
|
||||
{:ok, _} =
|
||||
CommonAPI.post(user_one, %{
|
||||
"status" => "Hi @#{user_two.nickname}!",
|
||||
"visibility" => "direct"
|
||||
})
|
||||
end)
|
||||
|
||||
res_conn =
|
||||
conn
|
||||
|> assign(:user, user_two)
|
||||
|> get("api/v1/timelines/direct")
|
||||
|
||||
statuses = json_response(res_conn, 200)
|
||||
assert length(statuses) == 20
|
||||
|
||||
res_conn =
|
||||
conn
|
||||
|> assign(:user, user_two)
|
||||
|> get("api/v1/timelines/direct", %{max_id: List.last(statuses)["id"]})
|
||||
|
||||
[status] = json_response(res_conn, 200)
|
||||
|
||||
assert status["url"] != direct.data["id"]
|
||||
end
|
||||
|
||||
test "replying to a status", %{conn: conn} do
|
||||
|
|
|
|||
33
test/web/mastodon_api/mastodon_socket_test.exs
Normal file
33
test/web/mastodon_api/mastodon_socket_test.exs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
defmodule Pleroma.Web.MastodonApi.MastodonSocketTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.MastodonApi.MastodonSocket
|
||||
alias Pleroma.Web.{Streamer, CommonAPI}
|
||||
alias Pleroma.User
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
test "public is working when non-authenticated" do
|
||||
user = insert(:user)
|
||||
|
||||
task =
|
||||
Task.async(fn ->
|
||||
assert_receive {:text, _}, 4_000
|
||||
end)
|
||||
|
||||
fake_socket = %{
|
||||
transport_pid: task.pid,
|
||||
assigns: %{}
|
||||
}
|
||||
|
||||
topics = %{
|
||||
"public" => [fake_socket]
|
||||
}
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "Test"})
|
||||
|
||||
Streamer.push_to_socket(topics, "public", activity)
|
||||
|
||||
Task.await(task)
|
||||
end
|
||||
end
|
||||
|
|
@ -2,6 +2,7 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|
|||
use Pleroma.Web.ConnCase
|
||||
import Pleroma.Factory
|
||||
alias Pleroma.{User, Repo}
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.OStatus.ActivityRepresenter
|
||||
|
||||
test "decodes a salmon", %{conn: conn} do
|
||||
|
|
@ -167,6 +168,32 @@ defmodule Pleroma.Web.OStatus.OStatusControllerTest do
|
|||
assert json_response(conn, 200)
|
||||
end
|
||||
|
||||
test "only gets a notice in AS2 format for Create messages", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
url = "/notice/#{note_activity.id}"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(url)
|
||||
|
||||
assert json_response(conn, 200)
|
||||
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
|
||||
url = "/notice/#{like_activity.id}"
|
||||
|
||||
assert like_activity.data["type"] == "Like"
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> put_req_header("accept", "application/activity+json")
|
||||
|> get(url)
|
||||
|
||||
assert response(conn, 404)
|
||||
end
|
||||
|
||||
test "gets an activity in AS2 format", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
[_, uuid] = hd(Regex.scan(~r/.+\/([\w-]+)$/, note_activity.data["id"]))
|
||||
|
|
|
|||
31
test/web/retry_queue_test.exs
Normal file
31
test/web/retry_queue_test.exs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
defmodule MockActivityPub do
|
||||
def publish_one(ret) do
|
||||
{ret, "success"}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Pleroma.ActivityTest do
|
||||
use Pleroma.DataCase
|
||||
alias Pleroma.Web.Federator.RetryQueue
|
||||
|
||||
@small_retry_count 0
|
||||
@hopeless_retry_count 10
|
||||
|
||||
test "failed posts are retried" do
|
||||
{:retry, _timeout} = RetryQueue.get_retry_params(@small_retry_count)
|
||||
|
||||
assert {:noreply, %{delivered: 1}} ==
|
||||
RetryQueue.handle_info({:send, :ok, MockActivityPub, @small_retry_count}, %{
|
||||
delivered: 0
|
||||
})
|
||||
end
|
||||
|
||||
test "posts that have been tried too many times are dropped" do
|
||||
{:drop, _timeout} = RetryQueue.get_retry_params(@hopeless_retry_count)
|
||||
|
||||
assert {:noreply, %{dropped: 1}} ==
|
||||
RetryQueue.handle_cast({:maybe_enqueue, %{}, nil, @hopeless_retry_count}, %{
|
||||
dropped: 0
|
||||
})
|
||||
end
|
||||
end
|
||||
|
|
@ -271,6 +271,43 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "GET /statuses/dm_timeline.json" do
|
||||
test "it show direct messages", %{conn: conn} do
|
||||
user_one = insert(:user)
|
||||
user_two = insert(:user)
|
||||
|
||||
{:ok, user_two} = User.follow(user_two, user_one)
|
||||
|
||||
{:ok, direct} =
|
||||
CommonAPI.post(user_one, %{
|
||||
"status" => "Hi @#{user_two.nickname}!",
|
||||
"visibility" => "direct"
|
||||
})
|
||||
|
||||
{:ok, direct_two} =
|
||||
CommonAPI.post(user_two, %{
|
||||
"status" => "Hi @#{user_one.nickname}!",
|
||||
"visibility" => "direct"
|
||||
})
|
||||
|
||||
{:ok, _follower_only} =
|
||||
CommonAPI.post(user_one, %{
|
||||
"status" => "Hi @#{user_two.nickname}!",
|
||||
"visibility" => "private"
|
||||
})
|
||||
|
||||
# Only direct should be visible here
|
||||
res_conn =
|
||||
conn
|
||||
|> assign(:user, user_two)
|
||||
|> get("/api/statuses/dm_timeline.json")
|
||||
|
||||
[status, status_two] = json_response(res_conn, 200)
|
||||
assert status["id"] == direct_two.id
|
||||
assert status_two["id"] == direct.id
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /statuses/mentions.json" do
|
||||
setup [:valid_user]
|
||||
|
||||
|
|
@ -1181,4 +1218,20 @@ defmodule Pleroma.Web.TwitterAPI.ControllerTest do
|
|||
assert relationship["follows_you"] == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/pleroma/search_user" do
|
||||
test "it returns users, ordered by similarity", %{conn: conn} do
|
||||
user = insert(:user, %{name: "eal"})
|
||||
user_two = insert(:user, %{name: "ean"})
|
||||
user_three = insert(:user, %{name: "ebn"})
|
||||
|
||||
resp =
|
||||
conn
|
||||
|> get(twitter_api_search__path(conn, :search_user), query: "eal")
|
||||
|> json_response(200)
|
||||
|
||||
assert length(resp) == 3
|
||||
assert [user.id, user_two.id, user_three.id] == Enum.map(resp, fn %{"id" => id} -> id end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ defmodule Pleroma.Web.TwitterAPI.UserViewTest do
|
|||
[user: user]
|
||||
end
|
||||
|
||||
test "A user with only a nickname", %{user: user} do
|
||||
user = %{user | name: nil, nickname: "scarlett@catgirl.science"}
|
||||
represented = UserView.render("show.json", %{user: user})
|
||||
assert represented["name"] == user.nickname
|
||||
assert represented["name_html"] == user.nickname
|
||||
end
|
||||
|
||||
test "A user with an avatar object", %{user: user} do
|
||||
image = "image"
|
||||
user = %{user | avatar: %{"url" => [%{"href" => image}]}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue