gini-api 0.9.2 → 0.9.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3e0a78fff7ea84fe5f7124e103861964da618830
4
- data.tar.gz: 5b381a2a131e6e117f94628b37f45e8b521c3b38
3
+ metadata.gz: 48a332c434967ccd2d8906da4f64f7a8a27346b3
4
+ data.tar.gz: a3143decf245e1ef61800716558726431d9601e3
5
5
  SHA512:
6
- metadata.gz: 94f60834223b3afdedee4ef203416b48869029eb1a61b077466026866589ed2cd95226bbeb6d9c3db13ac42dff16594227fb553483c4f2825e8d6407da221e5f
7
- data.tar.gz: 4afccb044306250c91d10b8e3cc64947f10aeada8cc72a6df41499ed67f294aaba60003c7e97ae0f1efe8a6e3d3fd6bfb145c976768c863c4d7a6f29f6906b9f
6
+ metadata.gz: 33f11fa2e20aefb5e51e68fa495445c1b5da7eb6f46612ef4ed3dcbc513d071753bd2241b0e8220c9b217cbfbbf141931927a17827c3813c1f359eb54741cee2
7
+ data.tar.gz: 9819589db65cde5ddb460ac6e75887d1adf976be5bffc58707b5daf413b5999e4234e7d951d8615e02d5a0b07f219b37eba9346b13f47bb23763b9c81d8bf82c
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  [![Build Status](https://travis-ci.org/gini/gini-api-ruby.png)](https://travis-ci.org/gini/gini-api-ruby)
6
6
  [![Code Climate](https://codeclimate.com/github/gini/gini-api-ruby.png)](https://codeclimate.com/github/gini/gini-api-ruby)
7
7
  [![Coverage Status](https://coveralls.io/repos/gini/gini-api-ruby/badge.png)](https://coveralls.io/r/gini/gini-api-ruby)
8
- [![Gem Version](https://badge.fury.io/rb/gini-api-ruby.png)](http://badge.fury.io/rb/gini-api-ruby)
8
+ [![Gem Version](https://badge.fury.io/rb/gini-api.png)](http://badge.fury.io/rb/gini-api)
9
9
 
10
10
  ## Resources
11
11
 
@@ -42,10 +42,10 @@ api = Gini::Api::Client.new(
42
42
  )
43
43
 
44
44
  # auth_code (has been extracted outside of gini-api-ruby)
45
- api.login('1234567890')
45
+ api.login(auth_code: '1234567890')
46
46
 
47
47
  # username/password
48
- api.login('user@example.com', 'password')
48
+ api.login(username: 'me@example.com', password: 'secret')
49
49
  ```
50
50
 
51
51
  ### Upload
@@ -68,7 +68,7 @@ list.total
68
68
  # => 15
69
69
  list.documents
70
70
  # => [Gini::Api::Document, Gini::Api::Document, ...]
71
- list.each do { |doc| puts doc.id }
71
+ list.each { |doc| puts doc.id }
72
72
  # => 1234567890-abc, 0987654321-cba, ...
73
73
  ```
74
74
 
@@ -97,7 +97,7 @@ search.total
97
97
  # => 5
98
98
  search.documents
99
99
  # => [Gini::Api::Document, Gini::Api::Document, ...]
100
- search.each do { |doc| puts doc.id }
100
+ search.each { |doc| puts doc.id }
101
101
  # => 1234567890-abc, 0987654321-cba, ...
102
102
  ```
103
103
 
@@ -176,7 +176,7 @@ rescue Gini::Api::OAuthError => e
176
176
  end
177
177
  ```
178
178
 
179
- Please keep in mind that the amount of availabe instance variables differs depending on the error situation. For details please refer to the [Gini::Api::Error class](Gini/Api/Error.html).
179
+ Please keep in mind that the amount of availabe instance variables differs depending on the error situation. For details please refer to the ```Gini::Api::Error``` class.
180
180
 
181
181
  ## Developers
182
182
 
data/gini-api.gemspec CHANGED
@@ -4,17 +4,9 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
4
 
5
5
  require 'gini-api/version'
6
6
 
7
- gem_version = Gini::Api::BASE_VERSION.dup
8
-
9
- if ENV.has_key?('BUILD_NUMBER')
10
- gem_version << ".#{ENV['BUILD_NUMBER']}"
11
- else
12
- gem_version = Gini::Api::VERSION
13
- end
14
-
15
7
  Gem::Specification.new do |spec|
16
8
  spec.name = 'gini-api'
17
- spec.version = gem_version
9
+ spec.version = Gini::Api::VERSION
18
10
  spec.authors = ['Daniel Kerwin']
19
11
  spec.email = ['tech@gini.net']
20
12
  spec.description = %q{Ruby client to interact with the Gini API.}
@@ -61,14 +61,6 @@ module Gini
61
61
  # Sanitize api_uri
62
62
  @api_uri.sub!(/(\/)+$/, '')
63
63
 
64
- # Create upload connection
65
- @upload_connection = Faraday.new(url: @api_uri) do |builder|
66
- builder.use(Faraday::Request::Multipart)
67
- builder.use(Faraday::Request::UrlEncoded)
68
- builder.request(:retry, 3)
69
- builder.adapter(Faraday.default_adapter)
70
- end
71
-
72
64
  # Register parser (json+xml) based on API version
73
65
  register_parser
74
66
 
@@ -135,7 +127,7 @@ module Gini
135
127
  }.merge(options)
136
128
 
137
129
  timeout(@processing_timeout) do
138
- @token.send(verb.to_sym, @api_uri + URI.parse(resource).path, opts)
130
+ @token.send(verb.to_sym, resource_to_location(resource).to_s , opts)
139
131
  end
140
132
  rescue OAuth2::Error => e
141
133
  raise Gini::Api::RequestError.new(
@@ -160,49 +152,26 @@ module Gini
160
152
  # doc = api.upload('/tmp/myfile.pdf') { |d| puts "Progress: #{d.progress}" }
161
153
  #
162
154
  def upload(file, &block)
163
- @log.info("Uploading #{file}")
164
-
165
- duration = {}
155
+ duration = Hash.new(0)
166
156
 
167
157
  # Document upload
168
- duration[:upload] = Benchmark.realtime do
169
- @response = @upload_connection.post do |req|
170
- req.options[:timeout] = @upload_timeout
171
- req.url 'documents/'
172
- req.headers['Content-Type'] = 'multipart/form-data'
173
- req.headers['Authorization'] = "Bearer #{@token.token}"
174
- req.headers.merge!(version_header)
175
- req.body = { file: Faraday::UploadIO.new(file, 'application/octet-stream') }
176
- end
177
- end
158
+ duration[:upload], response = upload_document(file)
178
159
 
179
160
  # Start polling (0.5s) when document has been uploaded successfully
180
- if @response.status == 201
181
- location = @response.headers['location']
182
- doc = Gini::Api::Document.new(self, location)
183
- begin
184
- timeout(@processing_timeout) do
185
- duration[:processing] = Benchmark.realtime do
186
- doc.poll(&block)
187
- end
188
- end
189
- rescue Timeout::Error => e
190
- ex = Gini::Api::ProcessingError.new(e.message)
191
- ex.docid = doc.id
192
- raise ex
193
- end
161
+ if response.status == 201
162
+ doc = Gini::Api::Document.new(self, response.headers['location'])
163
+ duration[:processing] = poll_document(doc, &block)
164
+
165
+ duration[:total] = duration.values.inject(:+)
166
+ doc.duration = duration
167
+
168
+ doc
194
169
  else
195
- raise Gini::Api::UploadError.new(
196
- "Document upload failed with HTTP code #{@response.status}",
197
- @response
170
+ fail Gini::Api::UploadError.new(
171
+ "Document upload failed with HTTP code #{response.status}",
172
+ response
198
173
  )
199
174
  end
200
-
201
- # Combine duration values and update doc object
202
- duration[:total] = duration[:upload] + duration[:processing]
203
- doc.duration = duration
204
-
205
- doc
206
175
  end
207
176
 
208
177
  # Delete document
@@ -279,6 +248,80 @@ module Gini
279
248
  end
280
249
  Gini::Api::DocumentSet.new(self, response.parsed)
281
250
  end
251
+
252
+ private
253
+
254
+ # Helper to covert resource to a valid location.
255
+ #
256
+ # @param [String] resource URI to be converted
257
+ #
258
+ # @return [URI::HTTPS] URI::HTTPS object create from resource
259
+ #
260
+ def resource_to_location(resource)
261
+ parsed_resource = URI.parse(resource)
262
+ @api_host ||= URI.parse(@api_uri).host
263
+
264
+ URI::HTTPS.build(
265
+ host: @api_host,
266
+ path: parsed_resource.path,
267
+ query: parsed_resource.query
268
+ )
269
+ end
270
+
271
+ # Poll document and duration
272
+ #
273
+ # @param [Gini::Api::Document] doc Document instance to poll
274
+ #
275
+ # @return [Integer] Processing duration
276
+ #
277
+ def poll_document(doc, &block)
278
+ duration = 0
279
+ timeout(@processing_timeout) do
280
+ duration = Benchmark.realtime do
281
+ doc.poll(&block)
282
+ end
283
+ end
284
+ duration
285
+ rescue Timeout::Error => e
286
+ ex = Gini::Api::ProcessingError.new(e.message)
287
+ ex.docid = doc.id
288
+ raise ex
289
+ end
290
+
291
+ # Setup API upload connection
292
+ #
293
+ # @return [Faraday] Faraday object to use in upload
294
+ #
295
+ def upload_connection
296
+ @upload_connection ||= Faraday.new(url: @api_uri) do |builder|
297
+ builder.use(Faraday::Request::Multipart)
298
+ builder.use(Faraday::Request::UrlEncoded)
299
+ builder.request(:retry, 3)
300
+ builder.adapter(Faraday.default_adapter)
301
+ end
302
+ end
303
+
304
+ # Helper to upload document
305
+ #
306
+ # @param [String] file location of document to be uploaded
307
+ #
308
+ # @return [Faraday::Response] Response object from upload
309
+ #
310
+ def upload_document(file)
311
+ response = nil
312
+ duration = Benchmark.realtime do
313
+ response = upload_connection.post do |req|
314
+ req.options[:timeout] = @upload_timeout
315
+ req.url 'documents/'
316
+ req.headers['Content-Type'] = 'multipart/form-data'
317
+ req.headers['Authorization'] = "Bearer #{@token.token}"
318
+ req.headers.merge!(version_header)
319
+ req.body = { file: Faraday::UploadIO.new(file, 'application/octet-stream') }
320
+ end
321
+ end
322
+
323
+ return duration, response
324
+ end
282
325
  end
283
326
  end
284
327
  end
@@ -37,7 +37,15 @@ module Gini
37
37
  if opts.key?(:auth_code) && !opts[:auth_code].empty?
38
38
  opts[:auth_code]
39
39
  else
40
- login_with_credentials(api, client, opts[:username], opts[:password])
40
+ # Generate CSRF token to verify the response
41
+ csrf_token = SecureRandom.hex
42
+ location = login_with_credentials(
43
+ api,
44
+ client,
45
+ csrf_token,
46
+ opts[:username],
47
+ opts[:password])
48
+ extract_auth_code(location, csrf_token)
41
49
  end
42
50
 
43
51
  # Exchange code for a real token.
@@ -71,102 +79,130 @@ module Gini
71
79
  end
72
80
  end
73
81
 
74
- # Login with username/password
82
+ # Destroy token
75
83
  #
76
- # @param [Gini::Api::Client] api API object
77
- # @param [OAuth2::Client] client OAuth2 client object
78
- # @param [String] username API username
79
- # @param [String] password API password
84
+ def destroy
85
+ @token.refresh_token && @token.refresh!
86
+ response = token.delete("/accessToken/#{@token.token}")
87
+ unless response.status == 204
88
+ fail_with_oauth_error(
89
+ "Failed to destroy token /accessToken/#{@token.token} "\
90
+ "(code=#{response.status})",
91
+ response
92
+ )
93
+ end
94
+ rescue OAuth2::Error => e
95
+ fail_with_oauth_error(
96
+ "Failed to destroy token (code=#{e.response.status})",
97
+ e.response
98
+ )
99
+ end
100
+
101
+ private
102
+
103
+ # Helper method to fail with Gini::Api::OAuthError
80
104
  #
81
- # @return [String] Collected authorization code
105
+ # @param [String] msg Exception message
106
+ # @param [OAuth2::Response] response Response object
82
107
  #
83
- def login_with_credentials(api, client, username, password)
84
- # Generate CSRF token to verify the response
85
- csrf_token = SecureRandom.hex
86
-
87
- # Build authentication URI
88
- auth_uri = client.auth_code.authorize_url(
89
- redirect_uri: api.oauth_redirect,
90
- state: csrf_token
108
+ def fail_with_oauth_error(msg, response = nil)
109
+ raise Gini::Api::OAuthError.new(
110
+ msg,
111
+ response
91
112
  )
113
+ end
92
114
 
93
- begin
94
- # Accquire auth code
95
- response = client.request(
96
- :post,
97
- auth_uri,
98
- body: { username: username, password: password }
99
- )
100
- unless response.status == 303
101
- raise Gini::Api::OAuthError.new(
102
- "API login failed (code=#{response.status})",
103
- response
104
- )
105
- end
106
- rescue OAuth2::Error => e
107
- raise Gini::Api::OAuthError.new(
108
- "Failed to acquire auth_code (code=#{e.response.status})",
109
- e.response
110
- )
111
- end
112
-
113
- # Parse the location header from the response and fill hash
114
- # query_params ({'code' => '123abc', 'state' => 'supersecret'})
115
- begin
116
- q = URI.parse(response.headers['location']).query
117
- query_params = Hash[*q.split(/\=|\&/)]
118
- rescue => e
119
- raise Gini::Api::OAuthError.new("Failed to parse location header: #{e.message}")
120
- end
115
+ # Extract auth_code from URI
116
+ #
117
+ # @param [String] location Location URI containing the auth_code
118
+ # @param [String] csrf_token CSRF token to verify request
119
+ #
120
+ # @return [String] Collected authorization code
121
+ #
122
+ def extract_auth_code(location, csrf_token)
123
+ query_params = parse_location(location)
121
124
 
122
125
  unless query_params['state'] == csrf_token
123
- raise Gini::Api::OAuthError.new(
126
+ fail_with_oauth_error(
124
127
  "CSRF token mismatch detected (should=#{csrf_token}, "\
125
128
  "is=#{query_params['state']})"
126
129
  )
127
130
  end
128
131
 
129
132
  unless query_params.key?('code') && !query_params['code'].empty?
130
- raise Gini::Api::OAuthError.new(
131
- "Failed to extract code from location #{response.headers['location']}"
133
+ fail_with_oauth_error(
134
+ "Failed to extract code from location #{location}"
132
135
  )
133
136
  end
134
137
 
135
138
  query_params['code']
136
139
  end
137
140
 
138
- # Exchange auth_code for a real token
141
+ # Parse auth_code and state from URI
142
+ #
143
+ # @param [String] location Location URI with auth_code and state
144
+ #
145
+ # @return [Hash] Hash with auth_code and state
146
+ #
147
+ def parse_location(location)
148
+ # Parse the location header from the response and return hash
149
+ # {'code' => '123abc', 'state' => 'supersecret'}
150
+ q = URI.parse(location).query
151
+ Hash[*q.split(/\=|\&/)]
152
+ rescue => e
153
+ fail_with_oauth_error("Failed to parse location header: #{e.message}")
154
+ end
155
+
156
+ # Login with username/password
139
157
  #
140
158
  # @param [Gini::Api::Client] api API object
141
159
  # @param [OAuth2::Client] client OAuth2 client object
142
- # @param [String] auth_code authorization code
160
+ # @param [String] csrf_token CSRF token to verify request
161
+ # @param [String] username API username
162
+ # @param [String] password API password
143
163
  #
144
- # @return [OAuth2::AccessToken] AccessToken object
164
+ # @return [String] Location header
145
165
  #
146
- def exchange_code_for_token(api, client, auth_code)
147
- client.auth_code.get_token(auth_code, redirect_uri: api.oauth_redirect)
166
+ def login_with_credentials(api, client, csrf_token, username, password)
167
+ # Build authentication URI
168
+ auth_uri = client.auth_code.authorize_url(
169
+ redirect_uri: api.oauth_redirect,
170
+ state: csrf_token
171
+ )
172
+
173
+ # Accquire auth code
174
+ response = client.request(
175
+ :post,
176
+ auth_uri,
177
+ body: { username: username, password: password }
178
+ )
179
+ unless response.status == 303
180
+ fail_with_oauth_error(
181
+ "API login failed (code=#{response.status})",
182
+ response
183
+ )
184
+ end
185
+ response.headers['location']
148
186
  rescue OAuth2::Error => e
149
- raise Gini::Api::OAuthError.new(
150
- "Failed to exchange auth_code for token (code=#{e.response.status})",
187
+ fail_with_oauth_error(
188
+ "Failed to acquire auth_code (code=#{e.response.status})",
151
189
  e.response
152
190
  )
153
191
  end
154
192
 
155
- # Destroy token
193
+ # Exchange auth_code for a real token
156
194
  #
157
- def destroy
158
- @token.refresh_token && @token.refresh!
159
- response = token.delete("/accessToken/#{@token.token}")
160
- unless response.status == 204
161
- raise Gini::Api::OAuthError.new(
162
- "Failed to destroy token /accessToken/#{@token.token} "\
163
- "(code=#{response.status})",
164
- response
165
- )
166
- end
195
+ # @param [Gini::Api::Client] api API object
196
+ # @param [OAuth2::Client] client OAuth2 client object
197
+ # @param [String] auth_code authorization code
198
+ #
199
+ # @return [OAuth2::AccessToken] AccessToken object
200
+ #
201
+ def exchange_code_for_token(api, client, auth_code)
202
+ client.auth_code.get_token(auth_code, redirect_uri: api.oauth_redirect)
167
203
  rescue OAuth2::Error => e
168
- raise Gini::Api::OAuthError.new(
169
- "Failed to destroy token (code=#{e.response.status})",
204
+ fail_with_oauth_error(
205
+ "Failed to exchange auth_code for token (code=#{e.response.status})",
170
206
  e.response
171
207
  )
172
208
  end
@@ -1,9 +1,5 @@
1
1
  module Gini
2
2
  module Api
3
- # Base version for semantic versioning
4
- BASE_VERSION = '0.9'
5
-
6
- # Version string
7
- VERSION = "#{BASE_VERSION}.2"
3
+ VERSION = "0.9.3"
8
4
  end
9
5
  end
@@ -62,8 +62,6 @@ describe Gini::Api::OAuth do
62
62
  )
63
63
  end
64
64
 
65
- it { should respond_to(:login_with_credentials) }
66
- it { should respond_to(:exchange_code_for_token) }
67
65
  it { should respond_to(:destroy) }
68
66
 
69
67
  it 'does set token' do
@@ -28,10 +28,14 @@ describe 'Gini::Api integration test' do
28
28
 
29
29
  context 'OAuth' do
30
30
 
31
- it '#login sets token' do
31
+ before do
32
32
  @api.login(username: @user, password: @pass)
33
+ end
34
+
35
+ it '#login sets token' do
33
36
  expect(@api.token.token).to match(/\w+-\w+/)
34
37
  expect(@api.token.expired?).to be_false
38
+ @api.logout
35
39
  end
36
40
 
37
41
  it '#logout destroys token' do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gini-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel Kerwin
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-03-17 00:00:00.000000000 Z
11
+ date: 2014-03-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: oauth2