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 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