diff options
author | pdp8 <pdp8@pdp8.info> | 2023-05-29 18:53:19 +0200 |
---|---|---|
committer | pdp8 <pdp8@pdp8.info> | 2023-05-29 18:53:19 +0200 |
commit | 5f309bae1ec181c20474f6bb3d18f86023dfb3df (patch) | |
tree | c242c57c0743cd459882e64a2c3c0397661a343d | |
parent | 451757d05a6464194a741c54e879b338d6329bd6 (diff) |
Create, Undo, Delete, Update, Follow implemented
-rw-r--r-- | activitypub.rb | 232 |
1 files changed, 77 insertions, 155 deletions
diff --git a/activitypub.rb b/activitypub.rb index 400465a..c5f5079 100644 --- a/activitypub.rb +++ b/activitypub.rb @@ -7,13 +7,13 @@ # test with pleroma etc =begin require 'json' -require 'uri' -require 'base64' require 'securerandom' require 'fileutils' -require 'digest/sha2' require 'nokogiri' =end +require 'uri' +require 'base64' +require 'digest/sha2' require 'net/http' require 'sinatra' @@ -33,12 +33,34 @@ set :port, 9292 post "/inbox" do request.body.rewind # in case someone already read it body = request.body.read - object = JSON.parse body - case object["type"] + action = JSON.parse body + + case action["type"] when "Create" - File.open(File.join("inbox", "#{object["published"]}-#{mention object["actor"]}.json"), "w+") { |f| - f.puts object["object"].to_json - } + create action["object"] + when "Delete" + delete action["object"] + when "Update" + delete action["object"] + create action["object"] + when "Follow" + File.open(File.join("public", "followers", mention(action["actor"]) + ".json"), "w+") { |f| f.puts body } + accept = { "@context" => "https://www.w3.org/ns/activitystreams", + "id" => File.join(SOCIAL_URL + "#accepts", SecureRandom.uuid), + "type" => "Accept", + "actor" => ACTOR, + "object" => object } + send_signed accept, accept["object"]["actor"] + when "Undo" + o = action["object"] + case o["type"] + when "Follow" + Dir["public/followers/*.json"].each do |follower| + FileUtils.rm follower if JSON.parse(File.read(follower))["actor"] == o["actor"] + end + end + else + p body end end @@ -51,12 +73,12 @@ get "/.well-known/webfinger" do end get "/inbox", :provides => 'html' do - template = "<!DOCTYPE html> + erb "<!DOCTYPE html> <html lang='en'> <body> - <% Dir['./inbox/*'].sort.each do |file| %> + <% Dir['./inbox/*'].sort.each_with_index do |file,i| %> <% item = JSON.parse(File.read(file)) %> - <b><%= mention item['actor'] %></b> <i><%= item['published'].sub('T', ' ') %></i> + <%= i+1 %> <b><%= mention item['attributedTo'] %></b> <i><%= item['published'].sub('T', ' ') %></i> <p><%= item['content'] %> <% if item['attachment'] item['attachment'].each do |att| @@ -90,11 +112,17 @@ get "/inbox", :provides => 'html' do <% end %> </body> </html>" - erb template end -get "/inbox", :provides => 'json' do - ordered_collection("inbox").to_json +def delete object + Dir["inbox/*.json"].each do |doc| + FileUtils.rm doc if JSON.parse(File.read(doc))["id"] == object["id"] + end +end + +def create object + doc = File.join("inbox", "#{object["published"]}-#{mention object["attributedTo"]}.json") + File.open(doc, "w+") { |f| f.puts object.to_json } end def mention actor @@ -121,6 +149,41 @@ def ordered_collection dir "orderedItems" => posts, } end + +def send_signed object, url + # https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb + keypair = OpenSSL::PKey::RSA.new(File.read('private.pem')) + date = Time.now.utc.httpdate + uri = URI.parse(url) + + sha256 = OpenSSL::Digest::SHA256.new + body = object.to_json + digest = "SHA-256=" + sha256.base64digest(body) + + signed_string = "(request-target): post #{inbox uri}\nhost: #{uri.host}\ndate: #{date}\ndigest: #{digest}\ncontent-type: application/activity+json" + signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string)) + signed_header = 'keyId="' + ACTOR + '#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="' + signature + '"' + + uri = URI.parse(get(url)["inbox"]) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + header = { + 'Content-Type' => 'application/activity+json', + 'Host' => uri.host, + 'Date' => date, + 'Digest' => digest, + 'Signature' => signed_header, + } + request = Net::HTTP::Post.new(uri.request_uri, header) + request.body = body + + http.request(request) +end + +def inbox uri + URI(get(uri)["inbox"]).request_uri +end + =begin post "/outbox" do @@ -138,47 +201,6 @@ class Application input = env["rack.input"].read case env["REQUEST_PATH"] - when "/inbox" # receive from server - if verify(env) - begin - object = JSON.parse(input) - case object["type"] - when "Delete" - puts input - when "Follow" - File.open(File.join("followers", SecureRandom.uuid + ".json"), "w+") { |f| f.puts input } - accept = { "@context" => "https://www.w3.org/ns/activitystreams", - "id" => File.join(SOCIAL_URL + "#accepts", SecureRandom.uuid), - "type" => "Accept", - "actor" => ACTOR, - "object" => JSON.parse(input) } - send accept, [accept["object"]["actor"]] - when "Undo" - o = object["object"] - case o["type"] - when "Follow" - Dir["followers/*.json"].each do |follower| - if JSON.parse(File.read(follower))["actor"] == o["actor"] - FileUtils.rm follower - end - end - else - puts input - end - else - puts input - end - code = 200 - response = "OK" - rescue => e - puts input, e.to_s - response = "Request body contains invalid json." - end - else - code = 403 - response = "Key verification failed for POST to #{env["REQUEST_URI"]}." - end - when %r{/delete} # receive from client if auth(env) FileUtils.rm env["REQUEST_URI"].sub("/delete/", "") @@ -235,67 +257,6 @@ class Application end end - when 'GET' - - case env["REQUEST_URI"] # REQUEST_PATH does not contain queries - - when "/inbox" - if auth(env) - case env["HTTP_ACCEPT"] - else - type = "text/html" - response = html env["REQUEST_PATH"] - end - code = 200 - else - code = 403 - response = "You are not allowed to GET #{env["REQUEST_URI"]}." - end - - when %r{/[outbox|following|followers|likes|shares]} - response = ordered_collection(env["REQUEST_PATH"]).to_json - code = 200 - end - - end - [code, { "Content-Type" => type }, [response]] - end - - def html path - - end -=end - -=begin - def html o - html = "<!DOCTYPE html> -<html lang='en'> - <body> - <b>#{mention o["actor"]}</b> <i>#{o["object"]["published"]}</i> - <p>#{o["object"]["content"]} - " - if o["object"]["attachment"] - o["object"]["attachment"].each do |att| - case att["mediaType"] - when /audio/ - html<< "\n<br><audio controls=''><source src='#{att["url"]}' type='#{att["mediaType"]}'></audio>" - when /image/ - html << "\n<br><a href='#{att["url"]}'><img src='#{att["url"]}'></a>" - when /video/ - html<< "\n<br><video controls=''><source src='#{att["url"]}' type='#{att["mediaType"]}'></video>" - else - html<< att + "<br>" - html << "\n<a href='#{att["url"]}'>#{att["url"]}</a>" - end - end - end - end - html << "\n\t</body>\n</html>" - html - end -=end - -=begin def parse input date = Time.now.strftime("%Y-%m-%dT%H:%M:%S") # TODO media attachments, hashtags @@ -355,45 +316,6 @@ class Application }[0]["href"] end - def send object, urls - # https://github.com/mastodon/mastodon/blob/main/app/lib/request.rb - keypair = OpenSSL::PKey::RSA.new(File.read('private.pem')) - responses = [] - urls.each do |url| - date = Time.now.utc.httpdate - uri = URI.parse(url) - - sha256 = OpenSSL::Digest::SHA256.new - body = object.to_json - digest = "SHA-256=" + sha256.base64digest(body) - - signed_string = "(request-target): post #{inbox uri}\nhost: #{uri.host}\ndate: #{date}\ndigest: #{digest}\ncontent-type: application/activity+json" - signature = Base64.strict_encode64(keypair.sign(OpenSSL::Digest.new('SHA256'), signed_string)) - signed_header = 'keyId="' + ACTOR + '#main-key",algorithm="rsa-sha256",headers="(request-target) host date digest content-type",signature="' + signature + '"' - - uri = URI.parse(get(url)["inbox"]) - http = Net::HTTP.new(uri.host, uri.port) - http.use_ssl = true - header = { - 'Content-Type' => 'application/activity+json', - 'Host' => uri.host, - 'Date' => date, - 'Digest' => digest, - 'Signature' => signed_header, - } - request = Net::HTTP::Post.new(uri.request_uri, header) - request.body = body - - responses << http.request(request) - end - # puts responses - responses - end - - def inbox uri - URI(get(uri)["inbox"]).request_uri - end - def path object object["id"].sub(SOCIAL_URL, '').sub('/', '') end |