rawscsi 1.3.1 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -81,6 +81,20 @@ Rawscsi.register 'Book' do |config|
81
81
  end
82
82
  ```
83
83
 
84
+ You can also specify AWS user credentials if you want to access the private serach domain.
85
+ (note: search domain and AWS credentials specified below are fictional and serve only as an example)
86
+
87
+ ```ruby
88
+ Rawscsi.register 'SuperSecretDiaryEntry' do |config|
89
+ config.domain_name = 'super-secret-diary-entries'
90
+ config.domain_id = '12be09027f865cba2d57719'
91
+ config.region = 'us-east-1'
92
+ config.api_version = '2013-01-01' # rawscsi only supports api version 2013-01-01
93
+ config.access_key_id = '7386b16f9bee30d6e5a98786'
94
+ config.secret_key = 'af86958e6919a03713408fd9'
95
+ end
96
+ ```
97
+
84
98
  ### Uploading indices
85
99
  ```ruby
86
100
  song_indexer = Rawscsi::Index.new('Song', :active_record => true)
@@ -309,6 +323,18 @@ search_songs.search(q: {and: [{artist: "Beatles"}]},
309
323
  limit: 5)
310
324
  ```
311
325
 
326
+ ## Request signing
327
+
328
+ [Signature V4 guide from Amazon](http://docs.aws.amazon.com/general/latest/gr/sigv4_signing.html)
329
+
330
+ Request signature is created by `Rawscsi::RequestSignature` class with simple public api with only a few methods:
331
+
332
+ + `#initialize` - accepts the hash with the data of request to sign.
333
+ Required keys are `:secret_key`, `:access_key_id`, `:region_name`, `:endpoint`, `:method`, `:host`.
334
+ Optional keys are `:debug`, `:payload`, `:service_name`, `:headers`, `:query`.
335
+
336
+ + `#build` - calculates and returns the hash with `:signature` key containing the headers to include in request.
337
+ If `:debug` is passed on object creation, it also provides the debug data in `:debug` key, including of results of intermidiate steps.
312
338
 
313
339
  ## Contributing
314
340
 
data/lib/rawscsi/index.rb CHANGED
@@ -52,6 +52,21 @@ module Rawscsi
52
52
  req.url "#{config.api_version}/documents/batch"
53
53
  req.headers["Content-type"] = "application/json"
54
54
  req.body = payload.to_json
55
+ if config.secret_key && config.access_key_id
56
+ signature = RequestSignature.new({
57
+ secret_key: config.secret_key,
58
+ access_key_id: config.access_key_id,
59
+ region_name: config.region,
60
+ endpoint: "/#{req.path}",
61
+ method: req.method.to_s.upcase,
62
+ headers: req.headers,
63
+ host: connection.url_prefix.hostname,
64
+ payload: req.body,
65
+ }).build
66
+ signature[:headers].each do |name, value|
67
+ req.headers[name] = value
68
+ end
69
+ end
55
70
  end
56
71
  resp.body
57
72
  end
@@ -7,7 +7,7 @@ module Rawscsi
7
7
  attr_reader :url
8
8
 
9
9
  def initialize(config)
10
- @url= "http://doc-#{config.domain_name}-#{config.domain_id}.#{config.region}.cloudsearch.amazonaws.com"
10
+ @url = config.index_domain || "http://doc-#{config.domain_name}-#{config.domain_id}.#{config.region}.cloudsearch.amazonaws.com"
11
11
  end
12
12
 
13
13
  def build
@@ -0,0 +1,165 @@
1
+ module Rawscsi
2
+ class RequestSignature
3
+ def initialize(options)
4
+ required_attributes_missing = []
5
+ require_attribute = lambda do |name|
6
+ return options[name] if options.has_key?(name)
7
+ required_attributes_missing << name
8
+ nil
9
+ end
10
+
11
+ self.secret_key = require_attribute[:secret_key]
12
+ self.access_key_id = require_attribute[:access_key_id]
13
+ self.region_name = require_attribute[:region_name]
14
+ self.endpoint = require_attribute[:endpoint]
15
+ self.method = require_attribute[:method]
16
+ self.host = require_attribute[:host]
17
+
18
+ unless required_attributes_missing.size == 0
19
+ raise "#{required_attributes_missing.join(',')} attributes required for a request signature"
20
+ end
21
+
22
+ self.debug_mode = options[:debug] || false
23
+ self.payload = options[:payload] || ''
24
+ self.service_name = options[:service_name] || 'cloudsearch'
25
+ self.headers = extract_headers(options)
26
+ self.query = options[:query] || ''
27
+ end
28
+
29
+ def build
30
+ result_headers = default_headers.dup
31
+ result_headers['Authorization'] = "#{algo} Credential=#{access_key_id}/#{credential}, SignedHeaders=#{canonical_headers_names_string}, Signature=#{signature}"
32
+
33
+ result = {
34
+ headers: result_headers,
35
+ }
36
+
37
+ if debug_mode
38
+ result[:debug] = debug_data
39
+ end
40
+
41
+ result
42
+ end
43
+
44
+ private
45
+
46
+ def extract_headers(options)
47
+ return default_headers unless opt_headers = options[:headers]
48
+ opt_headers.to_h.merge(default_headers)
49
+ end
50
+
51
+ def debug_data
52
+ {
53
+ canonical_request: canonical_request,
54
+ payload_digest: payload_digest,
55
+ canonical_request_digest: canonical_request_digest,
56
+ string_to_sign: string_to_sign,
57
+ signature: signature,
58
+ signed_headers: canonical_headers_names_string,
59
+ payload: payload,
60
+ }
61
+ end
62
+
63
+ attr_accessor :secret_key,
64
+ :access_key_id,
65
+ :headers,
66
+ :payload,
67
+ :region_name,
68
+ :service_name,
69
+ :method,
70
+ :endpoint,
71
+ :query,
72
+ :host,
73
+ :debug_mode
74
+
75
+ def signature
76
+ OpenSSL::HMAC.hexdigest('sha256', signature_key, string_to_sign)
77
+ end
78
+
79
+ def algo
80
+ 'AWS4-HMAC-SHA256'
81
+ end
82
+
83
+ def default_headers
84
+ {
85
+ 'X-Amz-Date' => amz_datetime,
86
+ 'Host' => host,
87
+ }
88
+ end
89
+
90
+ def signature_key
91
+ k_date = OpenSSL::HMAC.digest('sha256', "AWS4" + secret_key, date)
92
+ k_region = OpenSSL::HMAC.digest('sha256', k_date, region_name)
93
+ k_service = OpenSSL::HMAC.digest('sha256', k_region, service_name)
94
+ k_signing = OpenSSL::HMAC.digest('sha256', k_service, "aws4_request")
95
+
96
+ k_signing
97
+ end
98
+
99
+ def datetime
100
+ @datetime ||= Time.now.utc
101
+ end
102
+
103
+ def amz_datetime
104
+ datetime.strftime('%Y%m%dT%H%M%SZ')
105
+ end
106
+
107
+ def date
108
+ datetime.strftime('%Y%m%d')
109
+ end
110
+
111
+ def credential
112
+ "#{date}/#{region_name}/cloudsearch/aws4_request"
113
+ end
114
+
115
+ def string_to_sign
116
+ [
117
+ algo,
118
+ amz_datetime,
119
+ credential,
120
+ canonical_request_digest,
121
+ ].join("\n")
122
+ end
123
+
124
+ def canonical_query_string
125
+ # @NOTE gsubs are here because AWS expects us to escape everything but #encode_www_form encodes space as "+"
126
+ @canonical_query_string = URI.encode_www_form(CGI::parse(query).to_a.sort { |a, b| a.first <=> b.first }).gsub('+', '%20').gsub('*', '%2A')
127
+ end
128
+
129
+ def canonical_headers_names_string
130
+ canonical_headers.map(&:first).join(';')
131
+ end
132
+
133
+ def canonical_headers
134
+ @canonical_headers ||= headers.to_a.group_by(&:first).map do |name, values|
135
+ canonical_values = values.map(&:last).map do |value|
136
+ value.to_s.first == '"' ? value : value.squeeze(' ')
137
+ end
138
+ [ name.to_s.downcase, canonical_values.join(',') ]
139
+ end.sort { |a, b| a.first <=> b.first }
140
+ end
141
+
142
+ def canonical_headers_string
143
+ canonical_headers.map { |header| header.join(':') }.join("\n") + "\n"
144
+ end
145
+
146
+ def payload_digest
147
+ @payload_digest ||= OpenSSL::Digest::SHA256.hexdigest(payload)
148
+ end
149
+
150
+ def canonical_request
151
+ @canonical_request ||= [
152
+ method,
153
+ endpoint,
154
+ canonical_query_string,
155
+ canonical_headers_string,
156
+ canonical_headers_names_string,
157
+ payload_digest,
158
+ ].join("\n")
159
+ end
160
+
161
+ def canonical_request_digest
162
+ @canonical_request_digest ||= OpenSSL::Digest::SHA256.hexdigest(canonical_request)
163
+ end
164
+ end
165
+ end
@@ -24,24 +24,49 @@ module Rawscsi
24
24
  end
25
25
 
26
26
  private
27
+ def url_schema
28
+ config.use_https ? 'https' : 'http'
29
+ end
30
+
31
+ def canonical_url
32
+ "/#{config.api_version}/search"
33
+ end
34
+
35
+ def search_domain
36
+ config.search_domain || "search-#{config.domain_name}-#{config.domain_id}.#{config.region}.cloudsearch.amazonaws.com"
37
+ end
38
+
27
39
  def url(query)
28
40
  [
29
- "http://search-",
30
- "#{config.domain_name}-",
31
- "#{config.domain_id}.",
32
- "#{config.region}.",
33
- "cloudsearch.amazonaws.com/",
34
- "#{config.api_version}/",
35
- "search?",
41
+ "#{url_schema}",
42
+ "://",
43
+ "#{search_domain}",
44
+ "#{canonical_url}",
45
+ '?',
36
46
  query
37
47
  ].join
38
48
  end
39
49
 
40
50
  def send_request_to_aws(query)
41
51
  url_query = url(query)
42
- HTTParty.get(url_query)
52
+
53
+ signature = if config.access_key_id && config.secret_key
54
+ Rawscsi::RequestSignature.new({
55
+ secret_key: config.secret_key,
56
+ access_key_id: config.access_key_id,
57
+ region_name: config.region,
58
+ endpoint: canonical_url,
59
+ query: query,
60
+ method: 'GET',
61
+ host: search_domain,
62
+ }).build
63
+ else
64
+ {}
65
+ end
66
+ HTTParty.get(url_query, headers: signature[:headers])
43
67
  end
44
68
 
69
+
45
70
  def results_container(response)
46
71
  if is_active_record
47
72
  Rawscsi::SearchHelpers::ResultsActiveRecord.new(response, model)
@@ -1,3 +1,3 @@
1
1
  module Rawscsi
2
- VERSION = "1.3.1"
2
+ VERSION = "1.4.1"
3
3
  end
data/lib/rawscsi.rb CHANGED
@@ -1,10 +1,11 @@
1
1
  $:.unshift(File.expand_path("../", __FILE__))
2
2
 
3
3
  module Rawscsi
4
- autoload :VERSION, 'rawscsi/version'
5
- autoload :Base, 'rawscsi/base'
6
- autoload :Search, 'rawscsi/search'
7
- autoload :Index, 'rawscsi/index'
4
+ autoload :VERSION, 'rawscsi/version'
5
+ autoload :Base, 'rawscsi/base'
6
+ autoload :RequestSignature, 'rawscsi/request_signature'
7
+ autoload :Search, 'rawscsi/search'
8
+ autoload :Index, 'rawscsi/index'
8
9
 
9
10
  module Query
10
11
  autoload :Simple, "rawscsi/query/simple"
@@ -37,7 +38,12 @@ module Rawscsi
37
38
  :region,
38
39
  :api_version,
39
40
  :attributes,
40
- :batch_size
41
+ :batch_size,
42
+ :use_https,
43
+ :access_key_id,
44
+ :secret_key,
45
+ :search_domain,
46
+ :index_domain
41
47
  end
42
48
 
43
49
  def self.register(model)
@@ -0,0 +1,42 @@
1
+ $root = File.expand_path('../../../../', __FILE__)
2
+ require "#{$root}/spec/spec_helper"
3
+
4
+ describe Rawscsi::RequestSignature do
5
+ it "initializes config attrs correctly" do
6
+ options = {
7
+ secret_key: "test_secret_key",
8
+ access_key_id: "test_access_key_id",
9
+ region_name: "test_region_name",
10
+ endpoint: "test_endpoint",
11
+ method: "test_method",
12
+ host: "test_host"
13
+ }
14
+
15
+ rs = Rawscsi::RequestSignature.new(options)
16
+ expect(rs.send(:secret_key)).to eq("test_secret_key")
17
+ expect(rs.send(:access_key_id)).to eq("test_access_key_id")
18
+ expect(rs.send(:region_name)).to eq("test_region_name")
19
+ expect(rs.send(:endpoint)).to eq("test_endpoint")
20
+ expect(rs.send(:method)).to eq("test_method")
21
+ expect(rs.send(:host)).to eq("test_host")
22
+ end
23
+
24
+ it "#date and amz_datetime work" do
25
+ options = {
26
+ secret_key: "test_secret_key",
27
+ access_key_id: "test_access_key_id",
28
+ region_name: "test_region_name",
29
+ endpoint: "test_endpoint",
30
+ method: "test_method",
31
+ host: "test_host"
32
+ }
33
+
34
+ rs = Rawscsi::RequestSignature.new(options)
35
+ date = rs.send(:date)
36
+ amz_date = rs.send(:amz_datetime)
37
+
38
+ expect(/\d{8}/).to match(date)
39
+ expect(/\d{8}T\d{6}/).to match(amz_date)
40
+ end
41
+ end
42
+
metadata CHANGED
@@ -1,18 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rawscsi
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.1
5
+ prerelease:
5
6
  platform: ruby
6
7
  authors:
7
8
  - Steven Li
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
- date: 2015-03-28 00:00:00.000000000 Z
12
+ date: 2015-07-15 00:00:00.000000000 Z
12
13
  dependencies:
13
14
  - !ruby/object:Gem::Dependency
14
15
  name: bundler
15
16
  requirement: !ruby/object:Gem::Requirement
17
+ none: false
16
18
  requirements:
17
19
  - - ~>
18
20
  - !ruby/object:Gem::Version
@@ -20,6 +22,7 @@ dependencies:
20
22
  type: :development
21
23
  prerelease: false
22
24
  version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
23
26
  requirements:
24
27
  - - ~>
25
28
  - !ruby/object:Gem::Version
@@ -27,20 +30,23 @@ dependencies:
27
30
  - !ruby/object:Gem::Dependency
28
31
  name: rake
29
32
  requirement: !ruby/object:Gem::Requirement
33
+ none: false
30
34
  requirements:
31
- - - '>='
35
+ - - ! '>='
32
36
  - !ruby/object:Gem::Version
33
37
  version: '0'
34
38
  type: :development
35
39
  prerelease: false
36
40
  version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
37
42
  requirements:
38
- - - '>='
43
+ - - ! '>='
39
44
  - !ruby/object:Gem::Version
40
45
  version: '0'
41
46
  - !ruby/object:Gem::Dependency
42
47
  name: rspec
43
48
  requirement: !ruby/object:Gem::Requirement
49
+ none: false
44
50
  requirements:
45
51
  - - '='
46
52
  - !ruby/object:Gem::Version
@@ -48,6 +54,7 @@ dependencies:
48
54
  type: :development
49
55
  prerelease: false
50
56
  version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
51
58
  requirements:
52
59
  - - '='
53
60
  - !ruby/object:Gem::Version
@@ -55,62 +62,71 @@ dependencies:
55
62
  - !ruby/object:Gem::Dependency
56
63
  name: vcr
57
64
  requirement: !ruby/object:Gem::Requirement
65
+ none: false
58
66
  requirements:
59
- - - '>='
67
+ - - ! '>='
60
68
  - !ruby/object:Gem::Version
61
69
  version: '0'
62
70
  type: :development
63
71
  prerelease: false
64
72
  version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
65
74
  requirements:
66
- - - '>='
75
+ - - ! '>='
67
76
  - !ruby/object:Gem::Version
68
77
  version: '0'
69
78
  - !ruby/object:Gem::Dependency
70
79
  name: fakeweb
71
80
  requirement: !ruby/object:Gem::Requirement
81
+ none: false
72
82
  requirements:
73
- - - '>='
83
+ - - ! '>='
74
84
  - !ruby/object:Gem::Version
75
85
  version: '0'
76
86
  type: :development
77
87
  prerelease: false
78
88
  version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
79
90
  requirements:
80
- - - '>='
91
+ - - ! '>='
81
92
  - !ruby/object:Gem::Version
82
93
  version: '0'
83
94
  - !ruby/object:Gem::Dependency
84
95
  name: pry
85
96
  requirement: !ruby/object:Gem::Requirement
97
+ none: false
86
98
  requirements:
87
- - - '>='
99
+ - - ! '>='
88
100
  - !ruby/object:Gem::Version
89
101
  version: '0'
90
102
  type: :development
91
103
  prerelease: false
92
104
  version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
93
106
  requirements:
94
- - - '>='
107
+ - - ! '>='
95
108
  - !ruby/object:Gem::Version
96
109
  version: '0'
97
110
  - !ruby/object:Gem::Dependency
98
111
  name: activerecord
99
112
  requirement: !ruby/object:Gem::Requirement
113
+ none: false
100
114
  requirements:
101
- - - '>'
115
+ - - ! '>'
102
116
  - !ruby/object:Gem::Version
103
117
  version: '2.0'
104
118
  type: :development
105
119
  prerelease: false
106
120
  version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
107
122
  requirements:
108
- - - '>'
123
+ - - ! '>'
109
124
  - !ruby/object:Gem::Version
110
125
  version: '2.0'
111
126
  - !ruby/object:Gem::Dependency
112
127
  name: httparty
113
128
  requirement: !ruby/object:Gem::Requirement
129
+ none: false
114
130
  requirements:
115
131
  - - ~>
116
132
  - !ruby/object:Gem::Version
@@ -118,6 +134,7 @@ dependencies:
118
134
  type: :runtime
119
135
  prerelease: false
120
136
  version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
121
138
  requirements:
122
139
  - - ~>
123
140
  - !ruby/object:Gem::Version
@@ -125,6 +142,7 @@ dependencies:
125
142
  - !ruby/object:Gem::Dependency
126
143
  name: faraday
127
144
  requirement: !ruby/object:Gem::Requirement
145
+ none: false
128
146
  requirements:
129
147
  - - '='
130
148
  - !ruby/object:Gem::Version
@@ -132,6 +150,7 @@ dependencies:
132
150
  type: :runtime
133
151
  prerelease: false
134
152
  version_requirements: !ruby/object:Gem::Requirement
153
+ none: false
135
154
  requirements:
136
155
  - - '='
137
156
  - !ruby/object:Gem::Version
@@ -139,15 +158,17 @@ dependencies:
139
158
  - !ruby/object:Gem::Dependency
140
159
  name: faraday_middleware
141
160
  requirement: !ruby/object:Gem::Requirement
161
+ none: false
142
162
  requirements:
143
- - - '>='
163
+ - - ! '>='
144
164
  - !ruby/object:Gem::Version
145
165
  version: '0'
146
166
  type: :runtime
147
167
  prerelease: false
148
168
  version_requirements: !ruby/object:Gem::Requirement
169
+ none: false
149
170
  requirements:
150
- - - '>='
171
+ - - ! '>='
151
172
  - !ruby/object:Gem::Version
152
173
  version: '0'
153
174
  description: Ruby Amazon Web Services Cloud Search Interface
@@ -172,6 +193,7 @@ files:
172
193
  - lib/rawscsi/query/compound.rb
173
194
  - lib/rawscsi/query/simple.rb
174
195
  - lib/rawscsi/query/stringifier.rb
196
+ - lib/rawscsi/request_signature.rb
175
197
  - lib/rawscsi/search.rb
176
198
  - lib/rawscsi/search_helpers/results_active_record.rb
177
199
  - lib/rawscsi/search_helpers/results_hash.rb
@@ -199,6 +221,7 @@ files:
199
221
  - spec/lib/rawscsi/index_spec.rb
200
222
  - spec/lib/rawscsi/query/compound_spec.rb
201
223
  - spec/lib/rawscsi/query/simple_spec.rb
224
+ - spec/lib/rawscsi/request_signature_spec.rb
202
225
  - spec/lib/rawscsi/search_spec.rb
203
226
  - spec/lib/rawscsi/version_spec.rb
204
227
  - spec/rawscsi_spec.rb
@@ -206,26 +229,27 @@ files:
206
229
  homepage: https://github.com/stevenjl/rawscsi
207
230
  licenses:
208
231
  - MIT
209
- metadata: {}
210
232
  post_install_message:
211
233
  rdoc_options: []
212
234
  require_paths:
213
235
  - lib
214
236
  required_ruby_version: !ruby/object:Gem::Requirement
237
+ none: false
215
238
  requirements:
216
- - - '>='
239
+ - - ! '>='
217
240
  - !ruby/object:Gem::Version
218
241
  version: '0'
219
242
  required_rubygems_version: !ruby/object:Gem::Requirement
243
+ none: false
220
244
  requirements:
221
- - - '>='
245
+ - - ! '>='
222
246
  - !ruby/object:Gem::Version
223
247
  version: '0'
224
248
  requirements: []
225
249
  rubyforge_project:
226
- rubygems_version: 2.0.3
250
+ rubygems_version: 1.8.23
227
251
  signing_key:
228
- specification_version: 4
252
+ specification_version: 3
229
253
  summary: Adds service objects to upload and search active record models with AWS Cloud
230
254
  Search
231
255
  test_files:
@@ -248,6 +272,7 @@ test_files:
248
272
  - spec/lib/rawscsi/index_spec.rb
249
273
  - spec/lib/rawscsi/query/compound_spec.rb
250
274
  - spec/lib/rawscsi/query/simple_spec.rb
275
+ - spec/lib/rawscsi/request_signature_spec.rb
251
276
  - spec/lib/rawscsi/search_spec.rb
252
277
  - spec/lib/rawscsi/version_spec.rb
253
278
  - spec/rawscsi_spec.rb
checksums.yaml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- SHA1:
3
- metadata.gz: 923dce596e8cab8223bc36556fd4e2d0d6103069
4
- data.tar.gz: 0a1286047450f52f111b6e271747f37aff77ae5a
5
- SHA512:
6
- metadata.gz: 69cf3c92334604907b0102f5d83fd3b28ce0894f8b055fac24c07e725ff4bda614c2b764dba9746a5020d0ac7a28c1df365d234d43881a7f5f2135c119b49e02
7
- data.tar.gz: 8d1495fe10ffbc00b656272da73716deb5bd72a2e21dd978ff6f0c02c7799af1349998f8fe93d43fda76996aa7f2bac1d32930a5abd9cd30e556fb977347e00e