2018-12-23 20:04:54 +00:00
# Pleroma: A lightweight social networking server
2021-01-13 06:49:20 +00:00
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
2018-12-23 20:04:54 +00:00
# SPDX-License-Identifier: AGPL-3.0-only
2018-05-10 16:34:09 +00:00
defmodule Pleroma.Web.ActivityPub.MRF do
2020-11-10 16:18:53 +00:00
require Logger
2020-12-16 16:51:48 +00:00
@behaviour Pleroma.Web.ActivityPub.MRF.PipelineFiltering
2020-11-11 07:10:57 +00:00
@mrf_config_descriptions [
%{
group : :pleroma ,
key : :mrf ,
tab : :mrf ,
label : " MRF " ,
type : :group ,
description : " General MRF settings " ,
children : [
%{
key : :policies ,
type : [ :module , { :list , :module } ] ,
description :
" A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name. " ,
2021-08-14 11:42:32 +00:00
suggestions : { :list_behaviour_implementations , Pleroma.Web.ActivityPub.MRF.Policy }
2020-11-11 07:10:57 +00:00
} ,
%{
key : :transparency ,
label : " MRF transparency " ,
type : :boolean ,
description :
" Make the content of your Message Rewrite Facility settings public (via nodeinfo) "
} ,
%{
key : :transparency_exclusions ,
label : " MRF transparency exclusions " ,
2020-12-26 10:35:05 +00:00
type : { :list , :tuple } ,
key_placeholder : " instance " ,
value_placeholder : " reason " ,
2020-11-11 07:10:57 +00:00
description :
2020-12-26 10:35:05 +00:00
" Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. You can also provide a reason for excluding these instance names. The instances and reasons won't be publicly disclosed. " ,
2020-11-11 07:10:57 +00:00
suggestions : [
" exclusion.com "
]
2022-08-27 10:57:57 +00:00
} ,
%{
key : :transparency_obfuscate_domains ,
label : " MRF domain obfuscation " ,
type : { :list , :string } ,
description :
" Obfuscate domains in MRF transparency. This is useful if the domain you're blocking contains words you don't want displayed, but still want to disclose the MRF settings. " ,
suggestions : [
" badword.com "
]
2020-11-11 07:10:57 +00:00
}
]
}
]
2020-11-10 16:18:53 +00:00
@default_description %{
label : " " ,
2020-11-11 07:10:57 +00:00
description : " "
2020-11-10 16:18:53 +00:00
}
@required_description_keys [ :key , :related_policy ]
2022-12-09 20:01:38 +00:00
def filter_one ( policy , %{ " type " = > type } = message )
when type in [ " Undo " , " Block " , " Delete " ] and
policy != Pleroma.Web.ActivityPub.MRF.SimplePolicy do
{ :ok , message }
end
2022-09-06 19:24:02 +00:00
def filter_one ( policy , message ) do
2023-08-01 10:43:50 +00:00
Code . ensure_loaded! ( policy )
2022-09-06 19:24:02 +00:00
should_plug_history? =
if function_exported? ( policy , :history_awareness , 0 ) do
policy . history_awareness ( )
else
:manual
end
|> Kernel . == ( :auto )
if not should_plug_history? do
policy . filter ( message )
else
main_result = policy . filter ( message )
with { _ , { :ok , main_message } } <- { :main , main_result } ,
{ _ ,
%{
" formerRepresentations " = > %{
" orderedItems " = > [ _ | _ ]
}
} } = { _ , object } <- { :object , message [ " object " ] } ,
{ _ , { :ok , new_history } } <-
{ :history ,
Pleroma.Object.Updater . for_each_history_item (
object [ " formerRepresentations " ] ,
object ,
fn item ->
with { :ok , filtered } <- policy . filter ( Map . put ( message , " object " , item ) ) do
{ :ok , filtered [ " object " ] }
else
e -> e
end
end
) } do
{ :ok , put_in ( main_message , [ " object " , " formerRepresentations " ] , new_history ) }
else
{ :main , _ } -> main_result
{ :object , _ } -> main_result
{ :history , e } -> e
end
end
end
2020-09-12 10:05:36 +00:00
def filter ( policies , %{ } = message ) do
2019-06-02 09:44:42 +00:00
policies
2020-09-12 10:05:36 +00:00
|> Enum . reduce ( { :ok , message } , fn
2022-09-06 19:24:02 +00:00
policy , { :ok , message } -> filter_one ( policy , message )
2020-02-11 09:53:24 +00:00
_ , error -> error
2018-05-10 16:34:09 +00:00
end )
end
2019-06-02 09:44:42 +00:00
def filter ( %{ } = object ) , do : get_policies ( ) |> filter ( object )
2020-12-16 16:51:48 +00:00
@impl true
2020-09-12 10:05:36 +00:00
def pipeline_filter ( %{ } = message , meta ) do
object = meta [ :object_data ]
ap_id = message [ " object " ]
if object && ap_id do
with { :ok , message } <- filter ( Map . put ( message , " object " , object ) ) do
meta = Keyword . put ( meta , :object_data , message [ " object " ] )
{ :ok , Map . put ( message , " object " , ap_id ) , meta }
else
{ err , message } -> { err , message , meta }
end
else
{ err , message } = filter ( message )
{ err , message , meta }
end
end
2019-03-05 03:18:43 +00:00
def get_policies do
2020-12-28 22:21:53 +00:00
Pleroma.Config . get ( [ :mrf , :policies ] , [ ] )
|> get_policies ( )
2022-09-05 16:22:33 +00:00
|> Enum . concat ( [
Pleroma.Web.ActivityPub.MRF.HashtagPolicy ,
2022-11-26 21:06:20 +00:00
Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy ,
2023-05-22 22:53:44 +00:00
Pleroma.Web.ActivityPub.MRF.NormalizeMarkup ,
Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy
2022-09-05 16:22:33 +00:00
] )
|> Enum . uniq ( )
2018-05-10 16:34:09 +00:00
end
2018-05-10 16:51:58 +00:00
defp get_policies ( policy ) when is_atom ( policy ) , do : [ policy ]
defp get_policies ( policies ) when is_list ( policies ) , do : policies
defp get_policies ( _ ) , do : [ ]
2019-07-22 14:33:58 +00:00
2022-11-06 23:50:32 +00:00
# Matches the following:
# - https://baddomain.net
# - https://extra.baddomain.net/
# Does NOT match the following:
# - https://maybebaddomain.net/
2023-01-15 18:57:49 +00:00
# *.baddomain.net
2022-11-07 22:33:18 +00:00
def subdomain_regex ( " *. " <> domain ) , do : subdomain_regex ( domain )
2023-01-15 18:57:49 +00:00
# baddomain.net
2022-11-06 23:50:32 +00:00
def subdomain_regex ( domain ) do
2023-01-15 18:57:49 +00:00
if String . ends_with? ( domain , " .* " ) do
~r/ ^(.+ \. )? #{ Regex . escape ( String . replace_suffix ( domain , " .* " , " " ) ) } \. (.+)$ /i
else
~r/ ^(.+ \. )? #{ Regex . escape ( domain ) } $ /i
end
2022-11-06 23:50:32 +00:00
end
2019-07-22 14:33:58 +00:00
@spec subdomains_regex ( [ String . t ( ) ] ) :: [ Regex . t ( ) ]
def subdomains_regex ( domains ) when is_list ( domains ) do
2022-11-06 23:50:32 +00:00
Enum . map ( domains , & subdomain_regex / 1 )
2019-07-22 14:33:58 +00:00
end
@spec subdomain_match? ( [ Regex . t ( ) ] , String . t ( ) ) :: boolean ( )
def subdomain_match? ( domains , host ) do
Enum . any? ( domains , fn domain -> Regex . match? ( domain , host ) end )
end
2019-08-13 21:26:24 +00:00
2020-10-02 12:51:39 +00:00
@spec instance_list_from_tuples ( [ { String . t ( ) , String . t ( ) } ] ) :: [ String . t ( ) ]
def instance_list_from_tuples ( list ) do
Enum . map ( list , fn { instance , _ } -> instance end )
end
2019-08-13 21:26:24 +00:00
def describe ( policies ) do
2019-08-13 21:52:54 +00:00
{ :ok , policy_configs } =
policies
|> Enum . reduce ( { :ok , %{ } } , fn
policy , { :ok , data } ->
{ :ok , policy_data } = policy . describe ( )
{ :ok , Map . merge ( data , policy_data ) }
2019-08-13 21:26:24 +00:00
2019-08-13 21:52:54 +00:00
_ , error ->
error
end )
mrf_policies =
get_policies ( )
|> Enum . map ( fn policy -> to_string ( policy ) |> String . split ( " . " ) |> List . last ( ) end )
2020-03-21 06:47:05 +00:00
exclusions = Pleroma.Config . get ( [ :mrf , :transparency_exclusions ] )
2019-08-13 21:52:54 +00:00
base =
%{
mrf_policies : mrf_policies ,
2019-08-13 22:36:24 +00:00
exclusions : length ( exclusions ) > 0
2019-08-13 21:52:54 +00:00
}
|> Map . merge ( policy_configs )
{ :ok , base }
2019-08-13 21:26:24 +00:00
end
2019-08-13 22:36:24 +00:00
def describe , do : get_policies ( ) |> describe ( )
2020-11-10 16:18:53 +00:00
def config_descriptions do
2021-06-07 19:51:25 +00:00
Pleroma.Web.ActivityPub.MRF.Policy
2020-11-10 16:18:53 +00:00
|> Pleroma.Docs.Generator . list_behaviour_implementations ( )
|> config_descriptions ( )
end
def config_descriptions ( policies ) do
2020-11-11 07:10:57 +00:00
Enum . reduce ( policies , @mrf_config_descriptions , fn policy , acc ->
2020-11-10 16:18:53 +00:00
if function_exported? ( policy , :config_description , 0 ) do
description =
@default_description
|> Map . merge ( policy . config_description )
|> Map . put ( :group , :pleroma )
|> Map . put ( :tab , :mrf )
|> Map . put ( :type , :group )
if Enum . all? ( @required_description_keys , & Map . has_key? ( description , &1 ) ) do
[ description | acc ]
else
2023-08-01 10:43:50 +00:00
Logger . warning (
2021-10-06 06:08:21 +00:00
" #{ policy } config description doesn't have one or all required keys #{ inspect ( @required_description_keys ) } "
2020-11-10 16:18:53 +00:00
)
acc
end
else
2020-11-11 15:49:15 +00:00
Logger . debug (
2020-11-10 16:18:53 +00:00
" #{ policy } is excluded from config descriptions, because does not implement `config_description/0` method. "
)
acc
end
end )
end
2018-05-10 16:34:09 +00:00
end