gini-api 0.9.2 → 0.9.3

Sign up to get free protection for your applications and to get access to all the features.
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