Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F33102562
activity_pub_controller.ex
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
17 KB
Referenced Files
None
Subscribers
None
activity_pub_controller.ex
View Options
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule
Pleroma.Web.ActivityPub.ActivityPubController
do
use
Pleroma.Web
,
:controller
alias
Pleroma.Activity
alias
Pleroma.Delivery
alias
Pleroma.Object
alias
Pleroma.Object.Fetcher
alias
Pleroma.User
alias
Pleroma.Web.ActivityPub.ActivityPub
alias
Pleroma.Web.ActivityPub.InternalFetchActor
alias
Pleroma.Web.ActivityPub.ObjectView
alias
Pleroma.Web.ActivityPub.Pipeline
alias
Pleroma.Web.ActivityPub.Relay
alias
Pleroma.Web.ActivityPub.Transmogrifier
alias
Pleroma.Web.ActivityPub.UserView
alias
Pleroma.Web.ActivityPub.Utils
alias
Pleroma.Web.ActivityPub.Visibility
alias
Pleroma.Web.ControllerHelper
alias
Pleroma.Web.Endpoint
alias
Pleroma.Web.Federator
alias
Pleroma.Web.Plugs.EnsureAuthenticatedPlug
alias
Pleroma.Web.Plugs.FederatingPlug
require
Logger
action_fallback
(
:errors
)
@federating_only_actions
[
:internal_fetch
,
:relay
,
:relay_following
,
:relay_followers
]
plug
(
FederatingPlug
when
action
in
@federating_only_actions
)
plug
(
EnsureAuthenticatedPlug
,
[
unless_func
:
&
FederatingPlug
.
federating?
/
1
]
when
action
not
in
@federating_only_actions
)
# Note: :following and :followers must be served even without authentication (as via :api)
plug
(
EnsureAuthenticatedPlug
when
action
in
[
:read_inbox
,
:update_outbox
,
:whoami
,
:upload_media
]
)
plug
(
Majic.Plug
,
[
pool
:
Pleroma.MajicPool
]
when
action
in
[
:upload_media
])
plug
(
Pleroma.Web.Plugs.Cache
,
[
query_params
:
false
,
tracking_fun
:
&
__MODULE__
.
track_object_fetch
/
2
]
when
action
in
[
:activity
,
:object
]
)
plug
(
:log_inbox_metadata
when
action
in
[
:inbox
])
plug
(
:relay_active?
when
action
in
[
:relay
])
defp
relay_active?
(
conn
,
_
)
do
if
Pleroma.Config
.
get
([
:instance
,
:allow_relay
])
do
conn
else
conn
|>
render_error
(
:not_found
,
"not found"
)
|>
halt
()
end
end
def
user
(
conn
,
%{
"nickname"
=>
nickname
})
do
with
%
User
{
local
:
true
}
=
user
<-
User
.
get_cached_by_nickname
(
nickname
)
do
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"user.json"
,
%{
user
:
user
})
else
nil
->
{
:error
,
:not_found
}
%{
local
:
false
}
->
{
:error
,
:not_found
}
end
end
def
object
(%{
assigns
:
assigns
}
=
conn
,
_
)
do
with
ap_id
<-
Endpoint
.
url
()
<>
conn
.
request_path
,
%
Object
{}
=
object
<-
Object
.
get_cached_by_ap_id
(
ap_id
),
user
<-
Map
.
get
(
assigns
,
:user
,
nil
),
{
_
,
true
}
<-
{
:visible?
,
Visibility
.
visible_for_user?
(
object
,
user
)}
do
conn
|>
maybe_skip_cache
(
user
)
|>
assign
(
:tracking_fun_data
,
object
.
id
)
|>
set_cache_ttl_for
(
object
)
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
ObjectView
)
|>
render
(
"object.json"
,
object
:
object
)
else
{
:visible?
,
false
}
->
{
:error
,
:not_found
}
nil
->
{
:error
,
:not_found
}
end
end
def
track_object_fetch
(
conn
,
nil
),
do
:
conn
def
track_object_fetch
(
conn
,
object_id
)
do
with
%{
assigns
:
%{
user
:
%
User
{
id
:
user_id
}}}
<-
conn
do
Delivery
.
create
(
object_id
,
user_id
)
end
conn
end
def
activity
(%{
assigns
:
assigns
}
=
conn
,
_
)
do
with
ap_id
<-
Endpoint
.
url
()
<>
conn
.
request_path
,
%
Activity
{}
=
activity
<-
Activity
.
normalize
(
ap_id
),
{
_
,
true
}
<-
{
:local?
,
activity
.
local
},
user
<-
Map
.
get
(
assigns
,
:user
,
nil
),
{
_
,
true
}
<-
{
:visible?
,
Visibility
.
visible_for_user?
(
activity
,
user
)}
do
conn
|>
maybe_skip_cache
(
user
)
|>
maybe_set_tracking_data
(
activity
)
|>
set_cache_ttl_for
(
activity
)
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
ObjectView
)
|>
render
(
"object.json"
,
object
:
activity
)
else
{
:visible?
,
false
}
->
{
:error
,
:not_found
}
{
:local?
,
false
}
->
{
:error
,
:not_found
}
nil
->
{
:error
,
:not_found
}
end
end
defp
maybe_set_tracking_data
(
conn
,
%
Activity
{
data
:
%{
"type"
=>
"Create"
}}
=
activity
)
do
object_id
=
Object
.
normalize
(
activity
,
fetch
:
false
)
.
id
assign
(
conn
,
:tracking_fun_data
,
object_id
)
end
defp
maybe_set_tracking_data
(
conn
,
_activity
),
do
:
conn
defp
set_cache_ttl_for
(
conn
,
%
Activity
{
object
:
object
})
do
set_cache_ttl_for
(
conn
,
object
)
end
defp
set_cache_ttl_for
(
conn
,
entity
)
do
ttl
=
case
entity
do
%
Object
{
data
:
%{
"type"
=>
"Question"
}}
->
Pleroma.Config
.
get
([
:web_cache_ttl
,
:activity_pub_question
])
%
Object
{}
->
Pleroma.Config
.
get
([
:web_cache_ttl
,
:activity_pub
])
_
->
nil
end
assign
(
conn
,
:cache_ttl
,
ttl
)
end
def
maybe_skip_cache
(
conn
,
user
)
do
if
user
do
conn
|>
assign
(
:skip_cache
,
true
)
else
conn
end
end
# GET /relay/following
def
relay_following
(
conn
,
_params
)
do
with
%{
halted
:
false
}
=
conn
<-
FederatingPlug
.
call
(
conn
,
[])
do
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"following.json"
,
%{
user
:
Relay
.
get_actor
()})
end
end
def
following
(%{
assigns
:
%{
user
:
for_user
}}
=
conn
,
%{
"nickname"
=>
nickname
,
"page"
=>
page
})
do
with
%
User
{}
=
user
<-
User
.
get_cached_by_nickname
(
nickname
),
{
:show_follows
,
true
}
<-
{
:show_follows
,
(
for_user
&&
for_user
==
user
)
||
!
user
.
hide_follows
}
do
{
page
,
_
}
=
Integer
.
parse
(
page
)
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"following.json"
,
%{
user
:
user
,
page
:
page
,
for
:
for_user
})
else
{
:show_follows
,
_
}
->
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
send_resp
(
403
,
""
)
end
end
def
following
(%{
assigns
:
%{
user
:
for_user
}}
=
conn
,
%{
"nickname"
=>
nickname
})
do
with
%
User
{}
=
user
<-
User
.
get_cached_by_nickname
(
nickname
)
do
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"following.json"
,
%{
user
:
user
,
for
:
for_user
})
end
end
# GET /relay/followers
def
relay_followers
(
conn
,
_params
)
do
with
%{
halted
:
false
}
=
conn
<-
FederatingPlug
.
call
(
conn
,
[])
do
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"followers.json"
,
%{
user
:
Relay
.
get_actor
()})
end
end
def
followers
(%{
assigns
:
%{
user
:
for_user
}}
=
conn
,
%{
"nickname"
=>
nickname
,
"page"
=>
page
})
do
with
%
User
{}
=
user
<-
User
.
get_cached_by_nickname
(
nickname
),
{
:show_followers
,
true
}
<-
{
:show_followers
,
(
for_user
&&
for_user
==
user
)
||
!
user
.
hide_followers
}
do
{
page
,
_
}
=
Integer
.
parse
(
page
)
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"followers.json"
,
%{
user
:
user
,
page
:
page
,
for
:
for_user
})
else
{
:show_followers
,
_
}
->
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
send_resp
(
403
,
""
)
end
end
def
followers
(%{
assigns
:
%{
user
:
for_user
}}
=
conn
,
%{
"nickname"
=>
nickname
})
do
with
%
User
{}
=
user
<-
User
.
get_cached_by_nickname
(
nickname
)
do
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"followers.json"
,
%{
user
:
user
,
for
:
for_user
})
end
end
def
outbox
(
%{
assigns
:
%{
user
:
for_user
}}
=
conn
,
%{
"nickname"
=>
nickname
,
"page"
=>
page?
}
=
params
)
when
page?
in
[
true
,
"true"
]
do
with
%
User
{}
=
user
<-
User
.
get_cached_by_nickname
(
nickname
)
do
# "include_poll_votes" is a hack because postgres generates inefficient
# queries when filtering by 'Answer', poll votes will be hidden by the
# visibility filter in this case anyway
params
=
params
|>
Map
.
drop
([
"nickname"
,
"page"
])
|>
Map
.
put
(
"include_poll_votes"
,
true
)
|>
Map
.
new
(
fn
{
k
,
v
}
->
{
String
.
to_existing_atom
(
k
),
v
}
end
)
activities
=
ActivityPub
.
fetch_user_activities
(
user
,
for_user
,
params
)
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"activity_collection_page.json"
,
%{
activities
:
activities
,
pagination
:
ControllerHelper
.
get_pagination_fields
(
conn
,
activities
),
iri
:
"
#{
user
.
ap_id
}
/outbox"
})
end
end
def
outbox
(
conn
,
%{
"nickname"
=>
nickname
})
do
with
%
User
{}
=
user
<-
User
.
get_cached_by_nickname
(
nickname
)
do
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"activity_collection.json"
,
%{
iri
:
"
#{
user
.
ap_id
}
/outbox"
})
end
end
def
inbox
(%{
assigns
:
%{
valid_signature
:
true
}}
=
conn
,
%{
"nickname"
=>
nickname
}
=
params
)
do
with
{
:recipient_exists
,
%
User
{}
=
recipient
}
<-
{
:recipient_exists
,
User
.
get_cached_by_nickname
(
nickname
)},
{
:sender_exists
,
{
:ok
,
%
User
{}
=
actor
}}
<-
{
:sender_exists
,
User
.
get_or_fetch_by_ap_id
(
params
[
"actor"
])},
{
:recipient_active
,
true
}
<-
{
:recipient_active
,
recipient
.
is_active
},
{
:sender_active
,
true
}
<-
{
:sender_active
,
actor
.
is_active
},
true
<-
Utils
.
recipient_in_message
(
recipient
,
actor
,
params
),
params
<-
Utils
.
maybe_splice_recipient
(
recipient
.
ap_id
,
params
)
do
Federator
.
incoming_ap_doc
(
params
)
json
(
conn
,
"ok"
)
else
{
:recipient_exists
,
_
}
->
conn
|>
put_status
(
:not_found
)
|>
json
(
"User does not exist"
)
{
:sender_exists
,
_
}
->
conn
|>
put_status
(
:not_found
)
|>
json
(
"Sender does not exist"
)
{
:recipient_active
,
_
}
->
conn
|>
put_status
(
:not_found
)
|>
json
(
"User deactivated"
)
{
:sender_active
,
_
}
->
conn
|>
put_status
(
:not_found
)
|>
json
(
"Sender deactivated"
)
_
->
conn
|>
put_status
(
:bad_request
)
|>
json
(
"Invalid request."
)
end
end
def
inbox
(%{
assigns
:
%{
valid_signature
:
true
}}
=
conn
,
params
)
do
Federator
.
incoming_ap_doc
(
params
)
json
(
conn
,
"ok"
)
end
def
inbox
(%{
assigns
:
%{
valid_signature
:
false
}}
=
conn
,
params
)
do
Federator
.
incoming_ap_doc
(%{
method
:
conn
.
method
,
req_headers
:
conn
.
req_headers
,
request_path
:
conn
.
request_path
,
params
:
params
,
query_string
:
conn
.
query_string
})
json
(
conn
,
"ok"
)
end
# POST /relay/inbox -or- POST /internal/fetch/inbox
def
inbox
(
conn
,
%{
"type"
=>
"Create"
}
=
params
)
do
if
FederatingPlug
.
federating?
()
do
post_inbox_relayed_create
(
conn
,
params
)
else
conn
|>
put_status
(
403
)
|>
json
(
"Not federating"
)
end
end
def
inbox
(
conn
,
_params
)
do
conn
|>
put_status
(
:bad_request
)
|>
json
(
"error, missing HTTP Signature"
)
end
defp
post_inbox_relayed_create
(
conn
,
params
)
do
Logger
.
debug
(
"Signature missing or not from author, relayed Create message, fetching object from source"
)
Fetcher
.
fetch_object_from_id
(
params
[
"object"
][
"id"
])
json
(
conn
,
"ok"
)
end
defp
represent_service_actor
(%
User
{}
=
user
,
conn
)
do
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"user.json"
,
%{
user
:
user
})
end
defp
represent_service_actor
(
nil
,
_
),
do
:
{
:error
,
:not_found
}
def
relay
(
conn
,
_params
)
do
Relay
.
get_actor
()
|>
represent_service_actor
(
conn
)
end
def
internal_fetch
(
conn
,
_params
)
do
InternalFetchActor
.
get_actor
()
|>
represent_service_actor
(
conn
)
end
@doc
"Returns the authenticated user's ActivityPub User object or a 404 Not Found if non-authenticated"
def
whoami
(%{
assigns
:
%{
user
:
%
User
{}
=
user
}}
=
conn
,
_params
)
do
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"user.json"
,
%{
user
:
user
})
end
def
read_inbox
(
%{
assigns
:
%{
user
:
%
User
{
nickname
:
nickname
}
=
user
}}
=
conn
,
%{
"nickname"
=>
nickname
,
"page"
=>
page?
}
=
params
)
when
page?
in
[
true
,
"true"
]
do
params
=
params
|>
Map
.
drop
([
"nickname"
,
"page"
])
|>
Map
.
put
(
"blocking_user"
,
user
)
|>
Map
.
put
(
"user"
,
user
)
|>
Map
.
new
(
fn
{
k
,
v
}
->
{
String
.
to_existing_atom
(
k
),
v
}
end
)
activities
=
[
user
.
ap_id
|
User
.
following
(
user
)]
|>
ActivityPub
.
fetch_activities
(
params
)
|>
Enum
.
reverse
()
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"activity_collection_page.json"
,
%{
activities
:
activities
,
pagination
:
ControllerHelper
.
get_pagination_fields
(
conn
,
activities
),
iri
:
"
#{
user
.
ap_id
}
/inbox"
})
end
def
read_inbox
(%{
assigns
:
%{
user
:
%
User
{
nickname
:
nickname
}
=
user
}}
=
conn
,
%{
"nickname"
=>
nickname
})
do
conn
|>
put_resp_content_type
(
"application/activity+json"
)
|>
put_view
(
UserView
)
|>
render
(
"activity_collection.json"
,
%{
iri
:
"
#{
user
.
ap_id
}
/inbox"
})
end
def
read_inbox
(%{
assigns
:
%{
user
:
%
User
{
nickname
:
as_nickname
}}}
=
conn
,
%{
"nickname"
=>
nickname
})
do
err
=
dgettext
(
"errors"
,
"can't read inbox of %{nickname} as %{as_nickname}"
,
nickname
:
nickname
,
as_nickname
:
as_nickname
)
conn
|>
put_status
(
:forbidden
)
|>
json
(
err
)
end
defp
fix_user_message
(%
User
{
ap_id
:
actor
},
%{
"type"
=>
"Create"
,
"object"
=>
object
}
=
activity
)
when
is_map
(
object
)
do
length
=
[
object
[
"content"
],
object
[
"summary"
],
object
[
"name"
]]
|>
Enum
.
filter
(
&
is_binary
(
&1
))
|>
Enum
.
join
(
""
)
|>
String
.
length
()
limit
=
Pleroma.Config
.
get
([
:instance
,
:limit
])
if
length
<
limit
do
object
=
object
|>
Transmogrifier
.
strip_internal_fields
()
|>
Map
.
put
(
"attributedTo"
,
actor
)
|>
Map
.
put
(
"actor"
,
actor
)
|>
Map
.
put
(
"id"
,
Utils
.
generate_object_id
())
{
:ok
,
Map
.
put
(
activity
,
"object"
,
object
)}
else
{
:error
,
dgettext
(
"errors"
,
"Character limit (%{limit} characters) exceeded, contains %{length} characters"
,
limit
:
limit
,
length
:
length
)}
end
end
defp
fix_user_message
(
%
User
{
ap_id
:
actor
}
=
user
,
%{
"type"
=>
"Delete"
,
"object"
=>
object
}
=
activity
)
do
with
{
_
,
%
Object
{
data
:
object_data
}}
<-
{
:normalize
,
Object
.
normalize
(
object
,
fetch
:
false
)},
{
_
,
true
}
<-
{
:permission
,
user
.
is_moderator
||
actor
==
object_data
[
"actor"
]}
do
{
:ok
,
activity
}
else
{
:normalize
,
_
}
->
{
:error
,
"No such object found"
}
{
:permission
,
_
}
->
{
:forbidden
,
"You can't delete this object"
}
end
end
defp
fix_user_message
(%
User
{},
activity
)
do
{
:ok
,
activity
}
end
def
update_outbox
(
%{
assigns
:
%{
user
:
%
User
{
nickname
:
nickname
,
ap_id
:
actor
}
=
user
}}
=
conn
,
%{
"nickname"
=>
nickname
}
=
params
)
do
params
=
params
|>
Map
.
drop
([
"nickname"
])
|>
Map
.
put
(
"id"
,
Utils
.
generate_activity_id
())
|>
Map
.
put
(
"actor"
,
actor
)
with
{
:ok
,
params
}
<-
fix_user_message
(
user
,
params
),
{
:ok
,
activity
,
_
}
<-
Pipeline
.
common_pipeline
(
params
,
local
:
true
),
%
Activity
{
data
:
activity_data
}
<-
Activity
.
normalize
(
activity
)
do
conn
|>
put_status
(
:created
)
|>
put_resp_header
(
"location"
,
activity_data
[
"id"
])
|>
json
(
activity_data
)
else
{
:forbidden
,
message
}
->
conn
|>
put_status
(
:forbidden
)
|>
json
(
message
)
{
:error
,
message
}
when
is_binary
(
message
)
->
conn
|>
put_status
(
:bad_request
)
|>
json
(
message
)
e
->
Logger
.
warning
(
fn
->
"AP C2S:
#{
inspect
(
e
)
}
"
end
)
conn
|>
put_status
(
:bad_request
)
|>
json
(
"Bad Request"
)
end
end
def
update_outbox
(%{
assigns
:
%{
user
:
%
User
{}
=
user
}}
=
conn
,
%{
"nickname"
=>
nickname
})
do
err
=
dgettext
(
"errors"
,
"can't update outbox of %{nickname} as %{as_nickname}"
,
nickname
:
nickname
,
as_nickname
:
user
.
nickname
)
conn
|>
put_status
(
:forbidden
)
|>
json
(
err
)
end
defp
errors
(
conn
,
{
:error
,
:not_found
})
do
conn
|>
put_status
(
:not_found
)
|>
json
(
dgettext
(
"errors"
,
"Not found"
))
end
defp
errors
(
conn
,
_e
)
do
conn
|>
put_status
(
:internal_server_error
)
|>
json
(
dgettext
(
"errors"
,
"error"
))
end
defp
log_inbox_metadata
(%{
params
:
%{
"actor"
=>
actor
,
"type"
=>
type
}}
=
conn
,
_
)
do
Logger
.
metadata
(
actor
:
actor
,
type
:
type
)
conn
end
defp
log_inbox_metadata
(
conn
,
_
),
do
:
conn
def
upload_media
(%{
assigns
:
%{
user
:
%
User
{}
=
user
}}
=
conn
,
%{
"file"
=>
file
}
=
data
)
do
with
{
:ok
,
object
}
<-
ActivityPub
.
upload
(
file
,
actor
:
User
.
ap_id
(
user
),
description
:
Map
.
get
(
data
,
"description"
)
)
do
Logger
.
debug
(
inspect
(
object
))
conn
|>
put_status
(
:created
)
|>
json
(
object
.
data
)
end
end
def
pinned
(
conn
,
%{
"nickname"
=>
nickname
})
do
with
%
User
{}
=
user
<-
User
.
get_cached_by_nickname
(
nickname
)
do
conn
|>
put_resp_header
(
"content-type"
,
"application/activity+json"
)
|>
json
(
UserView
.
render
(
"featured.json"
,
%{
user
:
user
}))
end
end
end
File Metadata
Details
Attached
Mime Type
text/x-ruby
Expires
Tue, Jan 20, 1:09 PM (6 h, 3 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
973667
Default Alt Text
activity_pub_controller.ex (17 KB)
Attached To
Mode
rPUBE pleroma-upstream
Attached
Detach File
Event Timeline
Log In to Comment