webhook_system 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.reek +3 -0
- data/README.md +1 -1
- data/lib/webhook_system.rb +1 -0
- data/lib/webhook_system/encoder.rb +44 -46
- data/lib/webhook_system/job.rb +7 -20
- data/lib/webhook_system/subscription.rb +35 -1
- data/lib/webhook_system/version.rb +1 -1
- data/webhook_system.gemspec +1 -0
- metadata +15 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c8cf0345d592bd7eddd4df0589e308f442300f2e
|
4
|
+
data.tar.gz: e45bc8b040f4e262dcc29ebff36283975cdf658b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f73f386866cb8dacc605cebc14cd9bf2fa667b451a502eabf72cdef52cf7dfaa4b0e4c28787f5f1f9dc7a584046feb29a95baeb39c60eb343e4a0a99fc88a7a
|
7
|
+
data.tar.gz: bebab60ba8f6780a73aa93c8090acdd3e67ce7b416ab2c2634c249d61d0080010629b7b936a83d0a01e7390e400ba0166ab8a134030f033befbf532b39a13076
|
data/.reek
CHANGED
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
|
7
7
|
## Description
|
8
8
|
|
9
|
-
[](https://travis-ci.org/payrollhero/webhook_system)
|
10
10
|
[](https://codeclimate.com/github/payrollhero/webhook_system)
|
11
11
|
[](https://codeclimate.com/github/payrollhero/webhook_system)
|
12
12
|
[](https://gemnasium.com/payrollhero/webhook_system)
|
data/lib/webhook_system.rb
CHANGED
@@ -2,40 +2,40 @@ module WebhookSystem
|
|
2
2
|
|
3
3
|
# Class in charge of encoding and decoding encrypted payload
|
4
4
|
module Encoder
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
end
|
5
|
+
# Given a secret string, encode the passed payload to json
|
6
|
+
# encrypt it, base64 encode that, and wrap it in its own json wrapper
|
7
|
+
#
|
8
|
+
# @param [String] secret_string some secret string
|
9
|
+
# @param [Object#to_json] payload Any object that responds to to_json
|
10
|
+
# @return [String] The encoded string payload (its a JSON string)
|
11
|
+
def self.encode(secret_string, payload)
|
12
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
13
|
+
cipher.encrypt
|
14
|
+
iv = cipher.random_iv
|
15
|
+
cipher.key = key_from_secret(iv, secret_string)
|
16
|
+
encoded = cipher.update(payload.to_json) + cipher.final
|
17
|
+
Payload.encode(encoded, iv)
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
20
|
+
# Given a secret string, and an encrypted payload, unwrap it, bas64 decode it
|
21
|
+
# decrypt it, and JSON decode it
|
22
|
+
#
|
23
|
+
# @param [String] secret_string some secret string
|
24
|
+
# @param [String] payload String as returned from #encode
|
25
|
+
# @return [Object] return the JSON decode of the encrypted payload
|
26
|
+
def self.decode(secret_string, payload)
|
27
|
+
encoded, iv = Payload.decode(payload)
|
28
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
29
|
+
cipher.decrypt
|
30
|
+
cipher.iv = iv
|
31
|
+
cipher.key = key_from_secret(iv, secret_string)
|
32
|
+
decoded = cipher.update(encoded) + cipher.final
|
33
|
+
JSON.load(decoded)
|
34
|
+
rescue OpenSSL::Cipher::CipherError
|
35
|
+
raise DecodingError, 'Decoding Failed, probably mismatched secret'
|
36
|
+
end
|
38
37
|
|
38
|
+
class << self
|
39
39
|
private
|
40
40
|
|
41
41
|
def key_from_secret(iv, secret_string)
|
@@ -48,22 +48,20 @@ module WebhookSystem
|
|
48
48
|
# not exposed to the outside
|
49
49
|
# :nodoc:
|
50
50
|
module Payload
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
51
|
+
def self.encode(raw_encrypted_data, iv)
|
52
|
+
JSON.dump(
|
53
|
+
'format' => 'base64+aes256',
|
54
|
+
'payload' => Base64.encode64(raw_encrypted_data),
|
55
|
+
'iv' => Base64.encode64(iv)
|
56
|
+
)
|
57
|
+
end
|
59
58
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
end
|
65
|
-
[Base64.decode64(payload['payload']), Base64.decode64(payload['iv'])]
|
59
|
+
def self.decode(payload_string)
|
60
|
+
payload = JSON.load(payload_string)
|
61
|
+
unless payload['format'] == 'base64+aes256'
|
62
|
+
raise ArgumentError, 'only know how to handle base64+aes256 payloads'
|
66
63
|
end
|
64
|
+
[Base64.decode64(payload['payload']), Base64.decode64(payload['iv'])]
|
67
65
|
end
|
68
66
|
end
|
69
67
|
end
|
data/lib/webhook_system/job.rb
CHANGED
@@ -5,35 +5,22 @@ module WebhookSystem
|
|
5
5
|
|
6
6
|
def perform(subscription, event)
|
7
7
|
payload = Encoder.encode(subscription.secret, event)
|
8
|
-
|
9
|
-
client.post(payload)
|
8
|
+
self.class.post(subscription.url, payload)
|
10
9
|
end
|
11
10
|
|
12
|
-
|
13
|
-
|
14
|
-
# Just a simple internal class to wrap around the http requests to the endpoints
|
15
|
-
class HttpClient
|
16
|
-
def initialize(endpoint)
|
17
|
-
@endpoint = endpoint
|
18
|
-
end
|
19
|
-
|
20
|
-
def post(payload)
|
21
|
-
client.post do |req|
|
11
|
+
def self.post(endpoint, payload)
|
12
|
+
client_for(endpoint).post do |req|
|
22
13
|
req.headers['Content-Type'] = 'application/json; base64+aes256'
|
23
14
|
req.body = payload.to_s
|
24
15
|
end
|
25
16
|
end
|
26
17
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
def client
|
32
|
-
@client ||= Faraday.new(url: endpoint) do |faraday|
|
33
|
-
# faraday.request :url_encoded # form-encode POST params
|
34
|
-
faraday.response :logger # log requests to STDOUT
|
18
|
+
def self.client_for(endpoint)
|
19
|
+
Faraday.new(url: endpoint) do |faraday|
|
20
|
+
faraday.response :logger if ENV['WEBHOOK_DEBUG']
|
35
21
|
faraday.adapter Faraday.default_adapter
|
36
22
|
end
|
37
23
|
end
|
24
|
+
|
38
25
|
end
|
39
26
|
end
|
@@ -1,11 +1,14 @@
|
|
1
1
|
module WebhookSystem
|
2
|
+
|
3
|
+
# This is the model encompassing the actual record of a webhook subscription
|
2
4
|
class Subscription < ActiveRecord::Base
|
3
5
|
self.table_name = 'webhook_subscriptions'
|
4
6
|
|
5
|
-
validates :url, presence: true
|
7
|
+
validates :url, presence: true, url: { no_local: true }
|
6
8
|
validates :secret, presence: true
|
7
9
|
|
8
10
|
has_many :topics, class_name: 'WebhookSystem::SubscriptionTopic', dependent: :destroy
|
11
|
+
accepts_nested_attributes_for :topics, allow_destroy: true
|
9
12
|
|
10
13
|
scope :active, -> { where(active: true) }
|
11
14
|
scope :for_topic, -> (topic) {
|
@@ -13,5 +16,36 @@ module WebhookSystem
|
|
13
16
|
}
|
14
17
|
|
15
18
|
scope :interested_in_topic, -> (topic) { active.for_topic(topic) }
|
19
|
+
|
20
|
+
# Just a helper to get a nice representation of the subscription
|
21
|
+
def url_domain
|
22
|
+
URI.parse(url).host
|
23
|
+
end
|
24
|
+
|
25
|
+
# Abstraction around the topics relation, returns an array of the subscribed topic names
|
26
|
+
def topic_names
|
27
|
+
topics.map(&:name)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Abstraction around the topics relation, sets the topic names, requires save to take effect
|
31
|
+
def topic_names=(new_topics)
|
32
|
+
new_topics.reject!(&:blank?)
|
33
|
+
add_topics = new_topics - topic_names
|
34
|
+
|
35
|
+
new_topics_attributes = []
|
36
|
+
|
37
|
+
topics.each do |topic|
|
38
|
+
new_topics_attributes << {
|
39
|
+
id: topic.id,
|
40
|
+
name: topic.name,
|
41
|
+
_destroy: !new_topics.include?(topic.name),
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
new_topics_attributes += add_topics.map { |topic| { name: topic } }
|
46
|
+
|
47
|
+
self.topics_attributes = new_topics_attributes
|
48
|
+
end
|
49
|
+
|
16
50
|
end
|
17
51
|
end
|
data/webhook_system.gemspec
CHANGED
@@ -23,6 +23,7 @@ Gem::Specification.new do |gem|
|
|
23
23
|
gem.add_runtime_dependency 'activejob'
|
24
24
|
gem.add_runtime_dependency 'faraday'
|
25
25
|
gem.add_runtime_dependency 'ph_model'
|
26
|
+
gem.add_runtime_dependency 'validate_url', '~> 1.0'
|
26
27
|
|
27
28
|
gem.add_development_dependency 'bundler', '~> 1.0'
|
28
29
|
gem.add_development_dependency 'rake'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webhook_system
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Piotr Banasik
|
@@ -80,6 +80,20 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: validate_url
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.0'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
98
|
name: bundler
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|