nayutaya-webhook-dispatcher 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +10 -3
- data/example.rb +6 -36
- data/lib/webhook-dispatcher/acl/allow_entry.rb +12 -0
- data/lib/webhook-dispatcher/acl/deny_entry.rb +12 -0
- data/lib/webhook-dispatcher/acl/entry_base.rb +94 -0
- data/lib/webhook-dispatcher/acl.rb +54 -44
- data/lib/webhook-dispatcher/core.rb +107 -0
- data/lib/webhook-dispatcher/request/base.rb +9 -0
- data/lib/webhook-dispatcher/request/get.rb +4 -0
- data/lib/webhook-dispatcher/request/head.rb +12 -0
- data/lib/webhook-dispatcher/request/post.rb +17 -0
- data/lib/webhook-dispatcher/response.rb +2 -3
- data/lib/webhook-dispatcher/version.rb +1 -1
- data/test/acl/allow_entry_test.rb +17 -0
- data/test/acl/deny_entry_test.rb +13 -0
- data/test/acl/entry_base_test.rb +248 -0
- data/test/acl_test.rb +131 -34
- data/test/core_test.rb +336 -0
- data/test/request/base_test.rb +45 -0
- data/test/request/get_test.rb +30 -0
- data/test/request/head_test.rb +30 -0
- data/test/request/post_test.rb +45 -0
- data/test/response_test.rb +12 -15
- data/test/test_helper.rb +16 -0
- data/webhook-dispatcher.gemspec +20 -12
- data/webhook-dispatcher.gemspec.erb +1 -3
- metadata +21 -11
- data/test/request_base_test.rb +0 -20
- data/test/request_get_test.rb +0 -14
data/Rakefile
CHANGED
@@ -5,10 +5,11 @@ task :default => [:test]
|
|
5
5
|
|
6
6
|
Rake::TestTask.new do |test|
|
7
7
|
test.libs << "test"
|
8
|
-
test.test_files = Dir.glob("test
|
8
|
+
test.test_files = Dir.glob("test/**/*_test.rb")
|
9
9
|
test.verbose = true
|
10
10
|
end
|
11
11
|
|
12
|
+
desc "Generate gemspec file from template"
|
12
13
|
task :gemspec do
|
13
14
|
require "erb"
|
14
15
|
require "lib/webhook-dispatcher/version"
|
@@ -19,8 +20,14 @@ task :gemspec do
|
|
19
20
|
version = WebHookDispatcher::VERSION
|
20
21
|
date = Time.now.strftime("%Y-%m-%d")
|
21
22
|
|
22
|
-
files
|
23
|
-
|
23
|
+
files = Dir.glob("**/*").
|
24
|
+
select { |path| File.file?(path) }.
|
25
|
+
reject { |path| /^nbproject\// =~ path }.
|
26
|
+
sort
|
27
|
+
|
28
|
+
test_files = Dir.glob("test/**.rb").
|
29
|
+
select { |path| File.file?(path) }.
|
30
|
+
sort
|
24
31
|
|
25
32
|
File.open("webhook-dispatcher.gemspec", "wb") { |file|
|
26
33
|
file.write(erb.result(binding))
|
data/example.rb
CHANGED
@@ -1,47 +1,17 @@
|
|
1
1
|
|
2
2
|
# このスクリプトはイメージであり、動作しません
|
3
3
|
|
4
|
-
|
5
|
-
WebHookPublisher.read_timeout = 5 # sec
|
6
|
-
WebHookPublisher.user_agent = "hoge"
|
7
|
-
WebHookPublisher.acl = x
|
8
|
-
WebHookPublisher.acl_with {
|
9
|
-
}
|
4
|
+
require "webhook-dispatcher"
|
10
5
|
|
11
|
-
|
12
|
-
|
13
|
-
wp.read_timeout = 5 # sec
|
14
|
-
wp.user_agent = "hoge"
|
15
|
-
wp.acl = WebHookPublisher::Acl.with {
|
16
|
-
allow :all
|
17
|
-
allow "*", :all
|
18
|
-
allow "*", 1024..3000
|
19
|
-
allow "*", 0..1024
|
20
|
-
allow "*", [1,2,3,4]
|
21
|
-
deny IPAdd.new("0.0.0.0/0")
|
22
|
-
allow "127.0.0.0/8"
|
23
|
-
allow "localhost"
|
24
|
-
}
|
6
|
+
p dispatcher = WebHookDispatcher.new
|
7
|
+
p request = WebHookDispatcher::Request::Get.new(URI.parse("http://www.google.co.jp"))
|
25
8
|
|
26
|
-
|
27
|
-
acl.add_deny(...)
|
28
|
-
acl.add_allow(...)
|
29
|
-
acl.allow?(ipaddr)
|
30
|
-
acl.deny?(ipaddr)
|
31
|
-
acl.with { ... }
|
32
|
-
|
33
|
-
|
34
|
-
request_obj = WebHookPublisher::Request.new(:get, URI.new(..))
|
35
|
-
request_obj.http_method = :get
|
36
|
-
request_obj.uri = uri
|
9
|
+
p response = dispatcher.request(request)
|
37
10
|
|
11
|
+
=begin
|
38
12
|
res = wp.request(request_obj)
|
39
13
|
res = wp.get(url)
|
40
14
|
res = wp.head(url)
|
41
15
|
res = wp.post(url, data)
|
42
16
|
#=> WebHookPublisher::Response
|
43
|
-
|
44
|
-
res.status #=> :timeout
|
45
|
-
res.status_code #=> 200
|
46
|
-
res.status_message #=> OK
|
47
|
-
res.inner_exception #=> #<RuntimeError>
|
17
|
+
=end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
|
2
|
+
require "ipaddr"
|
3
|
+
|
4
|
+
class WebHookDispatcher
|
5
|
+
class Acl
|
6
|
+
class EntryBase
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class WebHookDispatcher::Acl::EntryBase
|
12
|
+
def initialize(options = nil)
|
13
|
+
case options
|
14
|
+
when nil, :all
|
15
|
+
@addr = nil
|
16
|
+
@name = nil
|
17
|
+
@port = nil
|
18
|
+
when Hash
|
19
|
+
options = options.dup
|
20
|
+
@addr = normalize_addr(options.delete(:addr))
|
21
|
+
@name = normalize_name(options.delete(:name))
|
22
|
+
@port = normalize_port(options.delete(:port))
|
23
|
+
raise(ArgumentError) unless options.empty?
|
24
|
+
else raise(ArgumentError)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :addr, :name, :port
|
29
|
+
|
30
|
+
def ==(other)
|
31
|
+
return false unless other.instance_of?(self.class)
|
32
|
+
return (self.to_a == other.to_a)
|
33
|
+
end
|
34
|
+
|
35
|
+
def match?(addr, name, port)
|
36
|
+
return match_addr?(addr) && match_name?(name) && match_port?(port)
|
37
|
+
end
|
38
|
+
|
39
|
+
def value
|
40
|
+
raise(NotImplementedError)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_a
|
44
|
+
return [@addr, @name, @port]
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def normalize_addr(addr)
|
50
|
+
case addr
|
51
|
+
when nil then return nil
|
52
|
+
when :all then return nil
|
53
|
+
when String then return IPAddr.new(addr)
|
54
|
+
when IPAddr then return addr
|
55
|
+
else raise(ArgumentError)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def normalize_name(name)
|
60
|
+
case name
|
61
|
+
when nil then return nil
|
62
|
+
when :all then return nil
|
63
|
+
when String then return name.downcase
|
64
|
+
when Regexp then return name
|
65
|
+
else raise(ArgumentError)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def normalize_port(port)
|
70
|
+
case port
|
71
|
+
when nil then return nil
|
72
|
+
when :all then return nil
|
73
|
+
when Integer then return [port]
|
74
|
+
when Array then return port.sort
|
75
|
+
when Range then return port
|
76
|
+
else raise(ArgumentError)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def match_addr?(addr)
|
81
|
+
return true if self.addr.nil?
|
82
|
+
return (!addr.nil? && self.addr.include?(addr))
|
83
|
+
end
|
84
|
+
|
85
|
+
def match_name?(name)
|
86
|
+
return true if self.name.nil?
|
87
|
+
return (!name.nil? && (self.name === name.downcase))
|
88
|
+
end
|
89
|
+
|
90
|
+
def match_port?(port)
|
91
|
+
return true if self.port.nil?
|
92
|
+
return (!port.nil? && self.port.include?(port))
|
93
|
+
end
|
94
|
+
end
|
@@ -1,26 +1,62 @@
|
|
1
1
|
|
2
2
|
require "ipaddr"
|
3
|
+
require "webhook-dispatcher/acl/allow_entry"
|
4
|
+
require "webhook-dispatcher/acl/deny_entry"
|
3
5
|
|
4
6
|
class WebHookDispatcher::Acl
|
5
7
|
def initialize
|
6
|
-
@
|
8
|
+
@entries = []
|
7
9
|
end
|
8
10
|
|
9
11
|
def self.with(&block)
|
10
12
|
return self.new.with(&block)
|
11
13
|
end
|
12
14
|
|
15
|
+
def self.allow_all
|
16
|
+
return self.with { allow :all }
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.deny_all
|
20
|
+
return self.with { deny :all }
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.ipaddr?(value)
|
24
|
+
return true if value.instance_of?(IPAddr)
|
25
|
+
begin
|
26
|
+
IPAddr.new(value)
|
27
|
+
return true
|
28
|
+
rescue ArgumentError
|
29
|
+
return false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.create_matching_targets(addr_or_name, port)
|
34
|
+
if self.ipaddr?(addr_or_name)
|
35
|
+
addr = addr_or_name
|
36
|
+
return [[IPAddr.new(addr), nil, port]]
|
37
|
+
else
|
38
|
+
name = addr_or_name
|
39
|
+
_name, _aliases, _type, *addresses = TCPSocket.gethostbyname(name)
|
40
|
+
return addresses.map { |addr| [IPAddr.new(addr), name, port] }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def ==(other)
|
45
|
+
return false unless other.instance_of?(self.class)
|
46
|
+
return (@entries == other.instance_eval { @entries })
|
47
|
+
end
|
48
|
+
|
13
49
|
def size
|
14
|
-
return @
|
50
|
+
return @entries.size
|
15
51
|
end
|
16
52
|
|
17
|
-
def add_allow(
|
18
|
-
@
|
53
|
+
def add_allow(options)
|
54
|
+
@entries << AllowEntry.new(options)
|
19
55
|
return self
|
20
56
|
end
|
21
57
|
|
22
|
-
def add_deny(
|
23
|
-
@
|
58
|
+
def add_deny(options)
|
59
|
+
@entries << DenyEntry.new(options)
|
24
60
|
return self
|
25
61
|
end
|
26
62
|
|
@@ -31,8 +67,8 @@ class WebHookDispatcher::Acl
|
|
31
67
|
|
32
68
|
this = self
|
33
69
|
(class << obj; self; end).class_eval {
|
34
|
-
define_method(:allow) { |
|
35
|
-
define_method(:deny) { |
|
70
|
+
define_method(:allow) { |options| this.add_allow(options) }
|
71
|
+
define_method(:deny) { |options| this.add_deny(options) }
|
36
72
|
private :allow, :deny
|
37
73
|
}
|
38
74
|
|
@@ -41,44 +77,18 @@ class WebHookDispatcher::Acl
|
|
41
77
|
return self
|
42
78
|
end
|
43
79
|
|
44
|
-
def allow?(
|
45
|
-
|
46
|
-
result = record.value if record.include?(ipaddr)
|
47
|
-
result
|
48
|
-
}
|
49
|
-
end
|
50
|
-
|
51
|
-
def deny?(ipaddr)
|
52
|
-
return !self.allow?(ipaddr)
|
53
|
-
end
|
54
|
-
|
55
|
-
class RecordBase
|
56
|
-
def initialize(ipaddr)
|
57
|
-
@ipaddr =
|
58
|
-
case ipaddr
|
59
|
-
when :all then IPAddr.new("0.0.0.0/0")
|
60
|
-
when String then IPAddr.new(ipaddr)
|
61
|
-
when IPAddr then ipaddr
|
62
|
-
else raise(ArgumentError, "invalid IP address")
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
attr_reader :ipaddr
|
67
|
-
|
68
|
-
def include?(ipaddr)
|
69
|
-
return @ipaddr.include?(ipaddr)
|
70
|
-
end
|
71
|
-
end
|
80
|
+
def allow?(addr_or_name, port = nil)
|
81
|
+
targets = self.class.create_matching_targets(addr_or_name, port)
|
72
82
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
83
|
+
return targets.all? { |taddr, tname, tport|
|
84
|
+
@entries.inject(true) { |result, entry|
|
85
|
+
result = entry.value if entry.match?(taddr, tname, tport)
|
86
|
+
result
|
87
|
+
}
|
88
|
+
}
|
77
89
|
end
|
78
90
|
|
79
|
-
|
80
|
-
|
81
|
-
return false
|
82
|
-
end
|
91
|
+
def deny?(addr_or_name, port = nil)
|
92
|
+
return !self.allow?(addr_or_name, port)
|
83
93
|
end
|
84
94
|
end
|
@@ -1,3 +1,110 @@
|
|
1
1
|
|
2
|
+
require "net/http"
|
3
|
+
require "webhook-dispatcher/version"
|
4
|
+
require "webhook-dispatcher/acl"
|
5
|
+
require "webhook-dispatcher/request/get"
|
6
|
+
require "webhook-dispatcher/request/head"
|
7
|
+
require "webhook-dispatcher/request/post"
|
8
|
+
require "webhook-dispatcher/response"
|
9
|
+
|
2
10
|
class WebHookDispatcher
|
11
|
+
def initialize(options = {})
|
12
|
+
options = options.dup
|
13
|
+
@open_timeout = options.delete(:open_timeout) || self.class.open_timeout || self.class.default_open_timeout
|
14
|
+
@read_timeout = options.delete(:read_timeout) || self.class.read_timeout || self.class.default_read_timeout
|
15
|
+
@user_agent = options.delete(:user_agent) || self.class.user_agent || self.class.default_user_agent
|
16
|
+
@acl = options.delete(:acl) || self.class.acl || self.class.default_acl
|
17
|
+
raise(ArgumentError, "invalid parameter") unless options.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
@open_timeout = nil
|
22
|
+
@read_timeout = nil
|
23
|
+
@user_agent = nil
|
24
|
+
@acl = nil
|
25
|
+
|
26
|
+
attr_accessor :open_timeout, :read_timeout, :user_agent, :acl
|
27
|
+
|
28
|
+
define_method(:default_open_timeout) { 10 }
|
29
|
+
define_method(:default_read_timeout) { 10 }
|
30
|
+
define_method(:default_user_agent) { "webhook-dispatcher #{self::VERSION}" }
|
31
|
+
define_method(:default_acl) { Acl.new }
|
32
|
+
|
33
|
+
def acl_with(&block)
|
34
|
+
self.acl = Acl.with(&block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_accessor :open_timeout, :read_timeout, :user_agent, :acl
|
39
|
+
|
40
|
+
def acl_with(&block)
|
41
|
+
self.acl = Acl.with(&block)
|
42
|
+
end
|
43
|
+
|
44
|
+
def request(request)
|
45
|
+
http_conn = request.create_http_connector
|
46
|
+
http_req = request.create_http_request
|
47
|
+
setup_http_connector(http_conn)
|
48
|
+
setup_http_request(http_req)
|
49
|
+
|
50
|
+
begin
|
51
|
+
if @acl.deny?(http_conn.address, http_conn.port)
|
52
|
+
return Response.new(
|
53
|
+
:status => :denied,
|
54
|
+
:message => "denied.")
|
55
|
+
end
|
56
|
+
|
57
|
+
http_res = http_conn.start { http_conn.request(http_req) }
|
58
|
+
|
59
|
+
return Response.new(
|
60
|
+
:status => (http_res.kind_of?(Net::HTTPSuccess) ? :success : :failure),
|
61
|
+
:http_code => http_res.code.to_i,
|
62
|
+
:message => http_res.message)
|
63
|
+
rescue TimeoutError => e
|
64
|
+
return Response.new(
|
65
|
+
:status => :timeout,
|
66
|
+
:message => "timeout.",
|
67
|
+
:exception => e)
|
68
|
+
rescue Errno::ECONNREFUSED => e
|
69
|
+
return Response.new(
|
70
|
+
:status => :refused,
|
71
|
+
:message => "connection refused.",
|
72
|
+
:exception => e)
|
73
|
+
rescue Errno::ECONNRESET => e
|
74
|
+
return Response.new(
|
75
|
+
:status => :reset,
|
76
|
+
:message => "connection reset by peer.",
|
77
|
+
:exception => e)
|
78
|
+
rescue => e
|
79
|
+
return Response.new(
|
80
|
+
:status => :error,
|
81
|
+
:message => "#{e.class}: #{e.message}",
|
82
|
+
:exception => e)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def get(uri)
|
87
|
+
return self.request(Request::Get.new(uri))
|
88
|
+
end
|
89
|
+
|
90
|
+
def head(uri)
|
91
|
+
return self.request(Request::Head.new(uri))
|
92
|
+
end
|
93
|
+
|
94
|
+
def post(uri, body = nil)
|
95
|
+
return self.request(Request::Post.new(uri, body))
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def setup_http_connector(http_conn)
|
101
|
+
http_conn.open_timeout = self.open_timeout
|
102
|
+
http_conn.read_timeout = self.read_timeout
|
103
|
+
return http_conn
|
104
|
+
end
|
105
|
+
|
106
|
+
def setup_http_request(http_request)
|
107
|
+
http_request["User-Agent"] = self.user_agent
|
108
|
+
http_request
|
109
|
+
end
|
3
110
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
|
2
2
|
require "uri"
|
3
|
+
require "webhook-dispatcher/core"
|
3
4
|
|
4
5
|
class WebHookDispatcher
|
5
6
|
module Request
|
@@ -12,4 +13,12 @@ class WebHookDispatcher::Request::Base
|
|
12
13
|
end
|
13
14
|
|
14
15
|
attr_accessor :uri
|
16
|
+
|
17
|
+
def create_http_connector
|
18
|
+
return Net::HTTP.new(self.uri.host, self.uri.port)
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_http_request
|
22
|
+
raise(NotImplementedError)
|
23
|
+
end
|
15
24
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
require "webhook-dispatcher/request/base"
|
3
|
+
|
4
|
+
class WebHookDispatcher::Request::Post < WebHookDispatcher::Request::Base
|
5
|
+
def initialize(uri, body = nil)
|
6
|
+
super(uri)
|
7
|
+
@body = body
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :body
|
11
|
+
|
12
|
+
def create_http_request
|
13
|
+
req = Net::HTTP::Post.new(self.uri.request_uri)
|
14
|
+
req.body = self.body
|
15
|
+
return req
|
16
|
+
end
|
17
|
+
end
|
@@ -2,7 +2,6 @@
|
|
2
2
|
class WebHookDispatcher::Response
|
3
3
|
def initialize(options = {})
|
4
4
|
options = options.dup
|
5
|
-
@success = (options.delete(:success) == true)
|
6
5
|
@status = options.delete(:status) || :unknown
|
7
6
|
@http_code = options.delete(:http_code) || nil
|
8
7
|
@message = options.delete(:message) || nil
|
@@ -10,9 +9,9 @@ class WebHookDispatcher::Response
|
|
10
9
|
raise(ArgumentError, "invalid parameter") unless options.empty?
|
11
10
|
end
|
12
11
|
|
13
|
-
attr_reader :
|
12
|
+
attr_reader :status, :http_code, :message, :exception
|
14
13
|
|
15
14
|
def success?
|
16
|
-
return self.success
|
15
|
+
return (self.status == :success)
|
17
16
|
end
|
18
17
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
require File.dirname(__FILE__) + "/../test_helper"
|
3
|
+
require "webhook-dispatcher/acl/allow_entry"
|
4
|
+
|
5
|
+
class AllowEntryTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@klass = WebHookDispatcher::Acl::AllowEntry
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# インスタンスメソッド
|
12
|
+
#
|
13
|
+
|
14
|
+
def test_value
|
15
|
+
assert_equal(true, @klass.new.value)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
|
2
|
+
require File.dirname(__FILE__) + "/../test_helper"
|
3
|
+
require "webhook-dispatcher/acl/deny_entry"
|
4
|
+
|
5
|
+
class DenyEntryTest < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
@klass = WebHookDispatcher::Acl::DenyEntry
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_value
|
11
|
+
assert_equal(false, @klass.new.value)
|
12
|
+
end
|
13
|
+
end
|