Skip to content

Commit

Permalink
activity pub first implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
michelson committed Sep 12, 2023
0 parents commit c15a105
Show file tree
Hide file tree
Showing 28 changed files with 1,000 additions and 0 deletions.
18 changes: 18 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
name: Ruby

on: [push,pull_request]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.0.0
- name: Run the default task
run: |
gem install bundler -v 2.2.3
bundle install
bundle exec rake
11 changes: 11 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/.bundle/
/.yardoc
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/

# rspec failure tracking
.rspec_status
3 changes: 3 additions & 0 deletions .rspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
--format documentation
--color
--require spec_helper
10 changes: 10 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Style/StringLiterals:
Enabled: true
EnforcedStyle: double_quotes

Style/StringLiteralsInInterpolation:
Enabled: true
EnforcedStyle: double_quotes

Layout/LineLength:
Max: 120
19 changes: 19 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# frozen_string_literal: true

source "https://rubygems.org"

# Specify your gem's dependencies in activitypub.gemspec
gemspec

gem "pry"

gem "webmock"

gem "sinatra"
gem "puma"

gem "rake", "~> 13.0"

gem "rspec", "~> 3.0"

gem "rubocop", "~> 0.80"
91 changes: 91 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
PATH
remote: .
specs:
activitypub (0.1.0)

GEM
remote: https://rubygems.org/
specs:
addressable (2.8.5)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
coderay (1.1.3)
crack (0.4.5)
rexml
diff-lcs (1.5.0)
hashdiff (1.0.1)
method_source (1.0.0)
mustermann (3.0.0)
ruby2_keywords (~> 0.0.1)
nio4r (2.5.9)
parallel (1.23.0)
parser (3.2.2.3)
ast (~> 2.4.1)
racc
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (5.0.3)
puma (6.3.1)
nio4r (~> 2.0)
racc (1.7.1)
rack (2.2.8)
rack-protection (3.1.0)
rack (~> 2.2, >= 2.2.4)
rainbow (3.1.1)
rake (13.0.6)
regexp_parser (2.8.1)
rexml (3.2.6)
rspec (3.12.0)
rspec-core (~> 3.12.0)
rspec-expectations (~> 3.12.0)
rspec-mocks (~> 3.12.0)
rspec-core (3.12.2)
rspec-support (~> 3.12.0)
rspec-expectations (3.12.3)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-mocks (3.12.6)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.12.0)
rspec-support (3.12.1)
rubocop (0.93.1)
parallel (~> 1.10)
parser (>= 2.7.1.5)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8)
rexml
rubocop-ast (>= 0.6.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (1.29.0)
parser (>= 3.2.1.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
sinatra (3.1.0)
mustermann (~> 3.0)
rack (~> 2.2, >= 2.2.4)
rack-protection (= 3.1.0)
tilt (~> 2.0)
tilt (2.2.0)
unicode-display_width (1.8.0)
webmock (3.19.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)

PLATFORMS
arm64-darwin-21

DEPENDENCIES
activitypub!
pry
puma
rake (~> 13.0)
rspec (~> 3.0)
rubocop (~> 0.80)
sinatra
webmock

BUNDLED WITH
2.2.3
133 changes: 133 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# ActivityPub Ruby Library

This library provides a simple interface to work with the ActivityPub protocol in Ruby applications.

## Features
- Create and manage ActivityPub Objects.
- Send and receive Activities using Outbox and Inbox functionality.
- Signature verification for secured interactions.
- Simple Sinatra example to demonstrate usage.

## Installation

```bash
gem install activitypub
```

Or add it to your Gemfile:

```ruby
gem 'activitypub'
```

Then run `bundle install`.

## Usage

### Creating an ActivityPub Object

```ruby
activity = ActivityPub::Object.new(type: "Note", content: "Hello ActivityPub!")
```

### Sending an Activity

```ruby
outbox = ActivityPub::Outbox.new("https://recipient.com/actors/2/inbox")
outbox.send(activity)
```

### Receiving an Activity

```ruby
inbox = ActivityPub::Inbox.new
received_activity = inbox.receive(request_body)
```

### Verifying Signatures

```ruby
signature = ActivityPub::Signature.new
if signature.verify(received_activity)
puts "Signature is valid!"
else
puts "Signature is invalid!"
end
```

## Sinatra Example

A simple Sinatra app is included to demonstrate the library's functionality. To run it:

1. Navigate to the `example` directory.
2. Run `bundle install`.
3. Run the app with `ruby app.rb`.

Visit `http://localhost:4567` to access the example interface.

## Contributing

Contributions are welcome! Please submit a pull request or open an issue to discuss any changes or features.

## License

This library is released under the MIT License. See `LICENSE` for details.

## Sinatra app testing:

To test the Sinatra app that we just set up, you'll need to interact with its endpoints, specifically sending and receiving activities to and from the outbox and inbox respectively.

Let's first ensure that the Sinatra app is running:

1. Run the Sinatra application:
```bash
bundle exec ruby app.rb
```

2. Visit `http://localhost:4567/` in your browser to confirm that the app is running.

Now, there are a few ways to interact with the application:

### 1. Using `curl`

**Sending an Activity to Outbox**:
Let's send a "Note" type activity to our actor's outbox which should forward it to another actor's inbox.
```bash
curl -X POST -d "inbox_url=http://recipient.com/actors/2/inbox" -d "activity_data={\"type\":\"Note\",\"content\":\"Hello from Sinatra!\"}" http://localhost:4567/actors/1/outbox
```
**Receiving an Activity in Inbox**:
You can simulate another actor sending an activity to our actor's inbox:

```bash
curl -X POST -H "Content-Type: application/json" -d "{\"type\":\"Note\",\"content\":\"Hello to Sinatra from another actor!\"}" http://localhost:4567/actors/1/inbox
```

### 2. Using a Web Browser

You can easily check the root endpoint by navigating to `http://localhost:4567/` in your browser. However, for POST requests like the outbox and inbox interactions, you'll need a tool more suited for the task.
### 3. Using Postman
[Postman](https://www.postman.com/) is a popular tool for testing API endpoints.
1. **Sending an Activity to Outbox**:
- Set the method to `POST`.
- URL: `http://localhost:4567/actors/1/outbox`
- Set the headers:
- `Content-Type: application/x-www-form-urlencoded`
- In the body, select `x-www-form-urlencoded` and add two keys:
- `inbox_url` with the value `http://recipient.com/actors/2/inbox`
- `activity_data` with the value `{"type":"Note","content":"Hello from Sinatra!"}`
- Click on "Send".
2. **Receiving an Activity in Inbox**:
- Set the method to `POST`.
- URL: `http://localhost:4567/actors/1/inbox`
- Set the headers:
- `Content-Type: application/json`
- In the body, select `raw` and paste in `{"type":"Note","content":"Hello to Sinatra from another actor!"}`
- Click on "Send".
**Note**: As this example is highly simplified, it doesn't handle potential errors or specific responses you'd expect from a fully-fledged ActivityPub service. Ensure that any production implementation follows the ActivityPub specification closely and manages potential risks.
12 changes: 12 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# frozen_string_literal: true

require "bundler/gem_tasks"
require "rspec/core/rake_task"

RSpec::Core::RakeTask.new(:spec)

require "rubocop/rake_task"

RuboCop::RakeTask.new

task default: %i[spec rubocop]
38 changes: 38 additions & 0 deletions activitypub.gemspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

require_relative "lib/activitypub/version"

Gem::Specification.new do |spec|
spec.name = "activitypub"
spec.version = Activitypub::VERSION
spec.authors = ["Miguel Michelson Martinez"]
spec.email = ["miguelmichelson@gmail.com"]

spec.summary = "A Ruby library for the ActivityPub protocol."
spec.description = "A comprehensive library for building and parsing ActivityPub content in Ruby."
spec.homepage = "https://github.com/rauversion/activitypub_ruby"
spec.license = "MIT"

spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")

# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"

spec.metadata["homepage_uri"] = spec.homepage
spec.metadata["source_code_uri"] = spec.homepage # "TODO: Put your gem's public repo URL here."
spec.metadata["changelog_uri"] = spec.homepage # "TODO: Put your gem's CHANGELOG.md URL here."

# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

# Uncomment to register a new dependency of your gem
# spec.add_dependency "example-gem", "~> 1.0"

# For more information and examples about making a new gem, checkout our
# guide at: https://bundler.io/guides/creating_gem.html
end
52 changes: 52 additions & 0 deletions app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require 'sinatra'
require_relative "lib/activitypub"

# For simplicity, we'll hardcode actor_id and generate a keypair on startup
actor_id = "https://example.com/actors/1"
keypair = ActivityPub::Signature.generate_keypair
outbox = ActivityPub::Outbox.new(actor_id: actor_id, private_key: keypair[:private])

post '/actors/1/outbox' do
target_inbox_url = params[:inbox_url] # expect the client to provide target inbox URL
activity_data = params[:activity_data] # and the activity data

response = outbox.send_activity(target_inbox_url, activity_data)
[response.code.to_i, response.body]
end

post '/actors/1/inbox' do
# This is where other actors would send activities to our actor
incoming_activity = JSON.parse(params['incoming_activity']) # JSON.parse(request.body.read)

# For demonstration purposes, we'll just print the activity to the console
puts "Received activity: #{incoming_activity}"

# Respond with a 200 OK for simplicity
[200, "Activity received"]
end



get '/' do
%(
"ActivityPub Sinatra Example"
<h2>Send an Activity to Outbox</h2>
<form id="outboxForm" action="/actors/1/outbox" method="post">
<label for="inbox_url">Recipient Inbox URL:</label>
<input type="text" id="inbox_url" name="inbox_url" required value="http://recipient.com/actors/2/inbox">
<br><br>
<label for="activity_data">Activity Data (JSON):</label>
<textarea id="activity_data" name="activity_data" rows="4" required>{"type":"Note","content":"Hello from Sinatra form!"}</textarea>
<br><br>
<input type="submit" value="Send to Outbox">
</form>
<br><hr><br>
<h2>Simulate Sending an Activity to our Inbox</h2>
<form id="inboxForm" action="/actors/1/inbox" method="post">
<label for="incoming_activity">Incoming Activity Data (JSON):</label>
<textarea id="incoming_activity" name="incoming_activity" rows="4" required>{"type":"Note","content":"Hello to Sinatra from form!"}</textarea>
<br><br>
<input type="submit" value="Send to our Inbox">
</form>
)
end
Loading

0 comments on commit c15a105

Please sign in to comment.