Merge branch 'feature/custom-runtime-modules' into 'develop'

Add support for custom modules at runtime

Closes #1448

See merge request pleroma/pleroma!2039
This commit is contained in:
rinpatch 2019-12-21 11:56:24 +00:00
commit c51e93978e
19 changed files with 84 additions and 19 deletions

View file

@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Deprecated `User.Info` embedded schema (fields moved to `User`) - Deprecated `User.Info` embedded schema (fields moved to `User`)
- Store status data inside Flag activity - Store status data inside Flag activity
- Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`). - Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`).
- Logger: default log level changed from `warn` to `info`.
<details> <details>
<summary>API Changes</summary> <summary>API Changes</summary>
@ -51,6 +52,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache). - Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache).
- MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers. - MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers.
- User notification settings: Add `privacy_option` option. - User notification settings: Add `privacy_option` option.
- Support for custom Elixir modules (such as MRF policies)
- User settings: Add _This account is a_ option. - User settings: Add _This account is a_ option.
- OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`). - OAuth: admin scopes support (relevant setting: `[:auth, :enforce_oauth_admin_scope_usage]`).
<details> <details>

View file

@ -621,6 +621,8 @@
activity_pub: nil, activity_pub: nil,
activity_pub_question: 30_000 activity_pub_question: 30_000
config :pleroma, :modules, runtime_dir: "instance/modules"
config :swarm, node_blacklist: [~r/myhtml_.*$/] config :swarm, node_blacklist: [~r/myhtml_.*$/]
# Import environment specific config. This must remain at the bottom # Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above. # of this file so it overrides the configuration defined above.

View file

@ -20,8 +20,8 @@
config :phoenix, serve_endpoints: true config :phoenix, serve_endpoints: true
# Do not print debug messages in production # Do not print debug messages in production
config :logger, :console, level: :warn config :logger, :console, level: :info
config :logger, :ex_syslogger, level: :warn config :logger, :ex_syslogger, level: :info
# ## SSL Support # ## SSL Support
# #

View file

@ -2,6 +2,7 @@
config :pleroma, :instance, static_dir: "/var/lib/pleroma/static" config :pleroma, :instance, static_dir: "/var/lib/pleroma/static"
config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads" config :pleroma, Pleroma.Uploaders.Local, uploads: "/var/lib/pleroma/uploads"
config :pleroma, :modules, runtime_dir: "/var/lib/pleroma/modules"
config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs" config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"

View file

@ -95,6 +95,8 @@
config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock config :pleroma, Pleroma.ReverseProxy.Client, Pleroma.ReverseProxy.ClientMock
config :pleroma, :modules, runtime_dir: "test/fixtures/modules"
if File.exists?("./config/test.secret.exs") do if File.exists?("./config/test.secret.exs") do
import_config "test.secret.exs" import_config "test.secret.exs"
else else

View file

@ -836,3 +836,7 @@ config :auto_linker,
rel: "ugc" rel: "ugc"
] ]
``` ```
## Custom Runtime Modules (`:modules`)
* `runtime_dir`: A path to custom Elixir modules (such as MRF policies).

View file

@ -5,6 +5,7 @@
defmodule Pleroma.Application do defmodule Pleroma.Application do
import Cachex.Spec import Cachex.Spec
use Application use Application
require Logger
@name Mix.Project.config()[:name] @name Mix.Project.config()[:name]
@version Mix.Project.config()[:version] @version Mix.Project.config()[:version]
@ -33,6 +34,7 @@ def start(_type, _args) do
Pleroma.HTML.compile_scrubbers() Pleroma.HTML.compile_scrubbers()
Pleroma.Config.DeprecationWarnings.warn() Pleroma.Config.DeprecationWarnings.warn()
setup_instrumenters() setup_instrumenters()
load_custom_modules()
# Define workers and child supervisors to be supervised # Define workers and child supervisors to be supervised
children = children =
@ -68,6 +70,28 @@ def start(_type, _args) do
Supervisor.start_link(children, opts) Supervisor.start_link(children, opts)
end end
def load_custom_modules do
dir = Pleroma.Config.get([:modules, :runtime_dir])
if dir && File.exists?(dir) do
dir
|> Pleroma.Utils.compile_dir()
|> case do
{:error, _errors, _warnings} ->
raise "Invalid custom modules"
{:ok, modules, _warnings} ->
if @env != :test do
Enum.each(modules, fn mod ->
Logger.info("Custom module loaded: #{inspect(mod)}")
end)
end
:ok
end
end
end
defp setup_instrumenters do defp setup_instrumenters do
require Prometheus.Registry require Prometheus.Registry

View file

@ -10,9 +10,7 @@ def compile_scrubbers do
dir = Path.join(:code.priv_dir(:pleroma), "scrubbers") dir = Path.join(:code.priv_dir(:pleroma), "scrubbers")
dir dir
|> File.ls!() |> Pleroma.Utils.compile_dir()
|> Enum.map(&Path.join(dir, &1))
|> Kernel.ParallelCompiler.compile()
|> case do |> case do
{:error, _errors, _warnings} -> {:error, _errors, _warnings} ->
raise "Compiling scrubbers failed" raise "Compiling scrubbers failed"

View file

@ -154,7 +154,7 @@ defp maybe_date_fetch(headers, date) do
end end
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
Logger.info("Fetching object #{id} via AP") Logger.debug("Fetching object #{id} via AP")
date = Pleroma.Signature.signed_date() date = Pleroma.Signature.signed_date()

12
lib/pleroma/utils.ex Normal file
View file

@ -0,0 +1,12 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Utils do
def compile_dir(dir) when is_binary(dir) do
dir
|> File.ls!()
|> Enum.map(&Path.join(dir, &1))
|> Kernel.ParallelCompiler.compile()
end
end

View file

@ -257,7 +257,7 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
# only accept relayed Creates # only accept relayed Creates
def inbox(conn, %{"type" => "Create"} = params) do def inbox(conn, %{"type" => "Create"} = params) do
Logger.info( Logger.debug(
"Signature missing or not from author, relayed Create message, fetching object from source" "Signature missing or not from author, relayed Create message, fetching object from source"
) )
@ -270,11 +270,11 @@ def inbox(conn, params) do
headers = Enum.into(conn.req_headers, %{}) headers = Enum.into(conn.req_headers, %{})
if String.contains?(headers["signature"], params["actor"]) do if String.contains?(headers["signature"], params["actor"]) do
Logger.info( Logger.debug(
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!" "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
) )
Logger.info(inspect(conn.req_headers)) Logger.debug(inspect(conn.req_headers))
end end
json(conn, dgettext("errors", "error")) json(conn, dgettext("errors", "error"))

View file

@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do
@impl true @impl true
def filter(object) do def filter(object) do
Logger.info("REJECTING #{inspect(object)}") Logger.debug("REJECTING #{inspect(object)}")
{:reject, object} {:reject, object}
end end

View file

@ -18,7 +18,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do
] ]
def perform(:prefetch, url) do def perform(:prefetch, url) do
Logger.info("Prefetching #{inspect(url)}") Logger.debug("Prefetching #{inspect(url)}")
url url
|> MediaProxy.url() |> MediaProxy.url()

View file

@ -48,7 +48,7 @@ def is_representable?(%Activity{} = activity) do
* `id`: the ActivityStreams URI of the message * `id`: the ActivityStreams URI of the message
""" """
def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do def publish_one(%{inbox: inbox, json: json, actor: %User{} = actor, id: id} = params) do
Logger.info("Federating #{id} to #{inbox}") Logger.debug("Federating #{id} to #{inbox}")
%{host: host, path: path} = URI.parse(inbox) %{host: host, path: path} = URI.parse(inbox)
digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64()) digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
@ -228,7 +228,7 @@ def publish(%User{} = actor, %Activity{} = activity) do
public = is_public?(activity) public = is_public?(activity)
if public && Config.get([:instance, :allow_relay]) do if public && Config.get([:instance, :allow_relay]) do
Logger.info(fn -> "Relaying #{activity.data["id"]} out" end) Logger.debug(fn -> "Relaying #{activity.data["id"]} out" end)
Relay.publish(activity) Relay.publish(activity)
end end

View file

@ -59,7 +59,7 @@ defmodule Pleroma.Web.Endpoint do
plug(Pleroma.Plugs.TrailingFormatPlug) plug(Pleroma.Plugs.TrailingFormatPlug)
plug(Plug.RequestId) plug(Plug.RequestId)
plug(Plug.Logger) plug(Plug.Logger, log: :debug)
plug(Pleroma.Plugs.Parsers) plug(Pleroma.Plugs.Parsers)

View file

@ -58,7 +58,7 @@ def perform(:publish, activity) do
end end
def perform(:incoming_ap_doc, params) do def perform(:incoming_ap_doc, params) do
Logger.info("Handling incoming AP activity") Logger.debug("Handling incoming AP activity")
params = Utils.normalize_params(params) params = Utils.normalize_params(params)
@ -71,13 +71,13 @@ def perform(:incoming_ap_doc, params) do
{:ok, activity} {:ok, activity}
else else
%Activity{} -> %Activity{} ->
Logger.info("Already had #{params["id"]}") Logger.debug("Already had #{params["id"]}")
:error :error
_e -> _e ->
# Just drop those for now # Just drop those for now
Logger.info("Unhandled activity") Logger.debug("Unhandled activity")
Logger.info(Jason.encode!(params, pretty: true)) Logger.debug(Jason.encode!(params, pretty: true))
:error :error
end end
end end

View file

@ -47,7 +47,7 @@ def publish(%User{} = user, %Activity{} = activity) do
Config.get([:instance, :federation_publisher_modules]) Config.get([:instance, :federation_publisher_modules])
|> Enum.each(fn module -> |> Enum.each(fn module ->
if module.is_representable?(activity) do if module.is_representable?(activity) do
Logger.info("Publishing #{activity.data["id"]} using #{inspect(module)}") Logger.debug("Publishing #{activity.data["id"]} using #{inspect(module)}")
module.publish(user, activity) module.publish(user, activity)
end end
end) end)

View file

@ -0,0 +1,9 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule RuntimeModule do
@moduledoc """
This is a dummy module to test custom runtime modules.
"""
end

11
test/runtime_test.exs Normal file
View file

@ -0,0 +1,11 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.RuntimeTest do
use ExUnit.Case, async: true
test "it loads custom runtime modules" do
assert Code.ensure_compiled?(RuntimeModule)
end
end