smile-identity-core 1.2.1 → 2.0.0
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/.github/workflows/release.yml +18 -0
- data/.github/workflows/test.yml +36 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +6 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +71 -30
- data/Gemfile +3 -1
- data/Gemfile.lock +35 -7
- data/README.md +33 -464
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/examples/biometric_kyc.rb +67 -0
- data/examples/business_verification.rb +37 -0
- data/examples/document_verification.rb +74 -0
- data/examples/enhanced_kyc.rb +39 -0
- data/examples/example-project/Gemfile +7 -0
- data/examples/example-project/Gemfile.lock +25 -0
- data/examples/example-project/README.md +14 -0
- data/examples/example-project/sample.env +4 -0
- data/examples/example-project/smart_bank.rb +216 -0
- data/examples/smart_selfie_authentication.rb +56 -0
- data/lib/smile-identity-core/business_verification.rb +109 -0
- data/lib/smile-identity-core/constants/env.rb +13 -0
- data/lib/smile-identity-core/constants/image_type.rb +14 -0
- data/lib/smile-identity-core/constants/job_type.rb +13 -0
- data/lib/smile-identity-core/id_api.rb +35 -58
- data/lib/smile-identity-core/signature.rb +15 -36
- data/lib/smile-identity-core/utilities.rb +28 -55
- data/lib/smile-identity-core/validations.rb +32 -0
- data/lib/smile-identity-core/version.rb +4 -1
- data/lib/smile-identity-core/web_api.rb +92 -136
- data/lib/smile-identity-core.rb +11 -5
- data/smile-identity-core.gemspec +26 -21
- metadata +82 -22
- data/.travis.yml +0 -11
@@ -1,50 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'tempfile'
|
3
5
|
require 'base64'
|
4
6
|
require 'openssl'
|
5
7
|
require 'uri'
|
6
|
-
|
7
8
|
require 'typhoeus'
|
8
9
|
require 'zip'
|
9
10
|
|
10
11
|
module SmileIdentityCore
|
12
|
+
# Allows Identity verifications of ids with images
|
11
13
|
class WebApi
|
12
|
-
|
13
14
|
def initialize(partner_id, default_callback, api_key, sid_server)
|
14
15
|
@partner_id = partner_id.to_s
|
15
16
|
@callback_url = default_callback
|
16
17
|
@api_key = api_key
|
17
|
-
|
18
18
|
@sid_server = sid_server
|
19
|
-
if
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@url = sid_server_mapping[sid_server.to_i]
|
25
|
-
else
|
26
|
-
@url = sid_server
|
27
|
-
end
|
19
|
+
@url = if sid_server !~ URI::DEFAULT_PARSER.make_regexp
|
20
|
+
SmileIdentityCore::ENV::SID_SERVER_MAPPING[sid_server.to_s]
|
21
|
+
else
|
22
|
+
sid_server
|
23
|
+
end
|
28
24
|
end
|
29
25
|
|
30
26
|
def submit_job(partner_params, images, id_info, options)
|
31
|
-
|
32
27
|
self.partner_params = symbolize_keys partner_params
|
33
|
-
if @partner_params[:job_type].to_i ==
|
28
|
+
if @partner_params[:job_type].to_i == JobType::ENHANCED_KYC
|
34
29
|
return SmileIdentityCore::IDApi.new(@partner_id, @api_key, @sid_server).submit_job(partner_params, id_info)
|
35
30
|
end
|
36
31
|
|
32
|
+
if @partner_params[:job_type].to_i == JobType::BUSINESS_VERIFICATION
|
33
|
+
return SmileIdentityCore::BusinessVerification.new(@partner_id, @api_key, @sid_server).submit_job(
|
34
|
+
partner_params, id_info
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
37
38
|
self.images = images
|
38
39
|
self.id_info = symbolize_keys id_info
|
39
40
|
self.options = symbolize_keys options
|
40
41
|
|
41
|
-
|
42
|
-
@callback_url = @options[:optional_callback]
|
43
|
-
end
|
42
|
+
@callback_url = @options[:optional_callback] if @options[:optional_callback]&.length&.positive?
|
44
43
|
|
45
|
-
if @partner_params[:job_type].to_i == 1
|
46
|
-
validate_enroll_with_id
|
47
|
-
end
|
44
|
+
validate_enroll_with_id if @partner_params[:job_type].to_i == 1
|
48
45
|
|
49
46
|
validate_return_data
|
50
47
|
|
@@ -59,21 +56,18 @@ module SmileIdentityCore
|
|
59
56
|
job_id = partner_params[:job_id]
|
60
57
|
|
61
58
|
utilities = SmileIdentityCore::Utilities.new(@partner_id, @api_key, @sid_server)
|
62
|
-
utilities.get_job_status(user_id, job_id, options)
|
59
|
+
utilities.get_job_status(user_id, job_id, options)
|
63
60
|
end
|
64
61
|
|
65
62
|
def partner_params=(partner_params)
|
66
|
-
if partner_params
|
67
|
-
raise ArgumentError, 'Please ensure that you send through partner params'
|
68
|
-
end
|
63
|
+
raise ArgumentError, 'Please ensure that you send through partner params' if partner_params.nil?
|
69
64
|
|
70
|
-
|
71
|
-
raise ArgumentError, 'Partner params needs to be a hash'
|
72
|
-
end
|
65
|
+
raise ArgumentError, 'Partner params needs to be a hash' unless partner_params.is_a?(Hash)
|
73
66
|
|
74
|
-
[
|
75
|
-
|
76
|
-
raise ArgumentError,
|
67
|
+
%i[user_id job_id job_type].each do |key|
|
68
|
+
if partner_params[key].to_s.empty?
|
69
|
+
raise ArgumentError,
|
70
|
+
"Please make sure that #{key} is included in the partner params"
|
77
71
|
end
|
78
72
|
end
|
79
73
|
|
@@ -81,46 +75,30 @@ module SmileIdentityCore
|
|
81
75
|
end
|
82
76
|
|
83
77
|
def images=(images)
|
84
|
-
if images
|
85
|
-
raise ArgumentError, 'Please ensure that you send through image details'
|
86
|
-
end
|
78
|
+
raise ArgumentError, 'Please ensure that you send through image details' if images.nil?
|
87
79
|
|
88
|
-
|
89
|
-
raise ArgumentError, 'Image details needs to be an array'
|
90
|
-
end
|
80
|
+
raise ArgumentError, 'Image details needs to be an array' unless images.is_a?(Array)
|
91
81
|
|
92
82
|
# all job types require atleast a selfie
|
93
|
-
if images.length
|
83
|
+
if images.length.zero? || images.none? { |h| (h[:image_type_id]).zero? || h[:image_type_id] == 2 }
|
94
84
|
raise ArgumentError, 'You need to send through at least one selfie image'
|
95
85
|
end
|
96
86
|
|
97
87
|
@images = images.map { |image| symbolize_keys image }
|
98
|
-
|
99
88
|
end
|
100
89
|
|
101
90
|
def id_info=(id_info)
|
102
|
-
|
103
|
-
updated_id_info = id_info
|
104
|
-
|
105
|
-
if updated_id_info.nil?
|
106
|
-
updated_id_info = {}
|
107
|
-
end
|
91
|
+
updated_id_info = id_info.nil? ? {} : id_info
|
108
92
|
|
109
93
|
# if it doesnt exist, set it false
|
110
|
-
if
|
111
|
-
updated_id_info[:entered] = "false"
|
112
|
-
end
|
94
|
+
updated_id_info[:entered] = 'false' if !updated_id_info.key?(:entered) || id_info[:entered].empty?
|
113
95
|
|
114
96
|
# if it's a boolean
|
115
|
-
if
|
116
|
-
updated_id_info[:entered] = id_info[:entered].to_s
|
117
|
-
end
|
97
|
+
updated_id_info[:entered] = id_info[:entered].to_s if !updated_id_info[:entered].nil? == updated_id_info[:entered]
|
118
98
|
|
119
99
|
if updated_id_info[:entered] && updated_id_info[:entered] == 'true'
|
120
|
-
[
|
121
|
-
|
122
|
-
raise ArgumentError, "Please make sure that #{key.to_s} is included in the id_info"
|
123
|
-
end
|
100
|
+
%i[country id_type id_number].each do |key|
|
101
|
+
raise ArgumentError, "Please make sure that #{key} is included in the id_info" if id_info[key].to_s.empty?
|
124
102
|
end
|
125
103
|
end
|
126
104
|
|
@@ -134,7 +112,6 @@ module SmileIdentityCore
|
|
134
112
|
updated_options[:return_job_status] = check_boolean(:return_job_status, options)
|
135
113
|
updated_options[:return_image_links] = check_boolean(:return_image_links, options)
|
136
114
|
updated_options[:return_history] = check_boolean(:return_history, options)
|
137
|
-
@use_new_signature = updated_options.fetch(:signature, false)
|
138
115
|
|
139
116
|
@options = updated_options
|
140
117
|
end
|
@@ -157,7 +134,13 @@ module SmileIdentityCore
|
|
157
134
|
private
|
158
135
|
|
159
136
|
def request_web_token(request_params)
|
160
|
-
request_params
|
137
|
+
request_params
|
138
|
+
.merge(SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_signature(Time.now.to_s))
|
139
|
+
.merge!(
|
140
|
+
{ partner_id: @partner_id,
|
141
|
+
source_sdk: SmileIdentityCore::SOURCE_SDK,
|
142
|
+
source_sdk_version: SmileIdentityCore::VERSION }
|
143
|
+
)
|
161
144
|
url = "#{@url}/token"
|
162
145
|
|
163
146
|
response = Typhoeus.post(
|
@@ -171,8 +154,8 @@ module SmileIdentityCore
|
|
171
154
|
raise "#{response.code}: #{response.body}"
|
172
155
|
end
|
173
156
|
|
174
|
-
def symbolize_keys
|
175
|
-
|
157
|
+
def symbolize_keys(params)
|
158
|
+
params.is_a?(Hash) ? params.transform_keys(&:to_sym) : params
|
176
159
|
end
|
177
160
|
|
178
161
|
def validate_return_data
|
@@ -182,25 +165,21 @@ module SmileIdentityCore
|
|
182
165
|
end
|
183
166
|
|
184
167
|
def validate_enroll_with_id
|
185
|
-
if(
|
168
|
+
if (@images.none? { |h| h[:image_type_id] == 1 || h[:image_type_id] == 3 }) && @id_info[:entered] != 'true'
|
186
169
|
raise ArgumentError, 'You are attempting to complete a job type 1 without providing an id card image or id info'
|
187
170
|
end
|
188
171
|
end
|
189
172
|
|
190
173
|
def check_boolean(key, obj)
|
191
|
-
if
|
192
|
-
return false
|
193
|
-
end
|
174
|
+
return false if !obj || !obj[key]
|
194
175
|
|
195
|
-
if
|
196
|
-
raise ArgumentError, "#{key} needs to be a boolean"
|
197
|
-
end
|
176
|
+
raise ArgumentError, "#{key} needs to be a boolean" if !obj[key].nil? != obj[key]
|
198
177
|
|
199
178
|
obj[key]
|
200
179
|
end
|
201
180
|
|
202
181
|
def check_string(key, obj)
|
203
|
-
if
|
182
|
+
if !obj || !obj[key]
|
204
183
|
''
|
205
184
|
else
|
206
185
|
obj[key]
|
@@ -217,55 +196,37 @@ module SmileIdentityCore
|
|
217
196
|
keys.select { |key| blank?(obj, key) }
|
218
197
|
end
|
219
198
|
|
220
|
-
def request_security(use_new_signature: true)
|
221
|
-
if use_new_signature
|
222
|
-
@timestamp = Time.now.to_s
|
223
|
-
{
|
224
|
-
signature: SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_signature(@timestamp)[:signature],
|
225
|
-
timestamp: @timestamp,
|
226
|
-
}
|
227
|
-
else
|
228
|
-
@timestamp = Time.now.to_i
|
229
|
-
{
|
230
|
-
sec_key: SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_sec_key(@timestamp)[:sec_key],
|
231
|
-
timestamp: @timestamp,
|
232
|
-
}
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
199
|
def configure_prep_upload_json
|
237
|
-
|
200
|
+
SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_signature(Time.now.to_s).merge(
|
238
201
|
file_name: 'selfie.zip',
|
239
202
|
smile_client_id: @partner_id,
|
240
203
|
partner_params: @partner_params,
|
241
204
|
model_parameters: {}, # what is this for
|
242
|
-
callback_url: @callback_url
|
205
|
+
callback_url: @callback_url,
|
206
|
+
source_sdk: SmileIdentityCore::SOURCE_SDK,
|
207
|
+
source_sdk_version: SmileIdentityCore::VERSION
|
243
208
|
).to_json
|
244
209
|
end
|
245
210
|
|
246
211
|
def setup_requests
|
247
|
-
|
248
212
|
url = "#{@url}/upload"
|
249
213
|
request = Typhoeus::Request.new(
|
250
214
|
url,
|
251
215
|
method: 'POST',
|
252
|
-
headers: {'Content-Type'=>
|
216
|
+
headers: { 'Content-Type' => 'application/json' },
|
253
217
|
body: configure_prep_upload_json
|
254
218
|
)
|
255
219
|
|
256
220
|
request.on_complete do |response|
|
257
221
|
if response.success?
|
258
222
|
# TODO: if/when we sign these responses, verify the signature here and raise if it's off.
|
259
|
-
# if updated_options[:signature]
|
260
223
|
# SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_signature(@timestamp)
|
261
|
-
# else
|
262
|
-
# SmileIdentityCore::Signature.new(@partner_id, @api_key).generate_sec_key(@timestamp)
|
263
|
-
# end
|
264
224
|
|
265
225
|
prep_upload_response = JSON.parse(response.body)
|
266
226
|
info_json = configure_info_json(prep_upload_response)
|
267
227
|
|
268
|
-
file_upload_response = upload_file(prep_upload_response['upload_url'], info_json,
|
228
|
+
file_upload_response = upload_file(prep_upload_response['upload_url'], info_json,
|
229
|
+
prep_upload_response['smile_job_id'])
|
269
230
|
return file_upload_response
|
270
231
|
end
|
271
232
|
|
@@ -275,43 +236,44 @@ module SmileIdentityCore
|
|
275
236
|
end
|
276
237
|
|
277
238
|
def configure_info_json(server_information)
|
278
|
-
|
239
|
+
{
|
279
240
|
"package_information": {
|
280
241
|
"apiVersion": {
|
281
242
|
"buildNumber": 0,
|
282
243
|
"majorVersion": 2,
|
283
244
|
"minorVersion": 0
|
284
245
|
},
|
285
|
-
"language":
|
246
|
+
"language": 'ruby'
|
286
247
|
},
|
287
|
-
"misc_information":
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
248
|
+
"misc_information": SmileIdentityCore::Signature.new(@partner_id, @api_key)
|
249
|
+
.generate_signature(Time.now.to_s)
|
250
|
+
.merge(
|
251
|
+
"retry": 'false',
|
252
|
+
"partner_params": @partner_params,
|
253
|
+
"file_name": 'selfie.zip', # figure out what to do here
|
254
|
+
"smile_client_id": @partner_id,
|
255
|
+
"callback_url": @callback_url,
|
256
|
+
"userData": { # TO ASK what goes here
|
257
|
+
"isVerifiedProcess": false,
|
258
|
+
"name": '',
|
259
|
+
"fbUserID": '',
|
260
|
+
"firstName": 'Bill',
|
261
|
+
"lastName": '',
|
262
|
+
"gender": '',
|
263
|
+
"email": '',
|
264
|
+
"phone": '',
|
265
|
+
"countryCode": '+',
|
266
|
+
"countryName": ''
|
267
|
+
}
|
268
|
+
),
|
306
269
|
"id_info": @id_info,
|
307
270
|
"images": configure_image_payload,
|
308
271
|
"server_information": server_information
|
309
272
|
}
|
310
|
-
info
|
311
273
|
end
|
312
274
|
|
313
275
|
def configure_image_payload
|
314
|
-
@images.map
|
276
|
+
@images.map do |i|
|
315
277
|
if image_file?(i[:image_type_id])
|
316
278
|
{
|
317
279
|
image_type_id: i[:image_type_id],
|
@@ -325,11 +287,11 @@ module SmileIdentityCore
|
|
325
287
|
file_name: ''
|
326
288
|
}
|
327
289
|
end
|
328
|
-
|
290
|
+
end
|
329
291
|
end
|
330
292
|
|
331
293
|
def image_file?(type)
|
332
|
-
type.to_i
|
294
|
+
type.to_i.zero? || type.to_i == 1
|
333
295
|
end
|
334
296
|
|
335
297
|
def zip_up_file(info_json)
|
@@ -338,9 +300,9 @@ module SmileIdentityCore
|
|
338
300
|
zos.put_next_entry('info.json')
|
339
301
|
zos.puts JSON.pretty_generate(info_json)
|
340
302
|
|
341
|
-
if @images.length
|
303
|
+
if @images.length.positive?
|
342
304
|
@images.each do |img|
|
343
|
-
if img[:image_type_id]
|
305
|
+
if (img[:image_type_id]).zero? || img[:image_type_id] == 1
|
344
306
|
zos.put_next_entry(File.basename(img[:image]))
|
345
307
|
zos.print IO.read(img[:image])
|
346
308
|
end
|
@@ -350,36 +312,31 @@ module SmileIdentityCore
|
|
350
312
|
end
|
351
313
|
|
352
314
|
def upload_file(url, info_json, smile_job_id)
|
353
|
-
|
354
315
|
file = zip_up_file(info_json)
|
355
316
|
file.rewind
|
356
317
|
|
357
318
|
request = Typhoeus::Request.new(
|
358
319
|
url,
|
359
320
|
method: 'PUT',
|
360
|
-
headers: {'Content-Type'=>
|
361
|
-
body: file.read
|
321
|
+
headers: { 'Content-Type' => 'application/zip' },
|
322
|
+
body: file.read
|
362
323
|
)
|
363
324
|
|
364
325
|
request.on_complete do |response|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
end
|
375
|
-
end
|
376
|
-
raise " #{response.code}: #{response.body}"
|
326
|
+
raise " #{response.code}: #{response.body}" unless response.success?
|
327
|
+
|
328
|
+
return { success: true, smile_job_id: smile_job_id }.to_json unless @options[:return_job_status]
|
329
|
+
|
330
|
+
@utilies_connection = SmileIdentityCore::Utilities.new(@partner_id, @api_key, @sid_server)
|
331
|
+
job_response = query_job_status
|
332
|
+
job_response['success'] = true
|
333
|
+
job_response['smile_job_id'] = smile_job_id
|
334
|
+
return job_response
|
377
335
|
end
|
378
336
|
request.run
|
379
|
-
|
380
337
|
end
|
381
338
|
|
382
|
-
def query_job_status(counter=0)
|
339
|
+
def query_job_status(counter = 0)
|
383
340
|
counter < 4 ? (sleep 2) : (sleep 6)
|
384
341
|
counter += 1
|
385
342
|
|
@@ -390,7 +347,6 @@ module SmileIdentityCore
|
|
390
347
|
else
|
391
348
|
query_job_status(counter)
|
392
349
|
end
|
393
|
-
|
394
350
|
end
|
395
351
|
end
|
396
352
|
end
|
data/lib/smile-identity-core.rb
CHANGED
@@ -1,8 +1,14 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'smile-identity-core/version'
|
4
|
+
require 'smile-identity-core/web_api'
|
5
|
+
require 'smile-identity-core/id_api'
|
6
|
+
require 'smile-identity-core/signature'
|
7
|
+
require 'smile-identity-core/utilities'
|
8
|
+
require 'smile-identity-core/business_verification'
|
9
|
+
require 'smile-identity-core/constants/env'
|
10
|
+
require 'smile-identity-core/constants/image_type'
|
11
|
+
require 'smile-identity-core/constants/job_type'
|
6
12
|
|
7
13
|
module SmileIdentityCore
|
8
14
|
class Error < StandardError; end
|
data/smile-identity-core.gemspec
CHANGED
@@ -1,39 +1,44 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
2
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
-
require
|
5
|
+
require 'smile-identity-core/version'
|
4
6
|
|
5
7
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name =
|
8
|
+
spec.name = 'smile-identity-core'
|
7
9
|
spec.version = SmileIdentityCore::VERSION
|
8
|
-
spec.authors = [
|
9
|
-
spec.email = [
|
10
|
+
spec.authors = ['Smile Identity']
|
11
|
+
spec.email = ['support@smileidentity.com']
|
10
12
|
|
11
|
-
spec.summary =
|
12
|
-
|
13
|
-
spec.
|
14
|
-
spec.
|
13
|
+
spec.summary = 'The Smile Identity Web API allows the user to access\
|
14
|
+
most of the features of the Smile Identity system through direct server to server queries.'
|
15
|
+
spec.description = 'The Official Smile Identity gem'
|
16
|
+
spec.homepage = 'https://www.smileidentity.com/'
|
17
|
+
spec.required_ruby_version = '>= 2.5'
|
15
18
|
spec.license = 'MIT'
|
16
19
|
|
17
|
-
spec.metadata[
|
18
|
-
spec.metadata[
|
19
|
-
spec.metadata[
|
20
|
-
spec.metadata[
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/smileidentity/smile-identity-core-ruby'
|
22
|
+
spec.metadata['documentation_uri'] = 'https://docs.smileidentity.com'
|
23
|
+
spec.metadata['changelog_uri'] = 'https://github.com/smileidentity/smile-identity-core-ruby/blob/master/CHANGELOG.md'
|
21
24
|
|
22
25
|
# Specify which files should be added to the gem when it is released.
|
23
26
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
-
spec.files
|
27
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
28
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
26
29
|
end
|
27
|
-
spec.bindir =
|
30
|
+
spec.bindir = 'exe'
|
28
31
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
29
|
-
spec.require_paths = [
|
32
|
+
spec.require_paths = ['lib']
|
30
33
|
|
31
|
-
spec.add_development_dependency
|
32
|
-
spec.add_development_dependency
|
33
|
-
spec.add_development_dependency
|
34
|
+
spec.add_development_dependency 'bundler', '~> 2.0'
|
35
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
36
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
37
|
+
spec.add_development_dependency 'rubocop', '~> 1.37.1'
|
38
|
+
spec.add_development_dependency 'rubocop-rake', '~> 0.6.0'
|
39
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 2.14.1'
|
34
40
|
spec.add_development_dependency 'simplecov', '~> 0.12.0'
|
35
41
|
|
36
|
-
spec.add_dependency 'typhoeus', '~> 1.0', '>= 1.0.1'
|
37
42
|
spec.add_dependency 'rubyzip', '~> 1.2', '>= 1.2.3'
|
38
|
-
|
43
|
+
spec.add_dependency 'typhoeus', '~> 1.0', '>= 1.0.1'
|
39
44
|
end
|