webhook_system 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.org/payrollhero/
|
9
|
+
[![Build Status](https://travis-ci.org/payrollhero/webhook_system.svg?branch=master)](https://travis-ci.org/payrollhero/webhook_system)
|
10
10
|
[![Code Climate](https://codeclimate.com/github/payrollhero/webhook_system/badges/gpa.svg)](https://codeclimate.com/github/payrollhero/webhook_system)
|
11
11
|
[![Issue Count](https://codeclimate.com/github/payrollhero/webhook_system/badges/issue_count.svg)](https://codeclimate.com/github/payrollhero/webhook_system)
|
12
12
|
[![Dependency Status](https://gemnasium.com/payrollhero/webhook_system.svg)](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
|