ostatus2 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -3
- data/lib/ostatus2/publication.rb +23 -0
- data/lib/ostatus2/salmon.rb +85 -0
- data/lib/ostatus2/subscription.rb +62 -0
- data/lib/ostatus2/version.rb +3 -0
- data/lib/ostatus2.rb +18 -0
- metadata +11 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 53864441f9a3c0a536a52f316390630d8982ccbf
|
4
|
+
data.tar.gz: c8699fed6438b98e12c82075c3aecdd3ca98bf6c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f5b426461d8508c31d82fc40253eddaaf06d45ed848324edfdc96186d28b71b2dd1bb6a62973659bbfbd5fa613698fce6a04f9336159d4733f74037e3ecfca1
|
7
|
+
data.tar.gz: 488bd014fe2b8d546e28e191eb48a07a3b9805e1e81020f958bfbebef21eded45fb654d3d205d941b38f93cde60155ff73051f8a0288d0b97ba85037a728cdee
|
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
OStatus2
|
2
|
-
|
2
|
+
==========
|
3
3
|
|
4
4
|
[![Gem Version](http://img.shields.io/gem/v/ostatus2.svg)][gem]
|
5
5
|
[![Build Status](http://img.shields.io/travis/Gargron/ostatus2.svg)][travis]
|
@@ -14,8 +14,6 @@ A Ruby toolset for interacting with the OStatus suite of protocols:
|
|
14
14
|
* Subscribing to and publishing feeds via PubSubHubbub
|
15
15
|
* Interacting with feeds via Salmon
|
16
16
|
|
17
|
-
The 2 in the name is not a reference to any protocol version. There used to be a different Ruby gem called "ostatus" which this one seeks to replace.
|
18
|
-
|
19
17
|
## Installation
|
20
18
|
|
21
19
|
gem install ostatus2
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module OStatus2
|
2
|
+
class Publication
|
3
|
+
# @param [String] url Topic URL
|
4
|
+
# @param [Array] hubs URLs of the hubs which should be notified about the update
|
5
|
+
def initialize(url, hubs = [])
|
6
|
+
@url = url
|
7
|
+
@hubs = hubs.map { |hub_url| Addressable::URI.parse(hub_url) }
|
8
|
+
end
|
9
|
+
|
10
|
+
# Notifies hubs about the update to the topic URL
|
11
|
+
# @raise [HTTP::Error] Error raised upon delivery failure
|
12
|
+
# @raise [OpenSSL::SSL::SSLError] Error raised upon SSL-related failure during delivery
|
13
|
+
def publish
|
14
|
+
@hubs.each { |hub| http_client.post(hub, form: { 'hub.mode' => 'publish', 'hub.url' => @url }) }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def http_client
|
20
|
+
HTTP
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module OStatus2
|
2
|
+
class Salmon
|
3
|
+
XMLNS = 'http://salmon-protocol.org/ns/magic-env'
|
4
|
+
|
5
|
+
# Create a magical envelope XML document around the original body
|
6
|
+
# and sign it with a private key
|
7
|
+
# @param [String] body
|
8
|
+
# @param [OpenSSL::PKey::RSA] key The private part of the key will be used
|
9
|
+
# @return [String] Magical envelope XML
|
10
|
+
def pack(body, key)
|
11
|
+
signed = plaintext_signature(body, 'application/atom+xml', 'base64url', 'RSA-SHA256')
|
12
|
+
signature = Base64.urlsafe_encode64(key.sign(digest, signed))
|
13
|
+
|
14
|
+
Nokogiri::XML::Builder.new do |xml|
|
15
|
+
xml['me'].env({ 'xmlns:me' => XMLNS }) do
|
16
|
+
xml['me'].data({ type: 'application/atom+xml' }, Base64.urlsafe_encode64(body))
|
17
|
+
xml['me'].encoding('base64url')
|
18
|
+
xml['me'].alg('RSA-SHA256')
|
19
|
+
xml['me'].sig({ key_id: Base64.urlsafe_encode64(key.public_key.to_s) }, signature)
|
20
|
+
end
|
21
|
+
end.to_xml
|
22
|
+
end
|
23
|
+
|
24
|
+
# Deliver the magical envelope to a Salmon endpoint
|
25
|
+
# @param [String] salmon_url Salmon endpoint URL
|
26
|
+
# @param [String] envelope Magical envelope
|
27
|
+
# @raise [HTTP::Error] Error raised upon delivery failure
|
28
|
+
# @raise [OpenSSL::SSL::SSLError] Error raised upon SSL-related failure during delivery
|
29
|
+
# @return [HTTP::Response]
|
30
|
+
def post(salmon_url, envelope)
|
31
|
+
http_client.headers(HTTP::Headers::CONTENT_TYPE => 'application/magic-envelope+xml').post(Addressable::URI.parse(salmon_url), body: envelope)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Unpack a magical envelope to get the content inside
|
35
|
+
# @param [String] raw_body Magical envelope
|
36
|
+
# @raise [OStatus2::BadSalmonError] Error raised if the envelope is malformed
|
37
|
+
# @return [String] Content inside the envelope
|
38
|
+
def unpack(raw_body)
|
39
|
+
body, _, _ = parse(raw_body)
|
40
|
+
body
|
41
|
+
end
|
42
|
+
|
43
|
+
# Verify the magical envelope's integrity
|
44
|
+
# @param [String] raw_body Magical envelope
|
45
|
+
# @param [OpenSSL::PKey::RSA] key The public part of the key will be used
|
46
|
+
# @return [Boolean]
|
47
|
+
def verify(raw_body, key)
|
48
|
+
_, plaintext, signature = parse(raw_body)
|
49
|
+
key.public_key.verify(digest, signature, plaintext)
|
50
|
+
rescue OStatus2::BadSalmonError
|
51
|
+
false
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def http_client
|
57
|
+
HTTP
|
58
|
+
end
|
59
|
+
|
60
|
+
def digest
|
61
|
+
OpenSSL::Digest::SHA256.new
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse(raw_body)
|
65
|
+
xml = Nokogiri::XML(raw_body)
|
66
|
+
|
67
|
+
raise OStatus2::BadSalmonError if xml.at_xpath('//me:data').nil? || xml.at_xpath('//me:data').attribute('type').nil? || xml.at_xpath('//me:sig').nil? || xml.at_xpath('//me:encoding').nil? || xml.at_xpath('//me:alg').nil?
|
68
|
+
|
69
|
+
data = xml.at_xpath('//me:data')
|
70
|
+
type = data.attribute('type').value
|
71
|
+
body = Base64::urlsafe_decode64(data.content)
|
72
|
+
sig = xml.at_xpath('//me:sig')
|
73
|
+
signature = Base64::urlsafe_decode64(sig.content)
|
74
|
+
encoding = xml.at_xpath('//me:encoding').content
|
75
|
+
alg = xml.at_xpath('//me:alg').content
|
76
|
+
plaintext = plaintext_signature(body, type, encoding, alg)
|
77
|
+
|
78
|
+
[body, plaintext, signature]
|
79
|
+
end
|
80
|
+
|
81
|
+
def plaintext_signature(data, type, encoding, alg)
|
82
|
+
[data, type, encoding, alg].map { |i| Base64.urlsafe_encode64(i) }.join('.')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module OStatus2
|
2
|
+
class Subscription
|
3
|
+
# @param [String] topic_url The URL of the topic of the subscription
|
4
|
+
# @param [Hash] options
|
5
|
+
# @option options [String] :webhook Webhook URL
|
6
|
+
# @option options [String] :hub Hub URL to work with
|
7
|
+
# @option options [String] :secret Secret key of the subscription
|
8
|
+
# @option options [String] :token Verification token of the subscription
|
9
|
+
def initialize(topic_url, options = {})
|
10
|
+
@topic_url = topic_url
|
11
|
+
@webhook_url = options[:webhook] || ''
|
12
|
+
@secret = options[:secret] || ''
|
13
|
+
@token = options[:token] || ''
|
14
|
+
@hub = options[:hub] || ''
|
15
|
+
end
|
16
|
+
|
17
|
+
# Subscribe to the topic via a specified hub
|
18
|
+
# @raise [HTTP::Error] Error raised upon delivery failure
|
19
|
+
# @raise [OpenSSL::SSL::SSLError] Error raised upon SSL-related failure during delivery
|
20
|
+
# @return [Boolean]
|
21
|
+
def subscribe
|
22
|
+
update_subscription(:subscribe)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Unsubscribe from the topic via a specified hub
|
26
|
+
# @raise [HTTP::Error] Error raised upon delivery failure
|
27
|
+
# @raise [OpenSSL::SSL::SSLError] Error raised upon SSL-related failure during delivery
|
28
|
+
# @return [Boolean]
|
29
|
+
def unsubscribe
|
30
|
+
update_subscription(:unsubscribe)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check if the hub is responding to the right subscription request
|
34
|
+
# @param [String] topic_url A hub.topic from the hub
|
35
|
+
# @param [String] token A hub.verify_token from the hub
|
36
|
+
# @return [Boolean]
|
37
|
+
def valid?(topic_url, token)
|
38
|
+
@topic_url == topic_url && @token == token
|
39
|
+
end
|
40
|
+
|
41
|
+
# Verify that the feed contents were meant for this subscription
|
42
|
+
# @param [String] content
|
43
|
+
# @param [String] signature
|
44
|
+
# @return [Boolean]
|
45
|
+
def verify(content, signature)
|
46
|
+
hmac = OpenSSL::HMAC.hexdigest('sha1', @secret, content)
|
47
|
+
signature == "sha1=#{hmac}"
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def update_subscription(mode)
|
53
|
+
hub_url = Addressable::URI.parse(@hub)
|
54
|
+
response = http_client.post(hub_url, form: { 'hub.mode' => mode.to_s, 'hub.callback' => @webhook_url, 'hub.verify' => 'async', 'hub.verify_token' => @token, 'hub.lease_seconds' => '', 'hub.secret' => @secret, 'hub.topic' => @topic_url })
|
55
|
+
response.code == 200
|
56
|
+
end
|
57
|
+
|
58
|
+
def http_client
|
59
|
+
HTTP
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/ostatus2.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
require 'http'
|
4
|
+
require 'addressable'
|
5
|
+
require 'nokogiri'
|
6
|
+
|
7
|
+
require 'ostatus2/version'
|
8
|
+
require 'ostatus2/publication'
|
9
|
+
require 'ostatus2/subscription'
|
10
|
+
require 'ostatus2/salmon'
|
11
|
+
|
12
|
+
module OStatus2
|
13
|
+
class Error < StandardError
|
14
|
+
end
|
15
|
+
|
16
|
+
class BadSalmonError < Error
|
17
|
+
end
|
18
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ostatus2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Eugen Rochko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-02-
|
11
|
+
date: 2016-02-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|
@@ -58,15 +58,15 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '1.
|
61
|
+
version: '1.3'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '1.
|
69
|
-
description: Toolset for interacting with the
|
68
|
+
version: '1.3'
|
69
|
+
description: Toolset for interacting with the OStatus2 suite of protocols
|
70
70
|
email:
|
71
71
|
- eugen@zeonfederated.com
|
72
72
|
executables: []
|
@@ -74,6 +74,11 @@ extensions: []
|
|
74
74
|
extra_rdoc_files: []
|
75
75
|
files:
|
76
76
|
- README.md
|
77
|
+
- lib/ostatus2.rb
|
78
|
+
- lib/ostatus2/publication.rb
|
79
|
+
- lib/ostatus2/salmon.rb
|
80
|
+
- lib/ostatus2/subscription.rb
|
81
|
+
- lib/ostatus2/version.rb
|
77
82
|
homepage: https://github.com/Gargron/ostatus2
|
78
83
|
licenses:
|
79
84
|
- MIT
|
@@ -97,6 +102,6 @@ rubyforge_project:
|
|
97
102
|
rubygems_version: 2.4.6
|
98
103
|
signing_key:
|
99
104
|
specification_version: 4
|
100
|
-
summary: Toolset for interacting with the
|
105
|
+
summary: Toolset for interacting with the OStatus2 suite of protocols
|
101
106
|
test_files: []
|
102
107
|
has_rdoc:
|