Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Pleroma
pleroma
Commits
8cf97ee8
Commit
8cf97ee8
authored
Feb 11, 2018
by
lain
Browse files
ActivityPub: Basic note federation with Mastodon.
parent
ce31f3a9
Pipeline
#709
failed with stage
in 2 minutes and 38 seconds
Changes
7
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
lib/pleroma/user.ex
View file @
8cf97ee8
...
...
@@ -383,10 +383,33 @@ def delete (%User{} = user) do
:ok
end
def
get_or_fetch_by_ap_id
(
ap_id
)
do
if
user
=
get_by_ap_id
(
ap_id
)
do
user
else
with
{
:ok
,
user
}
<-
ActivityPub
.
make_user_from_ap_id
(
ap_id
)
do
user
end
end
end
# AP style
def
public_key_from_info
(%{
"source_data"
=>
%{
"publicKey"
=>
%{
"publicKeyPem"
=>
public_key_pem
}}})
do
key
=
:public_key
.
pem_decode
(
public_key_pem
)
|>
hd
()
|>
:public_key
.
pem_entry_decode
()
{
:ok
,
key
}
end
# OStatus Magic Key
def
public_key_from_info
(%{
"magic_key"
=>
magic_key
})
do
{
:ok
,
Pleroma
.
Web
.
Salmon
.
decode_key
(
magic_key
)}
end
def
get_public_key_for_ap_id
(
ap_id
)
do
with
%
User
{}
=
user
<-
get_cached_by_ap_id
(
ap_id
),
%{
info:
%{
"magic_key"
=>
magic_key
}}
<-
user
,
public_key
<-
Pleroma
.
Web
.
Salmon
.
decode_key
(
magic_key
)
do
with
%
User
{}
=
user
<-
get_or_fetch_by_ap_id
(
ap_id
),
{
:ok
,
public_key
}
<-
public_key_from_info
(
user
.
info
)
do
{
:ok
,
public_key
}
else
_
->
:error
...
...
lib/pleroma/web/activity_pub/activity_pub.ex
View file @
8cf97ee8
...
...
@@ -223,18 +223,6 @@ def upload(file) do
Repo
.
insert
(%
Object
{
data:
data
})
end
def
prepare_incoming
(%{
"type"
=>
"Create"
,
"object"
=>
%{
"type"
=>
"Note"
}
=
object
}
=
data
)
do
with
{
:ok
,
user
}
<-
OStatus
.
find_or_make_user
(
data
[
"actor"
])
do
data
else
_e
->
:error
end
end
def
prepare_incoming
(
_
)
do
:error
end
def
make_user_from_ap_id
(
ap_id
)
do
with
{
:ok
,
%{
status_code:
200
,
body:
body
}}
<-
@httpoison
.
get
(
ap_id
,
[
"Accept"
:
"application/activity+json"
]),
{
:ok
,
data
}
<-
Poison
.
decode
(
body
)
...
...
@@ -252,4 +240,36 @@ def make_user_from_ap_id(ap_id) do
User
.
insert_or_update_user
(
user_data
)
end
end
# TODO: Extract to own module, align as close to Mastodon format as possible.
def
sanitize_outgoing_activity_data
(
data
)
do
data
|>
Map
.
put
(
"@context"
,
"https://www.w3.org/ns/activitystreams"
)
end
def
prepare_incoming
(%{
"type"
=>
"Create"
,
"object"
=>
%{
"type"
=>
"Note"
}
=
object
}
=
data
)
do
with
{
:ok
,
user
}
<-
OStatus
.
find_or_make_user
(
data
[
"actor"
])
do
{
:ok
,
data
}
else
_e
->
:error
end
end
def
prepare_incoming
(
_
)
do
:error
end
def
publish
(
actor
,
activity
)
do
remote_users
=
Pleroma
.
Web
.
Salmon
.
remote_users
(
activity
)
data
=
sanitize_outgoing_activity_data
(
activity
.
data
)
Enum
.
each
remote_users
,
fn
(
user
)
->
if
user
.
info
[
"ap_enabled"
]
do
inbox
=
user
.
info
[
"source_data"
][
"inbox"
]
Logger
.
info
(
"Federating
#{
activity
.
data
[
"id"
]
}
to
#{
inbox
}
"
)
host
=
URI
.
parse
(
inbox
)
.
host
signature
=
Pleroma
.
Web
.
HTTPSignatures
.
sign
(
actor
,
%{
host:
host
})
@httpoison
.
post
(
inbox
,
Poison
.
encode!
(
data
),
[{
"Content-Type"
,
"application/activity+json"
},
{
"signature"
,
signature
}])
end
end
end
end
lib/pleroma/web/activity_pub/activity_pub_controller.ex
View file @
8cf97ee8
...
...
@@ -23,6 +23,8 @@ def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
with
{
:ok
,
data
}
<-
ActivityPub
.
prepare_incoming
(
params
),
{
:ok
,
activity
}
<-
ActivityPub
.
insert
(
data
,
false
)
do
json
(
conn
,
"ok"
)
else
e
->
IO
.
inspect
(
e
)
end
end
end
lib/pleroma/web/federator/federator.ex
View file @
8cf97ee8
...
...
@@ -47,6 +47,9 @@ def handle(:publish, activity) do
Logger
.
debug
(
fn
->
"Sending
#{
activity
.
data
[
"id"
]
}
out via websub"
end
)
Websub
.
publish
(
Pleroma
.
Web
.
OStatus
.
feed_path
(
actor
),
actor
,
activity
)
Logger
.
debug
(
fn
->
"Sending
#{
activity
.
data
[
"id"
]
}
out via AP"
end
)
Pleroma
.
Web
.
ActivityPub
.
ActivityPub
.
publish
(
actor
,
activity
)
end
end
...
...
lib/pleroma/web/http_signatures/http_signatures.ex
View file @
8cf97ee8
# https://tools.ietf.org/html/draft-cavage-http-signatures-08
defmodule
Pleroma
.
Web
.
HTTPSignatures
do
alias
Pleroma
.
User
alias
Pleroma
.
Web
.
ActivityPub
.
ActivityPub
def
split_signature
(
sig
)
do
default
=
%{
"headers"
=>
"date"
}
...
...
@@ -28,7 +29,16 @@ def validate_conn(conn) do
# For now, fetch the key for the actor.
with
actor_id
<-
conn
.
params
[
"actor"
],
{
:ok
,
public_key
}
<-
User
.
get_public_key_for_ap_id
(
actor_id
)
do
validate_conn
(
conn
,
public_key
)
if
validate_conn
(
conn
,
public_key
)
do
true
else
# Fetch user anew and try one more time
with
actor_id
<-
conn
.
params
[
"actor"
],
{
:ok
,
_user
}
<-
ActivityPub
.
make_user_from_ap_id
(
actor_id
),
{
:ok
,
public_key
}
<-
User
.
get_public_key_for_ap_id
(
actor_id
)
do
validate_conn
(
conn
,
public_key
)
end
end
else
_
->
false
end
...
...
@@ -45,4 +55,22 @@ def build_signing_string(headers, used_headers) do
|>
Enum
.
map
(
fn
(
header
)
->
"
#{
header
}
:
#{
headers
[
header
]
}
"
end
)
|>
Enum
.
join
(
"
\n
"
)
end
def
sign
(
user
,
headers
)
do
with
{
:ok
,
%{
info:
%{
"keys"
=>
keys
}}}
<-
Pleroma
.
Web
.
WebFinger
.
ensure_keys_present
(
user
),
{
:ok
,
private_key
,
_
}
=
Pleroma
.
Web
.
Salmon
.
keys_from_pem
(
keys
)
do
sigstring
=
build_signing_string
(
headers
,
Map
.
keys
(
headers
))
signature
=
:public_key
.
sign
(
sigstring
,
:sha256
,
private_key
)
|>
Base
.
encode64
()
[
keyId:
user
.
ap_id
<>
"#main-key"
,
algorithm:
"rsa-sha256"
,
headers:
Map
.
keys
(
headers
)
|>
Enum
.
join
(
" "
),
signature:
signature
]
|>
Enum
.
map
(
fn
({
k
,
v
})
->
"
#{
k
}
=
\"
#{
v
}
\"
"
end
)
|>
Enum
.
join
(
","
)
end
end
end
test/user_test.exs
View file @
8cf97ee8
...
...
@@ -370,4 +370,8 @@ test ".delete deactivates a user, all follow relationships and all create activi
refute
Repo
.
get
(
Activity
,
activity
.
id
)
end
test
"get_public_key_for_ap_id fetches a user that's not in the db"
do
assert
{
:ok
,
_key
}
=
User
.
get_public_key_for_ap_id
(
"http://mastodon.example.org/users/admin"
)
end
end
test/web/http_sigs/http_sig_test.exs
View file @
8cf97ee8
...
...
@@ -3,6 +3,7 @@
defmodule
Pleroma
.
Web
.
HTTPSignaturesTest
do
use
Pleroma
.
DataCase
alias
Pleroma
.
Web
.
HTTPSignatures
import
Pleroma
.
Factory
@private_key
(
hd
(
:public_key
.
pem_decode
(
File
.
read!
(
"test/web/http_sigs/priv.key"
)))
|>
:public_key
.
pem_entry_decode
())
...
...
@@ -86,4 +87,29 @@ test "it validates a conn" do
assert
HTTPSignatures
.
validate_conn
(
conn
,
public_key
)
end
test
"it validates a conn and fetches the key"
do
conn
=
%{
params:
%{
"actor"
=>
"http://mastodon.example.org/users/admin"
},
req_headers:
[
{
"host"
,
"localtesting.pleroma.lol"
},
{
"x-forwarded-for"
,
"127.0.0.1"
},
{
"connection"
,
"close"
},
{
"content-length"
,
"2307"
},
{
"user-agent"
,
"http.rb/2.2.2 (Mastodon/2.1.0.rc3; +http://mastodon.example.org/)"
},
{
"date"
,
"Sun, 11 Feb 2018 17:12:01 GMT"
},
{
"digest"
,
"SHA-256=UXsAnMtR9c7mi1FOf6HRMtPgGI1yi2e9nqB/j4rZ99I="
},
{
"content-type"
,
"application/activity+json"
},
{
"signature"
,
"keyId=
\"
http://mastodon.example.org/users/admin#main-key
\"
,algorithm=
\"
rsa-sha256
\"
,headers=
\"
(request-target) user-agent host date digest content-type
\"
,signature=
\"
qXKqpQXUpC3d9bZi2ioEeAqP8nRMD021CzH1h6/w+LRk4Hj31ARJHDwQM+QwHltwaLDUepshMfz2WHSXAoLmzWtvv7xRwY+mRqe+NGk1GhxVZ/LSrO/Vp7rYfDpfdVtkn36LU7/Bzwxvvaa4ZWYltbFsRBL0oUrqsfmJFswNCQIG01BB52BAhGSCORHKtQyzo1IZHdxl8y80pzp/+FOK2SmHkqWkP9QbaU1qTZzckL01+7M5btMW48xs9zurEqC2sM5gdWMQSZyL6isTV5tmkTZrY8gUFPBJQZgihK44v3qgfWojYaOwM8ATpiv7NG8wKN/IX7clDLRMA8xqKRCOKw==
\"
"
},
{
"(request-target)"
,
"post /users/demiurge/inbox"
}
]
}
assert
HTTPSignatures
.
validate_conn
(
conn
)
end
test
"it generates a signature"
do
user
=
insert
(
:user
)
assert
HTTPSignatures
.
sign
(
user
,
%{
host:
"mastodon.example.org"
})
=~
"keyId=
\"
"
end
end
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment