uploadcare-ruby 1.0.6 → 1.1.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.
@@ -14,6 +14,7 @@ module Uploadcare
14
14
  static_url_base: 'https://ucarecdn.com',
15
15
  api_version: '0.3',
16
16
  cache_files: true,
17
+ auth_scheme: :secure
17
18
  }
18
19
 
19
20
  USER_AGENT = "uploadcare-ruby/#{Gem.ruby_version}/#{Uploadcare::VERSION}"
@@ -4,6 +4,7 @@ Dir[File.dirname(__FILE__) + '/utils/*.rb'].each {|file| require file }
4
4
  Dir[File.dirname(__FILE__) + '/errors/*.rb'].each {|file| require file }
5
5
  Dir[File.dirname(__FILE__) + '/rest/middlewares/*.rb'].each {|file| require file }
6
6
  Dir[File.dirname(__FILE__) + '/rest/connections/*.rb'].each {|file| require file }
7
+ Dir[File.dirname(__FILE__) + '/rest/auth/*.rb'].sort.each {|file| require file }
7
8
  Dir[File.dirname(__FILE__) + '/api/*.rb'].each {|file| require file }
8
9
  Dir[File.dirname(__FILE__) + '/resources/*.rb'].each {|file| require file }
9
10
 
@@ -11,7 +12,7 @@ Dir[File.dirname(__FILE__) + '/resources/*.rb'].each {|file| require file }
11
12
  module Uploadcare
12
13
  class Api
13
14
  attr_reader :options
14
-
15
+
15
16
  include Uploadcare::RawApi
16
17
  include Uploadcare::UploadingApi
17
18
  include Uploadcare::FileApi
@@ -5,7 +5,7 @@ module Uploadcare
5
5
  class File < OpenStruct
6
6
  def initialize api, uuid_or_cdn_url, data=nil
7
7
  result = Uploadcare::Parser.parse_file_string uuid_or_cdn_url
8
-
8
+
9
9
  file = {uuid: result.uuid, operations: result.operations}
10
10
 
11
11
  @api = api
@@ -65,7 +65,7 @@ module Uploadcare
65
65
  def store
66
66
  data = @api.put "/files/#{uuid}/storage/"
67
67
  set_data data
68
- self
68
+ self
69
69
  end
70
70
 
71
71
  # nil is returning if there is no way to say for sure
@@ -95,6 +95,8 @@ module Uploadcare
95
95
  # copy file to target location
96
96
  # note what file copied with operations
97
97
  def copy with_operations=true, target=nil
98
+ warn "[DEPRECATION] `copy` is deprecated and will be removed in "\
99
+ "version 3.0. Please use `internal_copy` or `external_copy` instead."
98
100
  data = Hash.new
99
101
  data[:target] = target if target
100
102
  data[:source] = self.cdn_url_with_operations if with_operations
@@ -103,6 +105,28 @@ module Uploadcare
103
105
  @api.post "/files/", data
104
106
  end
105
107
 
108
+ # Create a copy of the file in a default storage
109
+ def internal_copy(options={})
110
+ data = {
111
+ source: cdn_url(!options.fetch(:strip_operations){ false }),
112
+ store: options.fetch(:store){ nil }
113
+ }.reject{|_,v| v.nil?}
114
+
115
+ @api.post "/files/", data
116
+ end
117
+
118
+ # Copy file to a custom storage
119
+ def external_copy(target, options={})
120
+ data = {
121
+ source: cdn_url(!options.fetch(:strip_operations){ false }),
122
+ target: target,
123
+ pattern: options.fetch(:pattern){ nil },
124
+ make_public: options.fetch(:make_public){ nil },
125
+ }.reject{|_,v| v.nil?}
126
+
127
+ @api.post "/files/", data
128
+ end
129
+
106
130
 
107
131
  # Datetime methods
108
132
  # practicly try and parse the string to date objects
@@ -137,4 +161,4 @@ module Uploadcare
137
161
  end
138
162
  end
139
163
  end
140
- end
164
+ end
@@ -0,0 +1,31 @@
1
+ module Uploadcare
2
+ module Connections
3
+ module Auth
4
+
5
+ def self.strategy(options)
6
+ auth_scheme = options.fetch(:auth_scheme)
7
+
8
+ unless [:simple, :secure].include?(auth_scheme)
9
+ raise ArgumentError, "Unknown auth_scheme: '#{auth_scheme}'"
10
+ end
11
+
12
+ klass = const_get(auth_scheme.capitalize)
13
+ klass.new(options)
14
+ end
15
+
16
+ class Base
17
+ attr_reader :public_key, :private_key
18
+
19
+ def initialize(options)
20
+ @public_key = options.fetch(:public_key)
21
+ @private_key = options.fetch(:private_key)
22
+ end
23
+
24
+ def apply(env)
25
+ raise NotImplementedError
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ require 'time'
2
+
3
+ module Uploadcare
4
+ module Connections
5
+ module Auth
6
+ class Secure < Base
7
+
8
+ def apply(env)
9
+ date = Time.now.utc
10
+ headers(env, date).each{|k, v| env.request_headers[k] = v}
11
+ env
12
+ end
13
+
14
+ private
15
+
16
+ def headers(env, date)
17
+ {
18
+ "Date" => date.rfc2822,
19
+ "Authorization" => "Uploadcare #{public_key}:#{signature(env, date)}"
20
+ }
21
+ end
22
+
23
+ def signature(env, date)
24
+ sign_string = sign_string(env, date)
25
+ digest = OpenSSL::Digest.new('sha1')
26
+
27
+ OpenSSL::HMAC.hexdigest(digest, private_key, sign_string)
28
+ end
29
+
30
+ def sign_string(env, date)
31
+ verb = env.method.upcase.to_s
32
+ uri = env.url.request_uri
33
+ date_header = date.rfc2822
34
+ content_type = env.request_headers['Content-Type']
35
+ content_md5 = OpenSSL::Digest.new('md5').hexdigest(env.body || "")
36
+
37
+ [verb, content_md5, content_type, date_header, uri].join("\n")
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,16 @@
1
+ module Uploadcare
2
+ module Connections
3
+ module Auth
4
+ class Simple < Base
5
+
6
+ def apply(env)
7
+ auth_string = "Uploadcare.Simple #{public_key}:#{private_key}"
8
+ env.request_headers['Authorization'] = auth_string
9
+
10
+ env
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,21 +1,33 @@
1
1
  require 'faraday'
2
2
  require "faraday_middleware"
3
+
3
4
  module Uploadcare
4
5
  module Connections
5
6
  class ApiConnection < Faraday::Connection
7
+
6
8
  def initialize options
7
9
  super options[:api_url_base] do |frd|
8
- frd.request :url_encoded
9
- frd.use ::FaradayMiddleware::FollowRedirects, limit: 3
10
- frd.adapter :net_http # actually, default adapter, just to be clear
11
- frd.headers['Authorization'] = "Uploadcare.Simple #{options[:public_key]}:#{options[:private_key]}"
10
+ auth_strategy = Auth.strategy(options)
11
+
12
12
  frd.headers['Accept'] = "application/vnd.uploadcare-v#{options[:api_version]}+json"
13
13
  frd.headers['User-Agent'] = Uploadcare::user_agent(options)
14
14
 
15
- frd.response :raise_error
16
- frd.response :parse_json
15
+ # order of middleware matters!
16
+
17
+ # url_encoded changes request body and thus should be before
18
+ # uploadcare_auth which uses it to sign requests when secure auth
19
+ # strategy is being used
20
+ frd.request :url_encoded
21
+ frd.request :uploadcare_auth, auth_strategy
22
+
23
+ frd.response :uploadcare_raise_error
24
+ frd.response :follow_redirects, limit: 3, callback: lambda{|old, env| auth_strategy.apply(env) }
25
+ frd.response :uploadcare_parse_json
26
+
27
+ frd.adapter :net_http # actually, default adapter, just to be clear
17
28
  end
18
29
  end
30
+
19
31
  end
20
32
  end
21
- end
33
+ end
@@ -12,8 +12,8 @@ module Uploadcare
12
12
  frd.adapter :net_http
13
13
  frd.headers['User-Agent'] = Uploadcare::user_agent(options)
14
14
 
15
- frd.response :raise_error
16
- frd.response :parse_json
15
+ frd.response :uploadcare_raise_error
16
+ frd.response :uploadcare_parse_json
17
17
  end
18
18
  end
19
19
  end
@@ -0,0 +1,24 @@
1
+ require 'faraday'
2
+
3
+ module Uploadcare
4
+ module Connections
5
+ module Request
6
+ class Auth < Faraday::Middleware
7
+ attr_reader :auth_strategy
8
+
9
+ def initialize(app=nil, auth_strategy)
10
+ @auth_strategy = auth_strategy
11
+ super(app)
12
+ end
13
+
14
+ def call(env)
15
+ auth_strategy.apply(env)
16
+ @app.call(env)
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ Faraday::Request.register_middleware uploadcare_auth: Uploadcare::Connections::Request::Auth
@@ -30,4 +30,4 @@ module Uploadcare
30
30
  end
31
31
  end
32
32
 
33
- Faraday::Response.register_middleware :parse_json => Uploadcare::Connections::Response::ParseJson
33
+ Faraday::Response.register_middleware :uploadcare_parse_json => Uploadcare::Connections::Response::ParseJson
@@ -7,7 +7,7 @@ module Uploadcare
7
7
  def on_complete(response)
8
8
  @error_codes = Uploadcare::Error.errors.keys
9
9
  @status = response[:status]
10
-
10
+
11
11
  if @error_codes.include?(@status)
12
12
  error = Uploadcare::Error.errors[@status].new
13
13
  fail(error)
@@ -18,4 +18,4 @@ module Uploadcare
18
18
  end
19
19
  end
20
20
 
21
- Faraday::Response.register_middleware :raise_error => Uploadcare::Connections::Response::RaiseError
21
+ Faraday::Response.register_middleware :uploadcare_raise_error => Uploadcare::Connections::Response::RaiseError
@@ -1,3 +1,3 @@
1
1
  module Uploadcare
2
- VERSION = "1.0.6"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -1,6 +1,7 @@
1
1
  require 'spec_helper'
2
2
  require 'uri'
3
3
  require 'socket'
4
+ require 'securerandom'
4
5
 
5
6
  describe Uploadcare::Api::File do
6
7
  before :each do
@@ -111,12 +112,111 @@ describe Uploadcare::Api::File do
111
112
  end
112
113
 
113
114
 
114
- def retry_if(error, retries=3, &block)
115
- block.call
116
- rescue error
117
- raise if retries <= 0
118
- sleep 0.2
119
- retry_if(error, retries-1, &block)
115
+ describe '#internal_copy' do
116
+ describe 'integration' do
117
+ it 'creates an internal copy of the file' do
118
+ response = retry_if(Uploadcare::Error::RequestError::BadRequest){@file.internal_copy}
119
+
120
+ expect( response['type'] ).to eq 'file'
121
+ expect( response['result']['uuid'] ).not_to eq @file.uuid
122
+ end
123
+ end
124
+
125
+ describe 'params' do
126
+ let(:url_without_ops){ @api.file(SecureRandom.uuid).cdn_url }
127
+ let(:url_with_ops){ url_without_ops + "-/crop/5x5/center/" }
128
+ let(:file){ @api.file(url_with_ops) }
129
+
130
+ context 'if no params given' do
131
+ it 'requests server to create an unstored copy with operataions applied' do
132
+ expect(@api).to receive(:post)
133
+ .with('/files/', source: url_with_ops)
134
+
135
+ file.internal_copy
136
+ end
137
+ end
138
+
139
+ context 'if strip_operations: true given' do
140
+ it 'passes url without operations as a source for a copy' do
141
+ expect(@api).to receive(:post)
142
+ .with('/files/', source: url_without_ops)
143
+
144
+ file.internal_copy(strip_operations: true)
145
+ end
146
+ end
147
+
148
+ context 'if store: true given' do
149
+ it 'requests server to create a stored copy' do
150
+ expect(@api).to receive(:post)
151
+ .with('/files/', source: url_with_ops, store: true)
152
+
153
+ file.internal_copy(store: true)
154
+ end
155
+ end
156
+ end
120
157
  end
121
158
 
122
- end
159
+
160
+ describe '#external_copy' do
161
+ let(:target){ 'with-prefix' }
162
+
163
+ describe 'integration', :payed_feature do
164
+ it 'creates an external copy of the file' do
165
+ response = retry_if(Uploadcare::Error::RequestError::BadRequest) do
166
+ @file.external_copy(target)
167
+ end
168
+
169
+ expect( response['type'] ).to eq 'url'
170
+ expect( response['result'] ).to match(URI.regexp)
171
+ end
172
+ end
173
+
174
+ describe 'params' do
175
+ let(:url_without_ops){ @api.file(SecureRandom.uuid).cdn_url }
176
+ let(:url_with_ops){ url_without_ops + "-/resize/50x50/" }
177
+ let(:file){ @api.file(url_with_ops) }
178
+
179
+ context 'if only target is given' do
180
+ it 'requests server to create a private copy with default name and with operataions applied' do
181
+ expect(@api).to receive(:post)
182
+ .with('/files/', source: url_with_ops, target: target)
183
+
184
+ file.external_copy(target)
185
+ end
186
+ end
187
+
188
+ context 'if target is not given' do
189
+ it 'raises ArgumentError' do
190
+ expect{ file.external_copy }.to raise_error(ArgumentError)
191
+ end
192
+ end
193
+
194
+ context 'if strip_operations: true given' do
195
+ it 'passes url without operations as a source for a copy' do
196
+ expect(@api).to receive(:post)
197
+ .with('/files/', source: url_without_ops, target: target)
198
+
199
+ file.external_copy(target, strip_operations: true)
200
+ end
201
+ end
202
+
203
+ context 'if :make_public given' do
204
+ it 'requests server to create a copy with correspondent permissions' do
205
+ expect(@api).to receive(:post)
206
+ .with('/files/', source: url_with_ops, target: target, make_public: false)
207
+
208
+ file.external_copy(target, make_public: false)
209
+ end
210
+ end
211
+
212
+ context 'if :pattern given' do
213
+ it 'requests server to apply given pattern to name of a copy' do
214
+ expect(@api).to receive(:post)
215
+ .with('/files/', source: url_with_ops, target: target, pattern: 'test')
216
+
217
+ file.external_copy(target, pattern: 'test')
218
+ end
219
+ end
220
+ end
221
+ end
222
+ end
@@ -3,17 +3,66 @@ require 'uri'
3
3
  require 'socket'
4
4
 
5
5
  describe Uploadcare::Connections::ApiConnection do
6
- before(:all) do
7
- @settings = Uploadcare.default_settings
6
+ let(:settings){ Uploadcare.default_settings }
7
+
8
+ it 'is initializable with default settings' do
9
+ expect {described_class.new(settings)}.to_not raise_error
8
10
  end
9
11
 
10
- it 'should initialize api connection' do
11
- expect {Uploadcare::Connections::ApiConnection.new(@settings)}.to_not raise_error
12
+
13
+ describe 'default request headers' do
14
+ subject{ described_class.new(settings).headers }
15
+
16
+ it 'includes correct Accept header' do
17
+ expected = "application/vnd.uploadcare-v#{settings[:api_version]}+json"
18
+ expect(subject['Accept']).to eq expected
19
+ end
20
+
21
+ it 'includes correct User-Agent header' do
22
+ expected = Uploadcare::user_agent(settings)
23
+ expect(subject['User-Agent']).to eq expected
24
+ end
25
+ end
26
+
27
+
28
+ describe 'middleware' do
29
+ subject{ described_class.new(settings).builder.handlers }
30
+
31
+ it 'uses Request::Auth middleware' do
32
+ expect(subject).to include(Uploadcare::Connections::Request::Auth)
33
+ end
34
+
35
+ it 'uses Response::ParseJson middleware' do
36
+ expect(subject).to include(Uploadcare::Connections::Response::ParseJson)
37
+ end
38
+
39
+ it 'uses Response::RaiseError middleware' do
40
+ expect(subject).to include(Uploadcare::Connections::Response::RaiseError)
41
+ end
12
42
  end
13
43
 
14
- it 'should use ParseJson and RaiseError middlewares' do
15
- connection = Uploadcare::Connections::ApiConnection.new(@settings)
16
- connection.builder.handlers.include?(Uploadcare::Connections::Response::ParseJson).should == true
17
- connection.builder.handlers.include?(Uploadcare::Connections::Response::RaiseError).should == true
44
+
45
+ describe 'auth scheme' do
46
+ it 'uses simple auth when auth_scheme: :simple setting is provided' do
47
+ expect(Uploadcare::Connections::Auth::Simple).to receive(:new)
48
+ described_class.new(settings.merge(auth_scheme: :simple))
49
+ end
50
+
51
+ it 'uses secure auth when auth_scheme: :secure setting is provided' do
52
+ expect(Uploadcare::Connections::Auth::Secure).to receive(:new)
53
+ described_class.new(settings.merge(auth_scheme: :secure))
54
+ end
55
+
56
+ it 'raises KeyError when :auth_scheme options is not provided' do
57
+ expect{
58
+ described_class.new(settings.reject{|k,_| k == :auth_scheme})
59
+ }.to raise_error(KeyError)
60
+ end
61
+
62
+ it 'raises ArgumentError when provided :auth_scheme is unknown' do
63
+ expect{
64
+ described_class.new(settings.merge(auth_scheme: :unknown))
65
+ }.to raise_error(ArgumentError)
66
+ end
18
67
  end
19
- end
68
+ end