Merge branch 'develop' of https://git.pleroma.social/pleroma/pleroma into develop

This commit is contained in:
sadposter 2019-05-30 12:12:08 +01:00
commit df500529e2
65 changed files with 1020 additions and 444 deletions

View file

@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [unreleased] ## [unreleased]
### Added ### Added
- Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
- [MongooseIM](https://github.com/esl/MongooseIM) http authentication support.
- LDAP authentication - LDAP authentication
- External OAuth provider authentication - External OAuth provider authentication
- A [job queue](https://git.pleroma.social/pleroma/pleroma_job_queue) for federation, emails, web push, etc. - A [job queue](https://git.pleroma.social/pleroma/pleroma_job_queue) for federation, emails, web push, etc.
@ -39,6 +41,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Metadata: RelMe provider - Metadata: RelMe provider
- OAuth: added support for refresh tokens - OAuth: added support for refresh tokens
- Emoji packs and emoji pack manager - Emoji packs and emoji pack manager
- Object pruning (`mix pleroma.database prune_objects`)
- OAuth: added job to clean expired access tokens
- MRF: Support for rejecting reports from specific instances (`mrf_simple`)
- MRF: Support for stripping avatars and banner images from specific instances (`mrf_simple`)
### Changed ### Changed
- **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer - **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer
@ -73,6 +79,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Don't ship finmoji by default, they can be installed as an emoji pack - Don't ship finmoji by default, they can be installed as an emoji pack
- Hide deactivated users and their statuses - Hide deactivated users and their statuses
- Posts which are marked sensitive or tagged nsfw no longer have link previews. - Posts which are marked sensitive or tagged nsfw no longer have link previews.
- HTTP connection timeout is now set to 10 seconds.
- Respond with a 404 Not implemented JSON error message when requested API is not implemented
### Fixed ### Fixed
- Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended. - Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
@ -105,6 +113,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Mastodon API: Exposing default scope of the user to anyone - Mastodon API: Exposing default scope of the user to anyone
- Mastodon API: Make `irreversible` field default to `false` [`POST /api/v1/filters`] - Mastodon API: Make `irreversible` field default to `false` [`POST /api/v1/filters`]
- User-Agent is now sent correctly for all HTTP requests. - User-Agent is now sent correctly for all HTTP requests.
- MRF: Simple policy now properly delists imported or relayed statuses
## Removed ## Removed
- Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations` - Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations`

View file

@ -184,9 +184,6 @@ config :mime, :types, %{
"application/ld+json" => ["activity+json"] "application/ld+json" => ["activity+json"]
} }
config :pleroma, :websub, Pleroma.Web.Websub
config :pleroma, :ostatus, Pleroma.Web.OStatus
config :pleroma, :httpoison, Pleroma.HTTP
config :tesla, adapter: Tesla.Adapter.Hackney config :tesla, adapter: Tesla.Adapter.Hackney
# Configures http settings, upstream proxy etc. # Configures http settings, upstream proxy etc.
@ -239,7 +236,8 @@ config :pleroma, :instance,
welcome_message: nil, welcome_message: nil,
max_report_comment_size: 1000, max_report_comment_size: 1000,
safe_dm_mentions: false, safe_dm_mentions: false,
healthcheck: false healthcheck: false,
remote_post_retention_days: 90
config :pleroma, :app_account_creation, enabled: true, max_requests: 25, interval: 1800 config :pleroma, :app_account_creation, enabled: true, max_requests: 25, interval: 1800
@ -313,7 +311,9 @@ config :pleroma, :mrf_simple,
federated_timeline_removal: [], federated_timeline_removal: [],
report_removal: [], report_removal: [],
reject: [], reject: [],
accept: [] accept: [],
avatar_removal: [],
banner_removal: []
config :pleroma, :mrf_keyword, config :pleroma, :mrf_keyword,
reject: [], reject: [],
@ -384,6 +384,7 @@ config :pleroma, Pleroma.User,
"activities", "activities",
"api", "api",
"auth", "auth",
"check_password",
"dev", "dev",
"friend-requests", "friend-requests",
"inbox", "inbox",
@ -404,6 +405,7 @@ config :pleroma, Pleroma.User,
"status", "status",
"tag", "tag",
"user-search", "user-search",
"user_exists",
"users", "users",
"web" "web"
] ]
@ -478,7 +480,9 @@ config :pleroma, Pleroma.ScheduledActivity,
config :pleroma, :oauth2, config :pleroma, :oauth2,
token_expires_in: 600, token_expires_in: 600,
issue_new_refresh_token: true issue_new_refresh_token: true,
clean_expired_tokens: false,
clean_expired_tokens_interval: 86_400_000
config :pleroma, :database, rum_enabled: false config :pleroma, :database, rum_enabled: false

View file

@ -39,8 +39,6 @@ config :pleroma, Pleroma.Repo,
# Reduce hash rounds for testing # Reduce hash rounds for testing
config :pbkdf2_elixir, rounds: 1 config :pbkdf2_elixir, rounds: 1
config :pleroma, :websub, Pleroma.Web.WebsubMock
config :pleroma, :ostatus, Pleroma.Web.OStatusMock
config :tesla, adapter: Tesla.Mock config :tesla, adapter: Tesla.Mock
config :pleroma, :rich_media, enabled: false config :pleroma, :rich_media, enabled: false

View file

@ -104,6 +104,7 @@ config :pleroma, Pleroma.Emails.Mailer,
* `max_report_comment_size`: The maximum size of the report comment (Default: `1000`) * `max_report_comment_size`: The maximum size of the report comment (Default: `1000`)
* `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`) * `safe_dm_mentions`: If set to true, only mentions at the beginning of a post will be used to address people in direct messages. This is to prevent accidental mentioning of people when talking about them (e.g. "@friend hey i really don't like @enemy"). (Default: `false`)
* `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``. * `healthcheck`: if set to true, system data will be shown on ``/api/pleroma/healthcheck``.
* `remote_post_retention_days`: the default amount of days to retain remote posts when pruning the database
## :app_account_creation ## :app_account_creation
REST API for creating an account settings REST API for creating an account settings
@ -219,6 +220,9 @@ relates to mascots on the mastodon frontend
* `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline * `federated_timeline_removal`: List of instances to remove from Federated (aka The Whole Known Network) Timeline
* `reject`: List of instances to reject any activities from * `reject`: List of instances to reject any activities from
* `accept`: List of instances to accept any activities from * `accept`: List of instances to accept any activities from
* `report_removal`: List of instances to reject reports from
* `avatar_removal`: List of instances to strip avatars from
* `banner_removal`: List of instances to strip banners from
## :mrf_rejectnonpublic ## :mrf_rejectnonpublic
* `allow_followersonly`: whether to allow followers-only posts * `allow_followersonly`: whether to allow followers-only posts
@ -477,7 +481,7 @@ config :esshd,
password_authenticator: "Pleroma.BBS.Authenticator" password_authenticator: "Pleroma.BBS.Authenticator"
``` ```
Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT` Feel free to adjust the priv_dir and port number. Then you will have to create the key for the keys (in the example `priv/ssh_keys`) and create the host keys with `ssh-keygen -m PEM -N "" -b 2048 -t rsa -f ssh_host_rsa_key`. After restarting, you should be able to connect to your Pleroma instance with `ssh username@server -p $PORT`
## :auth ## :auth
@ -549,6 +553,8 @@ Configure OAuth 2 provider capabilities:
* `token_expires_in` - The lifetime in seconds of the access token. * `token_expires_in` - The lifetime in seconds of the access token.
* `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token. * `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token.
* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`.
* `clean_expired_tokens_interval` - Interval to run the job to clean expired tokens. Defaults to `86_400_000` (24 hours).
## :emoji ## :emoji
* `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]` * `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]`

View file

@ -0,0 +1,10 @@
# Configuring MongooseIM (XMPP Server) to use Pleroma for authentication
If you want to give your Pleroma users an XMPP (chat) account, you can configure [MongooseIM](https://github.com/esl/MongooseIM) to use your Pleroma server for user authentication, automatically giving every local user an XMPP account.
In general, you just have to follow the configuration described at [https://mongooseim.readthedocs.io/en/latest/authentication-backends/HTTP-authentication-module/](https://mongooseim.readthedocs.io/en/latest/authentication-backends/HTTP-authentication-module/) and do these changes to your mongooseim.cfg.
1. Set the auth_method to `{auth_method, http}`.
2. Add the http auth pool like this: `{http, global, auth, [{workers, 50}], [{server, "https://yourpleromainstance.com"}]}`
Restart your MongooseIM server, your users should now be able to connect with their Pleroma credentials.

View file

@ -10,7 +10,9 @@ example.tld {
gzip gzip
proxy / localhost:4000 { # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
# and `localhost.` resolves to [::0] on some systems: see issue #930
proxy / 127.0.0.1:4000 {
websocket websocket
transparent transparent
} }

View file

@ -58,8 +58,10 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined
RewriteRule /(.*) ws://localhost:4000/$1 [P,L] RewriteRule /(.*) ws://localhost:4000/$1 [P,L]
ProxyRequests off ProxyRequests off
ProxyPass / http://localhost:4000/ # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
ProxyPassReverse / http://localhost:4000/ # and `localhost.` resolves to [::0] on some systems: see issue #930
ProxyPass / http://127.0.0.1:4000/
ProxyPassReverse / http://127.0.0.1:4000/
RequestHeader set Host ${servername} RequestHeader set Host ${servername}
ProxyPreserveHost On ProxyPreserveHost On

View file

@ -69,7 +69,9 @@ server {
proxy_set_header Connection "upgrade"; proxy_set_header Connection "upgrade";
proxy_set_header Host $http_host; proxy_set_header Host $http_host;
proxy_pass http://localhost:4000; # this is explicitly IPv4 since Pleroma.Web.Endpoint binds on IPv4 only
# and `localhost.` resolves to [::0] on some systems: see issue #930
proxy_pass http://127.0.0.1:4000;
client_max_body_size 16m; client_max_body_size 16m;
} }

View file

@ -1,4 +1,4 @@
vcl 4.0; vcl 4.1;
import std; import std;
backend default { backend default {
@ -35,24 +35,6 @@ sub vcl_recv {
} }
return(purge); return(purge);
} }
# Pleroma MediaProxy - strip headers that will affect caching
if (req.url ~ "^/proxy/") {
unset req.http.Cookie;
unset req.http.Authorization;
unset req.http.Accept;
return (hash);
}
# Strip headers that will affect caching from all other static content
# This also permits caching of individual toots and AP Activities
if ((req.url ~ "^/(media|static)/") ||
(req.url ~ "(?i)\.(html|js|css|jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|mp4|ogg|webm|svg|swf|ttf|pdf|woff|woff2)$"))
{
unset req.http.Cookie;
unset req.http.Authorization;
return (hash);
}
} }
sub vcl_backend_response { sub vcl_backend_response {
@ -61,6 +43,12 @@ sub vcl_backend_response {
set beresp.do_gzip = true; set beresp.do_gzip = true;
} }
# Retry broken backend responses.
if (beresp.status == 503) {
set bereq.http.X-Varnish-Backend-503 = "1";
return (retry);
}
# CHUNKED SUPPORT # CHUNKED SUPPORT
if (bereq.http.x-range ~ "bytes=" && beresp.status == 206) { if (bereq.http.x-range ~ "bytes=" && beresp.status == 206) {
set beresp.ttl = 10m; set beresp.ttl = 10m;
@ -73,8 +61,6 @@ sub vcl_backend_response {
return (deliver); return (deliver);
} }
# Default object caching of 86400s;
set beresp.ttl = 86400s;
# Allow serving cached content for 6h in case backend goes down # Allow serving cached content for 6h in case backend goes down
set beresp.grace = 6h; set beresp.grace = 6h;
@ -90,20 +76,6 @@ sub vcl_backend_response {
set beresp.ttl = 30s; set beresp.ttl = 30s;
return (deliver); return (deliver);
} }
# Pleroma MediaProxy internally sets headers properly
if (bereq.url ~ "^/proxy/") {
return (deliver);
}
# Strip cache-restricting headers from Pleroma on static content that we want to cache
if (bereq.url ~ "(?i)\.(js|css|jpg|jpeg|png|gif|gz|tgz|bz2|tbz|mp3|mp4|ogg|webm|svg|swf|ttf|pdf|woff|woff2)$")
{
unset beresp.http.set-cookie;
unset beresp.http.Cache-Control;
unset beresp.http.x-request-id;
set beresp.http.Cache-Control = "public, max-age=86400";
}
} }
# The synthetic response for 301 redirects # The synthetic response for 301 redirects
@ -132,10 +104,32 @@ sub vcl_hash {
} }
sub vcl_backend_fetch { sub vcl_backend_fetch {
# Be more lenient for slow servers on the fediverse
if bereq.url ~ "^/proxy/" {
set bereq.first_byte_timeout = 300s;
}
# CHUNKED SUPPORT # CHUNKED SUPPORT
if (bereq.http.x-range) { if (bereq.http.x-range) {
set bereq.http.Range = bereq.http.x-range; set bereq.http.Range = bereq.http.x-range;
} }
if (bereq.retries == 0) {
# Clean up the X-Varnish-Backend-503 flag that is used internally
# to mark broken backend responses that should be retried.
unset bereq.http.X-Varnish-Backend-503;
} else {
if (bereq.http.X-Varnish-Backend-503) {
if (bereq.method != "POST" &&
std.healthy(bereq.backend) &&
bereq.retries <= 4) {
# Flush broken backend response flag & try again.
unset bereq.http.X-Varnish-Backend-503;
} else {
return (abandon);
}
}
}
} }
sub vcl_deliver { sub vcl_deliver {
@ -145,3 +139,9 @@ sub vcl_deliver {
unset resp.http.CR; unset resp.http.CR;
} }
} }
sub vcl_backend_error {
# Retry broken backend responses.
set bereq.http.X-Varnish-Backend-503 = "1";
return (retry);
}

View file

@ -5,6 +5,7 @@
defmodule Mix.Tasks.Pleroma.Database do defmodule Mix.Tasks.Pleroma.Database do
alias Mix.Tasks.Pleroma.Common alias Mix.Tasks.Pleroma.Common
alias Pleroma.Conversation alias Pleroma.Conversation
alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
require Logger require Logger
@ -23,6 +24,10 @@ defmodule Mix.Tasks.Pleroma.Database do
Options: Options:
- `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references - `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
## Prune old objects from the database
mix pleroma.database prune_objects
## Create a conversation for all existing DMs. Can be safely re-run. ## Create a conversation for all existing DMs. Can be safely re-run.
mix pleroma.database bump_all_conversations mix pleroma.database bump_all_conversations
@ -72,4 +77,46 @@ defmodule Mix.Tasks.Pleroma.Database do
Enum.each(users, &User.remove_duplicated_following/1) Enum.each(users, &User.remove_duplicated_following/1)
Enum.each(users, &User.update_follower_count/1) Enum.each(users, &User.update_follower_count/1)
end end
def run(["prune_objects" | args]) do
import Ecto.Query
{options, [], []} =
OptionParser.parse(
args,
strict: [
vacuum: :boolean
]
)
Common.start_pleroma()
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days])
Logger.info("Pruning objects older than #{deadline} days")
time_deadline =
NaiveDateTime.utc_now()
|> NaiveDateTime.add(-(deadline * 86_400))
public = "https://www.w3.org/ns/activitystreams#Public"
from(o in Object,
where: fragment("?->'to' \\? ? OR ?->'cc' \\? ?", o.data, ^public, o.data, ^public),
where: o.inserted_at < ^time_deadline,
where:
fragment("split_part(?->>'actor', '/', 3) != ?", o.data, ^Pleroma.Web.Endpoint.host())
)
|> Repo.delete_all(timeout: :infinity)
if Keyword.get(options, :vacuum) do
Logger.info("Runnning VACUUM FULL")
Repo.query!(
"vacuum full;",
[],
timeout: :infinity
)
end
end
end end

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Activity do
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.ThreadMute
alias Pleroma.User alias Pleroma.User
import Ecto.Changeset import Ecto.Changeset
@ -37,6 +38,7 @@ defmodule Pleroma.Activity do
field(:local, :boolean, default: true) field(:local, :boolean, default: true)
field(:actor, :string) field(:actor, :string)
field(:recipients, {:array, :string}, default: []) field(:recipients, {:array, :string}, default: [])
field(:thread_muted?, :boolean, virtual: true)
# This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark # This is a fake relation, do not use outside of with_preloaded_bookmark/get_bookmark
has_one(:bookmark, Bookmark) has_one(:bookmark, Bookmark)
has_many(:notifications, Notification, on_delete: :delete_all) has_many(:notifications, Notification, on_delete: :delete_all)
@ -90,6 +92,16 @@ defmodule Pleroma.Activity do
def with_preloaded_bookmark(query, _), do: query def with_preloaded_bookmark(query, _), do: query
def with_set_thread_muted_field(query, %User{} = user) do
from([a] in query,
left_join: tm in ThreadMute,
on: tm.user_id == ^user.id and tm.context == fragment("?->>'context'", a.data),
select: %Activity{a | thread_muted?: not is_nil(tm.id)}
)
end
def with_set_thread_muted_field(query, _), do: query
def get_by_ap_id(ap_id) do def get_by_ap_id(ap_id) do
Repo.one( Repo.one(
from( from(

View file

@ -110,6 +110,7 @@ defmodule Pleroma.Application do
hackney_pool_children() ++ hackney_pool_children() ++
[ [
worker(Pleroma.Web.Federator.RetryQueue, []), worker(Pleroma.Web.Federator.RetryQueue, []),
worker(Pleroma.Web.OAuth.Token.CleanWorker, []),
worker(Pleroma.Stats, []), worker(Pleroma.Stats, []),
worker(Task, [&Pleroma.Web.Push.init/0], restart: :temporary, id: :web_push_init), worker(Task, [&Pleroma.Web.Push.init/0], restart: :temporary, id: :web_push_init),
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary, id: :federator_init) worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary, id: :federator_init)
@ -131,19 +132,22 @@ defmodule Pleroma.Application do
defp setup_instrumenters do defp setup_instrumenters do
require Prometheus.Registry require Prometheus.Registry
:ok = if Application.get_env(:prometheus, Pleroma.Repo.Instrumenter) do
:telemetry.attach( :ok =
"prometheus-ecto", :telemetry.attach(
[:pleroma, :repo, :query], "prometheus-ecto",
&Pleroma.Repo.Instrumenter.handle_event/4, [:pleroma, :repo, :query],
%{} &Pleroma.Repo.Instrumenter.handle_event/4,
) %{}
)
Pleroma.Repo.Instrumenter.setup()
end
Prometheus.Registry.register_collector(:prometheus_process_collector) Prometheus.Registry.register_collector(:prometheus_process_collector)
Pleroma.Web.Endpoint.MetricsExporter.setup() Pleroma.Web.Endpoint.MetricsExporter.setup()
Pleroma.Web.Endpoint.PipelineInstrumenter.setup() Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
Pleroma.Web.Endpoint.Instrumenter.setup() Pleroma.Web.Endpoint.Instrumenter.setup()
Pleroma.Repo.Instrumenter.setup()
end end
def enabled_hackney_pools do def enabled_hackney_pools do

View file

@ -8,7 +8,7 @@ defmodule Pleroma.Formatter do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
@safe_mention_regex ~r/^(\s*(?<mentions>@.+?\s+)+)(?<rest>.*)/ @safe_mention_regex ~r/^(\s*(?<mentions>(@.+?\s+){1,})+)(?<rest>.*)/s
@link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui @link_regex ~r"((?:http(s)?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~%:/?#[\]@!\$&'\(\)\*\+,;=.]+)|[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+"ui
@markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/ @markdown_characters_regex ~r/(`|\*|_|{|}|[|]|\(|\)|#|\+|-|\.|!)/

View file

@ -8,7 +8,7 @@ defmodule Pleroma.HTTP.Connection do
""" """
@hackney_options [ @hackney_options [
connect_timeout: 2_000, connect_timeout: 10_000,
recv_timeout: 20_000, recv_timeout: 20_000,
follow_redirect: true, follow_redirect: true,
pool: :federation pool: :federation
@ -32,9 +32,11 @@ defmodule Pleroma.HTTP.Connection do
defp hackney_options(opts) do defp hackney_options(opts) do
options = Keyword.get(opts, :adapter, []) options = Keyword.get(opts, :adapter, [])
adapter_options = Pleroma.Config.get([:http, :adapter], []) adapter_options = Pleroma.Config.get([:http, :adapter], [])
proxy_url = Pleroma.Config.get([:http, :proxy_url], nil)
@hackney_options @hackney_options
|> Keyword.merge(adapter_options) |> Keyword.merge(adapter_options)
|> Keyword.merge(options) |> Keyword.merge(options)
|> Keyword.merge(proxy: proxy_url)
end end
end end

44
lib/pleroma/keys.ex Normal file
View file

@ -0,0 +1,44 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Keys do
# Native generation of RSA keys is only available since OTP 20+ and in default build conditions
# We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
try do
_ = :public_key.generate_key({:rsa, 2048, 65_537})
def generate_rsa_pem do
key = :public_key.generate_key({:rsa, 2048, 65_537})
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
{:ok, pem}
end
rescue
_ ->
def generate_rsa_pem do
port = Port.open({:spawn, "openssl genrsa"}, [:binary])
{:ok, pem} =
receive do
{^port, {:data, pem}} -> {:ok, pem}
end
Port.close(port)
if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
{:ok, pem}
else
:error
end
end
end
def keys_from_pem(pem) do
[private_key_code] = :public_key.pem_decode(pem)
private_key = :public_key.pem_entry_decode(private_key_code)
{:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
public_key = {:RSAPublicKey, modulus, exponent}
{:ok, private_key, public_key}
end
end

View file

@ -130,6 +130,13 @@ defmodule Pleroma.Object do
end end
end end
def prune(%Object{data: %{"id" => id}} = object) do
with {:ok, object} <- Repo.delete(object),
{:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do
{:ok, object}
end
end
def set_cache(%Object{data: %{"id" => ap_id}} = object) do def set_cache(%Object{data: %{"id" => ap_id}} = object) do
Cachex.put(:object_cache, "object:#{ap_id}", object) Cachex.put(:object_cache, "object:#{ap_id}", object)
{:ok, object} {:ok, object}

View file

@ -1,4 +1,5 @@
defmodule Pleroma.Object.Fetcher do defmodule Pleroma.Object.Fetcher do
alias Pleroma.HTTP
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Object.Containment alias Pleroma.Object.Containment
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
@ -6,7 +7,18 @@ defmodule Pleroma.Object.Fetcher do
require Logger require Logger
@httpoison Application.get_env(:pleroma, :httpoison) defp reinject_object(data) do
Logger.debug("Reinjecting object #{data["id"]}")
with data <- Transmogrifier.fix_object(data),
{:ok, object} <- Object.create(data) do
{:ok, object}
else
e ->
Logger.error("Error while processing object: #{inspect(e)}")
{:error, e}
end
end
# TODO: # TODO:
# This will create a Create activity, which we need internally at the moment. # This will create a Create activity, which we need internally at the moment.
@ -26,12 +38,17 @@ defmodule Pleroma.Object.Fetcher do
"object" => data "object" => data
}, },
:ok <- Containment.contain_origin(id, params), :ok <- Containment.contain_origin(id, params),
{:ok, activity} <- Transmogrifier.handle_incoming(params) do {:ok, activity} <- Transmogrifier.handle_incoming(params),
{:ok, Object.normalize(activity, false)} {:object, _data, %Object{} = object} <-
{:object, data, Object.normalize(activity, false)} do
{:ok, object}
else else
{:error, {:reject, nil}} -> {:error, {:reject, nil}} ->
{:reject, nil} {:reject, nil}
{:object, data, nil} ->
reinject_object(data)
object = %Object{} -> object = %Object{} ->
{:ok, object} {:ok, object}
@ -60,7 +77,7 @@ defmodule Pleroma.Object.Fetcher do
with true <- String.starts_with?(id, "http"), with true <- String.starts_with?(id, "http"),
{:ok, %{body: body, status: code}} when code in 200..299 <- {:ok, %{body: body, status: code}} when code in 200..299 <-
@httpoison.get( HTTP.get(
id, id,
[{:Accept, "application/activity+json"}] [{:Accept, "application/activity+json"}]
), ),

View file

@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.ReverseProxy do defmodule Pleroma.ReverseProxy do
alias Pleroma.HTTP
@keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++ @keep_req_headers ~w(accept user-agent accept-encoding cache-control if-modified-since) ++
~w(if-unmodified-since if-none-match if-range range) ~w(if-unmodified-since if-none-match if-range range)
@resp_cache_headers ~w(etag date last-modified cache-control) @resp_cache_headers ~w(etag date last-modified cache-control)
@ -60,7 +62,6 @@ defmodule Pleroma.ReverseProxy do
""" """
@hackney Application.get_env(:pleroma, :hackney, :hackney) @hackney Application.get_env(:pleroma, :hackney, :hackney)
@httpoison Application.get_env(:pleroma, :httpoison, HTTPoison)
@default_hackney_options [] @default_hackney_options []
@ -97,7 +98,7 @@ defmodule Pleroma.ReverseProxy do
hackney_opts = hackney_opts =
@default_hackney_options @default_hackney_options
|> Keyword.merge(Keyword.get(opts, :http, [])) |> Keyword.merge(Keyword.get(opts, :http, []))
|> @httpoison.process_request_options() |> HTTP.process_request_options()
req_headers = build_req_headers(conn.req_headers, opts) req_headers = build_req_headers(conn.req_headers, opts)

View file

@ -5,11 +5,10 @@
defmodule Pleroma.Signature do defmodule Pleroma.Signature do
@behaviour HTTPSignatures.Adapter @behaviour HTTPSignatures.Adapter
alias Pleroma.Keys
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Salmon
alias Pleroma.Web.WebFinger
def fetch_public_key(conn) do def fetch_public_key(conn) do
with actor_id <- Utils.get_ap_id(conn.params["actor"]), with actor_id <- Utils.get_ap_id(conn.params["actor"]),
@ -33,8 +32,8 @@ defmodule Pleroma.Signature do
end end
def sign(%User{} = user, headers) do def sign(%User{} = user, headers) do
with {:ok, %{info: %{keys: keys}}} <- WebFinger.ensure_keys_present(user), with {:ok, %{info: %{keys: keys}}} <- User.ensure_keys_present(user),
{:ok, private_key, _} <- Salmon.keys_from_pem(keys) do {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers) HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
end end
end end

View file

@ -4,11 +4,10 @@
defmodule Pleroma.Uploaders.MDII do defmodule Pleroma.Uploaders.MDII do
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.HTTP
@behaviour Pleroma.Uploaders.Uploader @behaviour Pleroma.Uploaders.Uploader
@httpoison Application.get_env(:pleroma, :httpoison)
# MDII-hosted images are never passed through the MediaPlug; only local media. # MDII-hosted images are never passed through the MediaPlug; only local media.
# Delegate to Pleroma.Uploaders.Local # Delegate to Pleroma.Uploaders.Local
def get_file(file) do def get_file(file) do
@ -25,7 +24,7 @@ defmodule Pleroma.Uploaders.MDII do
query = "#{cgi}?#{extension}" query = "#{cgi}?#{extension}"
with {:ok, %{status: 200, body: body}} <- with {:ok, %{status: 200, body: body}} <-
@httpoison.post(query, file_data, [], adapter: [pool: :default]) do HTTP.post(query, file_data, [], adapter: [pool: :default]) do
remote_file_name = String.split(body) |> List.first() remote_file_name = String.split(body) |> List.first()
public_url = "#{files}/#{remote_file_name}.#{extension}" public_url = "#{files}/#{remote_file_name}.#{extension}"
{:ok, {:url, public_url}} {:ok, {:url, public_url}}

View file

@ -10,6 +10,7 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2 alias Comeonin.Pbkdf2
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Keys
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Registration alias Pleroma.Registration
@ -1422,4 +1423,24 @@ defmodule Pleroma.User do
} }
} }
end end
def ensure_keys_present(user) do
info = user.info
if info.keys do
{:ok, user}
else
{:ok, pem} = Keys.generate_rsa_pem()
info_cng =
info
|> User.Info.set_keys(pem)
cng =
Ecto.Changeset.change(user)
|> Ecto.Changeset.put_embed(:info, info_cng)
update_and_set_cache(cng)
end
end
end end

View file

@ -834,6 +834,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> Activity.with_preloaded_bookmark(opts["user"]) |> Activity.with_preloaded_bookmark(opts["user"])
end end
defp maybe_set_thread_muted_field(query, %{"skip_preload" => true}), do: query
defp maybe_set_thread_muted_field(query, opts) do
query
|> Activity.with_set_thread_muted_field(opts["user"])
end
defp maybe_order(query, %{order: :desc}) do defp maybe_order(query, %{order: :desc}) do
query query
|> order_by(desc: :id) |> order_by(desc: :id)
@ -852,6 +859,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
base_query base_query
|> maybe_preload_objects(opts) |> maybe_preload_objects(opts)
|> maybe_preload_bookmarks(opts) |> maybe_preload_bookmarks(opts)
|> maybe_set_thread_muted_field(opts)
|> maybe_order(opts) |> maybe_order(opts)
|> restrict_recipients(recipients, opts["user"]) |> restrict_recipients(recipients, opts["user"])
|> restrict_tag(opts) |> restrict_tag(opts)
@ -901,7 +909,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end end
end end
def user_data_from_user_object(data) do defp object_to_user_data(data) do
avatar = avatar =
data["icon"]["url"] && data["icon"]["url"] &&
%{ %{
@ -948,9 +956,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, user_data} {:ok, user_data}
end end
def user_data_from_user_object(data) do
with {:ok, data} <- MRF.filter(data),
{:ok, data} <- object_to_user_data(data) do
{:ok, data}
else
e -> {:error, e}
end
end
def fetch_and_prepare_user_from_ap_id(ap_id) do def fetch_and_prepare_user_from_ap_id(ap_id) do
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
user_data_from_user_object(data) {:ok, data} <- user_data_from_user_object(data) do
{:ok, data}
else else
e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}") e -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
end end

View file

@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def user(conn, %{"nickname" => nickname}) do def user(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname), with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do {:ok, user} <- User.ensure_keys_present(user) do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("user.json", %{user: user})) |> json(UserView.render("user.json", %{user: user}))
@ -106,7 +106,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def following(conn, %{"nickname" => nickname, "page" => page}) do def following(conn, %{"nickname" => nickname, "page" => page}) do
with %User{} = user <- User.get_cached_by_nickname(nickname), with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do {:ok, user} <- User.ensure_keys_present(user) do
{page, _} = Integer.parse(page) {page, _} = Integer.parse(page)
conn conn
@ -117,7 +117,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def following(conn, %{"nickname" => nickname}) do def following(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname), with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do {:ok, user} <- User.ensure_keys_present(user) do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("following.json", %{user: user})) |> json(UserView.render("following.json", %{user: user}))
@ -126,7 +126,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def followers(conn, %{"nickname" => nickname, "page" => page}) do def followers(conn, %{"nickname" => nickname, "page" => page}) do
with %User{} = user <- User.get_cached_by_nickname(nickname), with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do {:ok, user} <- User.ensure_keys_present(user) do
{page, _} = Integer.parse(page) {page, _} = Integer.parse(page)
conn conn
@ -137,7 +137,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def followers(conn, %{"nickname" => nickname}) do def followers(conn, %{"nickname" => nickname}) do
with %User{} = user <- User.get_cached_by_nickname(nickname), with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do {:ok, user} <- User.ensure_keys_present(user) do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("followers.json", %{user: user})) |> json(UserView.render("followers.json", %{user: user}))
@ -146,7 +146,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def outbox(conn, %{"nickname" => nickname} = params) do def outbox(conn, %{"nickname" => nickname} = params) do
with %User{} = user <- User.get_cached_by_nickname(nickname), with %User{} = user <- User.get_cached_by_nickname(nickname),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do {:ok, user} <- User.ensure_keys_present(user) do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]})) |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
@ -195,7 +195,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
def relay(conn, _params) do def relay(conn, _params) do
with %User{} = user <- Relay.get_actor(), with %User{} = user <- Relay.get_actor(),
{:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do {:ok, user} <- User.ensure_keys_present(user) do
conn conn
|> put_resp_header("content-type", "application/activity+json") |> put_resp_header("content-type", "application/activity+json")
|> json(UserView.render("user.json", %{user: user})) |> json(UserView.render("user.json", %{user: user}))

View file

@ -74,8 +74,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
actor_host actor_host
), ),
user <- User.get_cached_by_ap_id(object["actor"]), user <- User.get_cached_by_ap_id(object["actor"]),
true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"], true <- "https://www.w3.org/ns/activitystreams#Public" in object["to"] do
true <- user.follower_address in object["cc"] do
to = to =
List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++ List.delete(object["to"], "https://www.w3.org/ns/activitystreams#Public") ++
[user.follower_address] [user.follower_address]
@ -104,9 +103,29 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
defp check_report_removal(_actor_info, object), do: {:ok, object} defp check_report_removal(_actor_info, object), do: {:ok, object}
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
if actor_host in Pleroma.Config.get([:mrf_simple, :avatar_removal]) do
{:ok, Map.delete(object, "icon")}
else
{:ok, object}
end
end
defp check_avatar_removal(_actor_info, object), do: {:ok, object}
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
if actor_host in Pleroma.Config.get([:mrf_simple, :banner_removal]) do
{:ok, Map.delete(object, "image")}
else
{:ok, object}
end
end
defp check_banner_removal(_actor_info, object), do: {:ok, object}
@impl true @impl true
def filter(object) do def filter(%{"actor" => actor} = object) do
actor_info = URI.parse(object["actor"]) actor_info = URI.parse(actor)
with {:ok, object} <- check_accept(actor_info, object), with {:ok, object} <- check_accept(actor_info, object),
{:ok, object} <- check_reject(actor_info, object), {:ok, object} <- check_reject(actor_info, object),
@ -119,4 +138,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
_e -> {:reject, nil} _e -> {:reject, nil}
end end
end end
def filter(%{"id" => actor, "type" => obj_type} = object)
when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do
actor_info = URI.parse(actor)
with {:ok, object} <- check_avatar_removal(actor_info, object),
{:ok, object} <- check_banner_removal(actor_info, object) do
{:ok, object}
else
_e -> {:reject, nil}
end
end
def filter(object), do: {:ok, object}
end end

View file

@ -19,10 +19,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do
end end
@impl true @impl true
def filter(object) do def filter(%{"actor" => actor} = object) do
actor_info = URI.parse(object["actor"]) actor_info = URI.parse(actor)
allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], []) allow_list = Config.get([:mrf_user_allowlist, String.to_atom(actor_info.host)], [])
filter_by_list(object, allow_list) filter_by_list(object, allow_list)
end end
def filter(object), do: {:ok, object}
end end

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.ActivityPub.Publisher do defmodule Pleroma.Web.ActivityPub.Publisher do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Config alias Pleroma.Config
alias Pleroma.HTTP
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Relay
@ -16,8 +17,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
require Logger require Logger
@httpoison Application.get_env(:pleroma, :httpoison)
@moduledoc """ @moduledoc """
ActivityPub outgoing federation module. ActivityPub outgoing federation module.
""" """
@ -63,7 +62,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
with {:ok, %{status: code}} when code in 200..299 <- with {:ok, %{status: code}} when code in 200..299 <-
result = result =
@httpoison.post( HTTP.post(
inbox, inbox,
json, json,
[ [

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.ActivityPub.UserView do defmodule Pleroma.Web.ActivityPub.UserView do
use Pleroma.Web, :view use Pleroma.Web, :view
alias Pleroma.Keys
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
@ -12,8 +13,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Endpoint alias Pleroma.Web.Endpoint
alias Pleroma.Web.Router.Helpers alias Pleroma.Web.Router.Helpers
alias Pleroma.Web.Salmon
alias Pleroma.Web.WebFinger
import Ecto.Query import Ecto.Query
@ -34,8 +33,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
# the instance itself is not a Person, but instead an Application # the instance itself is not a Person, but instead an Application
def render("user.json", %{user: %{nickname: nil} = user}) do def render("user.json", %{user: %{nickname: nil} = user}) do
{:ok, user} = WebFinger.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys) {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key]) public_key = :public_key.pem_encode([public_key])
@ -62,8 +61,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
end end
def render("user.json", %{user: user}) do def render("user.json", %{user: user}) do
{:ok, user} = WebFinger.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
{:ok, _, public_key} = Salmon.keys_from_pem(user.info.keys) {:ok, _, public_key} = Keys.keys_from_pem(user.info.keys)
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key]) public_key = :public_key.pem_encode([public_key])

View file

@ -16,17 +16,32 @@ defmodule Pleroma.Web.Endpoint do
plug(Pleroma.Plugs.UploadedMedia) plug(Pleroma.Plugs.UploadedMedia)
@static_cache_control "public, no-cache"
# InstanceStatic needs to be before Plug.Static to be able to override shipped-static files # InstanceStatic needs to be before Plug.Static to be able to override shipped-static files
# If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well # If you're adding new paths to `only:` you'll need to configure them in InstanceStatic as well
plug(Pleroma.Plugs.InstanceStatic, at: "/") # Cache-control headers are duplicated in case we turn off etags in the future
plug(Pleroma.Plugs.InstanceStatic,
at: "/",
gzip: true,
cache_control_for_etags: @static_cache_control,
headers: %{
"cache-control" => @static_cache_control
}
)
plug( plug(
Plug.Static, Plug.Static,
at: "/", at: "/",
from: :pleroma, from: :pleroma,
only: only:
~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc) ~w(index.html robots.txt static finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc),
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
gzip: true,
cache_control_for_etags: @static_cache_control,
headers: %{
"cache-control" => @static_cache_control
}
) )
plug(Plug.Static.IndexHtml, at: "/pleroma/admin/") plug(Plug.Static.IndexHtml, at: "/pleroma/admin/")

View file

@ -11,14 +11,11 @@ defmodule Pleroma.Web.Federator do
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.Federator.Publisher
alias Pleroma.Web.Federator.RetryQueue alias Pleroma.Web.Federator.RetryQueue
alias Pleroma.Web.WebFinger alias Pleroma.Web.OStatus
alias Pleroma.Web.Websub alias Pleroma.Web.Websub
require Logger require Logger
@websub Application.get_env(:pleroma, :websub)
@ostatus Application.get_env(:pleroma, :ostatus)
def init do def init do
# 1 minute # 1 minute
Process.sleep(1000 * 60) Process.sleep(1000 * 60)
@ -77,9 +74,8 @@ defmodule Pleroma.Web.Federator do
def perform(:publish, activity) do def perform(:publish, activity) do
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
with actor when not is_nil(actor) <- User.get_cached_by_ap_id(activity.data["actor"]) do with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]),
{:ok, actor} = WebFinger.ensure_keys_present(actor) {:ok, actor} <- User.ensure_keys_present(actor) do
Publisher.publish(actor, activity) Publisher.publish(actor, activity)
end end
end end
@ -89,12 +85,12 @@ defmodule Pleroma.Web.Federator do
"Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})" "Running WebSub verification for #{websub.id} (#{websub.topic}, #{websub.callback})"
end) end)
@websub.verify(websub) Websub.verify(websub)
end end
def perform(:incoming_doc, doc) do def perform(:incoming_doc, doc) do
Logger.info("Got document, trying to parse") Logger.info("Got document, trying to parse")
@ostatus.handle_incoming(doc) OStatus.handle_incoming(doc)
end end
def perform(:incoming_ap_doc, params) do def perform(:incoming_ap_doc, params) do

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
alias Pleroma.Conversation.Participation alias Pleroma.Conversation.Participation
alias Pleroma.Filter alias Pleroma.Filter
alias Pleroma.Formatter alias Pleroma.Formatter
alias Pleroma.HTTP
alias Pleroma.Notification alias Pleroma.Notification
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Object.Fetcher alias Pleroma.Object.Fetcher
@ -55,7 +56,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
when action in [:account_register] when action in [:account_register]
) )
@httpoison Application.get_env(:pleroma, :httpoison)
@local_mastodon_name "Mastodon-Local" @local_mastodon_name "Mastodon-Local"
action_fallback(:errors) action_fallback(:errors)
@ -1084,7 +1084,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
from([a, o] in Activity.with_preloaded_object(Activity), from([a, o] in Activity.with_preloaded_object(Activity),
where: fragment("?->>'type' = 'Create'", a.data), where: fragment("?->>'type' = 'Create'", a.data),
where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients, where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
limit: 20 limit: 40
) )
q = q =
@ -1691,7 +1691,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> String.replace("{{user}}", user) |> String.replace("{{user}}", user)
with {:ok, %{status: 200, body: body}} <- with {:ok, %{status: 200, body: body}} <-
@httpoison.get( HTTP.get(
url, url,
[], [],
adapter: [ adapter: [

View file

@ -112,7 +112,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
fields: fields, fields: fields,
bot: bot, bot: bot,
source: %{ source: %{
note: "", note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
sensitive: false, sensitive: false,
pleroma: %{} pleroma: %{}
}, },

View file

@ -157,6 +157,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil bookmarked = Activity.get_bookmark(activity, opts[:for]) != nil
thread_muted? =
case activity.thread_muted? do
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
nil -> CommonAPI.thread_muted?(user, activity)
end
attachment_data = object.data["attachment"] || [] attachment_data = object.data["attachment"] || []
attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment) attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
@ -228,7 +234,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
reblogged: reblogged?(activity, opts[:for]), reblogged: reblogged?(activity, opts[:for]),
favourited: present?(favorited), favourited: present?(favorited),
bookmarked: present?(bookmarked), bookmarked: present?(bookmarked),
muted: CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user), muted: thread_muted? || User.mutes?(opts[:for], user),
pinned: pinned?(activity, user), pinned: pinned?(activity, user),
sensitive: sensitive, sensitive: sensitive,
spoiler_text: summary_html, spoiler_text: summary_html,

View file

@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MongooseIM.MongooseIMController do
use Pleroma.Web, :controller
alias Comeonin.Pbkdf2
alias Pleroma.Repo
alias Pleroma.User
def user_exists(conn, %{"user" => username}) do
with %User{} <- Repo.get_by(User, nickname: username, local: true) do
conn
|> json(true)
else
_ ->
conn
|> put_status(:not_found)
|> json(false)
end
end
def check_password(conn, %{"user" => username, "pass" => password}) do
with %User{password_hash: password_hash} <-
Repo.get_by(User, nickname: username, local: true),
true <- Pbkdf2.checkpw(password, password_hash) do
conn
|> json(true)
else
false ->
conn
|> put_status(403)
|> json(false)
_ ->
conn
|> put_status(:not_found)
|> json(false)
end
end
end

View file

@ -12,8 +12,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.Federator.Publisher
plug(Pleroma.Web.FederatingPlug)
def schemas(conn, _params) do def schemas(conn, _params) do
response = %{ response = %{
links: [ links: [

View file

@ -5,7 +5,6 @@
defmodule Pleroma.Web.OAuth.Token do defmodule Pleroma.Web.OAuth.Token do
use Ecto.Schema use Ecto.Schema
import Ecto.Query
import Ecto.Changeset import Ecto.Changeset
alias Pleroma.Repo alias Pleroma.Repo
@ -13,6 +12,7 @@ defmodule Pleroma.Web.OAuth.Token do
alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.App
alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OAuth.Token.Query
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600) @expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
@type t :: %__MODULE__{} @type t :: %__MODULE__{}
@ -31,17 +31,17 @@ defmodule Pleroma.Web.OAuth.Token do
@doc "Gets token for app by access token" @doc "Gets token for app by access token"
@spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found} @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
def get_by_token(%App{id: app_id} = _app, token) do def get_by_token(%App{id: app_id} = _app, token) do
from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token) Query.get_by_app(app_id)
|> Query.get_by_token(token)
|> Repo.find_resource() |> Repo.find_resource()
end end
@doc "Gets token for app by refresh token" @doc "Gets token for app by refresh token"
@spec get_by_refresh_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found} @spec get_by_refresh_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
def get_by_refresh_token(%App{id: app_id} = _app, token) do def get_by_refresh_token(%App{id: app_id} = _app, token) do
from(t in __MODULE__, Query.get_by_app(app_id)
where: t.app_id == ^app_id and t.refresh_token == ^token, |> Query.get_by_refresh_token(token)
preload: [:user] |> Query.preload([:user])
)
|> Repo.find_resource() |> Repo.find_resource()
end end
@ -97,29 +97,25 @@ defmodule Pleroma.Web.OAuth.Token do
end end
def delete_user_tokens(%User{id: user_id}) do def delete_user_tokens(%User{id: user_id}) do
from( Query.get_by_user(user_id)
t in Token,
where: t.user_id == ^user_id
)
|> Repo.delete_all() |> Repo.delete_all()
end end
def delete_user_token(%User{id: user_id}, token_id) do def delete_user_token(%User{id: user_id}, token_id) do
from( Query.get_by_user(user_id)
t in Token, |> Query.get_by_id(token_id)
where: t.user_id == ^user_id, |> Repo.delete_all()
where: t.id == ^token_id end
)
def delete_expired_tokens do
Query.get_expired_tokens()
|> Repo.delete_all() |> Repo.delete_all()
end end
def get_user_tokens(%User{id: user_id}) do def get_user_tokens(%User{id: user_id}) do
from( Query.get_by_user(user_id)
t in Token, |> Query.preload([:app])
where: t.user_id == ^user_id
)
|> Repo.all() |> Repo.all()
|> Repo.preload(:app)
end end
def is_expired?(%__MODULE__{valid_until: valid_until}) do def is_expired?(%__MODULE__{valid_until: valid_until}) do

View file

@ -0,0 +1,41 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.CleanWorker do
@moduledoc """
The module represents functions to clean an expired oauth tokens.
"""
# 10 seconds
@start_interval 10_000
@interval Pleroma.Config.get(
# 24 hours
[:oauth2, :clean_expired_tokens_interval],
86_400_000
)
@queue :background
alias Pleroma.Web.OAuth.Token
def start_link, do: GenServer.start_link(__MODULE__, nil)
def init(_) do
if Pleroma.Config.get([:oauth2, :clean_expired_tokens], false) do
Process.send_after(self(), :perform, @start_interval)
{:ok, nil}
else
:ignore
end
end
@doc false
def handle_info(:perform, state) do
Process.send_after(self(), :perform, @interval)
PleromaJobQueue.enqueue(@queue, __MODULE__, [:clean])
{:noreply, state}
end
# Job Worker Callbacks
def perform(:clean), do: Token.delete_expired_tokens()
end

View file

@ -0,0 +1,55 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OAuth.Token.Query do
@moduledoc """
Contains queries for OAuth Token.
"""
import Ecto.Query, only: [from: 2]
@type query :: Ecto.Queryable.t() | Token.t()
alias Pleroma.Web.OAuth.Token
@spec get_by_refresh_token(query, String.t()) :: query
def get_by_refresh_token(query \\ Token, refresh_token) do
from(q in query, where: q.refresh_token == ^refresh_token)
end
@spec get_by_token(query, String.t()) :: query
def get_by_token(query \\ Token, token) do
from(q in query, where: q.token == ^token)
end
@spec get_by_app(query, String.t()) :: query
def get_by_app(query \\ Token, app_id) do
from(q in query, where: q.app_id == ^app_id)
end
@spec get_by_id(query, String.t()) :: query
def get_by_id(query \\ Token, id) do
from(q in query, where: q.id == ^id)
end
@spec get_expired_tokens(query, DateTime.t() | nil) :: query
def get_expired_tokens(query \\ Token, date \\ nil) do
expired_date = date || Timex.now()
from(q in query, where: fragment("?", q.valid_until) < ^expired_date)
end
@spec get_by_user(query, String.t()) :: query
def get_by_user(query \\ Token, user_id) do
from(q in query, where: q.user_id == ^user_id)
end
@spec preload(query, any) :: query
def preload(query \\ Token, assoc_preload \\ [])
def preload(query, assoc_preload) when is_list(assoc_preload) do
from(q in query, preload: ^assoc_preload)
end
def preload(query, _assoc_preload), do: query
end

View file

@ -3,13 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatus do defmodule Pleroma.Web.OStatus do
@httpoison Application.get_env(:pleroma, :httpoison)
import Ecto.Query import Ecto.Query
import Pleroma.Web.XML import Pleroma.Web.XML
require Logger require Logger
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.HTTP
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
@ -363,7 +362,7 @@ defmodule Pleroma.Web.OStatus do
def fetch_activity_from_atom_url(url) do def fetch_activity_from_atom_url(url) do
with true <- String.starts_with?(url, "http"), with true <- String.starts_with?(url, "http"),
{:ok, %{body: body, status: code}} when code in 200..299 <- {:ok, %{body: body, status: code}} when code in 200..299 <-
@httpoison.get( HTTP.get(
url, url,
[{:Accept, "application/atom+xml"}] [{:Accept, "application/atom+xml"}]
) do ) do
@ -380,7 +379,7 @@ defmodule Pleroma.Web.OStatus do
Logger.debug("Trying to fetch #{url}") Logger.debug("Trying to fetch #{url}")
with true <- String.starts_with?(url, "http"), with true <- String.starts_with?(url, "http"),
{:ok, %{body: body}} <- @httpoison.get(url, []), {:ok, %{body: body}} <- HTTP.get(url, []),
{:ok, atom_url} <- get_atom_url(body) do {:ok, atom_url} <- get_atom_url(body) do
fetch_activity_from_atom_url(atom_url) fetch_activity_from_atom_url(atom_url)
else else

View file

@ -707,9 +707,15 @@ defmodule Pleroma.Web.Router do
end end
end end
scope "/", Pleroma.Web.MongooseIM do
get("/user_exists", MongooseIMController, :user_exists)
get("/check_password", MongooseIMController, :check_password)
end
scope "/", Fallback do scope "/", Fallback do
get("/registration/:token", RedirectController, :registration_page) get("/registration/:token", RedirectController, :registration_page)
get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
get("/api*path", RedirectController, :api_not_implemented)
get("/*path", RedirectController, :redirector) get("/*path", RedirectController, :redirector)
options("/*path", RedirectController, :empty) options("/*path", RedirectController, :empty)
@ -721,6 +727,12 @@ defmodule Fallback.RedirectController do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.Metadata alias Pleroma.Web.Metadata
def api_not_implemented(conn, _params) do
conn
|> put_status(404)
|> json(%{error: "Not implemented"})
end
def redirector(conn, _params, code \\ 200) do def redirector(conn, _params, code \\ 200) do
conn conn
|> put_resp_content_type("text/html") |> put_resp_content_type("text/html")

View file

@ -5,12 +5,12 @@
defmodule Pleroma.Web.Salmon do defmodule Pleroma.Web.Salmon do
@behaviour Pleroma.Web.Federator.Publisher @behaviour Pleroma.Web.Federator.Publisher
@httpoison Application.get_env(:pleroma, :httpoison)
use Bitwise use Bitwise
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.HTTP
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.Keys
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.Federator.Publisher
@ -89,45 +89,6 @@ defmodule Pleroma.Web.Salmon do
"RSA.#{modulus_enc}.#{exponent_enc}" "RSA.#{modulus_enc}.#{exponent_enc}"
end end
# Native generation of RSA keys is only available since OTP 20+ and in default build conditions
# We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
try do
_ = :public_key.generate_key({:rsa, 2048, 65_537})
def generate_rsa_pem do
key = :public_key.generate_key({:rsa, 2048, 65_537})
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
{:ok, pem}
end
rescue
_ ->
def generate_rsa_pem do
port = Port.open({:spawn, "openssl genrsa"}, [:binary])
{:ok, pem} =
receive do
{^port, {:data, pem}} -> {:ok, pem}
end
Port.close(port)
if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
{:ok, pem}
else
:error
end
end
end
def keys_from_pem(pem) do
[private_key_code] = :public_key.pem_decode(pem)
private_key = :public_key.pem_entry_decode(private_key_code)
{:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
public_key = {:RSAPublicKey, modulus, exponent}
{:ok, private_key, public_key}
end
def encode(private_key, doc) do def encode(private_key, doc) do
type = "application/atom+xml" type = "application/atom+xml"
encoding = "base64url" encoding = "base64url"
@ -176,7 +137,7 @@ defmodule Pleroma.Web.Salmon do
def publish_one(%{recipient: url, feed: feed} = params) when is_binary(url) do def publish_one(%{recipient: url, feed: feed} = params) when is_binary(url) do
with {:ok, %{status: code}} when code in 200..299 <- with {:ok, %{status: code}} when code in 200..299 <-
@httpoison.post( HTTP.post(
url, url,
feed, feed,
[{"Content-Type", "application/magic-envelope+xml"}] [{"Content-Type", "application/magic-envelope+xml"}]
@ -227,7 +188,7 @@ defmodule Pleroma.Web.Salmon do
|> :xmerl.export_simple(:xmerl_xml) |> :xmerl.export_simple(:xmerl_xml)
|> to_string |> to_string
{:ok, private, _} = keys_from_pem(keys) {:ok, private, _} = Keys.keys_from_pem(keys)
{:ok, feed} = encode(private, feed) {:ok, feed} = encode(private, feed)
remote_users = remote_users(activity) remote_users = remote_users(activity)
@ -253,7 +214,7 @@ defmodule Pleroma.Web.Salmon do
def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end) def publish(%{id: id}, _), do: Logger.debug(fn -> "Keys missing for user #{id}" end)
def gather_webfinger_links(%User{} = user) do def gather_webfinger_links(%User{} = user) do
{:ok, _private, public} = keys_from_pem(user.info.keys) {:ok, _private, public} = Keys.keys_from_pem(user.info.keys)
magic_key = encode_key(public) magic_key = encode_key(public)
[ [

View file

@ -284,6 +284,12 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
) )
thread_muted? =
case activity.thread_muted? do
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
nil -> CommonAPI.thread_muted?(user, activity)
end
%{ %{
"id" => activity.id, "id" => activity.id,
"uri" => object.data["id"], "uri" => object.data["id"],
@ -314,7 +320,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
"summary" => summary, "summary" => summary,
"summary_html" => summary |> Formatter.emojify(object.data["emoji"]), "summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
"card" => card, "card" => card,
"muted" => CommonAPI.thread_muted?(user, activity) || User.mutes?(opts[:for], user) "muted" => thread_muted? || User.mutes?(opts[:for], user)
} }
end end

View file

@ -3,12 +3,10 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.WebFinger do defmodule Pleroma.Web.WebFinger do
@httpoison Application.get_env(:pleroma, :httpoison) alias Pleroma.HTTP
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web alias Pleroma.Web
alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.Federator.Publisher
alias Pleroma.Web.Salmon
alias Pleroma.Web.XML alias Pleroma.Web.XML
alias Pleroma.XmlBuilder alias Pleroma.XmlBuilder
require Jason require Jason
@ -61,7 +59,7 @@ defmodule Pleroma.Web.WebFinger do
end end
def represent_user(user, "JSON") do def represent_user(user, "JSON") do
{:ok, user} = ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
%{ %{
"subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}", "subject" => "acct:#{user.nickname}@#{Pleroma.Web.Endpoint.host()}",
@ -71,7 +69,7 @@ defmodule Pleroma.Web.WebFinger do
end end
def represent_user(user, "XML") do def represent_user(user, "XML") do
{:ok, user} = ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
links = links =
gather_links(user) gather_links(user)
@ -88,27 +86,6 @@ defmodule Pleroma.Web.WebFinger do
|> XmlBuilder.to_doc() |> XmlBuilder.to_doc()
end end
# This seems a better fit in Salmon
def ensure_keys_present(user) do
info = user.info
if info.keys do
{:ok, user}
else
{:ok, pem} = Salmon.generate_rsa_pem()
info_cng =
info
|> User.Info.set_keys(pem)
cng =
Ecto.Changeset.change(user)
|> Ecto.Changeset.put_embed(:info, info_cng)
User.update_and_set_cache(cng)
end
end
defp get_magic_key(magic_key) do defp get_magic_key(magic_key) do
"data:application/magic-public-key," <> magic_key = magic_key "data:application/magic-public-key," <> magic_key = magic_key
{:ok, magic_key} {:ok, magic_key}
@ -198,11 +175,11 @@ defmodule Pleroma.Web.WebFinger do
def find_lrdd_template(domain) do def find_lrdd_template(domain) do
with {:ok, %{status: status, body: body}} when status in 200..299 <- with {:ok, %{status: status, body: body}} when status in 200..299 <-
@httpoison.get("http://#{domain}/.well-known/host-meta", []) do HTTP.get("http://#{domain}/.well-known/host-meta", []) do
get_template_from_xml(body) get_template_from_xml(body)
else else
_ -> _ ->
with {:ok, %{body: body}} <- @httpoison.get("https://#{domain}/.well-known/host-meta", []) do with {:ok, %{body: body}} <- HTTP.get("https://#{domain}/.well-known/host-meta", []) do
get_template_from_xml(body) get_template_from_xml(body)
else else
e -> {:error, "Can't find LRDD template: #{inspect(e)}"} e -> {:error, "Can't find LRDD template: #{inspect(e)}"}
@ -231,7 +208,7 @@ defmodule Pleroma.Web.WebFinger do
end end
with response <- with response <-
@httpoison.get( HTTP.get(
address, address,
Accept: "application/xrd+xml,application/jrd+json" Accept: "application/xrd+xml,application/jrd+json"
), ),

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.Websub do defmodule Pleroma.Web.Websub do
alias Ecto.Changeset alias Ecto.Changeset
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.HTTP
alias Pleroma.Instances alias Pleroma.Instances
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
@ -24,9 +25,7 @@ defmodule Pleroma.Web.Websub do
@behaviour Pleroma.Web.Federator.Publisher @behaviour Pleroma.Web.Federator.Publisher
@httpoison Application.get_env(:pleroma, :httpoison) def verify(subscription, getter \\ &HTTP.get/3) do
def verify(subscription, getter \\ &@httpoison.get/3) do
challenge = Base.encode16(:crypto.strong_rand_bytes(8)) challenge = Base.encode16(:crypto.strong_rand_bytes(8))
lease_seconds = NaiveDateTime.diff(subscription.valid_until, subscription.updated_at) lease_seconds = NaiveDateTime.diff(subscription.valid_until, subscription.updated_at)
lease_seconds = lease_seconds |> to_string lease_seconds = lease_seconds |> to_string
@ -207,7 +206,7 @@ defmodule Pleroma.Web.Websub do
requester.(subscription) requester.(subscription)
end end
def gather_feed_data(topic, getter \\ &@httpoison.get/1) do def gather_feed_data(topic, getter \\ &HTTP.get/1) do
with {:ok, response} <- getter.(topic), with {:ok, response} <- getter.(topic),
status when status in 200..299 <- response.status, status when status in 200..299 <- response.status,
body <- response.body, body <- response.body,
@ -236,7 +235,7 @@ defmodule Pleroma.Web.Websub do
end end
end end
def request_subscription(websub, poster \\ &@httpoison.post/3, timeout \\ 10_000) do def request_subscription(websub, poster \\ &HTTP.post/3, timeout \\ 10_000) do
data = [ data = [
"hub.mode": "subscribe", "hub.mode": "subscribe",
"hub.topic": websub.topic, "hub.topic": websub.topic,
@ -294,7 +293,7 @@ defmodule Pleroma.Web.Websub do
Logger.info(fn -> "Pushing #{topic} to #{callback}" end) Logger.info(fn -> "Pushing #{topic} to #{callback}" end)
with {:ok, %{status: code}} when code in 200..299 <- with {:ok, %{status: code}} when code in 200..299 <-
@httpoison.post( HTTP.post(
callback, callback,
xml, xml,
[ [

View file

@ -42,7 +42,7 @@ defmodule Pleroma.Mixfile do
def application do def application do
[ [
mod: {Pleroma.Application, []}, mod: {Pleroma.Application, []},
extra_applications: [:logger, :runtime_tools, :comeonin, :esshd, :quack], extra_applications: [:logger, :runtime_tools, :comeonin, :quack],
included_applications: [:ex_syslogger] included_applications: [:ex_syslogger]
] ]
end end
@ -66,10 +66,7 @@ defmodule Pleroma.Mixfile do
{:plug_cowboy, "~> 2.0"}, {:plug_cowboy, "~> 2.0"},
{:phoenix_pubsub, "~> 1.1"}, {:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"}, {:phoenix_ecto, "~> 4.0"},
{:ecto_sql, {:ecto_sql, "~> 3.1"},
git: "https://github.com/elixir-ecto/ecto_sql",
ref: "14cb065a74c488d737d973f7a91bc036c6245f78",
override: true},
{:postgrex, ">= 0.13.5"}, {:postgrex, ">= 0.13.5"},
{:gettext, "~> 0.15"}, {:gettext, "~> 0.15"},
{:comeonin, "~> 4.1.1"}, {:comeonin, "~> 4.1.1"},
@ -120,7 +117,7 @@ defmodule Pleroma.Mixfile do
{:recon, github: "ferd/recon", tag: "2.4.0"}, {:recon, github: "ferd/recon", tag: "2.4.0"},
{:quack, "~> 0.1.1"}, {:quack, "~> 0.1.1"},
{:benchee, "~> 1.0"}, {:benchee, "~> 1.0"},
{:esshd, "~> 0.1.0"}, {:esshd, "~> 0.1.0", runtime: Application.get_env(:esshd, :enabled, false)},
{:ex_rated, "~> 1.2"}, {:ex_rated, "~> 1.2"},
{:plug_static_index_html, "~> 1.0.0"}, {:plug_static_index_html, "~> 1.0.0"},
{:excoveralls, "~> 0.11.1", only: :test} {:excoveralls, "~> 0.11.1", only: :test}

View file

@ -21,7 +21,7 @@
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"},
"earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm"},
"ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ecto": {:hex, :ecto, "3.1.4", "69d852da7a9f04ede725855a35ede48d158ca11a404fe94f8b2fb3b2162cd3c9", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
"ecto_sql": {:git, "https://github.com/elixir-ecto/ecto_sql", "14cb065a74c488d737d973f7a91bc036c6245f78", [ref: "14cb065a74c488d737d973f7a91bc036c6245f78"]}, "ecto_sql": {:hex, :ecto_sql, "3.1.3", "2c536139190492d9de33c5fefac7323c5eaaa82e1b9bf93482a14649042f7cd9", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
"esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"}, "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"},
"eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"}, "eternal": {:hex, :eternal, "1.2.0", "e2a6b6ce3b8c248f7dc31451aefca57e3bdf0e48d73ae5043229380a67614c41", [:mix], [], "hexpm"},
"ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"},

View file

@ -6,6 +6,7 @@ defmodule Pleroma.ActivityTest do
use Pleroma.DataCase use Pleroma.DataCase
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Bookmark alias Pleroma.Bookmark
alias Pleroma.ThreadMute
import Pleroma.Factory import Pleroma.Factory
test "returns an activity by it's AP id" do test "returns an activity by it's AP id" do
@ -47,6 +48,31 @@ defmodule Pleroma.ActivityTest do
assert queried_activity.bookmark == bookmark3 assert queried_activity.bookmark == bookmark3
end end
test "setting thread_muted?" do
activity = insert(:note_activity)
user = insert(:user)
annoyed_user = insert(:user)
{:ok, _} = ThreadMute.add_mute(annoyed_user.id, activity.data["context"])
activity_with_unset_thread_muted_field =
Ecto.Query.from(Activity)
|> Repo.one()
activity_for_user =
Ecto.Query.from(Activity)
|> Activity.with_set_thread_muted_field(user)
|> Repo.one()
activity_for_annoyed_user =
Ecto.Query.from(Activity)
|> Activity.with_set_thread_muted_field(annoyed_user)
|> Repo.one()
assert activity_with_unset_thread_muted_field.thread_muted? == nil
assert activity_for_user.thread_muted? == false
assert activity_for_annoyed_user.thread_muted? == true
end
describe "getting a bookmark" do describe "getting a bookmark" do
test "when association is loaded" do test "when association is loaded" do
user = insert(:user) user = insert(:user)

View file

@ -184,17 +184,19 @@ defmodule Pleroma.FormatterTest do
test "given the 'safe_mention' option, it will only mention people in the beginning" do test "given the 'safe_mention' option, it will only mention people in the beginning" do
user = insert(:user) user = insert(:user)
_other_user = insert(:user) other_user = insert(:user)
third_user = insert(:user) third_user = insert(:user)
text = " @#{user.nickname} hey dude i hate @#{third_user.nickname}" text = " @#{user.nickname} @#{other_user.nickname} hey dudes i hate @#{third_user.nickname}"
{expected_text, mentions, [] = _tags} = Formatter.linkify(text, safe_mention: true) {expected_text, mentions, [] = _tags} = Formatter.linkify(text, safe_mention: true)
assert mentions == [{"@#{user.nickname}", user}] assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}]
assert expected_text == assert expected_text ==
"<span class='h-card'><a data-user='#{user.id}' class='u-url mention' href='#{ "<span class='h-card'><a data-user='#{user.id}' class='u-url mention' href='#{
user.ap_id user.ap_id
}'>@<span>#{user.nickname}</span></a></span> hey dude i hate <span class='h-card'><a data-user='#{ }'>@<span>#{user.nickname}</span></a></span> <span class='h-card'><a data-user='#{
other_user.id
}' class='u-url mention' href='#{other_user.ap_id}'>@<span>#{other_user.nickname}</span></a></span> hey dudes i hate <span class='h-card'><a data-user='#{
third_user.id third_user.id
}' class='u-url mention' href='#{third_user.ap_id}'>@<span>#{third_user.nickname}</span></a></span>" }' class='u-url mention' href='#{third_user.ap_id}'>@<span>#{third_user.nickname}</span></a></span>"
end end
@ -206,6 +208,15 @@ defmodule Pleroma.FormatterTest do
assert mentions == [] assert mentions == []
assert expected_text == text assert expected_text == text
end end
test "given the 'safe_mention' option, it will keep text after newlines" do
user = insert(:user)
text = " @#{user.nickname}\n hey dude\n\nhow are you doing?"
{expected_text, _, _} = Formatter.linkify(text, safe_mention: true)
assert expected_text =~ "how are you doing?"
end
end end
describe ".parse_tags" do describe ".parse_tags" do

20
test/keys_test.exs Normal file
View file

@ -0,0 +1,20 @@
defmodule Pleroma.KeysTest do
use Pleroma.DataCase
alias Pleroma.Keys
test "generates an RSA private key pem" do
{:ok, key} = Keys.generate_rsa_pem()
assert is_binary(key)
assert Regex.match?(~r/RSA/, key)
end
test "returns a public and private key from a pem" do
pem = File.read!("test/fixtures/private_key.pem")
{:ok, private, public} = Keys.keys_from_pem(pem)
assert elem(private, 0) == :RSAPrivateKey
assert elem(public, 0) == :RSAPublicKey
end
end

View file

@ -87,4 +87,23 @@ defmodule Pleroma.Object.FetcherTest do
) )
end end
end end
describe "pruning" do
test "it can refetch pruned objects" do
object_id = "http://mastodon.example.org/@admin/99541947525187367"
{:ok, object} = Fetcher.fetch_object_from_id(object_id)
assert object
{:ok, _object} = Object.prune(object)
refute Object.get_by_ap_id(object_id)
{:ok, %Object{} = object_two} = Fetcher.fetch_object_from_id(object_id)
assert object.data["id"] == object_two.data["id"]
assert object.id != object_two.id
end
end
end end

View file

@ -0,0 +1,20 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.CacheControlTest do
use Pleroma.Web.ConnCase
alias Plug.Conn
test "Verify Cache-Control header on static assets", %{conn: conn} do
conn = get(conn, "/index.html")
assert Conn.get_resp_header(conn, "cache-control") == ["public, no-cache"]
end
test "Verify Cache-Control header on the API", %{conn: conn} do
conn = get(conn, "/api/v1/instance")
assert Conn.get_resp_header(conn, "cache-control") == ["max-age=0, private, must-revalidate"]
end
end

View file

@ -1,11 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.OStatusMock do
import Pleroma.Factory
def handle_incoming(_doc) do
insert(:note_activity)
end
end

View file

@ -1,9 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.WebsubMock do
def verify(sub) do
{:ok, sub}
end
end

View file

@ -902,7 +902,7 @@ defmodule Pleroma.UserTest do
assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark) assert [activity] == ActivityPub.fetch_public_activities(%{}) |> Repo.preload(:bookmark)
assert [activity] == assert [%{activity | thread_muted?: CommonAPI.thread_muted?(user2, activity)}] ==
ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2}) ActivityPub.fetch_activities([user2.ap_id | user2.following], %{"user" => user2})
{:ok, _user} = User.deactivate(user) {:ok, _user} = User.deactivate(user)
@ -1251,4 +1251,19 @@ defmodule Pleroma.UserTest do
refute user.info.confirmation_token refute user.info.confirmation_token
end end
end end
describe "ensure_keys_present" do
test "it creates keys for a user and stores them in info" do
user = insert(:user)
refute is_binary(user.info.keys)
{:ok, user} = User.ensure_keys_present(user)
assert is_binary(user.info.keys)
end
test "it doesn't create keys if there already are some" do
user = insert(:user, %{info: %{keys: "xxx"}})
{:ok, user} = User.ensure_keys_present(user)
assert user.info.keys == "xxx"
end
end
end end

View file

@ -1005,7 +1005,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do
describe "update" do describe "update" do
test "it creates an update activity with the new user data" do test "it creates an update activity with the new user data" do
user = insert(:user) user = insert(:user)
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user}) user_data = Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
{:ok, update} = {:ok, update} =

View file

@ -17,7 +17,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
federated_timeline_removal: [], federated_timeline_removal: [],
report_removal: [], report_removal: [],
reject: [], reject: [],
accept: [] accept: [],
avatar_removal: [],
banner_removal: []
) )
on_exit(fn -> on_exit(fn ->
@ -143,6 +145,24 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
assert SimplePolicy.filter(local_message) == {:ok, local_message} assert SimplePolicy.filter(local_message) == {:ok, local_message}
end end
test "has a matching host but only as:Public in to" do
{_actor, ftl_message} = build_ftl_actor_and_message()
ftl_message_actor_host =
ftl_message
|> Map.fetch!("actor")
|> URI.parse()
|> Map.fetch!(:host)
ftl_message = Map.put(ftl_message, "cc", [])
Config.put([:mrf_simple, :federated_timeline_removal], [ftl_message_actor_host])
assert {:ok, ftl_message} = SimplePolicy.filter(ftl_message)
refute "https://www.w3.org/ns/activitystreams#Public" in ftl_message["to"]
assert "https://www.w3.org/ns/activitystreams#Public" in ftl_message["cc"]
end
end end
defp build_ftl_actor_and_message do defp build_ftl_actor_and_message do
@ -206,6 +226,60 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
end end
end end
describe "when :avatar_removal" do
test "is empty" do
Config.put([:mrf_simple, :avatar_removal], [])
remote_user = build_remote_user()
assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
end
test "is not empty but it doesn't have a matching host" do
Config.put([:mrf_simple, :avatar_removal], ["non.matching.remote"])
remote_user = build_remote_user()
assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
end
test "has a matching host" do
Config.put([:mrf_simple, :avatar_removal], ["remote.instance"])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
refute filtered["icon"]
end
end
describe "when :banner_removal" do
test "is empty" do
Config.put([:mrf_simple, :banner_removal], [])
remote_user = build_remote_user()
assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
end
test "is not empty but it doesn't have a matching host" do
Config.put([:mrf_simple, :banner_removal], ["non.matching.remote"])
remote_user = build_remote_user()
assert SimplePolicy.filter(remote_user) == {:ok, remote_user}
end
test "has a matching host" do
Config.put([:mrf_simple, :banner_removal], ["remote.instance"])
remote_user = build_remote_user()
{:ok, filtered} = SimplePolicy.filter(remote_user)
refute filtered["image"]
end
end
defp build_local_message do defp build_local_message do
%{ %{
"actor" => "#{Pleroma.Web.base_url()}/users/alice", "actor" => "#{Pleroma.Web.base_url()}/users/alice",
@ -217,4 +291,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do
defp build_remote_message do defp build_remote_message do
%{"actor" => "https://remote.instance/users/bob"} %{"actor" => "https://remote.instance/users/bob"}
end end
defp build_remote_user do
%{
"id" => "https://remote.instance/users/bob",
"icon" => %{
"url" => "http://example.com/image.jpg",
"type" => "Image"
},
"image" => %{
"url" => "http://example.com/image.jpg",
"type" => "Image"
},
"type" => "Person"
}
end
end end

View file

@ -2,11 +2,12 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.User
alias Pleroma.Web.ActivityPub.UserView alias Pleroma.Web.ActivityPub.UserView
test "Renders a user, including the public key" do test "Renders a user, including the public key" do
user = insert(:user) user = insert(:user)
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
result = UserView.render("user.json", %{user: user}) result = UserView.render("user.json", %{user: user})
@ -18,7 +19,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
test "Does not add an avatar image if the user hasn't set one" do test "Does not add an avatar image if the user hasn't set one" do
user = insert(:user) user = insert(:user)
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
result = UserView.render("user.json", %{user: user}) result = UserView.render("user.json", %{user: user})
refute result["icon"] refute result["icon"]
@ -32,7 +33,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
} }
) )
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
result = UserView.render("user.json", %{user: user}) result = UserView.render("user.json", %{user: user})
assert result["icon"]["url"] == "https://someurl" assert result["icon"]["url"] == "https://someurl"
@ -42,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
describe "endpoints" do describe "endpoints" do
test "local users have a usable endpoints structure" do test "local users have a usable endpoints structure" do
user = insert(:user) user = insert(:user)
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
result = UserView.render("user.json", %{user: user}) result = UserView.render("user.json", %{user: user})
@ -58,7 +59,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
test "remote users have an empty endpoints structure" do test "remote users have an empty endpoints structure" do
user = insert(:user, local: false) user = insert(:user, local: false)
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
result = UserView.render("user.json", %{user: user}) result = UserView.render("user.json", %{user: user})
@ -68,7 +69,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
test "instance users do not expose oAuth endpoints" do test "instance users do not expose oAuth endpoints" do
user = insert(:user, nickname: nil, local: true) user = insert(:user, nickname: nil, local: true)
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
result = UserView.render("user.json", %{user: user}) result = UserView.render("user.json", %{user: user})

View file

@ -397,14 +397,14 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
end end
end end
test "/api/pleroma/admin/invite_token" do test "/api/pleroma/admin/users/invite_token" do
admin = insert(:user, info: %{is_admin: true}) admin = insert(:user, info: %{is_admin: true})
conn = conn =
build_conn() build_conn()
|> assign(:user, admin) |> assign(:user, admin)
|> put_req_header("accept", "application/json") |> put_req_header("accept", "application/json")
|> get("/api/pleroma/admin/invite_token") |> get("/api/pleroma/admin/users/invite_token")
assert conn.status == 200 assert conn.status == 200
end end
@ -437,27 +437,31 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
user = insert(:user, local: false, tags: ["foo", "bar"]) user = insert(:user, local: false, tags: ["foo", "bar"])
conn = get(conn, "/api/pleroma/admin/users?page=1") conn = get(conn, "/api/pleroma/admin/users?page=1")
users =
[
%{
"deactivated" => admin.info.deactivated,
"id" => admin.id,
"nickname" => admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"local" => true,
"tags" => []
},
%{
"deactivated" => user.info.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"local" => false,
"tags" => ["foo", "bar"]
}
]
|> Enum.sort_by(& &1["nickname"])
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"count" => 2, "count" => 2,
"page_size" => 50, "page_size" => 50,
"users" => [ "users" => users
%{
"deactivated" => admin.info.deactivated,
"id" => admin.id,
"nickname" => admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"local" => true,
"tags" => []
},
%{
"deactivated" => user.info.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"local" => false,
"tags" => ["foo", "bar"]
}
]
} }
end end
@ -659,35 +663,39 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|> assign(:user, admin) |> assign(:user, admin)
|> get("/api/pleroma/admin/users?filters=local") |> get("/api/pleroma/admin/users?filters=local")
users =
[
%{
"deactivated" => user.info.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"local" => true,
"tags" => []
},
%{
"deactivated" => admin.info.deactivated,
"id" => admin.id,
"nickname" => admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"local" => true,
"tags" => []
},
%{
"deactivated" => false,
"id" => old_admin.id,
"local" => true,
"nickname" => old_admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"tags" => []
}
]
|> Enum.sort_by(& &1["nickname"])
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"count" => 3, "count" => 3,
"page_size" => 50, "page_size" => 50,
"users" => [ "users" => users
%{
"deactivated" => user.info.deactivated,
"id" => user.id,
"nickname" => user.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"local" => true,
"tags" => []
},
%{
"deactivated" => admin.info.deactivated,
"id" => admin.id,
"nickname" => admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"local" => true,
"tags" => []
},
%{
"deactivated" => false,
"id" => old_admin.id,
"local" => true,
"nickname" => old_admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"tags" => []
}
]
} }
end end
@ -698,27 +706,31 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
conn = get(conn, "/api/pleroma/admin/users?filters=is_admin") conn = get(conn, "/api/pleroma/admin/users?filters=is_admin")
users =
[
%{
"deactivated" => false,
"id" => admin.id,
"nickname" => admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"local" => admin.local,
"tags" => []
},
%{
"deactivated" => false,
"id" => second_admin.id,
"nickname" => second_admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"local" => second_admin.local,
"tags" => []
}
]
|> Enum.sort_by(& &1["nickname"])
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"count" => 2, "count" => 2,
"page_size" => 50, "page_size" => 50,
"users" => [ "users" => users
%{
"deactivated" => false,
"id" => admin.id,
"nickname" => admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"local" => admin.local,
"tags" => []
},
%{
"deactivated" => false,
"id" => second_admin.id,
"nickname" => second_admin.nickname,
"roles" => %{"admin" => true, "moderator" => false},
"local" => second_admin.local,
"tags" => []
}
]
} }
end end
@ -753,27 +765,31 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second") conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second")
users =
[
%{
"deactivated" => false,
"id" => user1.id,
"nickname" => user1.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"local" => user1.local,
"tags" => ["first"]
},
%{
"deactivated" => false,
"id" => user2.id,
"nickname" => user2.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"local" => user2.local,
"tags" => ["second"]
}
]
|> Enum.sort_by(& &1["nickname"])
assert json_response(conn, 200) == %{ assert json_response(conn, 200) == %{
"count" => 2, "count" => 2,
"page_size" => 50, "page_size" => 50,
"users" => [ "users" => users
%{
"deactivated" => false,
"id" => user1.id,
"nickname" => user1.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"local" => user1.local,
"tags" => ["first"]
},
%{
"deactivated" => false,
"id" => user2.id,
"nickname" => user2.nickname,
"roles" => %{"admin" => false, "moderator" => false},
"local" => user2.local,
"tags" => ["second"]
}
]
} }
end end

View file

@ -0,0 +1,52 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.FallbackTest do
use Pleroma.Web.ConnCase
import Pleroma.Factory
test "GET /registration/:token", %{conn: conn} do
assert conn
|> get("/registration/foo")
|> html_response(200) =~ "<!--server-generated-meta-->"
end
test "GET /:maybe_nickname_or_id", %{conn: conn} do
user = insert(:user)
assert conn
|> get("/foo")
|> html_response(200) =~ "<!--server-generated-meta-->"
refute conn
|> get("/" <> user.nickname)
|> html_response(200) =~ "<!--server-generated-meta-->"
end
test "GET /api*path", %{conn: conn} do
assert conn
|> get("/api/foo")
|> json_response(404) == %{"error" => "Not implemented"}
end
test "GET /*path", %{conn: conn} do
assert conn
|> get("/foo")
|> html_response(200) =~ "<!--server-generated-meta-->"
assert conn
|> get("/foo/bar")
|> html_response(200) =~ "<!--server-generated-meta-->"
end
test "OPTIONS /*path", %{conn: conn} do
assert conn
|> options("/foo")
|> response(204) == ""
assert conn
|> options("/foo/bar")
|> response(204) == ""
end
end

View file

@ -55,7 +55,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
fields: [], fields: [],
bot: false, bot: false,
source: %{ source: %{
note: "", note: "valid html",
sensitive: false, sensitive: false,
pleroma: %{} pleroma: %{}
}, },
@ -120,7 +120,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
fields: [], fields: [],
bot: true, bot: true,
source: %{ source: %{
note: "", note: user.bio,
sensitive: false, sensitive: false,
pleroma: %{} pleroma: %{}
}, },
@ -209,7 +209,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
fields: [], fields: [],
bot: true, bot: true,
source: %{ source: %{
note: "", note: user.bio,
sensitive: false, sensitive: false,
pleroma: %{} pleroma: %{}
}, },

View file

@ -0,0 +1,59 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MongooseIMController do
use Pleroma.Web.ConnCase
import Pleroma.Factory
test "/user_exists", %{conn: conn} do
_user = insert(:user, nickname: "lain")
_remote_user = insert(:user, nickname: "alice", local: false)
res =
conn
|> get(mongoose_im_path(conn, :user_exists), user: "lain")
|> json_response(200)
assert res == true
res =
conn
|> get(mongoose_im_path(conn, :user_exists), user: "alice")
|> json_response(404)
assert res == false
res =
conn
|> get(mongoose_im_path(conn, :user_exists), user: "bob")
|> json_response(404)
assert res == false
end
test "/check_password", %{conn: conn} do
user = insert(:user, password_hash: Comeonin.Pbkdf2.hashpwsalt("cool"))
res =
conn
|> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "cool")
|> json_response(200)
assert res == true
res =
conn
|> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "uncool")
|> json_response(403)
assert res == false
res =
conn
|> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool")
|> json_response(404)
assert res == false
end
end

View file

@ -7,6 +7,22 @@ defmodule Pleroma.Web.NodeInfoTest do
import Pleroma.Factory import Pleroma.Factory
test "GET /.well-known/nodeinfo", %{conn: conn} do
links =
conn
|> get("/.well-known/nodeinfo")
|> json_response(200)
|> Map.fetch!("links")
Enum.each(links, fn link ->
href = Map.fetch!(link, "href")
conn
|> get(href)
|> json_response(200)
end)
end
test "nodeinfo shows staff accounts", %{conn: conn} do test "nodeinfo shows staff accounts", %{conn: conn} do
moderator = insert(:user, %{local: true, info: %{is_moderator: true}}) moderator = insert(:user, %{local: true, info: %{is_moderator: true}})
admin = insert(:user, %{local: true, info: %{is_admin: true}}) admin = insert(:user, %{local: true, info: %{is_admin: true}})
@ -32,70 +48,6 @@ defmodule Pleroma.Web.NodeInfoTest do
result["metadata"]["restrictedNicknames"] result["metadata"]["restrictedNicknames"]
end end
test "returns 404 when federation is disabled", %{conn: conn} do
instance =
Application.get_env(:pleroma, :instance)
|> Keyword.put(:federating, false)
Application.put_env(:pleroma, :instance, instance)
conn
|> get("/.well-known/nodeinfo")
|> json_response(404)
conn
|> get("/nodeinfo/2.1.json")
|> json_response(404)
instance =
Application.get_env(:pleroma, :instance)
|> Keyword.put(:federating, true)
Application.put_env(:pleroma, :instance, instance)
end
test "returns 200 when federation is enabled", %{conn: conn} do
conn
|> get("/.well-known/nodeinfo")
|> json_response(200)
conn
|> get("/nodeinfo/2.1.json")
|> json_response(200)
end
test "returns 404 when federation is disabled (nodeinfo 2.0)", %{conn: conn} do
instance =
Application.get_env(:pleroma, :instance)
|> Keyword.put(:federating, false)
Application.put_env(:pleroma, :instance, instance)
conn
|> get("/.well-known/nodeinfo")
|> json_response(404)
conn
|> get("/nodeinfo/2.0.json")
|> json_response(404)
instance =
Application.get_env(:pleroma, :instance)
|> Keyword.put(:federating, true)
Application.put_env(:pleroma, :instance, instance)
end
test "returns 200 when federation is enabled (nodeinfo 2.0)", %{conn: conn} do
conn
|> get("/.well-known/nodeinfo")
|> json_response(200)
conn
|> get("/nodeinfo/2.0.json")
|> json_response(200)
end
test "returns software.repository field in nodeinfo 2.1", %{conn: conn} do test "returns software.repository field in nodeinfo 2.1", %{conn: conn} do
conn conn
|> get("/.well-known/nodeinfo") |> get("/.well-known/nodeinfo")

View file

@ -69,4 +69,17 @@ defmodule Pleroma.Web.OAuth.TokenTest do
assert tokens == 2 assert tokens == 2
end end
test "deletes expired tokens" do
insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3))
insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3))
t3 = insert(:oauth_token)
t4 = insert(:oauth_token, valid_until: Timex.shift(Timex.now(), minutes: 10))
{tokens, _} = Token.delete_expired_tokens()
assert tokens == 2
available_tokens = Pleroma.Repo.all(Token)
token_ids = available_tokens |> Enum.map(& &1.id)
assert token_ids == [t3.id, t4.id]
end
end end

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Web.Salmon.SalmonTest do defmodule Pleroma.Web.Salmon.SalmonTest do
use Pleroma.DataCase use Pleroma.DataCase
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Keys
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.Federator.Publisher
@ -34,12 +35,6 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
assert Salmon.decode_and_validate(@wrong_magickey, salmon) == :error assert Salmon.decode_and_validate(@wrong_magickey, salmon) == :error
end end
test "generates an RSA private key pem" do
{:ok, key} = Salmon.generate_rsa_pem()
assert is_binary(key)
assert Regex.match?(~r/RSA/, key)
end
test "it encodes a magic key from a public key" do test "it encodes a magic key from a public key" do
key = Salmon.decode_key(@magickey) key = Salmon.decode_key(@magickey)
magic_key = Salmon.encode_key(key) magic_key = Salmon.encode_key(key)
@ -51,18 +46,10 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
_key = Salmon.decode_key(@magickey_friendica) _key = Salmon.decode_key(@magickey_friendica)
end end
test "returns a public and private key from a pem" do
pem = File.read!("test/fixtures/private_key.pem")
{:ok, private, public} = Salmon.keys_from_pem(pem)
assert elem(private, 0) == :RSAPrivateKey
assert elem(public, 0) == :RSAPublicKey
end
test "encodes an xml payload with a private key" do test "encodes an xml payload with a private key" do
doc = File.read!("test/fixtures/incoming_note_activity.xml") doc = File.read!("test/fixtures/incoming_note_activity.xml")
pem = File.read!("test/fixtures/private_key.pem") pem = File.read!("test/fixtures/private_key.pem")
{:ok, private, public} = Salmon.keys_from_pem(pem) {:ok, private, public} = Keys.keys_from_pem(pem)
# Let's try a roundtrip. # Let's try a roundtrip.
{:ok, salmon} = Salmon.encode(private, doc) {:ok, salmon} = Salmon.encode(private, doc)
@ -105,7 +92,7 @@ defmodule Pleroma.Web.Salmon.SalmonTest do
{:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]}) {:ok, activity} = Repo.insert(%Activity{data: activity_data, recipients: activity_data["to"]})
user = User.get_cached_by_ap_id(activity.data["actor"]) user = User.get_cached_by_ap_id(activity.data["actor"])
{:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) {:ok, user} = User.ensure_keys_present(user)
Salmon.publish(user, activity) Salmon.publish(user, activity)

View file

@ -105,19 +105,4 @@ defmodule Pleroma.Web.WebFingerTest do
assert template == "http://status.alpicola.com/main/xrd?uri={uri}" assert template == "http://status.alpicola.com/main/xrd?uri={uri}"
end end
end end
describe "ensure_keys_present" do
test "it creates keys for a user and stores them in info" do
user = insert(:user)
refute is_binary(user.info.keys)
{:ok, user} = WebFinger.ensure_keys_present(user)
assert is_binary(user.info.keys)
end
test "it doesn't create keys if there already are some" do
user = insert(:user, %{info: %{keys: "xxx"}})
{:ok, user} = WebFinger.ensure_keys_present(user)
assert user.info.keys == "xxx"
end
end
end end

View file

@ -5,7 +5,6 @@
defmodule Pleroma.Web.Websub.WebsubControllerTest do defmodule Pleroma.Web.Websub.WebsubControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
import Pleroma.Factory import Pleroma.Factory
alias Pleroma.Activity
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Web.Websub alias Pleroma.Web.Websub
alias Pleroma.Web.Websub.WebsubClientSubscription alias Pleroma.Web.Websub.WebsubClientSubscription
@ -52,7 +51,7 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do
end end
describe "websub_incoming" do describe "websub_incoming" do
test "handles incoming feed updates", %{conn: conn} do test "accepts incoming feed updates", %{conn: conn} do
websub = insert(:websub_client_subscription) websub = insert(:websub_client_subscription)
doc = "some stuff" doc = "some stuff"
signature = Websub.sign(websub.secret, doc) signature = Websub.sign(websub.secret, doc)
@ -64,8 +63,6 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do
|> post("/push/subscriptions/#{websub.id}", doc) |> post("/push/subscriptions/#{websub.id}", doc)
assert response(conn, 200) == "OK" assert response(conn, 200) == "OK"
assert length(Repo.all(Activity)) == 1
end end
test "rejects incoming feed updates with the wrong signature", %{conn: conn} do test "rejects incoming feed updates with the wrong signature", %{conn: conn} do
@ -80,8 +77,6 @@ defmodule Pleroma.Web.Websub.WebsubControllerTest do
|> post("/push/subscriptions/#{websub.id}", doc) |> post("/push/subscriptions/#{websub.id}", doc)
assert response(conn, 500) == "Error" assert response(conn, 500) == "Error"
assert Enum.empty?(Repo.all(Activity))
end end
end end
end end