summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpdp8 <pdp8@pdp8.info>2023-05-29 22:10:37 +0200
committerpdp8 <pdp8@pdp8.info>2023-05-29 22:10:37 +0200
commit86c8118c9b908af2f0c2a74d2bc9f7af431e4e12 (patch)
treef817b184721ad181585370f76c57cc82d9f7d8bc
parent5397e1838f628cd0902bcb79de77cc9b8de762ad (diff)
client post to outbox
-rw-r--r--activitypub.rb187
1 files changed, 88 insertions, 99 deletions
diff --git a/activitypub.rb b/activitypub.rb
index c5f5079..c019d6e 100644
--- a/activitypub.rb
+++ b/activitypub.rb
@@ -5,12 +5,6 @@
# federation
# client post media
# test with pleroma etc
-=begin
-require 'json'
-require 'securerandom'
-require 'fileutils'
-require 'nokogiri'
-=end
require 'uri'
require 'base64'
require 'digest/sha2'
@@ -30,7 +24,13 @@ use Rack::Reloader
set :default_content_type, 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
set :port, 9292
-post "/inbox" do
+before "/inbox" do
+ if request.request_method == "POST"
+ halt 400 unless verify_signature(request.env)
+ end
+end
+
+post "/inbox" do # server-server
request.body.rewind # in case someone already read it
body = request.body.read
action = JSON.parse body
@@ -64,6 +64,49 @@ post "/inbox" do
end
end
+post "/outbox" do # client-server
+ request.body.rewind # in case someone already read it
+ body = request.body.read
+ date = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
+ # TODO media attachments, hashtags
+ outbox_path = File.join("public/outbox", date + ".json")
+ object_path = File.join("public/objects", date + ".json")
+ create = {
+ "@context" => "https://www.w3.org/ns/activitystreams",
+ "id" => File.join(SOCIAL_URL, outbox_path),
+ "type" => "Create",
+ "actor" => ACTOR,
+ "object" => {
+ "id" => File.join(SOCIAL_URL, object_path),
+ "type" => "Note",
+ "attributedTo" => ACTOR,
+ "published" => date,
+ "content" => "",
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"]
+ },
+ "published" => date,
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"]
+ }
+ recipients = []
+ if /^@/.match body
+ mentions, body = body.split("\n", 2)
+ mentions.split(/, */).each do |m|
+ recipients << actor(m.chomp)
+ end
+ end
+ create["object"]["content"] = body.lines.select { |l| !l.empty? }.join("<br>")
+ recipients += Dir[File.join("public/followers", "*.json")].collect { |f| JSON.parse(File.read(f))["actor"] }
+ recipients.delete ACTOR
+ recipients.uniq!
+ create["object"]["to"] += recipients
+ create["to"] += recipients
+
+ File.open(outbox_path, "w+") { |f| f.puts create.to_json }
+ File.open(object_path, "w+") { |f| f.puts create["object"].to_json }
+
+ recipients.each { |r| send_signed create, r }
+end
+
get "/.well-known/webfinger" do
if request["resource"] == "acct:#{ACCOUNT}"
send_file "./webfinger", :type => "application/jrd+json"
@@ -121,7 +164,7 @@ def delete object
end
def create object
- doc = File.join("inbox", "#{object["published"]}-#{mention object["attributedTo"]}.json")
+ doc = File.join("inbox", "#{Time.now.strftime('%Y-%m-%dT%H:%M:%S.%N')}.json")
File.open(doc, "w+") { |f| f.puts object.to_json }
end
@@ -184,11 +227,46 @@ def inbox uri
URI(get(uri)["inbox"]).request_uri
end
-=begin
-post "/outbox" do
+def verify_signature env
+ # https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/signature_verification.rb
+ # TODO verify digest
+ signature_params = {}
+ env["HTTP_SIGNATURE"].split(',').each do |pair|
+ k, v = pair.split('=')
+ signature_params[k] = v.gsub('"', '')
+ end
+
+ key_id = signature_params['keyId']
+ headers = signature_params['headers']
+ signature = Base64.decode64(signature_params['signature'])
+
+ actor = get key_id
+ key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem'])
+
+ comparison = headers.split(' ').map do |signed_params_name|
+ if signed_params_name == '(request-target)'
+ '(request-target): post /inbox'
+ elsif signed_params_name == 'content-type'
+ "#{signed_params_name}: #{env["CONTENT_TYPE"]}"
+ else
+ "#{signed_params_name}: #{env["HTTP_" + signed_params_name.upcase]}"
+ end
+ end.join("\n")
+ key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison)
end
+def actor mention
+ mention = mention.sub(/^@/, '').chomp
+ user, server = mention.split("@")
+ get("https://#{server}/.well-known/webfinger?resource=acct:#{mention}",
+ "application/jrd+json")["links"].select { |l|
+ l["rel"] == "self"
+ }[0]["href"]
+end
+
+=begin
+
class Application
def call(env)
code = 404
@@ -211,7 +289,6 @@ class Application
p "outbox"
if auth(env)
p "OK"
- code, response = parse input
else
code = 403
response = "You are not allowed to POST to #{env["REQUEST_URI"]}."
@@ -257,64 +334,6 @@ class Application
end
end
- def parse input
- date = Time.now.strftime("%Y-%m-%dT%H:%M:%S")
- # TODO media attachments, hashtags
- note = {
- "@context" => "https://www.w3.org/ns/activitystreams",
- "id" => File.join(SOCIAL_URL, "note", SecureRandom.uuid + ".json"),
- "type" => "Note",
- "attributedTo" => ACTOR,
- "published" => date,
- "content" => "",
- "to" => ["https://www.w3.org/ns/activitystreams#Public"]
- }
- create = {
- "@context" => "https://www.w3.org/ns/activitystreams",
- "id" => File.join(SOCIAL_URL, "create", SecureRandom.uuid + ".json"),
- "type" => "Create",
- "actor" => ACTOR,
- "object" => note,
- "published" => date,
- "to" => ["https://www.w3.org/ns/activitystreams#Public"]
- }
- recipients = []
- if /^@/.match input
- mentions, input = input.split("\n", 2)
- mentions.split(/, */).each do |m|
- recipients << actor(m.chomp)
- end
- end
- note["content"] = input.lines.select { |l| !l.empty? }.join("<br>")
- recipients += Dir[File.join("followers", "*.json")].collect { |f| JSON.parse(File.read(f))["actor"] }
- recipients.delete ACTOR
- recipients.uniq!
- note["to"] += recipients
- create["to"] += recipients
-
- save create
- save note
- FileUtils.ln_s File.join('..', path(create)), "outbox"
-
- responses = send create, recipients
- if responses.collect { |r| r.code.to_i }.uniq.max < 400
- code = 200
- response = "OK"
- else
- code = 502
- response = responses.select { |r| r.code.to_i >= 400 }.collect { |r| r.body }.uniq
- end
- [code, response]
- end
-
- def actor mention
- mention = mention.sub(/^@/, '').chomp
- user, server = mention.split("@")
- get("https://#{server}/.well-known/webfinger?resource=acct:#{mention}",
- "application/jrd+json")["links"].select { |l|
- l["rel"] == "self"
- }[0]["href"]
- end
def path object
object["id"].sub(SOCIAL_URL, '').sub('/', '')
@@ -326,36 +345,6 @@ class Application
File.open(path, "w+") { |f| f.puts object.to_json }
end
- def verify env
- # https://github.com/mastodon/mastodon/blob/main/app/controllers/concerns/signature_verification.rb
- # TODO verify digest
- signature_params = {}
- env["HTTP_SIGNATURE"].split(',').each do |pair|
- k, v = pair.split('=')
- signature_params[k] = v.gsub('"', '')
- end
-
- key_id = signature_params['keyId']
- headers = signature_params['headers']
- signature = Base64.decode64(signature_params['signature'])
-
- actor = get key_id
- key = OpenSSL::PKey::RSA.new(actor['publicKey']['publicKeyPem'])
-
- comparison = headers.split(' ').map do |signed_params_name|
- if signed_params_name == '(request-target)'
- '(request-target): post /inbox'
- elsif signed_params_name == 'content-type'
- "#{signed_params_name}: #{env["CONTENT_TYPE"]}"
- else
- "#{signed_params_name}: #{env["HTTP_" + signed_params_name.upcase]}"
- end
- end.join("\n")
-
- key.verify(OpenSSL::Digest.new('SHA256'), signature, comparison)
- # true
- end
-
def auth env
auth = Rack::Auth::Basic::Request.new(env)
usr = File.read(".usr").chomp