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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 05503cb6981ef8a26c904b045fa7ad24b420f6f2
4
- data.tar.gz: c870e8c64ffcc3dd09785b4e856e50ee385d1f3d
3
+ metadata.gz: c8cf0345d592bd7eddd4df0589e308f442300f2e
4
+ data.tar.gz: e45bc8b040f4e262dcc29ebff36283975cdf658b
5
5
  SHA512:
6
- metadata.gz: 7f9fa83a0a0cdc22efac7c092807c86fc45e6df6bba6fa61d7b632dc33e146c90d58f7de1860abf15b9c92d0fd0b2683892b99b90b6c3fa690c750fa041770f2
7
- data.tar.gz: 309d84dd3b30e2ebe343475ce7ac02fd316b617a86f0e2ba78eff1d2e8a15704b825a7ebf272089b8bb197732a4bd95bea148ec9a9f74300a7c50bbb04203d5a
6
+ metadata.gz: 2f73f386866cb8dacc605cebc14cd9bf2fa667b451a502eabf72cdef52cf7dfaa4b0e4c28787f5f1f9dc7a584046feb29a95baeb39c60eb343e4a0a99fc88a7a
7
+ data.tar.gz: bebab60ba8f6780a73aa93c8090acdd3e67ce7b416ab2c2634c249d61d0080010629b7b936a83d0a01e7390e400ba0166ab8a134030f033befbf532b39a13076
data/.reek CHANGED
@@ -8,3 +8,6 @@ DataClump:
8
8
  max_copies: 4
9
9
  LongParameterList:
10
10
  max_params: 8
11
+
12
+ exclude_paths:
13
+ - spec/support
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ## Description
8
8
 
9
- [![Build Status](https://travis-ci.org/payrollhero/webhook_engine.svg?branch=master)](https://travis-ci.org/payrollhero/webhook_engine)
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)
@@ -2,6 +2,7 @@ require 'active_support/all'
2
2
  require 'active_record'
3
3
  require 'active_job'
4
4
  require 'ph_model'
5
+ require 'validate_url'
5
6
 
6
7
  module WebhookSystem
7
8
  extend ActiveSupport::Autoload
@@ -2,40 +2,40 @@ module WebhookSystem
2
2
 
3
3
  # Class in charge of encoding and decoding encrypted payload
4
4
  module Encoder
5
- class << self
6
- # Given a secret string, encode the passed payload to json
7
- # encrypt it, base64 encode that, and wrap it in its own json wrapper
8
- #
9
- # @param [String] secret_string some secret string
10
- # @param [Object#to_json] payload Any object that responds to to_json
11
- # @return [String] The encoded string payload (its a JSON string)
12
- def encode(secret_string, payload)
13
- cipher = OpenSSL::Cipher::AES256.new(:CBC)
14
- cipher.encrypt
15
- iv = cipher.random_iv
16
- cipher.key = key_from_secret(iv, secret_string)
17
- encoded = cipher.update(payload.to_json) + cipher.final
18
- Payload.encode(encoded, iv)
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
- # Given a secret string, and an encrypted payload, unwrap it, bas64 decode it
22
- # decrypt it, and JSON decode it
23
- #
24
- # @param [String] secret_string some secret string
25
- # @param [String] payload String as returned from #encode
26
- # @return [Object] return the JSON decode of the encrypted payload
27
- def decode(secret_string, payload)
28
- encoded, iv = Payload.decode(payload)
29
- cipher = OpenSSL::Cipher::AES256.new(:CBC)
30
- cipher.decrypt
31
- cipher.iv = iv
32
- cipher.key = key_from_secret(iv, secret_string)
33
- decoded = cipher.update(encoded) + cipher.final
34
- JSON.load(decoded)
35
- rescue OpenSSL::Cipher::CipherError
36
- raise DecodingError, 'Decoding Failed, probably mismatched secret'
37
- end
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
- class << self
52
- def encode(raw_encrypted_data, iv)
53
- JSON.dump(
54
- 'format' => 'base64+aes256',
55
- 'payload' => Base64.encode64(raw_encrypted_data),
56
- 'iv' => Base64.encode64(iv)
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
- def decode(payload_string)
61
- payload = JSON.load(payload_string)
62
- unless payload['format'] == 'base64+aes256'
63
- raise ArgumentError, 'only know how to handle base64+aes256 payloads'
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
@@ -5,35 +5,22 @@ module WebhookSystem
5
5
 
6
6
  def perform(subscription, event)
7
7
  payload = Encoder.encode(subscription.secret, event)
8
- client = HttpClient.new(subscription.url)
9
- client.post(payload)
8
+ self.class.post(subscription.url, payload)
10
9
  end
11
10
 
12
- end
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
- private
28
-
29
- attr_reader :endpoint, :client
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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module WebhookSystem
3
- VERSION = '0.0.1'.freeze
3
+ VERSION = '0.1.0'.freeze
4
4
  end
@@ -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.1
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