connect_client 0.2.2 → 0.3.1
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/.gitignore +39 -38
- data/Gemfile +2 -2
- data/LICENSE +22 -22
- data/README.md +45 -45
- data/Rakefile +21 -21
- data/connect_client.gemspec +24 -24
- data/data/cacert.pem +3988 -3988
- data/lib/connect_client.rb +48 -40
- data/lib/connect_client/client.rb +42 -42
- data/lib/connect_client/configuration.rb +11 -11
- data/lib/connect_client/event.rb +72 -72
- data/lib/connect_client/event_push_response.rb +48 -48
- data/lib/connect_client/http/deferred_http_response.rb +8 -8
- data/lib/connect_client/http/event_endpoint.rb +135 -131
- data/lib/connect_client/security/filtered_key_generation.rb +41 -0
- data/lib/connect_client/version.rb +1 -1
- data/spec/connect_client/client_spec.rb +68 -68
- data/spec/connect_client/configuration_spec.rb +54 -54
- data/spec/connect_client/event_push_response_spec.rb +149 -149
- data/spec/connect_client/event_spec.rb +93 -93
- data/spec/connect_client/http/http_event_endpoint_spec.rb +124 -124
- data/spec/connect_client/http/synchrony/{event_endpoint_spec.rb → http_event_endpoint_spec.rb} +59 -59
- data/spec/connect_client/security/filtered_key_generation_spec.rb +13 -0
- data/spec/connect_client_spec.rb +24 -24
- metadata +7 -4
@@ -1,132 +1,136 @@
|
|
1
|
-
require 'json'
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
"
|
11
|
-
"Accept
|
12
|
-
"
|
13
|
-
"X-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
require '
|
43
|
-
require 'net/
|
44
|
-
|
45
|
-
|
46
|
-
@
|
47
|
-
@
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
1
|
+
require 'json'
|
2
|
+
require 'cgi'
|
3
|
+
require_relative '../event_push_response'
|
4
|
+
|
5
|
+
module ConnectClient
|
6
|
+
module Http
|
7
|
+
class EventEndpoint
|
8
|
+
def initialize(config)
|
9
|
+
headers = {
|
10
|
+
"Content-Type" => "application/json",
|
11
|
+
"Accept" => "application/json",
|
12
|
+
"Accept-Encoding" => "identity",
|
13
|
+
"X-Api-Key" => config.api_key,
|
14
|
+
"X-Project-Id" => config.project_id
|
15
|
+
}
|
16
|
+
|
17
|
+
if config.async
|
18
|
+
@http = EmHttp.new config.base_url, headers
|
19
|
+
else
|
20
|
+
@http = NetHttp.new config.base_url, headers
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def push(collection_name, event)
|
26
|
+
path_uri_part = "/events/#{CGI.escape(collection_name.to_s)}"
|
27
|
+
|
28
|
+
@http.push_events path_uri_part, event.data.to_json, event
|
29
|
+
end
|
30
|
+
|
31
|
+
def push_batch(events_by_collection)
|
32
|
+
path_uri_part = "/events"
|
33
|
+
|
34
|
+
@http.push_events path_uri_part, events_by_collection.to_json, events_by_collection
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
class NetHttp
|
41
|
+
def initialize(base_url, headers)
|
42
|
+
require 'uri'
|
43
|
+
require 'net/http'
|
44
|
+
require 'net/https'
|
45
|
+
|
46
|
+
@headers = headers
|
47
|
+
@connect_uri = URI.parse(base_url)
|
48
|
+
@http = Net::HTTP.new(@connect_uri.host, @connect_uri.port)
|
49
|
+
setup_ssl if @connect_uri.scheme == 'https'
|
50
|
+
end
|
51
|
+
|
52
|
+
def push_events(path, body, events)
|
53
|
+
response = @http.post(path, body, @headers)
|
54
|
+
ConnectClient::EventPushResponse.new response.code, response['Content-Type'], response.body, events
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def setup_ssl
|
60
|
+
root_ca = "#{ConnectClient::gem_root}/data/cacert.pem"
|
61
|
+
standard_depth = 5
|
62
|
+
|
63
|
+
@http.use_ssl = true
|
64
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
65
|
+
@http.verify_depth = standard_depth
|
66
|
+
@http.ca_file = root_ca
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class EmHttp
|
71
|
+
|
72
|
+
def initialize(base_url, headers)
|
73
|
+
require 'em-http-request'
|
74
|
+
require_relative 'deferred_http_response'
|
75
|
+
|
76
|
+
@headers = headers
|
77
|
+
@base_url = base_url.chomp('/')
|
78
|
+
end
|
79
|
+
|
80
|
+
def push_events(path, body, events)
|
81
|
+
raise AsyncHttpError unless defined?(EventMachine) && EventMachine.reactor_running?
|
82
|
+
|
83
|
+
use_syncrony = defined?(EM::Synchrony)
|
84
|
+
|
85
|
+
if use_syncrony
|
86
|
+
push_events_using_synchrony(path, body, events)
|
87
|
+
else
|
88
|
+
push_events_using_deferred(path, body, events)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def push_events_using_deferred(path, body, events)
|
93
|
+
deferred = DeferredHttpResponse.new
|
94
|
+
url_string = "#{@base_url}#{path}".chomp('/')
|
95
|
+
http = EventMachine::HttpRequest.new(url_string).post(:body => body, :head => @headers)
|
96
|
+
http_callback = Proc.new do
|
97
|
+
begin
|
98
|
+
response = create_response http, events
|
99
|
+
deferred.succeed response
|
100
|
+
rescue => error
|
101
|
+
deferred.fail error
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
http.callback &http_callback
|
106
|
+
http.errback &http_callback
|
107
|
+
|
108
|
+
deferred
|
109
|
+
end
|
110
|
+
|
111
|
+
def push_events_using_synchrony(path, body, events)
|
112
|
+
url_string = "#{@base_url}#{path}".chomp('/')
|
113
|
+
http = EventMachine::HttpRequest.new(url_string).
|
114
|
+
post(:body => body, :head => @headers)
|
115
|
+
|
116
|
+
create_response http, events
|
117
|
+
end
|
118
|
+
|
119
|
+
def create_response(http_reponse, events)
|
120
|
+
status = http_reponse.response_header.status
|
121
|
+
content_type = http_reponse.response_header['Content-Type']
|
122
|
+
if (http_reponse.error.to_s.empty?)
|
123
|
+
ConnectClient::EventPushResponse.new status, content_type, http_reponse.response, events
|
124
|
+
else
|
125
|
+
ConnectClient::EventPushResponse.new status, content_type, http_reponse.error, events
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
class AsyncHttpError < StandardError
|
131
|
+
def message
|
132
|
+
"You have tried to push events asynchronously without an event machine event loop running. The easiest way to do this is by passing a block to EM.run"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
132
136
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module ConnectClient
|
4
|
+
module Security
|
5
|
+
def self.generate_filtered_key(key_json, master_key)
|
6
|
+
key = master_key
|
7
|
+
key = Digest::SHA256.digest(key) if(key.kind_of?(String) && 32 != key.bytesize)
|
8
|
+
aes = OpenSSL::Cipher.new('AES-256-CBC')
|
9
|
+
iv = aes.random_iv
|
10
|
+
aes.encrypt
|
11
|
+
aes.key = key
|
12
|
+
aes.iv = iv
|
13
|
+
encrypted = aes.update(key_json) + aes.final
|
14
|
+
|
15
|
+
"#{bin_to_hex(iv)}-#{bin_to_hex(encrypted)}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.generate_key_json(filtered_key, master_key)
|
19
|
+
iv_and_data = filtered_key.split('-')
|
20
|
+
iv = hex_to_bin(iv_and_data[0])
|
21
|
+
encrypted_key_json = hex_to_bin(iv_and_data[1])
|
22
|
+
|
23
|
+
key = master_key
|
24
|
+
key = Digest::SHA256.digest(key) if(key.kind_of?(String) && 32 != key.bytesize)
|
25
|
+
iv = Digest::MD5.digest(iv) if(iv.kind_of?(String) && 16 != iv.bytesize)
|
26
|
+
aes = OpenSSL::Cipher.new('AES-256-CBC')
|
27
|
+
aes.decrypt
|
28
|
+
aes.key = key
|
29
|
+
aes.iv = iv
|
30
|
+
aes.update(encrypted_key_json) + aes.final
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.bin_to_hex(binary_string)
|
34
|
+
binary_string.unpack("H*").first.to_s.upcase
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.hex_to_bin(hex_string)
|
38
|
+
hex_string.scan(/../).map { |x| x.hex }.pack('c*')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -1,69 +1,69 @@
|
|
1
|
-
require 'minitest/spec'
|
2
|
-
require 'minitest/autorun'
|
3
|
-
require 'webmock/minitest'
|
4
|
-
require 'connect_client/client'
|
5
|
-
require 'connect_client/event_push_response'
|
6
|
-
require 'connect_client/configuration'
|
7
|
-
require 'securerandom'
|
8
|
-
|
9
|
-
describe ConnectClient::Client do
|
10
|
-
before do
|
11
|
-
@client = ConnectClient::Client.new (ConnectClient::Configuration.new)
|
12
|
-
@async_client = ConnectClient::Client.new (ConnectClient::Configuration.new '', '', true)
|
13
|
-
@sample_event = { id: SecureRandom.uuid, timestamp: Time.now.utc.iso8601, name: 'sample' }
|
14
|
-
@sample_events = [@sample_event]
|
15
|
-
@sample_events_reponse = '{"sample": [{"success": true}]}'
|
16
|
-
@sample_collection = 'sample'
|
17
|
-
@sample_collection_sym = :sample
|
18
|
-
end
|
19
|
-
|
20
|
-
it "should get a push response back when pushing a single event to a collection passed by string" do
|
21
|
-
stub_request(:post, "https://api.getconnect.io/events/#{@sample_collection}").
|
22
|
-
with(:body => @sample_event).
|
23
|
-
to_return(:status => 200, :body => "", :headers => { 'Content-Type'=>'application/json' })
|
24
|
-
|
25
|
-
response = @client.push @sample_collection, @sample_event
|
26
|
-
|
27
|
-
response.must_be_instance_of ConnectClient::EventPushResponse
|
28
|
-
end
|
29
|
-
|
30
|
-
it "should get a push response back when pushing a single event to a collection passed by symbol" do
|
31
|
-
stub_request(:post, "https://api.getconnect.io/events/#{@sample_collection}").
|
32
|
-
with(:body => @sample_event).
|
33
|
-
to_return(:status => 200, :body => "", :headers => { 'Content-Type'=>'application/json' })
|
34
|
-
|
35
|
-
response = @client.push @sample_collection_sym, @sample_event
|
36
|
-
|
37
|
-
response.must_be_instance_of ConnectClient::EventPushResponse
|
38
|
-
end
|
39
|
-
|
40
|
-
it "should get a push response back when pushing multiple events to a collection passed by string" do
|
41
|
-
stub_request(:post, "https://api.getconnect.io/events").
|
42
|
-
with(:body => { @sample_collection.to_sym => @sample_events }).
|
43
|
-
to_return(:status => 200, :body => @sample_events_reponse, :headers => { 'Content-Type'=>'application/json' })
|
44
|
-
|
45
|
-
response = @client.push @sample_collection, @sample_events
|
46
|
-
|
47
|
-
response.must_be_instance_of ConnectClient::EventPushResponse
|
48
|
-
end
|
49
|
-
|
50
|
-
it "should get a push response back when pushing multiple events to a collection passed by symbol" do
|
51
|
-
stub_request(:post, "https://api.getconnect.io/events").
|
52
|
-
with(:body => { @sample_collection_sym => @sample_events }).
|
53
|
-
to_return(:status => 200, :body => @sample_events_reponse, :headers => { 'Content-Type'=>'application/json' })
|
54
|
-
|
55
|
-
response = @client.push @sample_collection_sym, @sample_events
|
56
|
-
|
57
|
-
response.must_be_instance_of ConnectClient::EventPushResponse
|
58
|
-
end
|
59
|
-
|
60
|
-
it "should get a push response back when pushing batches" do
|
61
|
-
batch = { @sample_collection.to_sym => @sample_events }
|
62
|
-
stub_request(:post, "https://api.getconnect.io/events").
|
63
|
-
with(:body => batch).
|
64
|
-
to_return(:status => 200, :body => @sample_events_reponse, :headers => { 'Content-Type'=>'application/json' })
|
65
|
-
|
66
|
-
response = @client.push batch
|
67
|
-
response.must_be_instance_of ConnectClient::EventPushResponse
|
68
|
-
end
|
1
|
+
require 'minitest/spec'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'webmock/minitest'
|
4
|
+
require 'connect_client/client'
|
5
|
+
require 'connect_client/event_push_response'
|
6
|
+
require 'connect_client/configuration'
|
7
|
+
require 'securerandom'
|
8
|
+
|
9
|
+
describe ConnectClient::Client do
|
10
|
+
before do
|
11
|
+
@client = ConnectClient::Client.new (ConnectClient::Configuration.new)
|
12
|
+
@async_client = ConnectClient::Client.new (ConnectClient::Configuration.new '', '', true)
|
13
|
+
@sample_event = { id: SecureRandom.uuid, timestamp: Time.now.utc.iso8601, name: 'sample' }
|
14
|
+
@sample_events = [@sample_event]
|
15
|
+
@sample_events_reponse = '{"sample": [{"success": true}]}'
|
16
|
+
@sample_collection = 'sample'
|
17
|
+
@sample_collection_sym = :sample
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should get a push response back when pushing a single event to a collection passed by string" do
|
21
|
+
stub_request(:post, "https://api.getconnect.io/events/#{@sample_collection}").
|
22
|
+
with(:body => @sample_event).
|
23
|
+
to_return(:status => 200, :body => "", :headers => { 'Content-Type'=>'application/json' })
|
24
|
+
|
25
|
+
response = @client.push @sample_collection, @sample_event
|
26
|
+
|
27
|
+
response.must_be_instance_of ConnectClient::EventPushResponse
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should get a push response back when pushing a single event to a collection passed by symbol" do
|
31
|
+
stub_request(:post, "https://api.getconnect.io/events/#{@sample_collection}").
|
32
|
+
with(:body => @sample_event).
|
33
|
+
to_return(:status => 200, :body => "", :headers => { 'Content-Type'=>'application/json' })
|
34
|
+
|
35
|
+
response = @client.push @sample_collection_sym, @sample_event
|
36
|
+
|
37
|
+
response.must_be_instance_of ConnectClient::EventPushResponse
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should get a push response back when pushing multiple events to a collection passed by string" do
|
41
|
+
stub_request(:post, "https://api.getconnect.io/events").
|
42
|
+
with(:body => { @sample_collection.to_sym => @sample_events }).
|
43
|
+
to_return(:status => 200, :body => @sample_events_reponse, :headers => { 'Content-Type'=>'application/json' })
|
44
|
+
|
45
|
+
response = @client.push @sample_collection, @sample_events
|
46
|
+
|
47
|
+
response.must_be_instance_of ConnectClient::EventPushResponse
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should get a push response back when pushing multiple events to a collection passed by symbol" do
|
51
|
+
stub_request(:post, "https://api.getconnect.io/events").
|
52
|
+
with(:body => { @sample_collection_sym => @sample_events }).
|
53
|
+
to_return(:status => 200, :body => @sample_events_reponse, :headers => { 'Content-Type'=>'application/json' })
|
54
|
+
|
55
|
+
response = @client.push @sample_collection_sym, @sample_events
|
56
|
+
|
57
|
+
response.must_be_instance_of ConnectClient::EventPushResponse
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should get a push response back when pushing batches" do
|
61
|
+
batch = { @sample_collection.to_sym => @sample_events }
|
62
|
+
stub_request(:post, "https://api.getconnect.io/events").
|
63
|
+
with(:body => batch).
|
64
|
+
to_return(:status => 200, :body => @sample_events_reponse, :headers => { 'Content-Type'=>'application/json' })
|
65
|
+
|
66
|
+
response = @client.push batch
|
67
|
+
response.must_be_instance_of ConnectClient::EventPushResponse
|
68
|
+
end
|
69
69
|
end
|