Commit f8e672ea authored by Alex Castaño's avatar Alex Castaño

More documentation about the code

parent 0e2d3e14
Pipeline #32796682 failed with stages
in 3 minutes and 2 seconds
......@@ -16,6 +16,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
# For Announce activities, we filter the recipients based on following status for any actors
# that match actual users. See issue #164 for more information about why this is necessary.
defp get_recipients(%{"type" => "Announce"} = data) do
# It seems it does not implement bto and bcc (private)
# probably because all is "public"
to = data["to"] || []
cc = data["cc"] || []
recipients = to ++ cc
......@@ -36,15 +38,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
defp get_recipients(data) do
# Again
# It seems it does not implement bto and bcc (private)
# probably because all is "public"
to = data["to"] || []
cc = data["cc"] || []
recipients = to ++ cc
{recipients, to, cc}
end
# This is Mastodon/Pleroma related stuff
defp check_actor_is_active(actor) do
if not is_nil(actor) do
with user <- User.get_cached_by_ap_id(actor),
# This is not standard
nil <- user.info["deactivated"] do
:ok
else
......@@ -55,22 +62,56 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
# Insert, it seems an activity insert only when needed
# It is called only by the same module in create function,
# so it should be private function.
# However, it also used by Test, for this reason it is public.
# Test should not relayes on "private" functions to work
# Why in insert is doing the notification thing and the stream,
# why not in create?
def insert(map, local \\ true) when is_map(map) do
# This is really really really confusing.
# I visited like 10 times this code and I didn't realize until know what is doing.
# So it is checking if the activity has been already inserted or not.
# The main path of the function is when the object is not inserted, so it inserts the activity
# To check it out uses the following line:
# with nil <- Activity.normalize(map),
# so what this really means is: find in our database this activity by id
# if is not found returns nil, so it goes on with the insertion (the main path)
# if it is found just returns it...
#
# Alternative (pseudo code):
#
# def insert(map, local \\ true) when is_map(map) do
# case Activity.find_local_by_id(map) do
# {:ok, activity} -> {:ok, activity}
# nil -> do_insert(map)
# end
#
# and in do_insert we go on
with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map),
# This only checks the present, maybe if a message
# is received with a delay it makes sense to create,
# because it was before the "deactivation" of the actor
# Anyway is Mastodon/Pleroma stuff
:ok <- check_actor_is_active(map["actor"]),
{:ok, map} <- MRF.filter(map),
:ok <- insert_full_object(map) do
{recipients, _, _} = get_recipients(map)
# Here the activity's object is saved maybe by id maybe as full object.
{:ok, activity} =
Repo.insert(%Activity{
data: map,
local: local,
actor: map["actor"],
# This is not an ActivityStream standar property so it should be hidden
recipients: recipients
})
# Notification are really a Mastodon/Pleroma/Twitter thing
# It mixed with AP stuff
Notification.create_notifications(activity)
stream_out(activity)
{:ok, activity}
......@@ -83,6 +124,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def stream_out(activity) do
public = "https://www.w3.org/ns/activitystreams#Public"
# Only streams (by websocket, not related with Federation) Create and Announce activities
# So only new tweets and retweet! Not even likes!
# It mixed with AP stuff
if activity.data["type"] in ["Create", "Announce"] do
Pleroma.Web.Streamer.stream("user", activity)
Pleroma.Web.Streamer.stream("list", activity)
......@@ -117,19 +161,40 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
# Called by:
# * CommonAPI so MastodonAPI and TwitterAPI
# * By OStatus
# * Transmogrifier when handle incoming activitities
# Not using transactions in the database.
# So some data can be saved and other not.
# This produces inconsistent data.
def create(%{to: to, actor: actor, context: context, object: object} = params) do
# Not an AP standard field, should be hidden
# Seems like only has the params cc and sometimes id!
additional = params[:additional] || %{}
# only accept false as false value
# To know if the activity is local or not is done previously (in another place)
# This is important because only local object, aka generated by this server,
# should be federated (send to other servers)
local = !(params[:local] == false)
published = params[:published]
# This seems like validation stuff, like remove "invalid" or "extra"
# fields received by the function. However this breaks absolutely
# our intentions to make extensible AS.
# But at the same time, it adds everything given by the additional params,
# but additional is used just internally, it is not received by external input.
# It does not use the ecto library, so it feels weird and clunky
with create_data <-
make_create_data(
%{to: to, actor: actor, published: published, context: context, object: object},
additional
),
# Inserts if not inserted before
# context could be nil before, but here it is set :S
{:ok, activity} <- insert(create_data, local),
:ok <- maybe_federate(activity),
# This is better doing it in database
{:ok, _actor} <- User.increase_note_count(actor) do
{:ok, activity}
end
......@@ -746,6 +811,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
),
{:ok, data} <- Jason.decode(body),
nil <- Object.normalize(data),
# It is creating imaginary creatied activities
# Maybe the object is an updated result
params <- %{
"type" => "Create",
"to" => data["to"],
......@@ -753,7 +820,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"actor" => data["attributedTo"],
"object" => data
},
# This is a validation!
:ok <- Transmogrifier.contain_origin(id, params),
# It creates imaginary create activity
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
{:ok, Object.normalize(activity.data["object"])}
else
......
defmodule Pleroma.Web.ActivityPub.MRF do
@callback filter(Map.t()) :: {:ok | :reject, Map.t()}
# https://git.pleroma.social/pleroma/pleroma/wikis/Message-Rewrite-Facility-configuration-(how-to-block-instances)
#
# Doc from the wiki:
#
# > The Message Rewrite Facility (MRF) is a subsystem that is implemented
# > as a series of hooks that allows the administrator to rewrite or discard messages.
#
# This is really interesting, it can help to the server admin to:
# * Filter spam
# * Content no wanted by like porn or racist messages
#
# I'm not a big of changing the content. I prefer to reject.
# I can understand add a tag like #nsfw, but you lose credibility as a hoster.
# People cannot trust the content you serve.
#
# We should also add a property (not standard of course),
# to say if the content was modified by the server.
# This way we can alert other server to fetch the original content
# (if they can, sometimes private content is possible)
# and they can apply their own filters.
# Client apps can also use this property to alert the user this content has been modified
#
# If we implement Linked-Data signatures: https://w3c-dvcg.github.io/ld-signatures/
# clients and servers can verify the authority of the author.
# (as long as the private key is only stored in the client app)
# Disclaimer: not an expert :P
def filter(object) do
# This code runs in runtime,
# it can be done in compilation time to go faster!
get_policies()
|> Enum.reduce({:ok, object}, fn
policy, {:ok, object} ->
......@@ -20,5 +48,10 @@ defmodule Pleroma.Web.ActivityPub.MRF do
defp get_policies(policy) when is_atom(policy), do: [policy]
defp get_policies(policies) when is_list(policies), do: policies
# This should crash instead of return a "default object".
# If the admin of the site is defining a wrong option
# the apps will work but the policies were never applied.
# If we crash here, the admin is notified that the conf is wrong
# so he has the possibility to fix it
defp get_policies(_), do: []
end
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
# This is for filter and modify content.
# It is cool!
# However it cold be improved in several ways.
# Currently it seems like the configuration is done in compilation time.
# This means if we want to change the configuration we have to recompile,
# bump a new version of the software and make a deployment.
#
# A better solution would be that the configuration lives in database.
# Adding a cache will make as fast as currently.
# The admin should have an interface to change this configuration.
# The configuration should be, IMHO, public.
# This way the user may know what filters and changes are made in realtime.
alias Pleroma.User
@behaviour Pleroma.Web.ActivityPub.MRF
......
......@@ -13,6 +13,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
require Logger
# It only accepts one actor by Activity.
# In AP this can be more than one!
def get_actor(%{"actor" => actor}) when is_binary(actor) do
actor
end
......@@ -69,6 +71,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
# Make to, cc, bto and bcc always a list, Good!
def fix_addressing(map) do
map
|> fix_addressing_list("to")
......@@ -200,8 +203,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# TODO: validate those with a Ecto scheme
# - tags
# - emoji
# So for the Activity Create it only accepts Article, Note and Video.
# There are a lot of more object types.
# It is used in two places:
# * Incoming request to inbox
# * To fetch data when we only have the id.
# This is very wrong :(
# because it generates imaginary activities
# when we receive an object that does not exists in our database
def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
when objtype in ["Article", "Note", "Video"] do
# one actor only
actor = get_actor(data)
data =
......@@ -209,6 +221,17 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> fix_addressing
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
# Get or fetch and create the actor in the database
# The code to do this is difficult to follow jumps to a lot of places:
# * To user model
# * To ActivityPub
# * To Transmogrifier again
# * To ActivityPub
# * To Transmogrifier agian
# * To User model
# * Possibly async code :O
# * To user model
# So there is not an hierarchy, all modules call to each other, no abstractions
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
object = fix_object(data["object"])
......
defmodule Pleroma.Web.ActivityPub.Utils do
# I don't understand why there are two modules ActivityPub and ActivityPub.Utils
# I don't see the differences between them
# This is module, that could be a private module for ActivityPub because its name,
# is called from MastodonAPI, TwitterAPI
# Why a function should be here and not in ActivityPub?
alias Pleroma.{Repo, Web, Activity, User}
alias ActivityStream.Object
alias Pleroma.Web.Router.Helpers
......@@ -60,6 +65,12 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"#{Web.base_url()}/#{type}/#{UUID.generate()}"
end
# In pleroma all activities should have a context
# One is created if is not already created
# https://www.w3.org/TR/activitystreams-vocabulary/#dfn-context
#
# "context_id" is not a standard in AP. It is an implementation details.
# It should be hidden so it does not conflict with future extensions
def create_context(context) do
context = context || generate_id("contexts")
%{"id" => context}
......@@ -79,6 +90,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Enqueues an activity for federation if it's local
"""
def maybe_federate(%Activity{local: true} = activity) do
# Only federates local messages, it makes sense
priority =
case activity.data["type"] do
"Delete" -> 10
......@@ -97,6 +109,15 @@ defmodule Pleroma.Web.ActivityPub.Utils do
also adds it to an included object
"""
def lazy_put_activity_defaults(map) do
# It seems all the activities should have a context
# Probably a Mastodon requirement
# One is created if is not already set
#
# AS def:
# > Identifies the context within which the object exists or an activity was performed.
# > The notion of "context" used is intentionally vague. The intended function is to serve as a
# > means of grouping objects and activities that share a common originating context or purpose.
# > An example could be all activities relating to a common project or event.
%{data: %{"id" => context}, id: context_id} = create_context(map["context"])
map =
......@@ -106,6 +127,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Map.put_new("context", context)
|> Map.put_new("context_id", context_id)
# So here is checking if the activity object is just the ID or is the full object.
# If it is an object adds some properties that are required by Mastodon/Pleroma
# If not it just returns the activity with the object id.
# I think this is incosistent, later we have to deal again with if object is
# the full object or the id again. It feels we need a better abstraction.
if is_map(map["object"]) do
object = lazy_put_object_defaults(map["object"], map)
%{map | "object" => object}
......@@ -118,6 +144,9 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Adds an id and published date if they aren't there.
"""
def lazy_put_object_defaults(map, activity \\ %{}) do
# It seems context is needed by Mastodon/Pleroma
# "context_id" is not a standard in AP. It is an implementation details.
# It should be hidden so it does not conflict with future extensions
map
|> Map.put_new_lazy("id", &generate_object_id/0)
|> Map.put_new_lazy("published", &make_date/0)
......@@ -129,6 +158,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Inserts a full object if it is contained in an activity.
"""
def insert_full_object(%{"object" => %{"type" => type} = object_data})
# IMPORTANT: Only inserts Articles, Note or Video
when is_map(object_data) and type in ["Article", "Note", "Video"] do
with {:ok, _} <- ActivityStream.create_object(object_data) do
:ok
......@@ -466,6 +496,13 @@ defmodule Pleroma.Web.ActivityPub.Utils do
#### Create-related helpers
def make_create_data(params, additional) do
# It continously adding a default published date around the whole code
# It should be done at the very beginning of receving the data.
# ie: if we received an invalid publication without published date
# maybe is better to save this way or discard if it is mandatory.
# ie: if we receive from the client an invalid date it should be verified
# ie: if we dont receive published date from a client we can add in this case
# All this different cases are just simple resolved adding a default date (now)
published = params.published || make_date()
%{
......
......@@ -101,6 +101,8 @@ defmodule Pleroma.Web.Federator do
params = Utils.normalize_params(params)
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
# What happen if the object is different for any reason?
# I don't know, maybe has different `to:`
nil <- Activity.normalize(params["id"]),
{:ok, _activity} <- Transmogrifier.handle_incoming(params) do
else
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment