rawscsi 1.3.1 → 1.4.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.
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